mirror of
https://gitcode.com/ageerle/ruoyi-ai.git
synced 2026-04-09 17:57:32 +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);
|
||||
}
|
||||
@@ -2,9 +2,11 @@ package org.ruoyi.common.sse.core;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.common.core.utils.SpringUtils;
|
||||
import org.ruoyi.common.redis.utils.RedisUtils;
|
||||
import org.ruoyi.common.sse.dto.SseEventDto;
|
||||
import org.ruoyi.common.sse.dto.SseMessageDto;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
@@ -65,7 +67,7 @@ public class SseEmitterManager {
|
||||
emitter.onCompletion(() -> {
|
||||
SseEmitter remove = emitters.remove(token);
|
||||
if (remove != null) {
|
||||
// remove.complete();
|
||||
remove.complete();
|
||||
}
|
||||
});
|
||||
emitter.onTimeout(() -> {
|
||||
@@ -174,9 +176,11 @@ public class SseEmitterManager {
|
||||
if (MapUtil.isNotEmpty(emitters)) {
|
||||
for (Map.Entry<String, SseEmitter> entry : emitters.entrySet()) {
|
||||
try {
|
||||
// 格式化为标准SSE JSON格式
|
||||
SseEventDto eventDto = SseEventDto.content(message);
|
||||
entry.getValue().send(SseEmitter.event()
|
||||
.name("message")
|
||||
.data(message));
|
||||
.data(JSONUtil.toJsonStr(eventDto)));
|
||||
} catch (Exception e) {
|
||||
SseEmitter remove = emitters.remove(entry.getKey());
|
||||
if (remove != null) {
|
||||
@@ -189,6 +193,33 @@ public class SseEmitterManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向指定的用户会话发送结构化事件
|
||||
*
|
||||
* @param userId 要发送消息的用户id
|
||||
* @param eventDto SSE事件对象
|
||||
*/
|
||||
public void sendEvent(Long userId, SseEventDto eventDto) {
|
||||
Map<String, SseEmitter> emitters = USER_TOKEN_EMITTERS.get(userId);
|
||||
if (MapUtil.isNotEmpty(emitters)) {
|
||||
for (Map.Entry<String, SseEmitter> entry : emitters.entrySet()) {
|
||||
try {
|
||||
entry.getValue().send(SseEmitter.event()
|
||||
.name(eventDto.getEvent())
|
||||
.data(JSONUtil.toJsonStr(eventDto)));
|
||||
} catch (Exception e) {
|
||||
SseEmitter remove = emitters.remove(entry.getKey());
|
||||
if (remove != null) {
|
||||
remove.complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
USER_TOKEN_EMITTERS.remove(userId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 本机全用户会话发送消息
|
||||
*
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package org.ruoyi.common.sse.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* SSE 事件数据传输对象
|
||||
* <p>
|
||||
* 标准的 SSE 消息格式,支持不同事件类型
|
||||
*
|
||||
* @author ageerle@163.com
|
||||
* @date 2025/03/19
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SseEventDto implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 事件类型
|
||||
*/
|
||||
private String event;
|
||||
|
||||
/**
|
||||
* 消息内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 推理内容(深度思考模式)
|
||||
*/
|
||||
private String reasoningContent;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
private String error;
|
||||
|
||||
/**
|
||||
* 是否完成
|
||||
*/
|
||||
private Boolean done;
|
||||
|
||||
/**
|
||||
* 创建内容事件
|
||||
*/
|
||||
public static SseEventDto content(String content) {
|
||||
return SseEventDto.builder()
|
||||
.event("content")
|
||||
.content(content)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建推理内容事件
|
||||
*/
|
||||
public static SseEventDto reasoning(String reasoningContent) {
|
||||
return SseEventDto.builder()
|
||||
.event("reasoning")
|
||||
.reasoningContent(reasoningContent)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建完成事件
|
||||
*/
|
||||
public static SseEventDto done() {
|
||||
return SseEventDto.builder()
|
||||
.event("done")
|
||||
.done(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建错误事件
|
||||
*/
|
||||
public static SseEventDto error(String error) {
|
||||
return SseEventDto.builder()
|
||||
.event("error")
|
||||
.error(error)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
package org.ruoyi.common.sse.utils;
|
||||
|
||||
import java.util.Collections;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.common.core.utils.SpringUtils;
|
||||
import org.ruoyi.common.sse.core.SseEmitterManager;
|
||||
import org.ruoyi.common.sse.dto.SseEventDto;
|
||||
import org.ruoyi.common.sse.dto.SseMessageDto;
|
||||
|
||||
/**
|
||||
@@ -27,6 +29,7 @@ public class SseMessageUtils {
|
||||
|
||||
/**
|
||||
* 向指定的SSE会话发送消息
|
||||
* 通过 Redis Pub/Sub 广播,确保跨模块消息可达
|
||||
*
|
||||
* @param userId 要发送消息的用户id
|
||||
* @param message 要发送的消息内容
|
||||
@@ -35,7 +38,11 @@ public class SseMessageUtils {
|
||||
if (!isEnable()) {
|
||||
return;
|
||||
}
|
||||
MANAGER.sendMessage(userId, message);
|
||||
// 通过 Redis 广播,让所有模块的 SseTopicListener 接收并转发到本地 SSE 连接
|
||||
SseMessageDto dto = new SseMessageDto();
|
||||
dto.setMessage(message);
|
||||
dto.setUserIds(Collections.singletonList(userId));
|
||||
MANAGER.publishMessage(dto);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,6 +93,58 @@ public class SseMessageUtils {
|
||||
MANAGER.disconnect(userId, tokenValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向指定的SSE会话发送结构化事件
|
||||
*
|
||||
* @param userId 要发送消息的用户id
|
||||
* @param eventDto SSE事件对象
|
||||
*/
|
||||
public static void sendEvent(Long userId, SseEventDto eventDto) {
|
||||
if (!isEnable()) {
|
||||
return;
|
||||
}
|
||||
MANAGER.sendEvent(userId, eventDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送内容事件
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param content 内容
|
||||
*/
|
||||
public static void sendContent(Long userId, String content) {
|
||||
sendEvent(userId, SseEventDto.content(content));
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送推理内容事件
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param reasoningContent 推理内容
|
||||
*/
|
||||
public static void sendReasoning(Long userId, String reasoningContent) {
|
||||
sendEvent(userId, SseEventDto.reasoning(reasoningContent));
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送完成事件
|
||||
*
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
public static void sendDone(Long userId) {
|
||||
sendEvent(userId, SseEventDto.done());
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送错误事件
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param error 错误信息
|
||||
*/
|
||||
public static void sendError(Long userId, String error) {
|
||||
sendEvent(userId, SseEventDto.error(error));
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否开启
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user