From 868f51c99db65def6d5e483c8dc4a3e9b6fbb0bb Mon Sep 17 00:00:00 2001 From: CT <164434275@qq.com> Date: Tue, 26 May 2026 02:02:06 +0000 Subject: [PATCH] =?UTF-8?q?!850=20=E5=A2=9E=E5=8A=A0snail-ai=E9=9B=86?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 修复冲突 * 1、修改使用Spring sse方式 * 恢复默认配置 * 恢复默认端口 * 菜单sql * 配置还原 * 修改数据库配置信息 * snail-ai测试版本 * 暂存修改 --- pom.xml | 19 ++ ruoyi-admin/pom.xml | 4 + .../src/main/resources/application-dev.yml | 35 +++ .../src/main/resources/application-prod.yml | 35 +++ .../src/main/resources/application.yml | 1 + ruoyi-common/pom.xml | 1 + ruoyi-common/ruoyi-common-ai/pom.xml | 41 +++ .../common/ai/config/SnailAiConfig.java | 16 ++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + ruoyi-common/ruoyi-common-bom/pom.xml | 6 + ruoyi-modules/pom.xml | 1 + ruoyi-modules/ruoyi-ai/pom.xml | 31 +++ .../ai/controller/OpenApiDemoController.java | 263 ++++++++++++++++++ .../java/org/dromara/ai/package-info.java | 1 + script/sql/ry_ai.sql | 1 + 15 files changed, 456 insertions(+) create mode 100644 ruoyi-common/ruoyi-common-ai/pom.xml create mode 100644 ruoyi-common/ruoyi-common-ai/src/main/java/org/dromara/common/ai/config/SnailAiConfig.java create mode 100644 ruoyi-common/ruoyi-common-ai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports create mode 100644 ruoyi-modules/ruoyi-ai/pom.xml create mode 100644 ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/controller/OpenApiDemoController.java create mode 100644 ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/package-info.java create mode 100644 script/sql/ry_ai.sql diff --git a/pom.xml b/pom.xml index f5c07b83e..a8666c382 100644 --- a/pom.xml +++ b/pom.xml @@ -33,6 +33,7 @@ 2.2.7 4.5.0 2.0.0 + 0.0.1 1.5.0 0.2.0 1.18.42 @@ -296,6 +297,18 @@ ${snailjob.version} + + com.aizuda + snail-ai-agent-starter + ${snailai.version} + + + + com.aizuda + snail-ai-openapi-starter + ${snailai.version} + + org.bouncycastle @@ -377,6 +390,12 @@ ${revision} + + org.dromara + ruoyi-ai + ${revision} + + org.dromara ruoyi-gen diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml index 974128778..0a26a3aaa 100644 --- a/ruoyi-admin/pom.xml +++ b/ruoyi-admin/pom.xml @@ -80,6 +80,10 @@ ruoyi-job + + org.dromara + ruoyi-ai + diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml index 1b52fab96..c808767fa 100644 --- a/ruoyi-admin/src/main/resources/application-dev.yml +++ b/ruoyi-admin/src/main/resources/application-dev.yml @@ -28,6 +28,41 @@ snail-job: # 客户端ip指定 host: +--- # snail-ai 配置 +snail-ai: + # 启用客户端模式 + enabled: true + # 聊天发送模式: stream(流式) / sync(同步) + chat-mode: stream + # ==================== Server 连接 ==================== + # Server 端 gRPC 地址(即 snail-ai-starter 的 snail-ai.server.grpc-port) + server: + host: 127.0.0.1 + port: 18888 + # ==================== 客户端配置 ==================== + # 本客户端 gRPC 端口(Server 通过此端口分发 Chat 请求) + # 应用 ID(在 Server「应用管理」页面创建后获取) + app-id: 1 + # 认证令牌(在 Server「应用管理」页面创建时自动生成) + token: SAI_ce6fbc820c50456baecc7cdcf2a14b1b + port: 18889 + # Skill 文件临时目录 + skill-temp-dir: /tmp/snail-ai-agent/skills + # ==================== OpenAPI Client 配置 ==================== + open-api: + # 启用 OpenAPI Client + enabled: true + # Server HTTP 端口 + web-port: 8080 + # 是否使用 HTTPS + https: false + # API 路径前缀 + prefix: snail-ai + # 超时配置(毫秒) + connect-timeout-ms: 5000 + read-timeout-ms: 60000 + chat-timeout-ms: 300000 + --- # 数据源配置 spring: datasource: diff --git a/ruoyi-admin/src/main/resources/application-prod.yml b/ruoyi-admin/src/main/resources/application-prod.yml index d77ddf57c..76274c81d 100644 --- a/ruoyi-admin/src/main/resources/application-prod.yml +++ b/ruoyi-admin/src/main/resources/application-prod.yml @@ -31,6 +31,41 @@ snail-job: # 客户端ip指定 host: +--- # snail-ai 配置 +snail-ai: + # 启用客户端模式 + enabled: true + # 聊天发送模式: stream(流式) / sync(同步) + chat-mode: stream + # ==================== Server 连接 ==================== + # Server 端 gRPC 地址(即 snail-ai-starter 的 snail-ai.server.grpc-port) + server: + host: 127.0.0.1 + port: 18888 + # ==================== 客户端配置 ==================== + # 本客户端 gRPC 端口(Server 通过此端口分发 Chat 请求) + # 应用 ID(在 Server「应用管理」页面创建后获取) + app-id: 1 + # 认证令牌(在 Server「应用管理」页面创建时自动生成) + token: SAI_ce6fbc820c50456baecc7cdcf2a14b1b + port: 18889 + # Skill 文件临时目录 + skill-temp-dir: /tmp/snail-ai-agent/skills + # ==================== OpenAPI Client 配置 ==================== + open-api: + # 启用 OpenAPI Client + enabled: true + # Server HTTP 端口 + web-port: 8080 + # 是否使用 HTTPS + https: false + # API 路径前缀 + prefix: snail-ai + # 超时配置(毫秒) + connect-timeout-ms: 5000 + read-timeout-ms: 60000 + chat-timeout-ms: 300000 + --- # 数据源配置 spring: datasource: diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index 8c9f26c19..081030690 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -109,6 +109,7 @@ security: - /*/api-docs - /*/api-docs/** - /warm-flow-ui/config + - /snail-ai/agent/*/chat/stream # MyBatisPlus配置 # https://baomidou.com/config/ diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml index 17e22d37c..2d1faa988 100644 --- a/ruoyi-common/pom.xml +++ b/ruoyi-common/pom.xml @@ -38,6 +38,7 @@ ruoyi-common-encrypt ruoyi-common-push ruoyi-common-mqtt + ruoyi-common-ai ruoyi-common-mcp diff --git a/ruoyi-common/ruoyi-common-ai/pom.xml b/ruoyi-common/ruoyi-common-ai/pom.xml new file mode 100644 index 000000000..f1eb0712b --- /dev/null +++ b/ruoyi-common/ruoyi-common-ai/pom.xml @@ -0,0 +1,41 @@ + + + + ruoyi-common + org.dromara + ${revision} + + 4.0.0 + + ruoyi-common-ai + + + ruoyi-common-ai AI公共模块 + + + + + + org.springframework.boot + spring-boot-autoconfigure + + + + com.aizuda + snail-ai-agent-starter + + + + com.aizuda + snail-ai-openapi-starter + + + + org.dromara + ruoyi-common-core + + + + diff --git a/ruoyi-common/ruoyi-common-ai/src/main/java/org/dromara/common/ai/config/SnailAiConfig.java b/ruoyi-common/ruoyi-common-ai/src/main/java/org/dromara/common/ai/config/SnailAiConfig.java new file mode 100644 index 000000000..5ade5cbdd --- /dev/null +++ b/ruoyi-common/ruoyi-common-ai/src/main/java/org/dromara/common/ai/config/SnailAiConfig.java @@ -0,0 +1,16 @@ +package org.dromara.common.ai.config; + +import com.aizuda.snail.ai.agent.starter.EnableSnailAiAgent; +import com.aizuda.snail.ai.openapi.client.starter.EnableSnailAiOpenApi; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; + +/** + * Snail AI 自动配置 + */ +@AutoConfiguration +@ConditionalOnProperty(prefix = "snail-ai", name = "enabled", havingValue = "true") +@EnableSnailAiAgent +@EnableSnailAiOpenApi +public class SnailAiConfig { +} diff --git a/ruoyi-common/ruoyi-common-ai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-ai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 000000000..32af79519 --- /dev/null +++ b/ruoyi-common/ruoyi-common-ai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +org.dromara.common.ai.config.SnailAiConfig diff --git a/ruoyi-common/ruoyi-common-bom/pom.xml b/ruoyi-common/ruoyi-common-bom/pom.xml index cba9a6f03..710cc4bc3 100644 --- a/ruoyi-common/ruoyi-common-bom/pom.xml +++ b/ruoyi-common/ruoyi-common-bom/pom.xml @@ -166,6 +166,12 @@ ${revision} + + + org.dromara + ruoyi-common-ai + + org.dromara diff --git a/ruoyi-modules/pom.xml b/ruoyi-modules/pom.xml index 971929a54..4a79d2500 100644 --- a/ruoyi-modules/pom.xml +++ b/ruoyi-modules/pom.xml @@ -21,6 +21,7 @@ ruoyi-job ruoyi-system ruoyi-workflow + ruoyi-ai diff --git a/ruoyi-modules/ruoyi-ai/pom.xml b/ruoyi-modules/ruoyi-ai/pom.xml new file mode 100644 index 000000000..1baf5cb8a --- /dev/null +++ b/ruoyi-modules/ruoyi-ai/pom.xml @@ -0,0 +1,31 @@ + + + + org.dromara + ruoyi-modules + ${revision} + + 4.0.0 + jar + ruoyi-ai + + + ai模块 + + + + + + org.dromara + ruoyi-common-ai + + + org.dromara + ruoyi-common-satoken + + + + + diff --git a/ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/controller/OpenApiDemoController.java b/ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/controller/OpenApiDemoController.java new file mode 100644 index 000000000..b27d9be6a --- /dev/null +++ b/ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/controller/OpenApiDemoController.java @@ -0,0 +1,263 @@ +package org.dromara.ai.controller; + +import com.aizuda.snail.ai.common.execption.SnailAiException; +import com.aizuda.snail.ai.common.model.PageResult; +import com.aizuda.snail.ai.common.model.Result; +import com.aizuda.snail.ai.common.openapi.dto.*; +import com.aizuda.snail.ai.openapi.client.core.api.OpenApiAgentClient; +import com.aizuda.snail.ai.openapi.client.core.api.OpenApiChatClient; +import com.aizuda.snail.ai.openapi.client.core.api.OpenApiConversationClient; +import com.aizuda.snail.ai.openapi.client.core.api.OpenApiUserClient; +import com.aizuda.snail.ai.openapi.client.core.listener.SseEventListener; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.satoken.utils.LoginHelper; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + * OpenAPI 使用示例 Controller + * 演示如何使用 OpenAPI Client 调用 Snail AI 服务端接口 + * + * @author opensnail + * @date 2026-04-25 + */ +@Slf4j +@RestController +@RequestMapping("/snail-ai") +@RequiredArgsConstructor +public class OpenApiDemoController { + + private final OpenApiAgentClient agentClient; + private final OpenApiChatClient chatClient; + private final OpenApiConversationClient conversationClient; + private final OpenApiUserClient userClient; + @Value("${snail-ai.chat-mode:stream}") + private String chatMode; + + // ==================== User 相关接口 ==================== + + /** + * 注册当前登录用户并返回 OpenAPI 用户信息。 + */ + @PostMapping("/user/register") + public Result registerCurrentUser() { + OpenApiUserVO user = ensureOpenApiUser(); + return Result.ok(user); + } + + /** + * 查询当前登录用户对应的 OpenAPI 用户信息。 + */ + @GetMapping("/user") + public Result getUser() { + String openId = ensureOpenId(); + OpenApiUserQueryRequest request = new OpenApiUserQueryRequest(); + request.setOpenId(openId); + return userClient.getUser(request); + } + + // ==================== Agent 相关接口 ==================== + + /** + * 查询当前用户可访问的智能体列表。 + */ + @GetMapping("/agents") + public Result> listAgents() { + return agentClient.listAgents(); + } + + /** + * 根据智能体 ID 查询智能体详情。 + */ + @GetMapping("/agent/{agentId}") + public Result getAgent( + @PathVariable Long agentId) { + OpenApiAgentIdentityRequest request = new OpenApiAgentIdentityRequest(); + request.setAgentId(agentId); + return agentClient.getAgent(request); + } + + // ==================== Conversation 相关接口 ==================== + + /** + * 为指定智能体创建新会话。 + */ + @PostMapping("/agent/{agentId}/conversation") + public Result createConversation( + @PathVariable Long agentId, + @RequestBody OpenApiCreateConversationRequest request) { + request.setAgentId(agentId); + request.setOpenId(ensureOpenId()); + return conversationClient.createConversation(request); + } + + /** + * 分页查询指定智能体下的会话列表。 + */ + @GetMapping("/agent/{agentId}/conversations") + public PageResult> listConversations( + @PathVariable Long agentId, + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "10") int size) { + OpenApiConversationQueryRequest request = new OpenApiConversationQueryRequest(); + request.setAgentId(agentId); + request.setOpenId(ensureOpenId()); + request.setPage(page); + request.setSize(size); + return conversationClient.listConversations(request); + } + + /** + * 查询指定会话的消息历史。 + */ + @GetMapping("/agent/{agentId}/conversation/{conversationId}/messages") + public Result> getMessages( + @PathVariable Long agentId, + @PathVariable String conversationId) { + OpenApiConversationIdentityRequest request = new OpenApiConversationIdentityRequest(); + request.setAgentId(agentId); + request.setConversationId(conversationId); + request.setOpenId(ensureOpenId()); + return conversationClient.getMessages(request); + } + + /** + * 删除指定会话。 + */ + @DeleteMapping("/agent/{agentId}/conversation/{conversationId}") + public Result deleteConversation( + @PathVariable Long agentId, + @PathVariable String conversationId) { + OpenApiConversationIdentityRequest request = new OpenApiConversationIdentityRequest(); + request.setAgentId(agentId); + request.setConversationId(conversationId); + request.setOpenId(ensureOpenId()); + return conversationClient.deleteConversation(request); + } + + // ==================== Chat 相关接口 ==================== + + /** + * 获取当前聊天发送模式。 + */ + @GetMapping("/chat/mode") + public Result> getChatMode() { + String mode = "sync".equalsIgnoreCase(chatMode) ? "sync" : "stream"; + return Result.ok(Map.of("mode", mode)); + } + + /** + * 同步对话接口。 + */ + @PostMapping("/agent/{agentId}/chat/sync") + public Result chatSync( + @PathVariable Long agentId, + @RequestBody OpenApiChatRequest request) { + request.setAgentId(agentId); + request.setOpenId(ensureOpenId()); + log.info("Sync chat request: agentId={}, content={}", agentId, request.getContent()); + return chatClient.chatSync(request); + } + + /** + * 流式对话接口,按 SSE 事件返回消息分片。 + */ + @GetMapping("/agent/{agentId}/chat/stream") + public SseEmitter chatStream( + @PathVariable Long agentId, + @RequestParam String content, + @RequestParam(required = false) String conversationId) { + SseEmitter emitter = new SseEmitter(300000L); + emitter.onTimeout(() -> { + safeSend(emitter, "error", "SSE stream timeout"); + safeSend(emitter, "done", ""); + emitter.complete(); + }); + emitter.onError(error -> log.warn("SSE emitter error: {}", error.getMessage())); + + OpenApiChatRequest request = new OpenApiChatRequest(); + request.setAgentId(agentId); + request.setOpenId(ensureOpenId()); + request.setContent(content); + request.setConversationId(conversationId); + + log.info("Stream chat request: agentId={}, content={}", agentId, content); + try { + chatClient.chatStream(request, new SseEventListener() { + @Override + public void onText(String text) { + safeSend(emitter, "text", text); + } + + @Override + public void onThinking(String thinking) { + safeSend(emitter, "thinking", thinking); + } + + @Override + public void onComplete(String data) { + safeSend(emitter, "done", data); + log.info("Stream chat completed"); + emitter.complete(); + } + + @Override + public void onError(String errorMessage) { + log.error("Stream chat error: {}", errorMessage); + safeSend(emitter, "error", errorMessage); + safeSend(emitter, "done", ""); + emitter.complete(); + } + }); + } catch (Exception e) { + log.error("Stream chat exception", e); + safeSend(emitter, "error", "stream exception: " + e.getMessage()); + safeSend(emitter, "done", ""); + emitter.complete(); + } + + return emitter; + } + + /** + * 输出一条 SSE 事件。 + */ + private void safeSend(SseEmitter emitter, String event, String data) { + try { + emitter.send(SseEmitter.event().name(event).data(data == null ? "" : data)); + } catch (IOException e) { + log.warn("SSE send failed, event={}", event, e); + } + } + + /** + * 获取当前登录用户对应的 openId,不存在时会自动注册。 + */ + private String ensureOpenId() { + return ensureOpenApiUser().getOpenId(); + } + + /** + * 确保当前登录用户已注册为 OpenAPI 用户。 + */ + private OpenApiUserVO ensureOpenApiUser() { + Long userId = LoginHelper.getUserId(); + String username = LoginHelper.getLoginUser().getNickname(); + + OpenApiUserRegisterRequest registerRequest = new OpenApiUserRegisterRequest(); + registerRequest.setExternalId(String.valueOf(userId)); + registerRequest.setNickname(username); + Result registerResult = userClient.register(registerRequest); + if (registerResult == null || registerResult.getData() == null) { + throw new SnailAiException("注册 OpenAPI 用户失败,返回为空"); + } + OpenApiUserVO user = registerResult.getData(); + return user; + } +} diff --git a/ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/package-info.java b/ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/package-info.java new file mode 100644 index 000000000..d97a8cbe5 --- /dev/null +++ b/ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/package-info.java @@ -0,0 +1 @@ +package org.dromara.ai; diff --git a/script/sql/ry_ai.sql b/script/sql/ry_ai.sql new file mode 100644 index 000000000..d52ff0db7 --- /dev/null +++ b/script/sql/ry_ai.sql @@ -0,0 +1 @@ +INSERT INTO sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query_param`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `active_menu`, `ext`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2056263986068676609, 'AI聊天', 0, 1, 'ai/chat', 'ai/chat/index', NULL, 'N', 'Y', 'C', '0', '0', NULL, 'checkbox', '', '', 1761000000000000103, 1761100000000000001, '2026-05-18 14:41:52', 1761100000000000001, '2026-05-18 14:43:41', ''); \ No newline at end of file