context:工作流和Ai Chat对话消息功能整合

This commit is contained in:
zengxb
2026-02-26 14:36:33 +08:00
parent d6e4a50d6e
commit 8954f59cd7
79 changed files with 548 additions and 254 deletions

View File

@@ -62,6 +62,12 @@
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-tenant</artifactId>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
<version>${swagger-annotations.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,121 @@
package org.ruoyi.common.chat.base;
import cn.dev33.satoken.stp.StpUtil;
import org.apache.commons.lang3.StringUtils;
import org.ruoyi.common.chat.entity.User;
import org.ruoyi.common.chat.enums.UserStatusEnum;
import org.ruoyi.common.core.domain.model.LoginUser;
import org.ruoyi.common.core.exception.base.BaseException;
import org.ruoyi.common.satoken.utils.LoginHelper;
import static org.ruoyi.common.chat.enums.ErrorEnum.A_USER_NOT_FOUND;
/**
* 线程上下文适配器,统一接入 Sa-Token 登录态。
*/
public class ThreadContext {
private static final ThreadLocal<User> CURRENT_USER = new ThreadLocal<>();
private static final ThreadLocal<String> CURRENT_TOKEN = new ThreadLocal<>();
private ThreadContext() {
}
/**
* 获取当前登录的工作流用户。
*/
public static User getCurrentUser() {
User cached = CURRENT_USER.get();
if (cached != null) {
return cached;
}
LoginUser loginUser = LoginHelper.getLoginUser();
if (loginUser == null) {
throw new BaseException(A_USER_NOT_FOUND.getInfo());
}
User mapped = mapToWorkflowUser(loginUser);
CURRENT_USER.set(mapped);
return mapped;
}
/**
* 允许在测试或特殊场景下显式设置当前用户。
*/
public static void setCurrentUser(User user) {
if (user == null) {
CURRENT_USER.remove();
} else {
CURRENT_USER.set(user);
}
}
/**
* 获取当前登录用户 ID。
*/
public static Long getCurrentUserId() {
Long userId = LoginHelper.getUserId();
if (userId != null) {
return userId;
}
return getCurrentUser().getId();
}
/**
* 获取当前访问 token。
*/
public static String getToken() {
String token = CURRENT_TOKEN.get();
if (StringUtils.isNotBlank(token)) {
return token;
}
try {
token = StpUtil.getTokenValue();
} catch (Exception ignore) {
token = null;
}
if (StringUtils.isNotBlank(token)) {
CURRENT_TOKEN.set(token);
}
return token;
}
public static void setToken(String token) {
if (StringUtils.isBlank(token)) {
CURRENT_TOKEN.remove();
} else {
CURRENT_TOKEN.set(token);
}
}
public static boolean isLogin() {
return LoginHelper.isLogin();
}
public static User getExistCurrentUser() {
return getCurrentUser();
}
public static void unload() {
CURRENT_USER.remove();
CURRENT_TOKEN.remove();
}
private static User mapToWorkflowUser(LoginUser loginUser) {
User user = new User();
user.setId(loginUser.getUserId());
user.setName(loginUser.getUsername());
user.setEmail(loginUser.getUsername());
user.setUuid(String.valueOf(loginUser.getUserId()));
user.setUserStatus(UserStatusEnum.NORMAL);
user.setIsAdmin(LoginHelper.isSuperAdmin(loginUser.getUserId()));
user.setUnderstandContextMsgPairNum(0);
user.setQuotaByTokenDaily(0);
user.setQuotaByTokenMonthly(0);
user.setQuotaByRequestDaily(0);
user.setQuotaByRequestMonthly(0);
user.setQuotaByImageDaily(0);
user.setQuotaByImageMonthly(0);
user.setIsDeleted(false);
return user;
}
}

View File

@@ -0,0 +1,77 @@
package org.ruoyi.common.chat.domain.bo.chat;
import io.github.linpeilie.annotations.AutoMapper;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.ruoyi.common.chat.entity.chat.ChatMessage;
import org.ruoyi.common.core.validate.AddGroup;
import org.ruoyi.common.core.validate.EditGroup;
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
/**
* 聊天消息业务对象 chat_message
*
* @author ageerle
* @date 2025-12-14
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = ChatMessage.class, reverseConvertGenerate = false)
public class ChatMessageBo extends BaseEntity {
/**
* 主键
*/
@NotNull(message = "主键不能为空", groups = { EditGroup.class })
private Long id;
/**
* 会话id
*/
private Long sessionId;
/**
* 用户id
*/
@NotNull(message = "用户id不能为空", groups = { AddGroup.class, EditGroup.class })
private Long userId;
/**
* 消息内容
*/
private String content;
/**
* 对话角色
*/
private String role;
/**
* 扣除金额
*/
private Long deductCost;
/**
* 累计 Tokens
*/
private Long totalTokens;
/**
* 模型名称
*/
private String modelName;
/**
* 计费类型1-token计费2-次数计费)
*/
private String billingType;
/**
* 备注
*/
private String remark;
}

