用 OpenAI API 给老 Java 系统“续命”:一个佛系 Vim 党的实战记录

线上救火队
2026-05-12 08:00
阅读 3876

上周五晚上,我正躺在工位上刷 GitHub Trending(没错,摸鱼是程序员的基本修养),突然钉钉弹出一条消息:“老板想在咱们后台加个 AI 功能,下周三上线。”
我一口冰美式差点喷出来——这需求来得比线上 P0 故障还猝不及防。

我是那种在公司待了三年多、每天准时打卡、代码写得比注释还清晰的老油条。Vim 是我的 IDE,.vimrc.bashrc 还长。团队里都说我“代码有洁癖”,其实我只是懒得后期维护别人写的天书。但这次……AI?我们还在用 Spring Boot 2.5 + MyBatis 的老古董系统啊!

不过转念一想:跳槽简历上写“集成过 OpenAI API”总比“优化过 SQL 查询”听起来高级点吧?行,干就完了。


为啥不用 LangChain?因为太重了!

一开始,我本能地去搜 “Java OpenAI SDK”。结果发现官方没有!社区倒是有个 OpenAI-Java,但更新慢得像我们公司的审批流程。后来听隔壁组的小年轻说 LangChain for Java 已经出来了,赶紧拉下来试了试。

结果……启动一个最简单的问答链,内存直接飙到 1.2G。我们生产环境 JVM 才给 2G,这玩意儿跑上去怕不是要触发 OOM 被运维大哥半夜电话问候。

佛系吐槽:LangChain 确实强大,抽象层做得漂亮,但对于只想“调个接口”的场景,简直像为了切苹果买了一整套米其林厨房。

于是我决定——自己封装 HTTP 调用。简单、可控、还能塞进我们那套老旧的工具类体系里。


手搓 OpenAI API:HTTP + Jackson 足矣

OpenAI 的 REST API 其实很直白。核心就是 POST 到 https://api.openai.com/v1/chat/completions,带个 JSON body 和 Authorization header。

但在 Java 里,怎么发请求才不显得“原始”?我们项目里本来就有 OkHttp 和 Jackson,那就别引入新依赖了——少一个 jar 包,少一次 Maven 冲突,少一次被测试提 Bug。

先定义个简单的 DTO:

public class ChatMessage {
    private String role; // "system", "user", "assistant"
    private String content;

    // 构造器、getter/setter 略,Vim 自动生成 yyds
}

public class ChatCompletionRequest {
    private String model = "gpt-3.5-turbo";
    private List<ChatMessage> messages;
    private Double temperature = 0.7;
    private Integer maxTokens = 512;

    // 同上
}

然后写个工具类(名字我都懒得想,就叫 AiHelper):

@Component
public class AiHelper {

    private final OkHttpClient client = new OkHttpClient();
    private final ObjectMapper objectMapper = new ObjectMapper();

    @Value("${openai.api.key}")
    private String apiKey;

    public String chat(List<ChatMessage> messages) {
        try {
            ChatCompletionRequest request = new ChatCompletionRequest();
            request.setMessages(messages);

            RequestBody body = RequestBody.create(
                objectMapper.writeValueAsString(request),
                MediaType.get("application/json; charset=utf-8")
            );

            Request req = new Request.Builder()
                .url("https://api.openai.com/v1/chat/completions")
                .header("Authorization", "Bearer " + apiKey)
                .header("Content-Type", "application/json")
                .post(body)
                .build();

            try (Response response = client.newCall(req).execute()) {
                if (!response.isSuccessful()) {
                    throw new RuntimeException("OpenAI API error: " + response.body().string());
                }
                String responseBody = response.body().string();
                // 简化解析,实际建议用 JsonNode 避免强依赖结构
                JsonNode root = objectMapper.readTree(responseBody);
                return root.get("choices").get(0).get("message").get("content").asText().trim();
            }
        } catch (Exception e) {
            log.error("调用 OpenAI 失败", e);
            // 降级返回空字符串 or 抛业务异常,看场景
            return "AI 今天心情不好,不想说话...";
        }
    }
}

看起来是不是平平无奇?但可读性可维护性才是重点。三个月后另一个同事接手这段代码,一眼就能看懂“哦,就是发了个 HTTP 请求”。


实战场景:自动生成 SQL 注释

我们系统有个痛点:数据库表几十张,字段几百个,但注释全是中文拼音缩写,比如 yhm(用户名)、sjh(手机号)。新来的实习生每次看表都像在玩密室逃脱。

产品经理灵机一动:“能不能让 AI 根据字段名自动猜注释?”
我说:“可以,但别指望它 100% 准确,当参考就行。”

