Java调用ChatGPT(基于SpringBoot和Vue)实现连续对话、流式输出和自定义baseUrl
'# Java调用ChatGPT(基于SpringBoot和Vue)实现连续对话、流式输出和自定义baseUrl
一、背景与问题
在现代化的Web应用开发中,集成AI聊天功能已成为常见需求。传统的ChatGPT集成方案通常面临三个核心挑战:
- 流式输出:需要支持实时数据传输,避免阻塞式API调用
- 连续对话:需要维护会话上下文,实现对话的延续性
- 灵活配置:需要支持自定义API地址和版本,适应不同部署环境
传统做法往往直接调用OpenAI官方API,但这种方案在以下场景中存在局限性:
- 需要绕过API限制时(如调用非官方API)
- 需要自定义认证机制时
- 需要实现更复杂的业务逻辑时
本文将探讨基于SpringBoot和Vue的完整解决方案,涵盖流式通信、会话管理、自定义API配置等核心要素。
二、基本原理
1. ChatGPT API通信机制
OpenAI的ChatGPT API采用RESTful架构,核心接口为/v1/chat/completions。调用时需要携带以下关键参数:
model:指定模型版本(如gpt-3.5-turbo)prompt:用户输入内容temperature:控制回复的随机性max_tokens:限制回复长度stream:布尔值控制是否启用流式输出
当stream=true时,API会返回data字段的多个JSON对象,每个对象包含content字段,最终会包含stop字段标记结束。
2. 流式通信协议
流式通信采用分块传输编码(chunked transfer encoding),在HTTP响应头中包含Transfer-Encoding: chunked。每个数据块以<开头,>结尾,格式如下:
<chunk-length>\r\n
chunk-data\r\n
\r\n在Java中需要特别处理这种分块数据,前端则需要使用EventSource(SSE)或WebSocket来接收流式数据。
3. 会话管理机制
连续对话需要维护会话上下文,通常采用以下策略:
- 前端维护一个会话状态机(session state machine)
- 后端在每次请求时携带上文内容
- 使用UUID作为会话标识符
- 简单的会话存储(如内存缓存或Redis)
三、环境准备
1. 依赖配置
SpringBoot项目需添加以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>Vue项目需安装axios和eventsource:
npm install axios eventsource2. 配置文件
在application.yml中配置自定义API信息:
chatgpt:
baseUrl: https://api.example.com/v1
apiKey: YOUR_API_KEY
model: gpt-3.5-turbo
maxTokens: 1024
temperature: 0.7四、核心实现
1. 流式响应处理(SpringBoot)
@RestController
public class ChatController {
@Autowired
private ChatService chatService;
@GetMapping("/stream")
public SseEmitter streamChat(@RequestParam String content, @RequestParam String sessionId) {
return chatService.handleStreamRequest(content, sessionId);
}
}@Service
public class ChatService {
private final String baseUrl;
private final String apiKey;
private final String model;
public ChatService() {
this.baseUrl = System.getenv("CHATGPT_BASE_URL");
this.apiKey = System.getenv("CHATGPT_API_KEY");
this.model = System.getenv("CHATGPT_MODEL");
}
public SseEmitter handleStreamRequest(String content, String sessionId) {
SseEmitter emitter = new SseEmitter(60_000L); // 60秒超时
try {
String url = baseUrl + "/chat/completions";
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(
String.format(
"{ \"model\": \"%s\", \"prompt\": \"%s\", \"stream\": true }",
model, content
)
))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
emitter.send(SseEmitter.event().data("error: " + response.statusCode()));
emitter.complete();
return null;
}
// 处理流式响应
new Thread(() -> {
try (InputStream stream = response.body().getBytes(StandardCharsets.UTF_8).inputStream()) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = stream.read(buffer)) > 0) {
String chunk = new String(buffer, 0, bytesRead, StandardCharsets.UTF_8);
if (chunk.startsWith("<")) {
String content = chunk.substring(1, chunk.length() - 1).trim();
if (content.contains("stop")) {
emitter.send(SseEmitter.event().data("end"));
} else {
emitter.send(SseEmitter.event().data(content));
}
}
}
} catch (Exception e) {
emitter.send(SseEmitter.event().data("error: " + e.getMessage()));
} finally {
emitter.complete();
}
}).start();
return emitter;
} catch (Exception e) {
emitter.send(SseEmitter.event().data("error: " + e.getMessage()));
emitter.complete();
return null;
}
}
}2. 前端流式处理(Vue)
<template>
<div>
<div v-if="chatHistory.length" class="chat-history">
<div v-for="(msg, index) in chatHistory" :key="index" class="chat-message">
{{ msg }}
</div>
</div>
<div class="chat-input">
<input v-model="userInput" placeholder="输入你的问题" />
<button @click="sendMessage">发送</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
userInput: '',
chatHistory: [],
emitter: null,
sessionId: '123456'
};
},
methods: {
async sendMessage() {
if (!this.userInput.trim()) return;
this.chatHistory.push('你: ' + this.userInput);
this.userInput = '';
this.emitter = new EventSource(`${process.env.VUE_APP_API_URL}/stream?sessionId=${this.sessionId}`);
this.emitter.onmessage = (event) => {
const data = event.data;
if (data === 'end') {
this.chatHistory.push('ChatGPT: 完成');
} else {
this.chatHistory.push('ChatGPT: ' + data);
}
};
this.emitter.onerror = (err) => {
console.error('EventSource error:', err);
this.chatHistory.push('ChatGPT: 错误');
this.emitter.close();
};
}
},
beforeUnmount() {
if (this.emitter) {
this.emitter.close();
}
}
};
</script>3. 会话管理优化
public class SessionManager {
private final Map<String, String> sessionContexts = new ConcurrentHashMap<>();
public String getConversationHistory(String sessionId) {
return sessionContexts.getOrDefault(sessionId, "");
}
public void saveConversationHistory(String sessionId, String history) {
sessionContexts.put(sessionId, history);
}
public void clearSession(String sessionId) {
sessionContexts.remove(sessionId);
}
}五、完整案例
1. 项目结构
src
├── main
│ ├── java
│ │ └── com.example
│ │ └── ChatApplication.java
│ │ └── controller
│ │ └── service
│ │ └── model
│ └── resources
│ └── application.yml
└── test2. 完整的SpringBoot服务端
@RestController
public class ChatController {
@Autowired
private ChatService chatService;
@Autowired
private SessionManager sessionManager;
@GetMapping("/stream")
public SseEmitter streamChat(@RequestParam String content, @RequestParam String sessionId) {
return chatService.handleStreamRequest(content, sessionId, sessionManager);
}
}@Service
public class ChatService {
private final String baseUrl;
private final String apiKey;
private final String model;
public ChatService() {
this.baseUrl = System.getenv("CHATGPT_BASE_URL");
this.apiKey = System.getenv("CHATGPT_API_KEY");
this.model = System.getenv("CHATGPT_MODEL");
}
public SseEmitter handleStreamRequest(String content, String sessionId, SessionManager sessionManager) {
SseEmitter emitter = new SseEmitter(60_000L);
String context = sessionManager.getConversationHistory(sessionId);
String fullContent = context + "\n" + content;
try {
String url = baseUrl + "/chat/completions";
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(
String.format(
"{ \"model\": \"%s\", \"prompt\": \"%s\", \"stream\": true }",
model, fullContent
)
))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
emitter.send(SseEmitter.event().data("error: " + response.statusCode()));
emitter.complete();
return null;
}
new Thread(() -> {
try (InputStream stream = response.body().getBytes(StandardCharsets.UTF_8).inputStream()) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = stream.read(buffer)) > 0) {
String chunk = new String(buffer, 0, bytesRead, StandardCharsets.UTF_8);
if (chunk.startsWith("<")) {
String content = chunk.substring(1, chunk.length() - 1).trim();
if (content.contains("stop")) {
emitter.send(SseEmitter.event().data("end"));
} else {
emitter.send(SseEmitter.event().data(content));
}
}
}
} catch (Exception e) {
emitter.send(SseEmitter.event().data("error: " + e.getMessage()));
} finally {
emitter.complete();
sessionManager.saveConversationHistory(sessionId, context + "\n" + content + "\n" + content);
}
}).start();
return emitter;
} catch (Exception e) {
emitter.send(SseEmitter.event().data("error: " + e.getMessage()));
emitter.complete();
return null;
}
}
}六、源码解析
1. 流式处理线程
new Thread(() -> {
try (InputStream stream = response.body().getBytes(StandardCharsets.UTF_8).inputStream()) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = stream.read(buffer)) > 0) {
String chunk = new String(buffer, 0, bytesRead, StandardCharsets.UTF_8);
if (chunk.startsWith("<")) {
String content = chunk.substring(1, chunk.length() - 1).trim();
if (content.contains("stop")) {
emitter.send(SseEmitter.event().data("end"));
} else {
emitter.send(SseEmitter.event().data(content));
}
}
}
} catch (Exception e) {
emitter.send(SseEmitter.event().data("error: " + e.getMessage()));
} finally {
emitter.complete();
sessionManager.saveConversationHistory(sessionId, context + "\n" + content + "\n" + content);
}
}).start();这段代码创建了一个独立线程处理流式响应。通过缓冲区读取每个数据块,判断是否包含stop字段来判断结束。注意在finally块中保存会话状态,确保会话上下文的持久化。
2. 前端事件处理
this.emitter.onmessage = (event) => {
const data = event.data;
if (data === 'end') {
this.chatHistory.push('ChatGPT: 完成');
} else {
this.chatHistory.push('ChatGPT: ' + data);
}
};前端通过EventSource监听事件,当收到end事件时表示对话结束,否则将接收到的文本内容追加到聊天记录中。
七、进阶使用
1. 高级会话管理
public class SessionManager {
private final Map<String, String> sessionContexts = new ConcurrentHashMap<>();
private final int maxSessionLength = 100; // 最大会话长度
public String getConversationHistory(String sessionId) {
return sessionContexts.getOrDefault(sessionId, "");
}
public void saveConversationHistory(String sessionId, String history) {
if (history.length() > maxSessionLength) {
history = history.substring(history.length() - maxSessionLength);
}
sessionContexts.put(sessionId, history);
}
public void clearSession(String sessionId) {
sessionContexts.remove(sessionId);
}
}增加会话长度限制,防止内存溢出。在保存会话历史时截断过长的文本。
2. 异步处理优化
public SseEmitter handleStreamRequest(String content, String sessionId, SessionManager sessionManager) {
SseEmitter emitter = new SseEmitter(60_000L);
String context = sessionManager.getConversationHistory(sessionId);
String fullContent = context + "\n" + content;
try {
String url = baseUrl + "/chat/completions";
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(
String.format(
"{ \"model\": \"%s\", \"prompt\": \"%s\", \"stream\": true }",
model, fullContent
)
))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
emitter.send(SseEmitter.event().data("error: " + response.statusCode()));
emitter.complete();
return null;
}
new Thread(() -> {
try (InputStream stream = response.body().getBytes(StandardCharsets.UTF_8).inputStream()) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = stream.read(buffer)) > 0) {
String chunk = new String(buffer, 0, bytesRead, StandardCharsets.UTF_8);
if (chunk.startsWith("<")) {
String content = chunk.substring(1, chunk.length() - 1).trim();
if (content.contains("stop")) {
emitter.send(SseEmitter.event().data("end"));
} else {
emitter.send(SseEmitter.event().data(content));
}
}
}
} catch (Exception e) {
emitter.send(SseEmitter.event().data("error: " + e.getMessage()));
} finally {
emitter.complete();
sessionManager.saveConversationHistory(sessionId, context + "\n" + content + "\n" + content);
}
}).start();
return emitter;
} catch (Exception e) {
emitter.send(SseEmitter.event().data("error: " + e.getMessage()));
emitter.complete();
return null;
}
}添加了异常处理和资源清理逻辑,确保在异常情况下也能正确释放资源。
八、性能与工程实践
1. 性能优化策略
- 连接复用:使用HttpClient的连接池功能
- 缓存机制:对频繁访问的API进行缓存
- 异步处理:使用CompletableFuture进行异步处理
- 限流控制:添加请求限流机制防止API被封禁
2. 安全考虑
- API密钥管理:使用Vault或Spring Cloud Config进行密钥管理
- HTTPS加密:确保所有通信都使用HTTPS
- 输入验证:对用户输入进行严格过滤
- 速率限制:防止恶意请求导致服务器过载
3. 异常处理机制
try {
// 原始代码
} catch (Exception e) {
emitter.send(SseEmitter.event().data("error: " + e.getMessage()));
emitter.complete();
return null;
}添加更详细的异常分类处理,区分网络错误、API错误等不同情况。
九、常见问题与踩坑
1. 常见错误分析
错误1:流式数据处理错误
String content = chunk.substring(1, chunk.length() - 1).trim();原因:未处理不完整的分块数据
解决:增加缓冲区处理逻辑
错误2:会话状态丢失
sessionManager.saveConversationHistory(sessionId, context + "\n" + content + "\n" + content);原因:未正确拼接会话历史
解决:使用更精确的字符串拼接方法
2. 常见问题解决方案
问题1:连接超时
解决方案:调整超时时间,增加重试机制
问题2:API限制
解决方案:添加速率限制,使用代理服务器
问题3:数据丢失
解决方案:增加日志记录,启用断点续传
十、最佳实践
1. 推荐方案
- 使用SSE实现流式通信
- 采用内存缓存+Redis持久化会话状态
- 使用HttpClient进行连接复用
- 对API进行封装,统一处理异常
2. 实施建议
- 对关键数据进行持久化存储
- 实现完善的日志系统
- 定期进行压力测试
- 使用监控系统跟踪API调用情况
十一、总结
本文深入探讨了基于SpringBoot和Vue实现ChatGPT集成的技术方案。通过分析流式通信、会话管理和自定义API配置等核心要素,给出了完整的实现方案。在实际开发中,这种方案适用于需要实时交互的场景,但需要注意以下事项:
适用场景:
- 需要实时对话的客服系统
- 需要保持上下文的智能问答系统
- 需要自定义API配置的混合部署环境
不适用场景:
- 需要高并发的分布式系统
- 需要复杂业务逻辑的微服务架构
- 需要安全敏感的金融系统
通过合理的设计和优化,这种方案可以在保证功能完整性的前提下,实现良好的性能和可维护性。在实际开发中,需要根据具体需求选择合适的实现方式,并注意安全性和性能优化。
评论已关闭