View File

@@ -4,7 +4,7 @@ import io.github.linpeilie.annotations.AutoMapper;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.ruoyi.common.chat.domain.entity.chat.ChatModel;
import org.ruoyi.common.chat.entity.chat.ChatModel;
import org.ruoyi.common.core.validate.EditGroup;
import org.ruoyi.common.mybatis.core.domain.BaseEntity;

View File

@@ -21,6 +21,21 @@ public class ChatRequest {
@NotEmpty(message = "传入的模型不能为空")
private String model;
/**
* 工作流请求体
*/
private WorkFlowRunner workFlowRunner;
/**
* 人机交互信息体
*/
private ReSumeRunner reSumeRunner;
/**
* 是否启用工作流
*/
private Boolean enableWorkFlow;
/**
* 会话id
*/
@@ -41,6 +56,11 @@ public class ChatRequest {
*/
private Long uuid;
/**
* 是否为人机交互用户继续输入
*/
private Boolean isResume;
/**
* 是否启用深度思考
*/

View File

@@ -0,0 +1,19 @@
package org.ruoyi.common.chat.domain.dto.request;
import lombok.Data;
/**
* 人机交互输入信息
*/
@Data
public class ReSumeRunner {
/**
* 运行节点UUID
*/
private String runtimeUuid;
/**
* 人机交互用户输入信息
*/
private String feedbackContent;
}

View File

@@ -0,0 +1,15 @@
package org.ruoyi.common.chat.domain.dto.request;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.Data;
import java.util.List;
/**
* 工作流请求体信息
*/
@Data
public class WorkFlowRunner {
private List<ObjectNode> inputs;
private String uuid;
}

View File

@@ -0,0 +1,91 @@
package org.ruoyi.common.chat.domain.vo.chat;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
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;
/**
* 聊天消息视图对象 chat_message
*
* @author ageerle
* @date 2025-12-14
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = ChatMessage.class)
public class ChatMessageVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@ExcelProperty(value = "主键")
private Long id;
/**
* 会话id
*/
@ExcelProperty(value = "会话id")
private Long sessionId;
/**
* 用户id
*/
@ExcelProperty(value = "用户id")
private Long userId;
/**
* 消息内容
*/
@ExcelProperty(value = "消息内容")
private String content;
/**
* 对话角色
*/
@ExcelProperty(value = "对话角色")
private String role;
/**
* 扣除金额
*/
@ExcelProperty(value = "扣除金额")
private Long deductCost;
/**
* 累计 Tokens
*/
@ExcelProperty(value = "累计 Tokens")
private Long totalTokens;
/**
* 模型名称
*/
@ExcelProperty(value = "模型名称")
private String modelName;
/**
* 计费类型1-token计费2-次数计费)
*/
@ExcelProperty(value = "计费类型", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "1=-token计费2-次数计费")
private String billingType;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
}

View File

@@ -5,7 +5,7 @@ import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.ruoyi.common.chat.domain.entity.chat.ChatModel;
import org.ruoyi.common.chat.entity.chat.ChatModel;
import java.io.Serial;
import java.io.Serializable;

View File

@@ -0,0 +1,29 @@
package org.ruoyi.common.chat.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
public class BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.AUTO)
private Long id;
@TableField(value = "create_time")
private LocalDateTime createTime;
@TableField(value = "update_time")
private LocalDateTime updateTime;
@Schema(title = "是否删除0未删除1已删除")
@TableField(value = "is_deleted")
private Boolean isDeleted;
}

