背景
今天接到个很有意思的需求,本来是前端直接对接AI大模型的接口并流式返回,并没有后端什么事。但是需要对请求做一些额外的组装,所以变成了前端去请求后端API接口,后端对接AI大模型,并且流式返回数据给前端。相当于套了一层娃。如下图所示
变成了
技术方案
一开始折腾了好久,后面终于找到了这个神器。
.SseEmitter
SseEmitter是 Spring Framework 提供的一个类,用于实现服务器发送事件(Server-Sent Events,简称 SSE)。SSE 是一种基于 HTTP 协议的单向通信机制,允许服务器向客户端推送实时数据,而不需要客户端频繁地轮询服务器
核心特点
.SSE 是单向的,仅支持服务器向客户端推送数据。
.基于 HTTP:使用标准的 HTTP 协议,兼容性好,无需额外协议支持。
.自动重连:客户端会在连接断开后自动尝试重新连接。
.轻量级:相比 WebSocket,SSE 更简单,适合单向推送场景。
使用场景
.实时通知(如消息提醒)。
.实时数据更新(如股票价格、新闻推送)。
.流式数据传输(如日志流、聊天应用)
上代码
附上okhttp相关pom.xml,SseEmitter由于是Spring Framework 的,我的是springboot项目,所以不需要再导包了
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp-sse</artifactId>
<version>4.2.0</version>
</dependency>
@Data
public class GptRequestDTO {
private String content;
private String role;
}
private SseEmitter callGPT() {
SseEmitter sseEmitter = new SseEmitter();
String prompt="你好";
String gptUrl = "你的ai大模型请求地址";
String token = "你的ai大模型请求秘钥";
//开始组装大模型的请求参数------可按实际情况修改
HashMap<String,Object> params = new HashMap<>();
params.put("chatId","xnkj");
params.put("stream",true);
params.put("detail",false);
HashMap<String,Object> variables = new HashMap<>();
variables.put("uid","asdfadsfasfd2323");
variables.put("detail","张三");
params.put("variables",variables);
// 这里我使用了实体类,使用其他形式封装都可以,看个人
List<GptRequestDTO> messages = new ArrayList<>();
GptRequestDTO message = new GptRequestDTO();
message.setRole("user");
message.setContent(prompt);
messages.add(message);
params.put("messages",messages);
// 转换为 JSON 字符串----我使用的是hutool工具
String jsonParams = JSONUtil.toJsonStr(params);
// 使用 OkHttpClient 发送请求
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(50, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.MINUTES)
.build();
Request request = new Request.Builder()
.url(gptUrl)
.addHeader("Authorization", "Bearer " + token)
.addHeader("Content-Type", "application/json")
.post(okhttp3.RequestBody.create(okhttp3.MediaType.parse("application/json; charset=utf-8"),
jsonParams))
.build();
// 发送请求并处理响应
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
log.error("请求失败", e);
sseEmitter.completeWithError(e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful()) {
log.error("Unexpected response code: " + response.code());
sseEmitter.completeWithError(new IOException("Unexpected response code: " + response.code()));
return;
}
try (BufferedReader reader = new BufferedReader(new InputStreamReader(response.body().byteStream()))) {
String line;
while ((line = reader.readLine()) != null) {
if (line.trim().isEmpty()) {
continue; // 跳过空行
}
sseEmitter.send(line); // 将每行数据发送到前端
}
} catch (Exception e) {
log.error("推送数据失败", e);
sseEmitter.completeWithError(e);
} finally {
sseEmitter.complete(); // 完成 SseEmitter
}
}
});
return sseEmitter; // 返回 SseEmitter 实例
}
postman请求接口后返回如下图
前端接受数据的时候,也需要流式接收。
至此,收工,有问题欢迎留言沟通交流。