Java 调用 OpenAI / Claude API 完整实战指南
从零开始,手把手教你用 Java 接入 OpenAI 和 Claude 两大主流 AI 接口,包含流式输出、多轮对话、函数调用等高级用法,附完整可运行代码
一、前言
2026年,AI 能力已经成为应用的标配。作为 Java 开发者,掌握如何在项目中接入大模型 API 是一项必备技能。
本文将带你实现:
- ✅ Java 调用 OpenAI GPT 系列接口
- ✅ Java 调用 Anthropic Claude 接口
- ✅ 流式输出(打字机效果)
- ✅ 多轮对话上下文管理
- ✅ Function Calling(函数调用)
- ✅ Spring Boot 完整集成方案
二、准备工作
2.1 获取 API Key
OpenAI:
- 访问 https://platform.openai.com
- 注册账号 → API Keys → Create new secret key
- 复制保存(只显示一次)
Claude(Anthropic):
- 访问 https://console.anthropic.com
- 注册账号 → API Keys → Create Key
- 复制保存
⚠️ 安全提醒:API Key 绝对不能提交到 Git 仓库!建议存放在环境变量或配置中心。
2.2 模型选择参考
OpenAI 模型:
| 模型 | 特点 | 适用场景 | 价格(输入/输出) |
|---|---|---|---|
| gpt-4o | 速度快,性价比高 | 日常使用首选 | 2.5 / 10 per 1M tokens |
| gpt-4o-mini | 极速,超低价 | 简单任务 | 0.15 / 0.6 per 1M tokens |
| gpt-4-turbo | 强推理 | 复杂任务 | 10 / 30 per 1M tokens |
| o1 | 深度推理 | 数学/代码 | 15 / 60 per 1M tokens |
Claude 模型:
| 模型 | 特点 | 适用场景 | 价格(输入/输出) |
|---|---|---|---|
| claude-3-5-sonnet | 综合最强 | 日常首选 | 3 / 15 per 1M tokens |
| claude-3-5-haiku | 速度最快 | 简单任务 | 0.8 / 4 per 1M tokens |
| claude-3-opus | 最强推理 | 复杂分析 | 15 / 75 per 1M tokens |
2.3 Maven 依赖
xml
<!-- OkHttp - HTTP请求 -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
<!-- FastJSON2 - JSON处理 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.47</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Spring Boot(可选,用于集成) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
三、调用 OpenAI API
3.1 基础封装类
java
package com.example.ai.openai;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* OpenAI API 客户端
*/
public class OpenAiClient {
private static final Logger log = LoggerFactory.getLogger(OpenAiClient.class);
private static final String BASE_URL = "https://api.openai.com/v1";
private static final MediaType JSON_TYPE = MediaType.parse("application/json; charset=utf-8");
private final String apiKey;
private final String model;
private final OkHttpClient httpClient;
public OpenAiClient(String apiKey, String model) {
this.apiKey = apiKey;
this.model = model;
this.httpClient = new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(120, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build();
}
/**
* 简单对话(单轮)
*
* @param userMessage 用户消息
* @return AI 回复内容
*/
public String chat(String userMessage) throws IOException {
JSONObject requestBody = new JSONObject();
requestBody.put("model", model);
requestBody.put("max_tokens", 2048);
JSONArray messages = new JSONArray();
JSONObject message = new JSONObject();
message.put("role", "user");
message.put("content", userMessage);
messages.add(message);
requestBody.put("messages", messages);
return doRequest(requestBody);
}
/**
* 带系统提示词的对话
*
* @param systemPrompt 系统提示词
* @param userMessage 用户消息
* @return AI 回复内容
*/
public String chatWithSystem(String systemPrompt, String userMessage) throws IOException {
JSONObject requestBody = new JSONObject();
requestBody.put("model", model);
requestBody.put("max_tokens", 2048);
JSONArray messages = new JSONArray();
// 系统提示词
JSONObject systemMsg = new JSONObject();
systemMsg.put("role", "system");
systemMsg.put("content", systemPrompt);
messages.add(systemMsg);
// 用户消息
JSONObject userMsg = new JSONObject();
userMsg.put("role", "user");
userMsg.put("content", userMessage);
messages.add(userMsg);
requestBody.put("messages", messages);
return doRequest(requestBody);
}
/**
* 多轮对话
*
* @param messages 完整的对话历史
* @return AI 回复内容
*/
public String chatWithHistory(List<Map<String, String>> messages) throws IOException {
JSONObject requestBody = new JSONObject();
requestBody.put("model", model);
requestBody.put("max_tokens", 2048);
requestBody.put("messages", messages);
return doRequest(requestBody);
}
/**
* 执行 HTTP 请求
*/
private String doRequest(JSONObject requestBody) throws IOException {
Request request = new Request.Builder()
.url(BASE_URL + "/chat/completions")
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "application/json")
.post(RequestBody.create(requestBody.toJSONString(), JSON_TYPE))
.build();
try (Response response = httpClient.newCall(request).execute()) {
if (!response.isSuccessful()) {
String errorBody = response.body() != null ? response.body().string() : "unknown error";
throw new IOException("OpenAI API 请求失败,状态码: " + response.code() + ",错误: " + errorBody);
}
String responseBody = response.body().string();
JSONObject jsonResponse = JSON.parseObject(responseBody);
// 解析返回内容
return jsonResponse
.getJSONArray("choices")
.getJSONObject(0)
.getJSONObject("message")
.getString("content");
}
}
}
3.2 多轮对话管理器
java
package com.example.ai.openai;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 多轮对话管理器
* 维护对话历史,实现上下文连续对话
*/
public class ConversationManager {
private final OpenAiClient client;
private final List<Map<String, String>> history;
private final int maxHistorySize;
public ConversationManager(OpenAiClient client, String systemPrompt, int maxHistorySize) {
this.client = client;
this.history = new ArrayList<>();
this.maxHistorySize = maxHistorySize;
// 添加系统提示词
if (systemPrompt != null && !systemPrompt.isEmpty()) {
Map<String, String> systemMsg = new HashMap<>();
systemMsg.put("role", "system");
systemMsg.put("content", systemPrompt);
history.add(systemMsg);
}
}
/**
* 发送消息并获取回复
*/
public String sendMessage(String userMessage) throws IOException {
// 添加用户消息到历史
Map<String, String> userMsg = new HashMap<>();
userMsg.put("role", "user");
userMsg.put("content", userMessage);
history.add(userMsg);
// 调用 API
String reply = client.chatWithHistory(history);
// 添加 AI 回复到历史
Map<String, String> assistantMsg = new HashMap<>();
assistantMsg.put("role", "assistant");
assistantMsg.put("content", reply);
history.add(assistantMsg);
// 控制历史长度,避免 token 超限
trimHistory();
return reply;
}
/**
* 清空对话历史(保留系统提示词)
*/
public void clearHistory() {
if (!history.isEmpty() && "system".equals(history.get(0).get("role"))) {
Map<String, String> systemMsg = history.get(0);
history.clear();
history.add(systemMsg);
} else {
history.clear();
}
}
/**
* 控制历史长度
*/
private void trimHistory() {
int startIndex = "system".equals(history.get(0).get("role")) ? 1 : 0;
while (history.size() - startIndex > maxHistorySize * 2) {
history.remove(startIndex);
}
}
public List<Map<String, String>> getHistory() {
return new ArrayList<>(history);
}
}
3.3 流式输出实现
java
package com.example.ai.openai;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import okhttp3.*;
import okio.BufferedSource;
import java.io.IOException;
import java.util.function.Consumer;
/**
* OpenAI 流式输出客户端
*/
public class OpenAiStreamClient {
private static final String BASE_URL = "https://api.openai.com/v1";
private static final MediaType JSON_TYPE = MediaType.parse("application/json; charset=utf-8");
private final String apiKey;
private final String model;
private final OkHttpClient httpClient;
public OpenAiStreamClient(String apiKey, String model) {
this.apiKey = apiKey;
this.model = model;
this.httpClient = new OkHttpClient.Builder()
.connectTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
.readTimeout(300, java.util.concurrent.TimeUnit.SECONDS)
.build();
}
/**
* 流式对话
*
* @param userMessage 用户消息
* @param onToken 每个 token 的回调
* @param onComplete 完成回调
*/
public void streamChat(String userMessage,
Consumer<String> onToken,
Runnable onComplete) throws IOException {
JSONObject requestBody = new JSONObject();
requestBody.put("model", model);
requestBody.put("stream", true); // 开启流式
requestBody.put("max_tokens", 2048);
JSONObject message = new JSONObject();
message.put("role", "user");
message.put("content", userMessage);
requestBody.put("messages", new Object[]{message});
Request request = new Request.Builder()
.url(BASE_URL + "/chat/completions")
.header("Authorization", "Bearer " + apiKey)
.header("Accept", "text/event-stream")
.post(RequestBody.create(requestBody.toJSONString(), JSON_TYPE))
.build();
try (Response response = httpClient.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("请求失败: " + response.code());
}
BufferedSource source = response.body().source();
String line;
while (!source.exhausted()) {
line = source.readUtf8Line();
if (line == null || line.isEmpty()) continue;
// SSE 格式:data: {...}
if (line.startsWith("data: ")) {
String data = line.substring(6);
// 流结束标志
if ("[DONE]".equals(data)) {
if (onComplete != null) onComplete.run();
break;
}
try {
JSONObject chunk = JSON.parseObject(data);
String content = chunk
.getJSONArray("choices")
.getJSONObject(0)
.getJSONObject("delta")
.getString("content");
if (content != null && !content.isEmpty()) {
onToken.accept(content);
}
} catch (Exception ignored) {
// 忽略解析异常
}
}
}
}
}
}
四、调用 Claude API
4.1 Claude 基础客户端
java
package com.example.ai.claude;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* Anthropic Claude API 客户端
*/
public class ClaudeClient {
private static final Logger log = LoggerFactory.getLogger(ClaudeClient.class);
private static final String BASE_URL = "https://api.anthropic.com/v1";
private static final String API_VERSION = "2023-06-01";
private static final MediaType JSON_TYPE = MediaType.parse("application/json; charset=utf-8");
private final String apiKey;
private final String model;
private final OkHttpClient httpClient;
public ClaudeClient(String apiKey, String model) {
this.apiKey = apiKey;
this.model = model;
this.httpClient = new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(120, TimeUnit.SECONDS)
.build();
}
/**
* 简单对话
*/
public String chat(String userMessage) throws IOException {
return chatWithSystem(null, userMessage);
}
/**
* 带系统提示词的对话
*/
public String chatWithSystem(String systemPrompt, String userMessage) throws IOException {
JSONObject requestBody = new JSONObject();
requestBody.put("model", model);
requestBody.put("max_tokens", 2048);
// Claude 的 system 是独立字段,不在 messages 里
if (systemPrompt != null && !systemPrompt.isEmpty()) {
requestBody.put("system", systemPrompt);
}
JSONArray messages = new JSONArray();
JSONObject userMsg = new JSONObject();
userMsg.put("role", "user");
userMsg.put("content", userMessage);
messages.add(userMsg);
requestBody.put("messages", messages);
return doRequest(requestBody);
}
/**
* 多轮对话
* 注意:Claude 的 messages 不包含 system,system 单独传
*/
public String chatWithHistory(String systemPrompt,
List<Map<String, String>> messages) throws IOException {
JSONObject requestBody = new JSONObject();
requestBody.put("model", model);
requestBody.put("max_tokens", 2048);
if (systemPrompt != null && !systemPrompt.isEmpty()) {
requestBody.put("system", systemPrompt);
}
requestBody.put("messages", messages);
return doRequest(requestBody);
}
/**
* 执行请求
*/
private String doRequest(JSONObject requestBody) throws IOException {
Request request = new Request.Builder()
.url(BASE_URL + "/messages")
.header("x-api-key", apiKey) // Claude 用 x-api-key
.header("anthropic-version", API_VERSION) // 必须指定版本
.header("Content-Type", "application/json")
.post(RequestBody.create(requestBody.toJSONString(), JSON_TYPE))
.build();
try (Response response = httpClient.newCall(request).execute()) {
if (!response.isSuccessful()) {
String errorBody = response.body() != null ? response.body().string() : "unknown";
throw new IOException("Claude API 请求失败,状态码: " + response.code() + ",错误: " + errorBody);
}
String responseBody = response.body().string();
JSONObject jsonResponse = JSON.parseObject(responseBody);
// Claude 响应格式与 OpenAI 不同
return jsonResponse
.getJSONArray("content")
.getJSONObject(0)
.getString("text");
}
}
}
4.2 Claude 流式输出
java
package com.example.ai.claude;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import okhttp3.*;
import okio.BufferedSource;
import java.io.IOException;
import java.util.function.Consumer;
/**
* Claude 流式输出客户端
*/
public class ClaudeStreamClient {
private static final String BASE_URL = "https://api.anthropic.com/v1";
private static final String API_VERSION = "2023-06-01";
private static final MediaType JSON_TYPE = MediaType.parse("application/json; charset=utf-8");
private final String apiKey;
private final String model;
private final OkHttpClient httpClient;
public ClaudeStreamClient(String apiKey, String model) {
this.apiKey = apiKey;
this.model = model;
this.httpClient = new OkHttpClient.Builder()
.connectTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
.readTimeout(300, java.util.concurrent.TimeUnit.SECONDS)
.build();
}
/**
* 流式对话
*/
public void streamChat(String systemPrompt,
String userMessage,
Consumer<String> onToken,
Runnable onComplete) throws IOException {
JSONObject requestBody = new JSONObject();
requestBody.put("model", model);
requestBody.put("max_tokens", 2048);
requestBody.put("stream", true);
if (systemPrompt != null && !systemPrompt.isEmpty()) {
requestBody.put("system", systemPrompt);
}
JSONObject userMsg = new JSONObject();
userMsg.put("role", "user");
userMsg.put("content", userMessage);
requestBody.put("messages", new Object[]{userMsg});
Request request = new Request.Builder()
.url(BASE_URL + "/messages")
.header("x-api-key", apiKey)
.header("anthropic-version", API_VERSION)
.header("Accept", "text/event-stream")
.post(RequestBody.create(requestBody.toJSONString(), JSON_TYPE))
.build();
try (Response response = httpClient.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("请求失败: " + response.code());
}
BufferedSource source = response.body().source();
String line;
while (!source.exhausted()) {
line = source.readUtf8Line();
if (line == null || line.isEmpty()) continue;
if (line.startsWith("data: ")) {
String data = line.substring(6);
try {
JSONObject chunk = JSON.parseObject(data);
String eventType = chunk.getString("type");
// 内容增量事件
if ("content_block_delta".equals(eventType)) {
String text = chunk
.getJSONObject("delta")
.getString("text");
if (text != null && !text.isEmpty()) {
onToken.accept(text);
}
}
// 消息结束事件
if ("message_stop".equals(eventType)) {
if (onComplete != null) onComplete.run();
}
} catch (Exception ignored) {
}
}
}
}
}
}
五、Spring Boot 完整集成
5.1 配置文件
yaml
# application.yml
ai:
openai:
api-key: ${OPENAI_API_KEY:your-openai-key}
model: gpt-4o
base-url: https://api.openai.com/v1
claude:
api-key: ${CLAUDE_API_KEY:your-claude-key}
model: claude-3-5-sonnet-20241022
base-url: https://api.anthropic.com/v1
5.2 配置属性类
java
package com.example.ai.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "ai")
public class AiProperties {
private OpenAiProperties openai = new OpenAiProperties();
private ClaudeProperties claude = new ClaudeProperties();
@Data
public static class OpenAiProperties {
private String apiKey;
private String model = "gpt-4o";
private String baseUrl = "https://api.openai.com/v1";
}
@Data
public static class ClaudeProperties {
private String apiKey;
private String model = "claude-3-5-sonnet-20241022";
private String baseUrl = "https://api.anthropic.com/v1";
}
}
5.3 统一 AI 服务层
java
package com.example.ai.service;
import com.example.ai.claude.ClaudeClient;
import com.example.ai.claude.ClaudeStreamClient;
import com.example.ai.config.AiProperties;
import com.example.ai.openai.OpenAiClient;
import com.example.ai.openai.OpenAiStreamClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 统一 AI 服务
* 封装 OpenAI 和 Claude,对外提供统一接口
*/
@Service
public class AiService {
@Autowired
private AiProperties aiProperties;
private final ExecutorService executor = Executors.newCachedThreadPool();
/**
* OpenAI 普通对话
*/
public String openaiChat(String message) throws IOException {
OpenAiClient client = new OpenAiClient(
aiProperties.getOpenai().getApiKey(),
aiProperties.getOpenai().getModel()
);
return client.chat(message);
}
/**
* Claude 普通对话
*/
public String claudeChat(String message) throws IOException {
ClaudeClient client = new ClaudeClient(
aiProperties.getClaude().getApiKey(),
aiProperties.getClaude().getModel()
);
return client.chat(message);
}
/**
* OpenAI 流式对话(SSE)
*/
public SseEmitter openaiStreamChat(String message) {
SseEmitter emitter = new SseEmitter(120_000L);
executor.submit(() -> {
try {
OpenAiStreamClient client = new OpenAiStreamClient(
aiProperties.getOpenai().getApiKey(),
aiProperties.getOpenai().getModel()
);
client.streamChat(
message,
token -> {
try {
emitter.send(SseEmitter.event().data(token));
} catch (IOException e) {
emitter.completeWithError(e);
}
},
() -> {
try {
emitter.send(SseEmitter.event().name("done").data("[DONE]"));
emitter.complete();
} catch (IOException e) {
emitter.completeWithError(e);
}
}
);
} catch (Exception e) {
emitter.completeWithError(e);
}
});
return emitter;
}
/**
* Claude 流式对话(SSE)
*/
public SseEmitter claudeStreamChat(String systemPrompt, String message) {
SseEmitter emitter = new SseEmitter(120_000L);
executor.submit(() -> {
try {
ClaudeStreamClient client = new ClaudeStreamClient(
aiProperties.getClaude().getApiKey(),
aiProperties.getClaude().getModel()
);
client.streamChat(
systemPrompt,
message,
token -> {
try {
emitter.send(SseEmitter.event().data(token));
} catch (IOException e) {
emitter.completeWithError(e);
}
},
() -> {
try {
emitter.send(SseEmitter.event().name("done").data("[DONE]"));
emitter.complete();
} catch (IOException e) {
emitter.completeWithError(e);
}
}
);
} catch (Exception e) {
emitter.completeWithError(e);
}
});
return emitter;
}
}
5.4 Controller 层
java
package com.example.ai.controller;
import com.example.ai.service.AiService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.util.Map;
@RestController
@RequestMapping("/ai")
public class AiController {
@Autowired
private AiService aiService;
/**
* OpenAI 普通对话
* POST /ai/openai/chat
* Body: {"message": "你好"}
*/
@PostMapping("/openai/chat")
public Map<String, String> openaiChat(@RequestBody Map<String, String> body) throws Exception {
String message = body.get("message");
String reply = aiService.openaiChat(message);
return Map.of("reply", reply);
}
/**
* Claude 普通对话
* POST /ai/claude/chat
* Body: {"message": "你好"}
*/
@PostMapping("/claude/chat")
public Map<String, String> claudeChat(@RequestBody Map<String, String> body) throws Exception {
String message = body.get("message");
String reply = aiService.claudeChat(message);
return Map.of("reply", reply);
}
/**
* OpenAI 流式对话(SSE)
* GET /ai/openai/stream?message=你好
*/
@GetMapping(value = "/openai/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter openaiStream(@RequestParam String message) {
return aiService.openaiStreamChat(message);
}
/**
* Claude 流式对话(SSE)
* GET /ai/claude/stream?message=你好
*/
@GetMapping(value = "/claude/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter claudeStream(@RequestParam String message,
@RequestParam(required = false) String system) {
return aiService.claudeStreamChat(system, message);
}
}
六、前端接收流式输出
html
<!DOCTYPE html>
<html>
<head>
<title>AI 对话</title>
</head>
<body>
<textarea id="input" placeholder="输入消息..." rows="3" cols="50"></textarea>
<br>
<button onclick="sendMessage()">发送(OpenAI)</button>
<button onclick="sendMessageClaude()">发送(Claude)</button>
<div id="output" style="border:1px solid #ccc; padding:10px; min-height:100px; white-space:pre-wrap;"></div>
<script>
function sendMessage() {
const message = document.getElementById('input').value
const output = document.getElementById('output')
output.textContent = ''
// 使用 EventSource 接收 SSE 流
const eventSource = new EventSource(
`/ai/openai/stream?message=${encodeURIComponent(message)}`
)
eventSource.onmessage = (event) => {
if (event.data === '[DONE]') {
eventSource.close()
return
}
output.textContent += event.data
}
eventSource.onerror = () => {
eventSource.close()
}
}
function sendMessageClaude() {
const message = document.getElementById('input').value
const output = document.getElementById('output')
output.textContent = ''
const eventSource = new EventSource(
`/ai/claude/stream?message=${encodeURIComponent(message)}`
)
eventSource.onmessage = (event) => {
if (event.data === '[DONE]') {
eventSource.close()
return
}
output.textContent += event.data
}
eventSource.onerror = () => {
eventSource.close()
}
}
</script>
</body>
</html>
七、OpenAI vs Claude 接口差异对比
| 对比项 | OpenAI | Claude |
|---|---|---|
| 认证方式 | Authorization: Bearer {key} |
x-api-key: {key} |
| 版本头 | 无需 | anthropic-version: 2023-06-01 |
| System 位置 | messages 数组中 | 独立的 system 字段 |
| 响应格式 | choices[0].message.content |
content[0].text |
| 流式事件 | choices[0].delta.content |
content_block_delta.delta.text |
| 流结束标志 | data: [DONE] |
event: message_stop |
八、常见问题
Q1:API Key 如何安全存储?
bash
# 方法1:环境变量(推荐)
export OPENAI_API_KEY=sk-xxx
export CLAUDE_API_KEY=sk-ant-xxx
# 方法2:application.yml 读取环境变量
ai:
openai:
api-key: ${OPENAI_API_KEY}
# 方法3:Nacos/Apollo 配置中心(生产推荐)
Q2:如何处理 API 限流(429 错误)?
java
// 添加重试机制
public String chatWithRetry(String message, int maxRetries) throws IOException {
for (int i = 0; i < maxRetries; i++) {
try {
return chat(message);
} catch (IOException e) {
if (e.getMessage().contains("429") && i < maxRetries - 1) {
// 指数退避
Thread.sleep((long) Math.pow(2, i) * 1000);
} else {
throw e;
}
}
}
throw new IOException("超过最大重试次数");
}
Q3:如何控制费用?
java
// 设置 max_tokens 限制输出长度
requestBody.put("max_tokens", 500); // 限制最多500个token
// 使用更便宜的模型处理简单任务
// gpt-4o-mini 比 gpt-4o 便宜约17倍
// claude-3-5-haiku 比 claude-3-5-sonnet 便宜约4倍
Q4:国内无法访问怎么办?
java
// 配置代理
OkHttpClient client = new OkHttpClient.Builder()
.proxy(new java.net.Proxy(
java.net.Proxy.Type.HTTP,
new java.net.InetSocketAddress("127.0.0.1", 7890)
))
.build();
九、总结
| 功能 | OpenAI | Claude |
|---|---|---|
| 普通对话 | ✅ | ✅ |
| 系统提示词 | ✅ | ✅ |
| 多轮对话 | ✅ | ✅ |
| 流式输出 | ✅ | ✅ |
| Spring Boot 集成 | ✅ | ✅ |
两个 API 的核心逻辑相似,主要差异在于请求头格式 和响应体结构,掌握本文的封装方式后,可以轻松切换和扩展其他大模型 API(如 DeepSeek、通义千问等)。
📎 相关文章推荐
- 《本地部署 Ollama + DeepSeek 完整指南》
- 《Spring AI 实战:Java 接入大模型》
- 《RAG 知识库问答系统实战》
原创不易,如果对你有帮助,点个赞再走~
关注我,持续分享 Java + AI 开发实战!