View File

@@ -0,0 +1,66 @@
package org.ruoyi.common.chat.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.ruoyi.common.chat.enums.UserStatusEnum;
import java.time.LocalDateTime;
@EqualsAndHashCode(callSuper = true)
@Data
@TableName("adi_user")
@Schema(title = "User对象")
public class User extends BaseEntity {
@Schema(name = "用户名称")
@TableField("name")
private String name;
@TableField("email")
private String email;
@TableField("password")
private String password;
@TableField("uuid")
private String uuid;
@Schema(name = "上下文理解中需要携带的消息对数量(提示词及回复)")
@TableField("understand_context_msg_pair_num")
private Integer understandContextMsgPairNum;
@Schema(name = "token quota in one day")
@TableField("quota_by_token_daily")
private Integer quotaByTokenDaily;
@Schema(name = "token quota in one month")
@TableField("quota_by_token_monthly")
private Integer quotaByTokenMonthly;
@Schema(name = "request quota in one day")
@TableField("quota_by_request_daily")
private Integer quotaByRequestDaily;
@Schema(name = "request quota in one month")
@TableField("quota_by_request_monthly")
private Integer quotaByRequestMonthly;
@TableField("quota_by_image_daily")
private Integer quotaByImageDaily;
@TableField("quota_by_image_monthly")
private Integer quotaByImageMonthly;
@TableField("user_status")
private UserStatusEnum userStatus;
@TableField("active_time")
private LocalDateTime activeTime;
@Schema(title = "是否管理员01")
@TableField(value = "is_admin")
private Boolean isAdmin;
}

View File

@@ -1,4 +1,4 @@
package org.ruoyi.common.chat.domain.entity.chat;
package org.ruoyi.common.chat.entity.chat;
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
import jakarta.validation.constraints.NotNull;

View File

@@ -0,0 +1,77 @@
package org.ruoyi.common.chat.entity.chat;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.ruoyi.common.tenant.core.TenantEntity;
import java.io.Serial;
/**
* 聊天消息对象 chat_message
*
* @author ageerle
* @date 2025-12-14
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("chat_message")
public class ChatMessage extends TenantEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id")
private Long id;
/**
* 会话id
*/
private Long sessionId;
/**
* 用户id
*/
private Long userId;
/**
* 消息内容
*/
private String content;
/**
* 对话角色
*/
private String role;
/**
* 扣除金额
*/
private Long deductCost;
/**
* 累计 Tokens
*/
private Long totalTokens;
/**
* 模型名称
*/
private String modelName;
/**
* 计费类型1-token计费2-次数计费)
*/
private String billingType;
/**
* 备注
*/
private String remark;
}

View File

@@ -1,4 +1,4 @@
package org.ruoyi.common.chat.domain.entity.chat;
package org.ruoyi.common.chat.entity.chat;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

View File

@@ -1,4 +1,4 @@
package org.ruoyi.common.chat.domain.entity.image;
package org.ruoyi.common.chat.entity.image;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;

View File

@@ -0,0 +1,12 @@
package org.ruoyi.common.chat.enums;
import com.baomidou.mybatisplus.annotation.IEnum;
public interface BaseEnum extends IEnum<Integer> {
/**
* 获取对应名称
*
* @return String
*/
String getDesc();
}

View File

