集成ai部分
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
package com.ruoyi.cms.controller.app;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.ruoyi.cms.domain.ai.AiChatHistory;
|
||||
import com.ruoyi.cms.domain.chat.ChatRequest;
|
||||
import com.ruoyi.cms.service.AiChatHistoryService;
|
||||
import com.ruoyi.common.annotation.BussinessLog;
|
||||
import com.ruoyi.cms.config.ChatClient;
|
||||
import com.ruoyi.cms.config.ChatConfig;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.utils.SiteSecurityUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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 javax.annotation.PreDestroy;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.ruoyi.common.enums.BusinessType.QUERY;
|
||||
|
||||
@RestController
|
||||
@RequestMapping(value = "/app/chat")
|
||||
@Slf4j
|
||||
@Api(tags = "移动端:ai对话")
|
||||
public class ChatController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
ChatClient chatClient;
|
||||
@Autowired
|
||||
ChatConfig chatConfig;
|
||||
@Autowired
|
||||
AiChatHistoryService aiChatHistoryService;
|
||||
|
||||
//private final ExecutorService executor = Executors.newCachedThreadPool();
|
||||
// 可优化线程池配置,避免无限创建线程
|
||||
private final ExecutorService executor = new ThreadPoolExecutor(
|
||||
2, // 核心线程数
|
||||
10, // 最大线程数
|
||||
30, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(50), // 任务队列
|
||||
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略(避免任务丢失)
|
||||
);
|
||||
|
||||
@BussinessLog(title = "查询用户的聊天历史记录",businessType = QUERY)
|
||||
@ApiOperation("查询用户的聊天历史记录")
|
||||
@GetMapping(value = "/getHistory")
|
||||
public AjaxResult getChatHistoryList(AiChatHistory chatHistory) {
|
||||
return AjaxResult.success(aiChatHistoryService.getList(chatHistory));
|
||||
}
|
||||
|
||||
@BussinessLog(title = "查询用户的聊天详情",businessType = QUERY)
|
||||
@ApiOperation("查询用户的聊天详情")
|
||||
@GetMapping(value = "/detail")
|
||||
public AjaxResult getChatDetail(ChatRequest request) {
|
||||
return AjaxResult.success(aiChatHistoryService.getDetailList(request.getSessionId()));
|
||||
}
|
||||
|
||||
// 处理前端聊天请求,返回 SSE 发射器
|
||||
@ApiOperation("用户AI聊天")
|
||||
@PostMapping("/chat")
|
||||
@ResponseBody
|
||||
@BussinessLog(title = "用户AI聊天",businessType = QUERY)
|
||||
public SseEmitter chatStream(@RequestBody ChatRequest request) {
|
||||
// 设置超时时间(30分钟)
|
||||
SseEmitter emitter = new SseEmitter(1800000L);
|
||||
long userId = 0;
|
||||
if(ObjectUtil.isNotEmpty(request.getUserId())&&request.getUserId()!=0){
|
||||
userId = request.getUserId();
|
||||
}else{
|
||||
try {
|
||||
userId = SiteSecurityUtils.getLoginUser().getUserId();
|
||||
}catch (Exception e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
JSONObject contentObject = new JSONObject();
|
||||
contentObject.put("content",request.getData());
|
||||
contentObject.put("role","user");
|
||||
JSONArray array = aiChatHistoryService.getChatHistoryData(request.getSessionId());
|
||||
if(array==null||array.isEmpty()||array.size()==0){
|
||||
array = new JSONArray();
|
||||
}
|
||||
array.add(contentObject);
|
||||
request.setMessages(array);
|
||||
|
||||
List<String> answerList = new ArrayList<>();
|
||||
long[] timeStart = {0};
|
||||
// 异步处理请求并推送数据
|
||||
executor.submit(() -> {
|
||||
try {
|
||||
// 2. 调用ChatClient的流式聊天方法,正确传递参数
|
||||
chatClient.sendStreamingChat(
|
||||
request,
|
||||
new ChatClient.StreamCallback() { // 使用内部类完整路径
|
||||
@Override
|
||||
public void onData(String chunk) {
|
||||
try {
|
||||
if(timeStart[0] == 0){
|
||||
timeStart[0] = System.currentTimeMillis();
|
||||
}
|
||||
// 1. 处理SSE协议格式
|
||||
String processedChunk = chunk.trim();
|
||||
// 解析返回的chunk数据
|
||||
String content = parseChatChunk(processedChunk);
|
||||
if (StringUtils.isNotEmpty(content)) {
|
||||
answerList.add(content);
|
||||
}
|
||||
emitter.send(processedChunk, MediaType.TEXT_EVENT_STREAM);
|
||||
} catch (IOException e) {
|
||||
emitter.completeWithError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
emitter.complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
try {
|
||||
emitter.send(SseEmitter.event()
|
||||
.data("{\"status\":\"error\",\"message\":\"" + e.getMessage() + "\"}")
|
||||
.name("error"));
|
||||
} catch (IOException ex) {
|
||||
// 记录日志
|
||||
} finally {
|
||||
emitter.completeWithError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (Exception e) {
|
||||
emitter.completeWithError(e);
|
||||
}
|
||||
});
|
||||
|
||||
// 处理连接关闭
|
||||
long finalUserId = userId;
|
||||
emitter.onCompletion(() -> {
|
||||
// 此处仅做资源清理,不关闭线程池
|
||||
log.info("连接关闭,清理资源");
|
||||
long timeEnd = System.currentTimeMillis();
|
||||
double duration = (timeEnd - timeStart[0]) / 1000.0;
|
||||
AiChatHistory chatHistory = new AiChatHistory();
|
||||
chatHistory.setChatId(request.getSessionId());
|
||||
chatHistory.setUserId((finalUserId==0)?null:finalUserId);
|
||||
chatHistory.setAppId(chatConfig.getAppId());
|
||||
chatHistory.setDataId(request.getDataId());
|
||||
chatHistory.setTitle(request.getData());
|
||||
chatHistory.setAnswerStringList(answerList);
|
||||
chatHistory.setDurationSeconds(duration);
|
||||
aiChatHistoryService.saveChatHistory(chatHistory);
|
||||
});
|
||||
return emitter;
|
||||
}
|
||||
|
||||
@BussinessLog(title = "提供推测出的问询建议",businessType = QUERY)
|
||||
@ApiOperation("提供推测出的问询建议")
|
||||
@PostMapping(value = "/guest")
|
||||
public AjaxResult getChatGuest(@RequestBody ChatRequest request) throws IOException {
|
||||
JSONArray array = aiChatHistoryService.getChatHistoryData(request.getSessionId());
|
||||
request.setMessages(array);
|
||||
String result = chatClient.sendChatGuest(request);
|
||||
try {
|
||||
String[] strList = result.split("?");
|
||||
List<String> list = new ArrayList<>();
|
||||
for(String str:strList){
|
||||
if(StringUtils.isNotEmpty(str)){
|
||||
str = str+"?";
|
||||
list.add(str);
|
||||
}
|
||||
}
|
||||
return AjaxResult.success(list);
|
||||
}catch (Exception e) {
|
||||
return AjaxResult.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 解析返回的流式数据
|
||||
private String parseChatChunk(String chunk) {
|
||||
try {
|
||||
String processed = chunk.trim();
|
||||
if (processed.startsWith("data:")) {
|
||||
processed = processed.substring("data:".length()).trim();
|
||||
}
|
||||
if (processed.isEmpty() || "[DONE]".equals(processed)) {
|
||||
return null;
|
||||
}
|
||||
JSONObject json = JSONObject.parseObject(processed);
|
||||
String choices = json.getString("choices");
|
||||
if (choices != null && !choices.trim().isEmpty()) {
|
||||
JSONArray jsonArray = JSONArray.parseArray(choices);
|
||||
json = JSONObject.parseObject(jsonArray.getString(0));
|
||||
json = json.getJSONObject("delta");
|
||||
String content = json.getString("content");// 消息内容
|
||||
|
||||
return content;
|
||||
}
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
executor.shutdown(); // 应用退出前关闭线程池
|
||||
try {
|
||||
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
executor.shutdownNow(); // 强制关闭未完成的任务
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
executor.shutdownNow();
|
||||
log.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user