OpenAI API使用教程:快速接入AI能力
去年十月,成都的秋天刚刚有点凉意,我还在啃《深入理解计算机系统》。作为一个双非院校大二学生,白天上课、晚上刷LeetCode,周末还得去图书馆抢座——典型的“卷又卷不动,躺又躺不平”的状态。但谁想到,就在我用MacBook Air M1跑第一个Spring Boot项目时,命运(其实是隔壁组的学长)给我丢了一个需求:“能不能在咱们那个小破论坛里加个AI回复功能?”
我心想,这不就是调个API的事儿?结果……打脸来得太快,就像龙卷风。
起因:被“赋能”了
事情是这样的。我们学校有个学生技术社区,用的是自己搭的Spring Boot后端 + Vue前端,部署在阿里云学生机上(99一年那种,别笑)。产品负责人(其实就是辅导员兼任的)突然说:“现在不是AI很火嘛?咱们也搞点智能互动,提升用户粘性!”——典型的PPT式需求,连PRD都没写清楚。
更离谱的是,Deadline定在下周五上线,而今天已经是周三晚上十点。我坐在玉林路的小茶馆里,捧着一杯冰美式,看着微信里产品经理发来的“这个功能很简单吧?”的消息,差点一口咖啡喷出来。
但没办法,为了简历上能多一行“主导AI模块开发”,硬着头皮也得上。
初探OpenAI API:你以为的简单,其实全是坑
一开始我以为:不就是HTTP请求发个JSON,拿个response回来渲染一下?结果光是认证就卡了我俩小时。
OpenAI用的是Authorization: Bearer sk-xxx,但Spring Boot里怎么优雅地管理这个key?总不能硬编码在代码里吧?Git提交一不小心就泄露了,轻则被封号,重则被扫走余额——听说有人一天被刷掉几千刀,想想就冒冷汗。
安全第一:配置别乱放
我最后用了Spring Boot的application.yml + 环境变量:
openai:
api-key: ${OPENAI_API_KEY}
model: gpt-3.5-turbo
timeout: 30s
然后本地启动时:
export OPEN AI_API_KEY=sk-xxxxxx
./mvnw spring-boot:run
生产环境直接让运维(其实就是我自己)在服务器上配环境变量。这样至少不会把密钥提交到GitHub——虽然我的仓库是私有的,但保不齐哪天手滑点成public。
血泪教训:千万别在代码里写死API Key!我见过学长因为这事被GitHub机器人自动关了账号,哭着求客服解封。
封装Client:别每次都new RestTemplate
第一次我直接在Controller里写了:
RestTemplate restTemplate = new RestTemplate();
// 构造headers、body、发送请求...
结果测试时发现超时、重试、日志都没法统一处理。而且GPT返回的内容有时候带Markdown,前端渲染还出问题。
于是痛定思痛,写了个OpenAIService:
@Service
@Slf4j
public class OpenAIService {
private final RestTemplate restTemplate;
private final String apiKey;
private final String model;
public OpenAIService(@Value("${openai.api-key}") String apiKey,
@Value("${openai.model}") String model) {
this.apiKey = apiKey;
this.model = model;
this.restTemplate = createRestTemplate();
}
private RestTemplate createRestTemplate() {
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setConnectTimeout(5000);
factory.setReadTimeout(30000); // OpenAI默认最长60s,但咱不能等太久
return new RestTemplate(factory);
}
public String generateResponse(String prompt) {
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(apiKey);
headers.setContentType(MediaType.APPLICATION_JSON);
Map<String, Object> requestBody = Map.of(
"model", model,
"messages", List.of(Map.of("role", "user", "content", prompt)),
"temperature", 0.7
);
HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, headers);
try {
ResponseEntity<OpenAIResponse> response = restTemplate.postForEntity(
"https://api.openai.com/v1/chat/completions",
request,
OpenAIResponse.class
);
return response.getBody().getChoices().get(0).getMessage().getContent().trim();
} catch (Exception e) {
log.error("调用OpenAI失败: {}", e.getMessage());
throw new RuntimeException("AI暂时罢工,请稍后再试");
}
}
}
这里有几个细节:
- 超时设置很重要:GPT-3.5一般几秒内返回,但网络抖动或高负载时可能卡住。用户可不想等一分钟。
- 异常要兜底:别让用户看到500错误,至少给个友好提示。
- 日志记录prompt:方便debug,但千万别记API Key和完整response,有隐私风险!
实战场景:论坛自动回复机器人
我们的需求是在用户发帖后,AI自动生成一个“参考回复”。比如有人问:“Spring Boot怎么整合Redis?”,AI就回一段示例代码。
但直接丢问题过去,效果很差。GPT经常答非所问,或者啰嗦半天。后来我加了system prompt:
“你是一个Java后端开发者,擅长Spring Boot。请用简洁、专业的语言回答技术问题,必要时提供代码示例。不要解释你是AI。”
效果立竿见影!准确率从60%提到85%以上。
// 在messages里加一条system消息
List<Map<String, String>> messages = new ArrayList<>();
messages.add(Map.of("role", "system", "content", SYSTEM_PROMPT));
messages.add(Map.of("role", "user", "content", userQuestion));
开发心得:Prompt engineering真的是一门玄学。有时候改一个词,效果天差地别。建议建个Excel记录不同prompt的效果,比瞎调强多了。
性能与成本:别让老板破产
GPT-3.5按token计费,一次对话大概0.002美元。看起来不多,但如果论坛每天有1000个帖子,每人触发一次AI回复,一个月就是60美元——对我们这种学生项目来说,已经算巨款了。
所以必须做缓存和频率限制。
我用Redis做了两级缓存:
- 相同问题直接返回历史答案(比如“如何配置DataSource?”这种高频问题)
- 用户限流:每个IP每分钟最多请求3次
@Cacheable(value = "aiResponses", key = "#prompt")
public String getCachedResponse(String prompt) {
return openAIService.generateResponse(prompt);
}
同时,在Nginx层面加了限流(虽然我们没用Nginx,但假装有 😅)。
| 策略 | 成本降低 | 用户体验影响 |
|---|---|---|
| 缓存相同问题 | ~40% | 几乎无感 |
| IP限流 | ~20% | 偶尔提示“操作太频繁” |
| 截断长输入 | ~15% | 需要前端配合 |
测试环节:被QA虐哭的一晚
上周五晚上,我刚提测,测试同学(我们社团的学妹)就甩过来一堆Bug:
- “输入‘你好’,AI回了300字人生哲理!”
- “特殊字符导致JSON解析失败”
- “中文标点被转义了”
原来是我没处理输入清洗和输出转义。
赶紧补上:
// 输入清洗:去掉危险字符、限制长度
String safePrompt = prompt.replaceAll("[<>]", "")
.substring(0, Math.min(prompt.length(), 500));
// 输出转义:防止XSS
String escapedContent = StringEscapeUtils.escapeHtml4(aiResponse);
还加了单元测试:
@Test
void shouldReturnValidJsonWhenInputIsNormal() {
String result = openAIService.generateResponse("什么是Spring Boot?");
assertThat(result).isNotEmpty().doesNotContain("<script>");
}
那一晚,我凌晨两点才离开实验室,路上还在想:写代码容易,写健壮的代码难啊。
上线后:意外收获
功能上线后,论坛活跃度涨了30%。更神奇的是,有些用户开始故意问奇怪问题来“调戏”AI,比如“你能帮我写情书吗?”、“用李白的风格写一段Controller代码”。
虽然这些不属于技术范畴,但说明AI的“人格化”能带来情感连接——这可能是产品经理没想到的价值。
不过最让我开心的,是收到了一家成都本地初创公司的实习面试邀请。他们看到我在GitHub上开源了这个模块,直接问:“你对LLM应用落地有什么看法?”
那一刻,我觉得熬夜值了。
总结:给同样在折腾的你
作为一个非科班、没大厂背景的学生,我能搞定OpenAI接入,你肯定也可以。几点真心话:
- 别怕动手:API文档其实很友好,官方例子跑通就成功一半。
- 安全永远第一:API Key、用户数据、XSS,一个都不能漏。
- Prompt是核心资产:花时间打磨它,比调参重要得多。
- 监控和日志:上线后一定要看调用量、错误率、响应时间。
- 别追求完美:先跑起来,再优化。我们第一版连缓存都没有,但用户反馈很好。
最后,分享一句我贴在显示器上的话:
“代码可以重构,机会只有一次。”
如果你也在成都,用Mac写Spring Boot,喜欢抠底层原理——欢迎来玉林路一起喝杯咖啡,聊聊AI,或者吐槽产品经理。反正生活节奏舒服,bug慢慢修嘛。
附:关键依赖(pom.xml)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.10.0</version>
</dependency>
注:本文所有代码已在GitHub开源,搜索“campus-forum-ai”就能找到。别忘了Star~(手动狗头)

评论 0