|
|
@@ -0,0 +1,71 @@
|
|
|
+package com.mangoo.rating.recommend.app.controller;
|
|
|
+
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
+import com.mangoo.rating.recommend.ai.service.AiChatService;
|
|
|
+import com.mangoo.rating.recommend.request.ai.AiChatRequest;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.web.bind.annotation.*;
|
|
|
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
|
|
+
|
|
|
+import javax.annotation.Resource;
|
|
|
+import java.util.UUID;
|
|
|
+import java.util.concurrent.Executor;
|
|
|
+
|
|
|
+/** 小程序 AI 会话入口(SSE 流式打字机输出) */
|
|
|
+@Slf4j
|
|
|
+@RestController
|
|
|
+@RequestMapping("/api/ai")
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class AiChatController {
|
|
|
+
|
|
|
+ private final AiChatService aiChatService;
|
|
|
+
|
|
|
+ @Resource(name = "aiChatExecutor")
|
|
|
+ private Executor aiChatExecutor;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 发起一轮 AI 会话,SSE 流式返回。
|
|
|
+ * 用户身份:主线程取 X-USER-BASE64 原始头,透传给下游工具 Feign 调 ratingApp(由其解析隔离)。
|
|
|
+ */
|
|
|
+ @PostMapping("/chat/stream")
|
|
|
+ public SseEmitter chatStream(@RequestBody AiChatRequest req,
|
|
|
+ @RequestHeader(value = "X-USER-BASE64", required = false) String userHeader) {
|
|
|
+ final String sessionId = (req.getSessionId() == null || req.getSessionId().isEmpty())
|
|
|
+ ? UUID.randomUUID().toString().replace("-", "") : req.getSessionId();
|
|
|
+ final String message = req.getMessage();
|
|
|
+ final String header = userHeader;
|
|
|
+
|
|
|
+ SseEmitter emitter = new SseEmitter(600000L); // 10 分钟超时
|
|
|
+ aiChatExecutor.execute(() -> {
|
|
|
+ try {
|
|
|
+ aiChatService.chat(header, sessionId, message, delta -> {
|
|
|
+ try {
|
|
|
+ JSONObject d = new JSONObject();
|
|
|
+ d.put("text", delta);
|
|
|
+ emitter.send(SseEmitter.event().name("delta").data(d.toJSONString()));
|
|
|
+ } catch (Exception se) {
|
|
|
+ throw new RuntimeException(se);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ JSONObject done = new JSONObject();
|
|
|
+ done.put("sessionId", sessionId);
|
|
|
+ done.put("finishReason", "end_turn");
|
|
|
+ emitter.send(SseEmitter.event().name("done").data(done.toJSONString()));
|
|
|
+ emitter.complete();
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("AI 会话处理失败 sessionId={}", sessionId, e);
|
|
|
+ try {
|
|
|
+ JSONObject err = new JSONObject();
|
|
|
+ err.put("code", -1);
|
|
|
+ err.put("msg", "服务繁忙,请稍后再试");
|
|
|
+ emitter.send(SseEmitter.event().name("error").data(err.toJSONString()));
|
|
|
+ emitter.complete();
|
|
|
+ } catch (Exception ignore) {
|
|
|
+ emitter.completeWithError(e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return emitter;
|
|
|
+ }
|
|
|
+}
|