@@ -0,0 +1,127 @@
package org.ruoyi.common.chat.enums;
import lombok.Getter;
@Getter
public enum ErrorEnum {
SUCCESS("00000", "成功"),
A_URL_NOT_FOUND("A0001", "地址不存在"),
A_PARAMS_ERROR("A0002", "参数校验不通过"),
A_REQUEST_TOO_MUCH("A0003", "访问次数太多"),
A_LOGIN_ERROR("A0004", "登陆失败,账号或密码错误"),
A_LOGIN_ERROR_MAX("A0005", "失败次数太多,请输入验证码重试"),
A_LOGIN_CAPTCHA_ERROR("A0006", "验证码不正确"),
A_USER_NOT_EXIST("A0007", "用户不存在"),
A_CONVERSATION_NOT_EXIST("A0008", "对话不存在"),
A_IMAGE_NUMBER_ERROR("A0009", "图片数量不对"),
A_IMAGE_SIZE_ERROR("A0010", "图片尺寸不对"),
A_FILE_NOT_EXIST("A0011", "文件不存在"),
A_DRAWING("A0012", "作图还未完成"),
A_USER_EXIST("A0013", "账号已经存在,请使用账号密码登录"),
A_FIND_PASSWORD_CODE_ERROR("A0014", "重置码已过期或不存在"),
A_USER_WAIT_CONFIRM("A0015", "用户未激活"),
A_USER_NOT_AUTH("A0016", "用户无权限"),
A_DATA_NOT_FOUND("A0017", "数据不存在"),
A_UPLOAD_FAIL("A0018", "上传失败"),
A_QA_ASK_LIMIT("A0019", "请求次数太多"),
A_QA_ITEM_LIMIT("A0020", "知识点生成已超额度"),
A_CONVERSATION_EXIST("A0021", "会话(角色)已存在"),
A_MODEL_NOT_FOUND("A0022", "模型不存在"),
A_MODEL_ALREADY_EXIST("A0023", "模型已存在"),
A_CONVERSATION_NOT_FOUND("A0024", "会话(角色)找不到"),
A_AI_IMAGE_NOT_FOUND("A0024", "图片找不到"),
A_ENABLE_MODEL_NOT_FOUND("A0025", "没有可用的模型"),
A_DOC_INDEX_DOING("A0026", "文档索引正在进行中,请稍后重试"),
A_PRESET_CONVERSATION_NOT_EXIST("A0027", "预设会话或角色不存在"),
A_CONVERSATION_TITLE_EXIST("A0028", "会话(角色)标题已存在"),
A_AI_IMAGE_NO_AUTH("A0029", "无权限查看该图片"),
A_USER_NOT_FOUND("A0030", "用户不存在"),
A_ACTIVE_CODE_INVALID("A0031", "激活码已失效"),
A_OLD_PASSWORD_INVALID("A0032", "原密码不正确"),
A_OPT_TOO_FREQUENTLY("A0032", "操作太频繁"),
A_DRAW_NOT_FOUND("A00033", "绘图记录找不到"),
A_WF_NOT_FOUND("A00034", "工作流找不到"),
A_WF_DISABLED("A0035", "工作流已停用"),
A_WF_NODE_NOT_FOUND("A0036", "工作流节点找不到"),
A_WF_NODE_CONFIG_NOT_FOUND("A0037", "工作流节点配置找不到"),
A_WF_NODE_CONFIG_ERROR("A0038", "工作流节点配置异常"),
A_WF_INPUT_INVALID("A0039", "工作流输入参数错误"),
A_WF_INPUT_MISSING("A0040", "工作流输入缺少参数"),
A_WF_MULTIPLE_START_NODE("A0041", "多个开始节点"),
A_WF_START_NODE_NOT_FOUND("A0042", "没有开始节点"),
A_WF_END_NODE_NOT_FOUND("A0043", "没有结束节点"),
A_WF_EDGE_NOT_FOUND("A0044", "工作流的边找不到"),
A_WF_RUNTIME_NOT_FOUND("A00045", "工作流运行时数据找不到"),
A_SEARCH_QUERY_IS_EMPTY("A00046", "搜索内容不能为空"),
A_WF_COMPONENT_NOT_FOUND("A00047", "工作流基础组件找不到"),
A_WF_RESUME_FAIL("A00048", "工作流恢复执行时失败"),
A_MAIL_SENDER_EMPTY("A00049", "邮件发送人不能为空"),
A_MAIL_SENDER_CONFIG_ERROR("A00050", "邮件发送人配置错误"),
A_MAIL_RECEIVER_EMPTY("A00051", "邮件接收人不能为空"),
A_MCP_SERVER_NOT_FOUND("A00052", "MCP服务找不到"),
A_USER_MCP_SERVER_NOT_FOUND("A00053", "用户的MCP服务找不到"),
A_PARAMS_INVALID_BY_("A00054", "参数校验异常:{0}"),
A_AI_MESSAGE_NOT_FOUND("A00055", "找不到AI的消息"),
A_USER_QUESTION_NOT_FOUND("A00056", "用户问题不存在"),
A_PLATFORM_NOT_MATCH("A0057", "平台不匹配"),
B_UNCAUGHT_ERROR("B0001", "未捕捉异常"),
B_COMMON_ERROR("B0002", "业务出错"),
B_GLOBAL_ERROR("B0003", "全局异常"),
B_SAVE_IMAGE_ERROR("B0004", "保存图片异常"),
B_FIND_IMAGE_404("B0005", "无法找到图片"),
B_DAILY_QUOTA_USED("B0006", "今天额度已经用完"),
B_MONTHLY_QUOTA_USED("B0007", "当月额度已经用完"),
B_LLM_NOT_SUPPORT("B0008", "LLM不支持该功能"),
B_LLM_SECRET_KEY_NOT_SET("B0009", "LLM的secret key没设置"),
B_MESSAGE_NOT_FOUND("B0008", "消息不存在"),
B_LLM_SERVICE_DISABLED("B0009", "LLM服务不可用"),
B_KNOWLEDGE_BASE_IS_EMPTY("B0010", "知识库内容为空"),
B_NO_ANSWER("B0011", "[无答案]"),
B_SAVE_FILE_ERROR("B0012", "保存文件异常"),
B_BREAK_SEARCH("B0013", "中断搜索"),
B_GRAPH_FILTER_NOT_FOUND("B0014", "图过滤器未定义"),
B_DB_ERROR("B0015", "数据库查询异常"),
B_ACTIVE_USER_ERROR("B0016", "激活用户失败"),
B_RESET_PASSWORD_ERROR("B0017", "重置密码失败"),
B_IMAGE_LOAD_ERROR("B0018", "加载图片失败"),
B_IO_EXCEPTION("B0019", "IO异常"),
B_SERVER_EXCEPTION("B0020", "服务端异常"),
B_DELETE_FILE_ERROR("B0021", "删除文件异常"),
B_WF_RUN_ERROR("B0022", "工作流运行异常"),
B_WF_NODE_DEFINITION_NOT_FOUND("B0023", "工作流节点定义找不到"),
B_DIR_CREATE_FAIL("B0024", "创建目录失败"),
B_LLM_TEMPERATURE_ERROR("B0025", "采样温度应该在 0.1-1之间"),
B_ASR_SETTING_NOT_FOUND("B0026", "语音识别设置未找到"),
B_URL_INVALID("B0027", "不是有效的网络地址"),
B_ASR_MODEL_NOT_FOUND("B0028", "语音识别模型未找到"),
B_TTS_SETTING_NOT_FOUND("B0029", "语音合成设置未找到"),
B_TTS_MODEL_NOT_FOUND("B0030", "语音合成模型未找到"),
B_VOICE_NOT_FOUND("B0031", "声音不存在"),
C_DRAW_FAIL("C0001", "大模型生成图片失败,原因:{0}"),
C_ALI_OSS_CONFIG_ERROR("C0002", "阿里云OSS初始化失败,原因:{0}"),
C_LLM_RESPONSE_INVALID("C0003", "大模型生成结果内容无效"),
C_WF_COMPONENT_DELETED_FAIL_BY_USED("C0004", "工作流组件已经被使用,无法被删除,可先停用");
private final String code;
private final String info;
ErrorEnum(String code, String info) {
this.code = code;
this.info = info;
}
public static ErrorEnum getErrorEnum(String code) {
ErrorEnum result = null;
for (ErrorEnum c : ErrorEnum.values()) {
if (c.getCode().equals(code)) {
result = c;
break;
}
}
if (null == result) {
result = B_COMMON_ERROR;
}
return result;
}
}

