diff --git a/README.md b/README.md index e7256341..3983a78c 100644 --- a/README.md +++ b/README.md @@ -135,8 +135,9 @@ #### 1. 全栈式开源系统 - 全套开源系统:提供完整的前端应用、后台管理,基于MIT协议,开箱即用。 #### 2. 本地化 RAG 方案 -- 基于 **Langchain4j** 框架,支持 Milvus/Weaviate/Qdrant 向量库,结合 BGE-large-zh-v1.5 本地向量化模型 实现高效文档检索与知识库构建。 -- 支持 本地 LLM 接入,结合私有知识库实现安全可控的问答系统,避免依赖云端服务的隐私风险。 +- 基于 **Langchain4j** 框架,支持 Milvus/Weaviate/Qdrant 向量库,结合 BGE-large-zh-v1.5 本地向量化模型 实现高效文档检索与知识库构建。 +- 支持 本地 LLM 接入,结合私有知识库实现安全可控的问答系统,避免依赖云端服务的隐私风险。 +- 支持 ollama、vLLm等平台部署模型。 #### 3. 多模态 AI 引擎与工具集成 - 智能对话:支持 OpenAI GPT-4、Azure、ChatGLM 等主流模型,内置 SSE/WebSocket 协议实现低延迟交互,兼容 **扣子**、**DIFY** 等平台 API 调用。 - **Spring AI MCP** 支持:通过注解快速定义本地工具,支持调用 MCP 广场 的海量 MCP Server 服务,扩展模型能力边界。 diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/FastGPTAnswerResponse.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/FastGPTAnswerResponse.java new file mode 100644 index 00000000..856bec81 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/FastGPTAnswerResponse.java @@ -0,0 +1,15 @@ +package org.ruoyi.common.chat.entity.chat; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.util.List; +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FastGPTAnswerResponse { + private String id; + private String object; + private long created; + private String model; + private List choices; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/FastGPTChatChoice.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/FastGPTChatChoice.java new file mode 100644 index 00000000..11e9280b --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/FastGPTChatChoice.java @@ -0,0 +1,25 @@ +package org.ruoyi.common.chat.entity.chat; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FastGPTChatChoice implements Serializable { + private long index; + /** + * 请求参数stream为true返回是delta + */ + @JsonProperty("delta") + private Message delta; + /** + * 请求参数stream为false返回是message + */ + @JsonProperty("message") + private Message message; + @JsonProperty("finish_reason") + private String finishReason; +} \ No newline at end of file diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/FastGPTChatCompletion.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/FastGPTChatCompletion.java new file mode 100644 index 00000000..8e8dc933 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/FastGPTChatCompletion.java @@ -0,0 +1,41 @@ +package org.ruoyi.common.chat.entity.chat; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.io.Serializable; + +@Data +@SuperBuilder +@AllArgsConstructor +@NoArgsConstructor +public class FastGPTChatCompletion extends ChatCompletion implements Serializable { + + /** + * 是否使用FastGPT提供的上下文 + */ + private String chatId; + + + /** + * 是否返回详细信息;stream模式下会通过event进行区分,非stream模式结果保存在responseData中. + */ + private boolean detail; + + + /** + * 运行时变量 + * 模块变量,一个对象,会替换模块中,输入fastgpt框内容里的{{key}} + */ + private Variables variables; + + /** + * responseChatItemId: string | undefined 。 + * 如果传入,则会将该值作为本次对话的响应消息的 ID, + * FastGPT 会自动将该 ID 存入数据库。请确保, + * 在当前chatId下,responseChatItemId是唯一的。 + */ + private String responseChatItemId; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/Variables.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/Variables.java new file mode 100644 index 00000000..3af364ac --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/Variables.java @@ -0,0 +1,20 @@ +package org.ruoyi.common.chat.entity.chat; + + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class Variables implements Serializable { + + private String uid; + + private String name; +} diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/service/BaseContext.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/service/BaseContext.java new file mode 100644 index 00000000..27af6ba0 --- /dev/null +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/service/BaseContext.java @@ -0,0 +1,25 @@ +package org.ruoyi.common.core.service; + +/** + * @description: 基于ThreadLocal封装工具类,用户保存和获取当前登录用户Sa-Token token值 + * @author: yzm + **/ +public class BaseContext { + private static ThreadLocal threadLocal = new ThreadLocal<>(); + + /** + * @description: 设置值 + * @author: yzm + * @param: [token] 线程token + **/ + public static void setCurrentToken(String token){ + threadLocal.set(token); + } + /** + * @description: 获取值 + * @author: yzm + **/ + public static String getCurrentToken(){ + return threadLocal.get(); + } +} \ No newline at end of file diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/handler/InjectionMetaObjectHandler.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/handler/InjectionMetaObjectHandler.java index d1b9b8f5..ad908346 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/handler/InjectionMetaObjectHandler.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/handler/InjectionMetaObjectHandler.java @@ -7,6 +7,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.reflection.MetaObject; import org.ruoyi.common.core.domain.model.LoginUser; import org.ruoyi.common.core.exception.ServiceException; +import org.ruoyi.common.core.service.BaseContext; import org.ruoyi.common.core.utils.ObjectUtils; import org.ruoyi.common.satoken.utils.LoginHelper; import org.ruoyi.core.domain.BaseEntity; @@ -91,7 +92,8 @@ public class InjectionMetaObjectHandler implements MetaObjectHandler { private LoginUser getLoginUser() { LoginUser loginUser; try { - loginUser = LoginHelper.getLoginUser(); + String token = BaseContext.getCurrentToken(); + loginUser = LoginHelper.getLoginUser(token); } catch (Exception e) { log.warn("自动注入警告 => 用户未登录"); return null; diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/ruoyi/common/satoken/utils/LoginHelper.java b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/ruoyi/common/satoken/utils/LoginHelper.java index 735a79c4..b562ba8a 100644 --- a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/ruoyi/common/satoken/utils/LoginHelper.java +++ b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/ruoyi/common/satoken/utils/LoginHelper.java @@ -9,6 +9,7 @@ import cn.hutool.core.convert.Convert; import cn.hutool.core.util.ObjectUtil; import lombok.AccessLevel; import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.ruoyi.common.core.constant.TenantConstants; import org.ruoyi.common.core.constant.UserConstants; import org.ruoyi.common.core.domain.model.LoginUser; @@ -29,6 +30,7 @@ import java.util.Set; * * @author Lion Li */ +@Slf4j @NoArgsConstructor(access = AccessLevel.PRIVATE) public class LoginHelper { @@ -82,6 +84,15 @@ public class LoginHelper { return loginUser; } + + public static T getLoginUser(String token) { + SaSession session = StpUtil.getTokenSessionByToken(token); + if (ObjectUtil.isNull(session)) { + return null; + } + return (T) session.get(LOGIN_USER_KEY); + } + /** * 获取用户id */ diff --git a/ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/bo/ChatMessageForUniappBo.java b/ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/bo/ChatMessageForUniappBo.java new file mode 100644 index 00000000..e713b577 --- /dev/null +++ b/ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/bo/ChatMessageForUniappBo.java @@ -0,0 +1,116 @@ +package org.ruoyi.domain.bo; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.core.domain.BaseEntity; + +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * 聊天消息业务对象(uniapp) chat_message + * + * @author ageerle + * @date 2025-04-08 + */ +@Data +public class ChatMessageForUniappBo implements Serializable { + + /** + * 主键 + */ + @NotNull(message = "主键不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 用户id + */ + @NotNull(message = "用户id不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long userId; + + /** + * 消息内容 + */ + @NotBlank(message = "消息内容不能为空", groups = { AddGroup.class, EditGroup.class }) + private String content; + + /** + * 会话id + */ + private Long sessionId; + + /** + * 对话角色 + */ + private String role; + + /** + * 扣除金额 + */ + private Double deductCost; + + /** + * 累计 Tokens + */ + private Integer totalTokens; + + /** + * 模型名称 + */ + private String modelName ; + + + /** + * 备注 + */ + private String remark; + + + /** + * 搜索值 + */ + private String searchValue; + + /** + * 创建部门 + */ + private Long createDept; + + /** + * 创建者 + */ + private Long createBy; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新者 + */ + private Long updateBy; + + /** + * 更新时间 + */ + private Date updateTime; + + /** + * 请求参数 + */ + private Map params = new HashMap<>(); + + + +} diff --git a/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/VectorStoreServiceImpl.java b/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/VectorStoreServiceImpl.java index 282c2b63..db17a580 100644 --- a/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/VectorStoreServiceImpl.java +++ b/ruoyi-modules-api/ruoyi-knowledge-api/src/main/java/org/ruoyi/service/impl/VectorStoreServiceImpl.java @@ -10,6 +10,9 @@ import dev.langchain4j.store.embedding.EmbeddingMatch; import dev.langchain4j.store.embedding.EmbeddingSearchRequest; import dev.langchain4j.store.embedding.EmbeddingStore; import dev.langchain4j.store.embedding.weaviate.WeaviateEmbeddingStore; +import io.weaviate.client.Config; +import io.weaviate.client.WeaviateClient; +import io.weaviate.client.base.Result; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @@ -83,10 +86,20 @@ public class VectorStoreServiceImpl implements VectorStoreService { @Override - public void removeById(String id, String modelName) { - createSchema(id, modelName); - // 根据条件删除向量数据 - embeddingStore.remove(id); + @SneakyThrows + public void removeById(String id, String modelName) { + String protocol = configService.getConfigValue("weaviate", "protocol"); + String host = configService.getConfigValue("weaviate", "host"); + String className = configService.getConfigValue("weaviate", "classname"); + String finalClassName = className + id; + WeaviateClient client = new WeaviateClient(new Config(protocol, host)); + Result result = client.schema().classDeleter().withClassName(finalClassName).run(); + if (result.hasErrors()) { + log.error("失败删除向量: " + result.getError()); + throw new ServiceException("失败删除向量数据!"); + } else { + log.info("成功删除向量数据: " + result.getResult()); + } } /** diff --git a/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/impl/SysRoleServiceImpl.java b/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/impl/SysRoleServiceImpl.java index 044f5398..b3b24f41 100644 --- a/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/impl/SysRoleServiceImpl.java +++ b/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/impl/SysRoleServiceImpl.java @@ -440,7 +440,7 @@ public class SysRoleServiceImpl implements ISysRoleService { return; } // 角色关联的在线用户量过大会导致redis阻塞卡顿 谨慎操作 - keys.parallelStream().forEach(key -> { + keys.forEach(key -> { String token = StringUtils.substringAfterLast(key, ":"); // 如果已经过期则跳过 if (StpUtil.stpLogic.getTokenActivityTimeoutByToken(token) < -1) { diff --git a/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/impl/SysUserServiceImpl.java b/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/impl/SysUserServiceImpl.java index b5165cd3..d5b89de5 100644 --- a/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/impl/SysUserServiceImpl.java +++ b/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/impl/SysUserServiceImpl.java @@ -325,9 +325,9 @@ public class SysUserServiceImpl implements ISysUserService, UserService { @Transactional(rollbackFor = Exception.class) public int updateUser(SysUserBo user) { // 新增用户与角色管理 - //insertUserRole(user, true); + insertUserRole(user, true); // 新增用户与岗位管理 - //insertUserPost(user, true); + insertUserPost(user, true); SysUser sysUser = MapstructUtils.convert(user, SysUser.class); // 防止错误更新后导致的数据误删除 int flag = baseMapper.updateById(sysUser); diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/controller/chat/ChatMessageController.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/controller/chat/ChatMessageController.java index 660d70ac..10ddd3c6 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/controller/chat/ChatMessageController.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/controller/chat/ChatMessageController.java @@ -1,6 +1,5 @@ package org.ruoyi.chat.controller.chat; -import cn.dev33.satoken.annotation.SaCheckPermission; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; @@ -16,6 +15,7 @@ import org.ruoyi.common.web.core.BaseController; import org.ruoyi.core.page.PageQuery; import org.ruoyi.core.page.TableDataInfo; import org.ruoyi.domain.bo.ChatMessageBo; +import org.ruoyi.domain.bo.ChatMessageForUniappBo; import org.ruoyi.domain.vo.ChatMessageVo; import org.ruoyi.service.IChatMessageService; import org.springframework.validation.annotation.Validated; @@ -66,6 +66,37 @@ public class ChatMessageController extends BaseController { return R.ok(chatMessageService.queryById(id)); } + + + /** + * 查询聊天消息列表 uniapp + */ + @GetMapping("/listForUniapp") + public TableDataInfo list(ChatMessageForUniappBo uniappBo, PageQuery pageQuery) { + ChatMessageBo bo = new ChatMessageBo(); + bo.setUserId(uniappBo.getUserId()); + bo.setSessionId(Long.parseLong(uniappBo.getUserId().toString() + "2024")); + return chatMessageService.queryPageList(bo, pageQuery); + } + + + /** + * 新增聊天消息 uniapp + */ + @Log(title = "聊天消息", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping("/addForUniapp") + public R addForUniapp(@Validated(AddGroup.class) @RequestBody ChatMessageForUniappBo uniappBo) { + ChatMessageBo bo = new ChatMessageBo(); + bo.setUserId(uniappBo.getUserId()); + bo.setRole(uniappBo.getRole()); + bo.setContent(uniappBo.getContent()); + bo.setSessionId(Long.parseLong(uniappBo.getUserId().toString() + "2024")); + chatMessageService.insertByBo(bo); + return R.ok(uniappBo.getId()); + } + + /** * 新增聊天消息 */ diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/enums/ChatModeType.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/enums/ChatModeType.java index 13a39d90..aa09f1eb 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/enums/ChatModeType.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/enums/ChatModeType.java @@ -17,7 +17,9 @@ public enum ChatModeType { VECTOR("vector", "知识库向量模型"), - IMAGE("image", "图片识别模型"); + IMAGE("image", "图片识别模型"), + + FASTGPT("fastgpt", "FASTGPT"); private final String code; private final String description; diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/listener/FastGPTSSEEventSourceListener.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/listener/FastGPTSSEEventSourceListener.java new file mode 100644 index 00000000..0d58176d --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/listener/FastGPTSSEEventSourceListener.java @@ -0,0 +1,70 @@ +package org.ruoyi.chat.listener; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import okhttp3.Response; +import okhttp3.ResponseBody; +import okhttp3.sse.EventSource; +import okhttp3.sse.EventSourceListener; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.util.Objects; + +@Slf4j +@Component +@RequiredArgsConstructor +public class FastGPTSSEEventSourceListener extends EventSourceListener { + + private SseEmitter emitter; + + @Autowired(required = false) + public FastGPTSSEEventSourceListener(SseEmitter emitter) { + this.emitter = emitter; + } + + @Override + public void onOpen(EventSource eventSource, Response response) { + log.info("FastGPT sse连接成功"); + } + + @Override + public void onEvent(@NotNull EventSource eventSource, String id, String type, @NotNull String data) { + try { + log.debug("事件类型为: {}", type); + log.debug("事件数据为: {}", data); + if ("flowResponses".equals(type)){ + emitter.send(data); + emitter.complete(); + } else { + emitter.send(data); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void onClosed(EventSource eventSource) { + log.info("FastGPT sse连接关闭"); + } + + @Override + @SneakyThrows + public void onFailure(EventSource eventSource, Throwable t, Response response) { + if (Objects.isNull(response)) { + return; + } + ResponseBody body = response.body(); + if (Objects.nonNull(body)) { + log.error("FastGPT sse连接异常data:{},异常:{}", body.string(), t); + } else { + log.error("FastGPT sse连接异常data:{},异常:{}", response, t); + } + eventSource.cancel(); + } +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/listener/SSEEventSourceListener.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/listener/SSEEventSourceListener.java index 938a1243..c91e28de 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/listener/SSEEventSourceListener.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/listener/SSEEventSourceListener.java @@ -1,6 +1,7 @@ package org.ruoyi.chat.listener; +import cn.dev33.satoken.stp.StpUtil; import cn.hutool.core.collection.CollectionUtil; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; @@ -15,6 +16,7 @@ import org.ruoyi.chat.service.chat.IChatCostService; import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse; import org.ruoyi.common.chat.entity.chat.Message; import org.ruoyi.common.chat.request.ChatRequest; +import org.ruoyi.common.core.service.BaseContext; import org.ruoyi.common.core.utils.SpringUtils; import org.ruoyi.common.core.utils.StringUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -40,11 +42,14 @@ public class SSEEventSourceListener extends EventSourceListener { private Long sessionId; + private String token; + @Autowired(required = false) - public SSEEventSourceListener(SseEmitter emitter,Long userId,Long sessionId) { + public SSEEventSourceListener(SseEmitter emitter,Long userId,Long sessionId, String token) { this.emitter = emitter; this.userId = userId; this.sessionId = sessionId; + this.token = token; } @@ -80,6 +85,8 @@ public class SSEEventSourceListener extends EventSourceListener { chatRequest.setUserId(userId); chatRequest.setSessionId(sessionId); chatRequest.setPrompt(stringBuffer.toString()); + // 记录会话token + BaseContext.setCurrentToken(token); chatCostService.deductToken(chatRequest); return; } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/ChatCostServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/ChatCostServiceImpl.java index 4d53a22d..035f5133 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/ChatCostServiceImpl.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/ChatCostServiceImpl.java @@ -169,6 +169,7 @@ public class ChatCostServiceImpl implements IChatCostService { /** * 获取用户Id */ + @Override public Long getUserId() { LoginUser loginUser = LoginHelper.getLoginUser(); if (loginUser == null) { diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/FastGPTServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/FastGPTServiceImpl.java new file mode 100644 index 00000000..15acf6f2 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/FastGPTServiceImpl.java @@ -0,0 +1,52 @@ +package org.ruoyi.chat.service.chat.impl; + +import org.ruoyi.chat.config.ChatConfig; +import org.ruoyi.chat.enums.ChatModeType; +import org.ruoyi.chat.listener.FastGPTSSEEventSourceListener; +import org.ruoyi.chat.service.chat.IChatService; +import org.ruoyi.common.chat.entity.chat.FastGPTChatCompletion; +import org.ruoyi.common.chat.entity.chat.Message; +import org.ruoyi.common.chat.openai.OpenAiStreamClient; +import org.ruoyi.common.chat.request.ChatRequest; +import org.ruoyi.domain.vo.ChatModelVo; +import org.ruoyi.service.IChatModelService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.util.List; + +/** + * FastGpt 聊天管理 + * 项目整体沿用Openai接口范式,根据FastGPT文档增加相应的参数 + * + * @author yzm + */ +@Service +public class FastGPTServiceImpl implements IChatService { + + @Autowired + private IChatModelService chatModelService; + + @Override + public SseEmitter chat(ChatRequest chatRequest, SseEmitter emitter) { + ChatModelVo chatModelVo = chatModelService.selectModelByName(chatRequest.getModel()); + OpenAiStreamClient openAiStreamClient = ChatConfig.createOpenAiStreamClient(chatModelVo.getApiHost(), chatModelVo.getApiKey()); + List messages = chatRequest.getMessages(); + FastGPTSSEEventSourceListener listener = new FastGPTSSEEventSourceListener(emitter); + FastGPTChatCompletion completion = FastGPTChatCompletion + .builder() + .messages(messages) + // 开启后sse会返回event值 + .detail(true) + .stream(true) + .build(); + openAiStreamClient.streamChatCompletion(completion, listener); + return emitter; + } + + @Override + public String getCategory() { + return ChatModeType.FASTGPT.getCode(); + } +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/ImageServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/ImageServiceImpl.java index a7eee8e6..709c58ef 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/ImageServiceImpl.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/ImageServiceImpl.java @@ -1,5 +1,6 @@ package org.ruoyi.chat.service.chat.impl; +import cn.dev33.satoken.stp.StpUtil; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.ruoyi.chat.config.ChatConfig; @@ -127,8 +128,10 @@ public class ImageServiceImpl implements IChatService { OpenAiStreamClient openAiStreamClient = ChatConfig.createOpenAiStreamClient(chatModelVo.getApiHost(), chatModelVo.getApiKey()); List messages = chatRequest.getMessages(); + // 获取会话token + String token = StpUtil.getTokenValue(); // 创建 SSE 事件源监听器 - SSEEventSourceListener listener = new SSEEventSourceListener(emitter, chatRequest.getUserId(), chatRequest.getSessionId()); + SSEEventSourceListener listener = new SSEEventSourceListener(emitter, chatRequest.getUserId(), chatRequest.getSessionId(), token); // 构建聊天完成请求 ChatCompletion completion = ChatCompletion diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/OpenAIServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/OpenAIServiceImpl.java index 170dddba..50693617 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/OpenAIServiceImpl.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/OpenAIServiceImpl.java @@ -1,5 +1,6 @@ package org.ruoyi.chat.service.chat.impl; +import cn.dev33.satoken.stp.StpUtil; import io.modelcontextprotocol.client.McpSyncClient; import lombok.extern.slf4j.Slf4j; import org.ruoyi.chat.config.ChatConfig; @@ -56,7 +57,8 @@ public class OpenAIServiceImpl implements IChatService { Message userMessage = Message.builder().content("工具返回信息:"+toolString).role(Message.Role.USER).build(); messages.add(userMessage); } - SSEEventSourceListener listener = new SSEEventSourceListener(emitter,chatRequest.getUserId(),chatRequest.getSessionId()); + String token = StpUtil.getTokenValue(); + SSEEventSourceListener listener = new SSEEventSourceListener(emitter,chatRequest.getUserId(),chatRequest.getSessionId(), token); ChatCompletion completion = ChatCompletion .builder() .messages(messages) diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/KnowledgeInfoServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/KnowledgeInfoServiceImpl.java index 4a048270..ea8cc367 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/KnowledgeInfoServiceImpl.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/KnowledgeInfoServiceImpl.java @@ -179,13 +179,14 @@ public class KnowledgeInfoServiceImpl implements IKnowledgeInfoService { Map map = new HashMap<>(); KnowledgeInfo knowledgeInfo = baseMapper.selectById(id); check(knowledgeInfo); - map.put("kid",knowledgeInfo.getKid()); + map.put("kid",knowledgeInfo.getId()); // 删除向量数据 vectorStoreService.removeById(String.valueOf(knowledgeInfo.getId()),knowledgeInfo.getVectorModelName()); // 删除附件和知识片段 fragmentMapper.deleteByMap(map); attachMapper.deleteByMap(map); // 删除知识库 + map.put("kid",knowledgeInfo.getKid()); baseMapper.deleteByMap(map); } diff --git a/script/sql/ruoyi-ai.sql b/script/sql/ruoyi-ai.sql index a66031d2..2e1ef130 100644 --- a/script/sql/ruoyi-ai.sql +++ b/script/sql/ruoyi-ai.sql @@ -493,6 +493,16 @@ INSERT INTO `sys_dict_data` VALUES (1776109853377916929, '000000', 0, '次数计 INSERT INTO `sys_dict_data` VALUES (1780264338471858177, '000000', 0, '未支付', '1', 'pay_state', '', 'info', 'N', '0', 103, 1, '2024-04-16 23:57:49', 1, '2024-04-16 23:58:29', ''); INSERT INTO `sys_dict_data` VALUES (1780264431589601282, '000000', 2, '已支付', '2', 'pay_state', '', 'success', 'N', '0', 103, 1, '2024-04-16 23:58:11', 1, '2024-04-16 23:58:21', ''); INSERT INTO `sys_dict_data` VALUES (1933094189606670338, '000000', 0, '知识库', 'vector', 'prompt_template_type', null, '', 'N', '0', 103, 1, '2025-06-12 17:29:05', 1, '2025-06-12 17:29:05', null); +INSERT INTO `sys_dict_data` VALUES (1938226101050925057, '000000', 1, '中转模型-chat', 'chat', 'chat_model_category', null, '', 'N', '0', 103, 1, '2025-06-26 21:21:28', 1, '2025-06-26 21:22:26', null); +INSERT INTO `sys_dict_data` VALUES (1938226833825193985, '000000', 1, '本地部署模型-ollama', 'ollama', 'chat_model_category', null, '', 'N', '0', 103, 1, '2025-06-26 21:24:22', 1, '2025-06-26 21:24:22', null); +INSERT INTO `sys_dict_data` VALUES (1938226919661625345, '000000', 1, 'DIFY-dify', 'dify', 'chat_model_category', null, '', 'N', '0', 103, 1, '2025-06-26 21:24:43', 1, '2025-06-26 21:24:43', null); +INSERT INTO `sys_dict_data` VALUES (1938226981422751746, '000000', 1, '扣子-coze', 'coze', 'chat_model_category', null, '', 'N', '0', 103, 1, '2025-06-26 21:24:58', 1, '2025-06-26 21:24:58', null); +INSERT INTO `sys_dict_data` VALUES (1938227034350673922, '000000', 1, '智谱清言-zhipu', 'zhipu', 'chat_model_category', null, '', 'N', '0', 103, 1, '2025-06-26 21:25:10', 1, '2025-06-26 21:25:10', null); +INSERT INTO `sys_dict_data` VALUES (1938227086750113793, '000000', 1, '深度求索-deepseek', 'deepseek', 'chat_model_category', null, '', 'N', '0', 103, 1, '2025-06-26 21:25:23', 1, '2025-06-26 21:25:23', null); +INSERT INTO `sys_dict_data` VALUES (1938227141741633537, '000000', 1, '通义千问-qianwen', 'qianwen', 'chat_model_category', null, '', 'N', '0', 103, 1, '2025-06-26 21:25:36', 1, '2025-06-26 21:25:36', null); +INSERT INTO `sys_dict_data` VALUES (1938227191834206209, '000000', 1, '知识库向量模型-vector', 'vector', 'chat_model_category', null, '', 'N', '0', 103, 1, '2025-06-26 21:25:48', 1, '2025-06-26 21:25:48', null); +INSERT INTO `sys_dict_data` VALUES (1938227249417805826, '000000', 1, '图片识别模型-image', 'image', 'chat_model_category', null, '', 'N', '0', 103, 1, '2025-06-26 21:26:01', 1, '2025-06-26 21:26:01', null); +INSERT INTO `sys_dict_data` VALUES (1940594785010503681, '000000', 1, 'FASTGPT-fastgpt', 'fastgpt', 'chat_model_category', null, '', 'N', '0', 103, 1, '2025-07-03 10:13:46', 1, '2025-07-03 10:13:46', null); -- ---------------------------- -- Table structure for sys_dict_type @@ -537,7 +547,8 @@ INSERT INTO `sys_dict_type` VALUES (1775756736895438849, '000000', '用户等级 INSERT INTO `sys_dict_type` VALUES (1776109665045278721, '000000', '模型计费方式', 'sys_model_billing', '0', 103, 1, '2024-04-05 12:48:37', 1, '2024-04-08 11:22:18', '模型计费方式'); INSERT INTO `sys_dict_type` VALUES (1780263881368219649, '000000', '支付状态', 'pay_state', '0', 103, 1, '2024-04-16 23:56:00', 1, '2025-03-29 15:21:57', '支付状态'); INSERT INTO `sys_dict_type` VALUES (1904565568803217409, '000000', '状态类型', 'status_type', '0', 103, 1, '2025-03-26 00:06:31', 1, '2025-03-26 00:06:31', NULL); -INSERT INTO `sys_dict_type` VALUES (1933093946274123777, '000000', '提示词模板分类', 'prompt_template_type', '0', 103, 1, '2025-06-12 17:28:07', 1, '2025-06-12 17:28:07', null); +INSERT INTO `sys_dict_type` VALUES (1933093946274123777, '000000', '提示词模板分类', 'prompt_template_type', '0', 103, 1, '2025-06-12 17:28:07', 1, '2025-06-12 17:28:07', '提示词模板类型'); +INSERT INTO `sys_dict_type` VALUES (1938225899023884289, '000000', '模型分类', 'chat_model_category', '0', 103, 1, '2025-06-26 21:20:39', 1, '2025-06-26 21:20:39', '模型分类'); -- ----------------------------