mirror of
https://gitcode.com/ageerle/ruoyi-ai.git
synced 2026-04-15 21:03:40 +00:00
refactor: 重构聊天模块架构
- 删除废弃的ChatMessageDTO、ChatContext、AbstractChatMessageService等类 - 迁移ChatServiceFactory和IChatMessageService到ruoyi-chat模块 - 重构ChatHandler体系,移除DefaultChatHandler和ChatContextBuilder - 优化SSE消息处理,新增SseEventDto - 简化各AI服务提供商实现类代码 - 优化工作流节点消息处理逻辑 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,45 +0,0 @@
|
||||
package org.ruoyi.common.chat.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 聊天消息DTO - 用于上下文传递
|
||||
*
|
||||
* @author ageerle@163.com
|
||||
* @date 2025/12/13
|
||||
*/
|
||||
@Data
|
||||
public class ChatMessageDTO {
|
||||
|
||||
/**
|
||||
* 消息角色: system/user/assistant
|
||||
*/
|
||||
private String role;
|
||||
|
||||
/**
|
||||
* 消息内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
public static ChatMessageDTO system(String content) {
|
||||
ChatMessageDTO msg = new ChatMessageDTO();
|
||||
msg.role = "system";
|
||||
msg.content = content;
|
||||
return msg;
|
||||
}
|
||||
|
||||
public static ChatMessageDTO user(String content) {
|
||||
ChatMessageDTO msg = new ChatMessageDTO();
|
||||
msg.role = "user";
|
||||
msg.content = content;
|
||||
return msg;
|
||||
}
|
||||
|
||||
public static ChatMessageDTO assistant(String content) {
|
||||
ChatMessageDTO msg = new ChatMessageDTO();
|
||||
msg.role = "assistant";
|
||||
msg.content = content;
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package org.ruoyi.common.chat.domain.dto.request;
|
||||
|
||||
import dev.langchain4j.data.message.ChatMessage;
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Data;
|
||||
import org.ruoyi.common.chat.domain.dto.ChatMessageDTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 对话请求对象
|
||||
@@ -16,11 +16,15 @@ import java.util.List;
|
||||
@Data
|
||||
public class ChatRequest {
|
||||
|
||||
@NotEmpty(message = "对话消息不能为空")
|
||||
private List<ChatMessageDTO> messages;
|
||||
@NotEmpty(message = "传入的模型不能为空")
|
||||
private String model;
|
||||
|
||||
/**
|
||||
* 对话消息
|
||||
*/
|
||||
@NotEmpty(message = "对话消息不能为空")
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 工作流请求体
|
||||
*/
|
||||
@@ -31,59 +35,49 @@ public class ChatRequest {
|
||||
*/
|
||||
private ReSumeRunner reSumeRunner;
|
||||
|
||||
/**
|
||||
* 是否为人机交互用户继续输入
|
||||
*/
|
||||
private Boolean isResume = false;
|
||||
|
||||
/**
|
||||
* 是否启用工作流
|
||||
*/
|
||||
private Boolean enableWorkFlow;
|
||||
private Boolean enableWorkFlow = false;
|
||||
|
||||
/**
|
||||
* 会话id
|
||||
*/
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
@JSONField(serializeUsing = String.class)
|
||||
private Long sessionId;
|
||||
|
||||
/**
|
||||
* 应用ID
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* 知识库id
|
||||
*/
|
||||
private String knowledgeId;
|
||||
|
||||
/**
|
||||
* 对话id(每个聊天窗口都不一样)
|
||||
* 应用ID
|
||||
*/
|
||||
private Long uuid;
|
||||
private String appId;
|
||||
|
||||
|
||||
/**
|
||||
* 是否为人机交互用户继续输入
|
||||
* 对话id(每个聊天窗口都不一样)
|
||||
*/
|
||||
private Boolean isResume;
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
@JSONField(serializeUsing = String.class)
|
||||
private Long uuid;
|
||||
|
||||
/**
|
||||
* 是否启用深度思考
|
||||
*/
|
||||
private Boolean enableThinking;
|
||||
|
||||
/**
|
||||
* 是否自动切换模型
|
||||
*/
|
||||
private Boolean autoSelectModel;
|
||||
private Boolean enableThinking = false;
|
||||
|
||||
/**
|
||||
* 是否支持联网
|
||||
*/
|
||||
private Boolean enableInternet;
|
||||
|
||||
/**
|
||||
* 会话令牌(为避免在非Web线程中获取Request,入口处注入)
|
||||
*/
|
||||
private String token;
|
||||
|
||||
/**
|
||||
* 原生对话对象
|
||||
*/
|
||||
private List<ChatMessage> chatMessages;
|
||||
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@ import cn.idev.excel.annotation.ExcelProperty;
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatMessage;
|
||||
import org.ruoyi.common.excel.annotation.ExcelDictFormat;
|
||||
import org.ruoyi.common.excel.convert.ExcelDictConvert;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
@@ -6,12 +6,14 @@ import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class BaseEntity implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
package org.ruoyi.common.chat.entity.chat;
|
||||
|
||||
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||
import org.ruoyi.common.chat.service.chat.IChatService;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
/**
|
||||
* 聊天对话上下文对象
|
||||
*
|
||||
* @author zengxb
|
||||
* @date 2026-02-14
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Builder
|
||||
public class ChatContext {
|
||||
|
||||
/**
|
||||
* 模型管理视图对象
|
||||
*/
|
||||
@NotNull(message = "模型管理视图对象不能为空")
|
||||
private ChatModelVo chatModelVo;
|
||||
|
||||
/**
|
||||
* 对话请求对象
|
||||
*/
|
||||
@NotNull(message = "对话请求对象不能为空")
|
||||
private ChatRequest chatRequest;
|
||||
|
||||
/**
|
||||
* SSe连接对象
|
||||
*/
|
||||
@NotNull(message = "SSe连接对象不能为空")
|
||||
private SseEmitter emitter;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
@NotNull(message = "用户ID不能为空")
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* Token
|
||||
*/
|
||||
@NotNull(message = "Token不能为空")
|
||||
private String tokenValue;
|
||||
|
||||
/**
|
||||
* 响应处理器
|
||||
*/
|
||||
private StreamingChatResponseHandler handler;
|
||||
|
||||
/**
|
||||
* 聊天服务实例
|
||||
*/
|
||||
private IChatService chatService;
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package org.ruoyi.common.chat.factory;
|
||||
|
||||
import org.ruoyi.common.chat.service.chat.IChatService;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 聊天服务工厂类
|
||||
*
|
||||
* @author ageerle@163.com
|
||||
* @date 2025-12-13
|
||||
*/
|
||||
@Component
|
||||
public class ChatServiceFactory implements ApplicationContextAware {
|
||||
|
||||
private final Map<String, IChatService> chatServiceMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
// 初始化时收集所有IChatService的实现
|
||||
Map<String, IChatService> serviceMap = applicationContext.getBeansOfType(IChatService.class);
|
||||
for (IChatService service : serviceMap.values()) {
|
||||
if (service != null ) {
|
||||
chatServiceMap.put(service.getProviderName(), service);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取原始服务(不包装代理)
|
||||
*/
|
||||
public IChatService getOriginalService(String category) {
|
||||
IChatService service = chatServiceMap.get(category);
|
||||
if (service == null) {
|
||||
throw new IllegalArgumentException("不支持的模型类别: " + category);
|
||||
}
|
||||
return service;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
package org.ruoyi.common.chat.service.chat;
|
||||
|
||||
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
|
||||
import jakarta.validation.Valid;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatContext;
|
||||
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
/**
|
||||
@@ -12,10 +13,15 @@ public interface IChatService {
|
||||
/**
|
||||
* 客户端发送对话消息到服务端
|
||||
*/
|
||||
SseEmitter chat(@Valid ChatContext chatContext);
|
||||
SseEmitter chat(@Valid ChatRequest chatRequest);
|
||||
|
||||
/**
|
||||
* 获取服务提供商名称
|
||||
* 支持外部 handler 的对话接口(跨模块调用)
|
||||
* 同时发送到 SSE 和外部 handler
|
||||
*
|
||||
* @param chatRequest 聊天请求
|
||||
* @param externalHandler 外部响应处理器(可为 null)
|
||||
*/
|
||||
String getProviderName();
|
||||
void chat(@Valid ChatRequest chatRequest, StreamingChatResponseHandler externalHandler);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
package org.ruoyi.common.chat.service.chatMessage;
|
||||
|
||||
import org.ruoyi.common.chat.domain.bo.chat.ChatMessageBo;
|
||||
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* 聊天信息抽象基类 - 保存聊天信息
|
||||
*
|
||||
* @author Zengxb
|
||||
* @date 2026-02-24
|
||||
*/
|
||||
public abstract class AbstractChatMessageService {
|
||||
|
||||
/**
|
||||
* 创建日志对象
|
||||
*/
|
||||
Logger log = LoggerFactory.getLogger(AbstractChatMessageService.class);
|
||||
|
||||
@Autowired
|
||||
private IChatMessageService chatMessageService;
|
||||
|
||||
/**
|
||||
* 保存聊天信息
|
||||
*/
|
||||
public void saveChatMessage(ChatRequest chatRequest, Long userId, String content, String role, ChatModelVo chatModelVo){
|
||||
try {
|
||||
// 验证必要的上下文信息
|
||||
if (chatRequest == null || userId == null) {
|
||||
log.warn("缺少必要的聊天上下文信息,无法保存消息");
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建ChatMessageBo对象
|
||||
ChatMessageBo messageBO = new ChatMessageBo();
|
||||
messageBO.setUserId(userId);
|
||||
messageBO.setSessionId(chatRequest.getSessionId());
|
||||
messageBO.setContent(content);
|
||||
messageBO.setRole(role);
|
||||
messageBO.setModelName(chatRequest.getModel());
|
||||
messageBO.setRemark(null);
|
||||
|
||||
chatMessageService.insertByBo(messageBO);
|
||||
} catch (Exception e) {
|
||||
log.error("保存{}聊天消息时出错: {}", getProviderName(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取服务提供商名称
|
||||
*/
|
||||
protected String getProviderName(){
|
||||
return "默认工作流大模型";
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
package org.ruoyi.common.chat.service.chatMessage;
|
||||
|
||||
import org.ruoyi.common.chat.domain.bo.chat.ChatMessageBo;
|
||||
import org.ruoyi.common.chat.domain.dto.ChatMessageDTO;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatMessageVo;
|
||||
import org.ruoyi.common.mybatis.core.page.PageQuery;
|
||||
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 聊天消息Service接口
|
||||
*
|
||||
* @author ageerle
|
||||
* @date 2025-12-14
|
||||
*/
|
||||
public interface IChatMessageService {
|
||||
|
||||
/**
|
||||
* 查询聊天消息
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 聊天消息
|
||||
*/
|
||||
ChatMessageVo queryById(Long id);
|
||||
|
||||
/**
|
||||
* 分页查询聊天消息列表
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @param pageQuery 分页参数
|
||||
* @return 聊天消息分页列表
|
||||
*/
|
||||
TableDataInfo<ChatMessageVo> queryPageList(ChatMessageBo bo, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 查询符合条件的聊天消息列表
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @return 聊天消息列表
|
||||
*/
|
||||
List<ChatMessageVo> queryList(ChatMessageBo bo);
|
||||
|
||||
/**
|
||||
* 新增聊天消息
|
||||
*
|
||||
* @param bo 聊天消息
|
||||
* @return 是否新增成功
|
||||
*/
|
||||
Boolean insertByBo(ChatMessageBo bo);
|
||||
|
||||
/**
|
||||
* 修改聊天消息
|
||||
*
|
||||
* @param bo 聊天消息
|
||||
* @return 是否修改成功
|
||||
*/
|
||||
Boolean updateByBo(ChatMessageBo bo);
|
||||
|
||||
/**
|
||||
* 校验并批量删除聊天消息信息
|
||||
*
|
||||
* @param ids 待删除的主键集合
|
||||
* @param isValid 是否进行有效性校验
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
||||
|
||||
/**
|
||||
* 根据会话ID获取所有消息
|
||||
* 用于长期记忆功能
|
||||
*
|
||||
* @param sessionId 会话ID
|
||||
* @return 消息DTO列表
|
||||
*/
|
||||
List<ChatMessageDTO> getMessagesBySessionId(Long sessionId);
|
||||
|
||||
/**
|
||||
* 根据会话ID删除所有消息
|
||||
* 用于清理会话历史
|
||||
*
|
||||
* @param sessionId 会话ID
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
Boolean deleteBySessionId(Long sessionId);
|
||||
}
|
||||
Reference in New Issue
Block a user