View File

@@ -0,0 +1,26 @@
package org.ruoyi.common.chat.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 角色枚举
*
* @author ageerle@163.com
* @date 2025-12-17
*/
@Getter
@AllArgsConstructor
public enum RoleType {
SYSTEM("system"),
USER("user"),
ASSISTANT("assistant"),
FUNCTION("function"),
TOOL("tool"),
WORKFLOW("workFlow")
;
private final String name;
}

View File

@@ -0,0 +1,23 @@
package org.ruoyi.common.chat.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
@Getter
@AllArgsConstructor
public enum UserStatusEnum implements BaseEnum {
WAIT_CONFIRM(1, "待验证"),
NORMAL(2, "正常"),
FREEZE(3, "冻结");
private final Integer value;
private final String desc;
public static UserStatusEnum getByValue(Integer val) {
return Arrays.stream(UserStatusEnum.values()).filter(item -> item.value.equals(val)).findFirst().orElse(null);
}
}

View File

@@ -1,6 +1,6 @@
package org.ruoyi.common.chat.factory;
import org.ruoyi.common.chat.Service.IChatService;
import org.ruoyi.common.chat.service.chat.IChatService;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

View File

@@ -1,6 +1,6 @@
package org.ruoyi.common.chat.factory;
import org.ruoyi.common.chat.Service.IImageGenerationService;
import org.ruoyi.common.chat.service.image.IImageGenerationService;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