于是搞了个小功能:用户选中一张表,点击“AI 注释建议”,后端把字段列表发给 GPT,让它生成每个字段的可能含义。

Prompt 设计很关键。一开始我写:

请解释以下数据库字段名的含义:yhm, sjh, dzyx

结果 GPT 回:“yhm 可能是‘用户号码’,也可能是‘英文名’;sjh 可能是‘手机号’或‘设备号’……”

太模糊了!后来改成带上下文的 system message:

List<ChatMessage> messages = new ArrayList<>();
messages.add(new ChatMessage("system", 
    "你是一个资深数据库管理员,熟悉中国互联网常用字段命名规范。" +
    "请根据常见业务场景,为以下字段名提供最可能的中文注释。" +
    "只返回 JSON 格式:{ \"字段名\": \"注释\" },不要任何其他内容。"));

messages.add(new ChatMessage("user", 
    "字段列表:[\"yhm\", \"sjh\", \"dzyx\", \"zcsj\"]"));

这次返回:

{"yhm":"用户名","sjh":"手机号","dzyx":"电子邮箱","zcsj":"注册时间"}

完美!直接解析成 Map 塞进前端表格,产品看了直呼“高科技”。

经验之谈:Prompt 不是写一次就完事的。我前后调了 7 版,最后固定成模板。建议把 prompt 也抽成配置文件,方便非技术同学调整。


OpenCode?抱歉,我们还没资格谈这个

说到 OpenCode,我知道这是指开源代码模型(比如 StarCoder、CodeLlama)。但现实很骨感——我们连 GPU 服务器都没有,更别说 fine-tune 一个 15B 参数的模型了。

所以现阶段,用好 OpenAI API 就是性价比最高的“AI 能力”。特别是 gpt-3.5-turbo,便宜($0.001/1k tokens)、快(平均 800ms 响应)、够用。

不过我也偷偷试过本地跑 CodeLlama-7b-quantized,结果在我 16G 内存的 MacBook 上,光加载模型就卡了 3 分钟,推理速度 2 token/s……算了,还是把钱省下来买咖啡吧。


性能 & 成本:别让老板破产

接入 AI 最怕两件事:响应慢账单爆炸

我们做了三件事控制风险:

  1. 限流:用 Guava 的 RateLimiter 控制每分钟最多 10 次调用,避免测试同学疯狂点击。
  2. 缓存:相同输入(比如同样的字段列表)缓存 1 小时,Redis 存 JSON 字符串。
  3. token 监控:在日志里打印每次请求/响应的 token 数,定期分析高频使用场景。

下面是两周内的使用数据:

日期 请求次数 输入 tokens 输出 tokens 费用(USD)
2024-04-01 128 4,200 3,800 $0.008
2024-04-02 95 3,100 2,900 $0.006
... ... ... ... ...
总计 1,842 61,200 55,300 $0.116

看到没?一个月不到 12 美分。老板看了报表后,居然主动问:“还有别的地方能用 AI 吗?”


LangChain 的正确打开方式

虽然我没在主流程用 LangChain,但它在调试和原型验证阶段帮了大忙。

比如我想试试“先查数据库再让 AI 总结”,用 LangChain 的 RetrievalQA 链几行代码就跑通了。确认逻辑可行后,再手撸轻量版集成到生产系统。

所以我的建议是:

  • POC / 快速验证 → 用 LangChain,效率高
  • 生产核心链路 → 自己封装,可控性强

写在最后:技术人别把自己逼太紧

说实话,这次接 OpenAI API,我原以为要啃一堆文档、调参、处理异步流式响应……结果三天搞定,还顺手优化了几个工具类。

有时候我们总想着“要用最先进的框架”、“要上最酷的架构”,但解决问题的本质,往往是找到最合适的工具,而不是最炫的工具

我现在依然每天躺平摸鱼,Vim 里敲着老旧的 Java 代码。但至少,我的简历上可以写一句:“成功将生成式 AI 能力集成至千万级用户系统,成本可控,效果达标。”

下家面试官要是问我 LangChain 和 OpenCode 的区别?我就说:“LangChain 是瑞士军刀,OpenCode 是炼丹炉,而我,是个会用菜刀的厨子。”

毕竟,能跑起来的代码,才是好代码。不是吗?


附:避坑清单

  • ❌ 别用 gpt-4 做高频功能,贵且慢
  • temperature=0 适合确定性任务(如格式转换)
  • ✅ 一定要处理 rate limit(429 错误),加 retry + backoff
  • ✅ 敏感数据别传给 OpenAI!合规红线
  • ✅ 日志里别打 full request/response,token 泄露等于钱泄露

就这样吧,我要去续杯咖啡了。AI 再强,也得靠人喂数据啊 😴

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