View File

@@ -1,4 +1,4 @@
package org.ruoyi.common.chat.Service;
package org.ruoyi.common.chat.service.chat;
import org.ruoyi.common.chat.domain.bo.chat.ChatModelBo;
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;

View File

@@ -1,7 +1,7 @@
package org.ruoyi.common.chat.Service;
package org.ruoyi.common.chat.service.chat;
import jakarta.validation.Valid;
import org.ruoyi.common.chat.domain.entity.chat.ChatContext;
import org.ruoyi.common.chat.entity.chat.ChatContext;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
/**

View File

@@ -0,0 +1,59 @@
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.setBillingType(chatModelVo.getModelType());
messageBO.setRemark(null);
chatMessageService.insertByBo(messageBO);
} catch (Exception e) {
log.error("保存{}聊天消息时出错: {}", getProviderName(), e.getMessage(), e);
}
}
/**
* 获取服务提供商名称
*/
protected String getProviderName(){
return "默认工作流大模型";
}
}

View File

@@ -0,0 +1,87 @@
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);
}

View File

@@ -1,7 +1,7 @@
package org.ruoyi.common.chat.Service;
package org.ruoyi.common.chat.service.image;
import jakarta.validation.Valid;
import org.ruoyi.common.chat.domain.entity.image.ImageContext;
import org.ruoyi.common.chat.entity.image.ImageContext;
/**
* 公共文生图接口

View File

@@ -0,0 +1,33 @@
package org.ruoyi.common.chat.service.workFlow;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.ruoyi.common.chat.entity.User;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.util.List;
/**
* 工作流启动Service接口
*
* @author Zengxb
* @date 2026-02-24
*/
public interface IWorkFlowStarterService {
/**
* 启动工作流
* @param user 用户
* @param workflowUuid 工作流UUID
* @param userInputs 用户输入信息
* @return 流式输出结果
*/
SseEmitter streaming(User user, String workflowUuid, List<ObjectNode> userInputs, Long sessionId);
/**
* 恢复工作流
* @param runtimeUuid 运行时UUID
* @param userInput 用户输入
* @param sseEmitter SSE连接对象
*/
void resumeFlow(String runtimeUuid, String userInput, SseEmitter sseEmitter);
}

View File

@@ -65,7 +65,7 @@ public class SseEmitterManager {
emitter.onCompletion(() -> {
SseEmitter remove = emitters.remove(token);
if (remove != null) {
remove.complete();
// remove.complete();
}
});
emitter.onTimeout(() -> {