From 0f0645401b3d9a389ba5e0024e6448e8edf403fb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=96=AF=E7=8B=82=E7=9A=84=E7=8B=AE=E5=AD=90Li?=
<15040126243@163.com>
Date: Tue, 26 May 2026 13:54:54 +0800
Subject: [PATCH] =?UTF-8?q?add=20=E6=96=B0=E5=A2=9E=20ruoyi-snailai-server?=
=?UTF-8?q?=20=E7=8B=AC=E7=AB=8B=E6=9C=8D=E5=8A=A1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
pom.xml | 2 +-
.../src/main/resources/application-dev.yml | 4 +-
.../src/main/resources/application-prod.yml | 4 +-
ruoyi-extend/pom.xml | 1 +
ruoyi-extend/ruoyi-snailai-server/Dockerfile | 22 +
ruoyi-extend/ruoyi-snailai-server/pom.xml | 46 ++
.../ai/starter/filter/ActuatorAuthFilter.java | 63 +++
.../ai/starter/filter/SecurityConfig.java | 29 +
.../snailai/SnailAiServerApplication.java | 18 +
.../src/main/resources/application-dev.yml | 28 +
.../src/main/resources/application-prod.yml | 28 +
.../src/main/resources/application.yml | 67 +++
.../src/main/resources/banner.txt | 9 +
.../src/main/resources/logback-plus.xml | 87 +++
script/sql/oracle/oracle_ry_vue.sql | 2 +-
script/sql/postgres/postgres_ry_vue.sql | 2 +-
script/sql/ry_ai.sql | 516 ++++++++++++++++++
script/sql/ry_vue.sql | 2 +-
script/sql/sqlserver/sqlserver_ry_vue.sql | 2 +-
19 files changed, 923 insertions(+), 9 deletions(-)
create mode 100644 ruoyi-extend/ruoyi-snailai-server/Dockerfile
create mode 100644 ruoyi-extend/ruoyi-snailai-server/pom.xml
create mode 100644 ruoyi-extend/ruoyi-snailai-server/src/main/java/com/aizuda/snail/ai/starter/filter/ActuatorAuthFilter.java
create mode 100644 ruoyi-extend/ruoyi-snailai-server/src/main/java/com/aizuda/snail/ai/starter/filter/SecurityConfig.java
create mode 100644 ruoyi-extend/ruoyi-snailai-server/src/main/java/org/dromara/snailai/SnailAiServerApplication.java
create mode 100644 ruoyi-extend/ruoyi-snailai-server/src/main/resources/application-dev.yml
create mode 100644 ruoyi-extend/ruoyi-snailai-server/src/main/resources/application-prod.yml
create mode 100644 ruoyi-extend/ruoyi-snailai-server/src/main/resources/application.yml
create mode 100644 ruoyi-extend/ruoyi-snailai-server/src/main/resources/banner.txt
create mode 100644 ruoyi-extend/ruoyi-snailai-server/src/main/resources/logback-plus.xml
create mode 100644 script/sql/ry_ai.sql
diff --git a/pom.xml b/pom.xml
index 2f2e657aa..02de7fe43 100644
--- a/pom.xml
+++ b/pom.xml
@@ -56,7 +56,7 @@
3.0.2
7.17.28
- 2.0.0-M7
+ 2.0.0-M6
3.5.0
diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml
index fdc4e95f7..18045380d 100644
--- a/ruoyi-admin/src/main/resources/application-dev.yml
+++ b/ruoyi-admin/src/main/resources/application-dev.yml
@@ -44,7 +44,7 @@ snail-ai:
# 应用 ID(在 Server「应用管理」页面创建后获取)
app-id: 1
# 认证令牌(在 Server「应用管理」页面创建时自动生成)
- token: SAI_ce6fbc820c50456baecc7cdcf2a14b1b
+ token: SAI_7c557fc05a304b0482a65ca67afdb0b8
port: 18889
# Skill 文件临时目录
skill-temp-dir: /tmp/snail-ai-agent/skills
@@ -53,7 +53,7 @@ snail-ai:
# 启用 OpenAPI Client
enabled: true
# Server HTTP 端口
- web-port: 8080
+ web-port: 8900
# 是否使用 HTTPS
https: false
# API 路径前缀
diff --git a/ruoyi-admin/src/main/resources/application-prod.yml b/ruoyi-admin/src/main/resources/application-prod.yml
index 18383c1b9..18d036f7e 100644
--- a/ruoyi-admin/src/main/resources/application-prod.yml
+++ b/ruoyi-admin/src/main/resources/application-prod.yml
@@ -47,7 +47,7 @@ snail-ai:
# 应用 ID(在 Server「应用管理」页面创建后获取)
app-id: 1
# 认证令牌(在 Server「应用管理」页面创建时自动生成)
- token: SAI_ce6fbc820c50456baecc7cdcf2a14b1b
+ token: SAI_7c557fc05a304b0482a65ca67afdb0b8
port: 18889
# Skill 文件临时目录
skill-temp-dir: /tmp/snail-ai-agent/skills
@@ -56,7 +56,7 @@ snail-ai:
# 启用 OpenAPI Client
enabled: true
# Server HTTP 端口
- web-port: 8080
+ web-port: 8900
# 是否使用 HTTPS
https: false
# API 路径前缀
diff --git a/ruoyi-extend/pom.xml b/ruoyi-extend/pom.xml
index d7280cea3..d084361de 100644
--- a/ruoyi-extend/pom.xml
+++ b/ruoyi-extend/pom.xml
@@ -13,6 +13,7 @@
ruoyi-monitor-admin
+ ruoyi-snailai-server
ruoyi-snailjob-server
diff --git a/ruoyi-extend/ruoyi-snailai-server/Dockerfile b/ruoyi-extend/ruoyi-snailai-server/Dockerfile
new file mode 100644
index 000000000..c4aca4b75
--- /dev/null
+++ b/ruoyi-extend/ruoyi-snailai-server/Dockerfile
@@ -0,0 +1,22 @@
+# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
+FROM bellsoft/liberica-openjdk-rocky:21.0.8-cds
+#FROM findepi/graalvm:java21-native
+
+LABEL maintainer="Lion Li"
+
+RUN mkdir -p /ruoyi/snailai/logs
+
+WORKDIR /ruoyi/snailai
+
+ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS=""
+
+EXPOSE 8900
+EXPOSE 18888
+
+ADD ./target/ruoyi-snailai-server.jar ./app.jar
+
+SHELL ["/bin/bash", "-c"]
+
+ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom \
+ -XX:+HeapDumpOnOutOfMemoryError -XX:+UseZGC ${JAVA_OPTS} \
+ -jar app.jar
diff --git a/ruoyi-extend/ruoyi-snailai-server/pom.xml b/ruoyi-extend/ruoyi-snailai-server/pom.xml
new file mode 100644
index 000000000..13f698cb8
--- /dev/null
+++ b/ruoyi-extend/ruoyi-snailai-server/pom.xml
@@ -0,0 +1,46 @@
+
+
+
+ org.dromara
+ ruoyi-extend
+ ${revision}
+
+ 4.0.0
+
+ ruoyi-snailai-server
+
+
+
+ com.aizuda
+ snail-ai-starter
+ ${snailai.version}
+
+
+
+ de.codecentric
+ spring-boot-admin-starter-client
+ ${spring-boot-admin.version}
+
+
+
+
+ ${project.artifactId}
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ ${spring-boot.version}
+
+
+
+ repackage
+
+
+
+
+
+
+
+
diff --git a/ruoyi-extend/ruoyi-snailai-server/src/main/java/com/aizuda/snail/ai/starter/filter/ActuatorAuthFilter.java b/ruoyi-extend/ruoyi-snailai-server/src/main/java/com/aizuda/snail/ai/starter/filter/ActuatorAuthFilter.java
new file mode 100644
index 000000000..688775541
--- /dev/null
+++ b/ruoyi-extend/ruoyi-snailai-server/src/main/java/com/aizuda/snail/ai/starter/filter/ActuatorAuthFilter.java
@@ -0,0 +1,63 @@
+package com.aizuda.snail.ai.starter.filter;
+
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.FilterConfig;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+public class ActuatorAuthFilter implements Filter {
+
+ private final String username;
+ private final String password;
+
+ public ActuatorAuthFilter(String username, String password) {
+ this.username = username;
+ this.password = password;
+ }
+
+ @Override
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
+ HttpServletRequest request = (HttpServletRequest) servletRequest;
+ HttpServletResponse response = (HttpServletResponse) servletResponse;
+
+ String authHeader = request.getHeader("Authorization");
+ if (authHeader == null || !authHeader.startsWith("Basic ")) {
+ response.setHeader("WWW-Authenticate", "Basic realm=\"realm\"");
+ response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
+ return;
+ }
+
+ String base64Credentials = authHeader.substring("Basic ".length());
+ byte[] credDecoded = Base64.getDecoder().decode(base64Credentials);
+ String credentials = new String(credDecoded, StandardCharsets.UTF_8);
+ String[] split = credentials.split(":");
+ if (split.length != 2) {
+ response.setHeader("WWW-Authenticate", "Basic realm=\"realm\"");
+ response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
+ return;
+ }
+ if (!username.equals(split[0]) || !password.equals(split[1])) {
+ response.setHeader("WWW-Authenticate", "Basic realm=\"realm\"");
+ response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
+ return;
+ }
+ filterChain.doFilter(request, response);
+ }
+
+ @Override
+ public void init(FilterConfig filterConfig) {
+ }
+
+ @Override
+ public void destroy() {
+ }
+
+}
diff --git a/ruoyi-extend/ruoyi-snailai-server/src/main/java/com/aizuda/snail/ai/starter/filter/SecurityConfig.java b/ruoyi-extend/ruoyi-snailai-server/src/main/java/com/aizuda/snail/ai/starter/filter/SecurityConfig.java
new file mode 100644
index 000000000..cbeebc4b1
--- /dev/null
+++ b/ruoyi-extend/ruoyi-snailai-server/src/main/java/com/aizuda/snail/ai/starter/filter/SecurityConfig.java
@@ -0,0 +1,29 @@
+package com.aizuda.snail.ai.starter.filter;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 权限安全配置
+ *
+ * @author Lion Li
+ */
+@Configuration
+public class SecurityConfig {
+
+ @Value("${spring.boot.admin.client.username}")
+ private String username;
+ @Value("${spring.boot.admin.client.password}")
+ private String password;
+
+ @Bean
+ public FilterRegistrationBean actuatorFilterRegistrationBean() {
+ FilterRegistrationBean registrationBean = new FilterRegistrationBean<>();
+ registrationBean.setFilter(new ActuatorAuthFilter(username, password));
+ registrationBean.addUrlPatterns("/actuator", "/actuator/*");
+ return registrationBean;
+ }
+
+}
diff --git a/ruoyi-extend/ruoyi-snailai-server/src/main/java/org/dromara/snailai/SnailAiServerApplication.java b/ruoyi-extend/ruoyi-snailai-server/src/main/java/org/dromara/snailai/SnailAiServerApplication.java
new file mode 100644
index 000000000..b2aa4d94a
--- /dev/null
+++ b/ruoyi-extend/ruoyi-snailai-server/src/main/java/org/dromara/snailai/SnailAiServerApplication.java
@@ -0,0 +1,18 @@
+package org.dromara.snailai;
+
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * Snail AI Server 启动程序
+ *
+ * @author Lion Li
+ * @date 2026-05-26
+ */
+@SpringBootApplication
+public class SnailAiServerApplication {
+
+ public static void main(String[] args) {
+ com.aizuda.snail.ai.starter.SnailAiSpringbootApplication.main(args);
+ }
+
+}
diff --git a/ruoyi-extend/ruoyi-snailai-server/src/main/resources/application-dev.yml b/ruoyi-extend/ruoyi-snailai-server/src/main/resources/application-dev.yml
new file mode 100644
index 000000000..df9ca1a21
--- /dev/null
+++ b/ruoyi-extend/ruoyi-snailai-server/src/main/resources/application-dev.yml
@@ -0,0 +1,28 @@
+spring:
+ datasource:
+ type: com.zaxxer.hikari.HikariDataSource
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
+ username: root
+ password: root
+ hikari:
+ connection-timeout: 30000
+ validation-timeout: 5000
+ minimum-idle: 10
+ maximum-pool-size: 20
+ idle-timeout: 600000
+ max-lifetime: 900000
+ keepaliveTime: 30000
+
+--- # 监控中心配置
+spring.boot.admin.client:
+ # 增加客户端开关
+ enabled: true
+ url: http://localhost:9090/admin
+ instance:
+ service-host-type: IP
+ metadata:
+ username: ${spring.boot.admin.client.username}
+ userpassword: ${spring.boot.admin.client.password}
+ username: @monitor.username@
+ password: @monitor.password@
diff --git a/ruoyi-extend/ruoyi-snailai-server/src/main/resources/application-prod.yml b/ruoyi-extend/ruoyi-snailai-server/src/main/resources/application-prod.yml
new file mode 100644
index 000000000..df9ca1a21
--- /dev/null
+++ b/ruoyi-extend/ruoyi-snailai-server/src/main/resources/application-prod.yml
@@ -0,0 +1,28 @@
+spring:
+ datasource:
+ type: com.zaxxer.hikari.HikariDataSource
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
+ username: root
+ password: root
+ hikari:
+ connection-timeout: 30000
+ validation-timeout: 5000
+ minimum-idle: 10
+ maximum-pool-size: 20
+ idle-timeout: 600000
+ max-lifetime: 900000
+ keepaliveTime: 30000
+
+--- # 监控中心配置
+spring.boot.admin.client:
+ # 增加客户端开关
+ enabled: true
+ url: http://localhost:9090/admin
+ instance:
+ service-host-type: IP
+ metadata:
+ username: ${spring.boot.admin.client.username}
+ userpassword: ${spring.boot.admin.client.password}
+ username: @monitor.username@
+ password: @monitor.password@
diff --git a/ruoyi-extend/ruoyi-snailai-server/src/main/resources/application.yml b/ruoyi-extend/ruoyi-snailai-server/src/main/resources/application.yml
new file mode 100644
index 000000000..36e1dd756
--- /dev/null
+++ b/ruoyi-extend/ruoyi-snailai-server/src/main/resources/application.yml
@@ -0,0 +1,67 @@
+server:
+ port: 8900
+ servlet:
+ context-path: /snail-ai
+
+spring:
+ servlet:
+ multipart:
+ enabled: true
+ # 单个文件的最大限制 (根据 RAG 需求建议设为 50MB - 100MB)
+ max-file-size: 50MB
+ # 单次请求的总文件大小限制 (如果支持多文件并行上传,调大此项)
+ max-request-size: 100MB
+ web:
+ resources:
+ static-locations: classpath:admin/
+ profiles:
+ active: @profiles.active@
+snail-ai:
+ server:
+ grpc-port: 18888
+ skill:
+ upload-dir: ./upload/skills
+ crypto:
+ secret-key: ${SNAIL_AI_CRYPTO_KEY:0123456789abcdef0123456789abcdef}
+ iv: ${SNAIL_AI_CRYPTO_IV:fedcba9876543210fedcba9876543210}
+ resource:
+ storage-type: LOCAL
+ upload-dir: ./upload/resource
+ minio:
+ endpoint: http://localhost:9000
+ access-key: minioadmin
+ secret-key: minioadmin
+ bucket: snail-ai
+ # 短期记忆配置
+ memory:
+ short-term:
+ # 存储类型选项: memory(内存) | db(数据库)
+ # - memory: 适用于单机部署,高性能,重启后数据丢失
+ # - db: 适用于分布式部署,数据持久化,性能较低
+ store-type: db
+
+mybatis-plus:
+ mapper-locations: classpath:/mapper/*.xml
+ typeAliasesPackage: com.example.snail.job.po
+ global-config:
+ db-config:
+ capital-mode: false
+ logic-delete-value: 1
+ logic-not-delete-value: 0
+ configuration:
+ map-underscore-to-camel-case: true
+ cache-enabled: true
+
+logging:
+ config: classpath:logback-plus.xml
+
+management:
+ endpoints:
+ web:
+ exposure:
+ include: '*'
+ endpoint:
+ health:
+ show-details: ALWAYS
+ logfile:
+ external-file: ./logs/ruoyi-snailai-server/console.log
diff --git a/ruoyi-extend/ruoyi-snailai-server/src/main/resources/banner.txt b/ruoyi-extend/ruoyi-snailai-server/src/main/resources/banner.txt
new file mode 100644
index 000000000..d797f2594
--- /dev/null
+++ b/ruoyi-extend/ruoyi-snailai-server/src/main/resources/banner.txt
@@ -0,0 +1,9 @@
+Application Version: ${revision}
+Spring Boot Version: ${spring-boot.version}
+ _ _ _
+ (_) | (_)
+ ___ _ __ __ _ _| |______ __ _ _
+/ __| '_ \ / _` | | |______/ _` | |
+\__ \ | | | (_| | | | | (_| | |
+|___/_| |_|\__,_|_|_| \__,_|_|
+
diff --git a/ruoyi-extend/ruoyi-snailai-server/src/main/resources/logback-plus.xml b/ruoyi-extend/ruoyi-snailai-server/src/main/resources/logback-plus.xml
new file mode 100644
index 000000000..a6ec3c7d6
--- /dev/null
+++ b/ruoyi-extend/ruoyi-snailai-server/src/main/resources/logback-plus.xml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+ ${console.log.pattern}
+ utf-8
+
+
+
+
+
+ ${log.path}/console.log
+
+
+ ${log.path}/console.%d{yyyy-MM-dd}.log
+
+ 1
+
+
+ ${log.pattern}
+ utf-8
+
+
+
+ INFO
+
+
+
+
+ ${log.path}/info.log
+
+ ${log.path}/info.%d{yyyy-MM-dd}.log
+ 60
+
+
+ ${log.pattern}
+
+
+ INFO
+ ACCEPT
+ DENY
+
+
+
+
+ ${log.path}/error.log
+
+ ${log.path}/error.%d{yyyy-MM-dd}.log
+
+ 60
+
+
+ ${log.pattern}
+
+
+ ERROR
+ ACCEPT
+ DENY
+
+
+
+
+ 100
+ 1024
+
+
+
+
+ 100
+ 1024
+
+
+
+
+
+
+
+
+
+
+
diff --git a/script/sql/oracle/oracle_ry_vue.sql b/script/sql/oracle/oracle_ry_vue.sql
index 631a2fa0f..3f1d1dc54 100644
--- a/script/sql/oracle/oracle_ry_vue.sql
+++ b/script/sql/oracle/oracle_ry_vue.sql
@@ -346,7 +346,7 @@ insert into sys_menu values(1761400000000000001, '系统管理', 0, 1, 'system',
insert into sys_menu values(1761400000000000002, '系统监控', 0, 3, 'monitor', null, '', 'N', 'Y', 'M', '0', '0', '', 'monitor', '', '', 1761000000000000103, 1761100000000000001, sysdate, null, null, '系统监控目录');
insert into sys_menu values(1761400000000000003, '系统工具', 0, 4, 'tool', null, '', 'N', 'Y', 'M', '0', '0', '', 'tool', '', '', 1761000000000000103, 1761100000000000001, sysdate, null, null, '系统工具目录');
insert into sys_menu values(1761400000000000005, '测试菜单', 0, 5, 'demo', null, '', 'N', 'Y', 'M', '0', '0', null, 'star', '', '', 1761000000000000103, 1761100000000000001, sysdate, null, null, '');
-insert into sys_menu values(1761400000000000006, 'AI会话', 0, 6, 'ai/chat', 'ai/chat/index', '', 'N', 'Y', 'C', '0', '0', '', 'checkbox', '', '', 1761000000000000103, 1761100000000000001, sysdate, null, null, 'AI聊天菜单');
+insert into sys_menu values(1761400000000000006, 'AI会话', 0, 8, 'ai/chat', 'ai/chat/index', '', 'N', 'Y', 'C', '0', '0', '', 'checkbox', '', '', 1761000000000000103, 1761100000000000001, sysdate, null, null, 'AI聊天菜单');
insert into sys_menu values(1761400000000000004, 'PLUS官网', 0, 9, 'https://gitee.com/dromara/RuoYi-Vue-Plus', null, '', 'Y', 'Y', 'M', '0', '0', '', 'guide', '', '', 1761000000000000103, 1761100000000000001, sysdate, null, null, 'RuoYi-Vue-Plus官网地址');
-- 二级菜单
insert into sys_menu values(1761400000000000100, '用户管理', 1761400000000000001, 1, 'user', 'system/user/index', '', 'N', 'Y', 'C', '0', '0', 'system:user:list', 'user', '', '', 1761000000000000103, 1761100000000000001, sysdate, null, null, '用户管理菜单');
diff --git a/script/sql/postgres/postgres_ry_vue.sql b/script/sql/postgres/postgres_ry_vue.sql
index ac2a81012..8829ab3b7 100644
--- a/script/sql/postgres/postgres_ry_vue.sql
+++ b/script/sql/postgres/postgres_ry_vue.sql
@@ -343,7 +343,7 @@ insert into sys_menu values(1761400000000000001, '系统管理', 0, 1, 'system',
insert into sys_menu values(1761400000000000002, '系统监控', 0, 3, 'monitor', null, '', 'N', 'Y', 'M', '0', '0', '', 'monitor', '', '', 1761000000000000103, 1761100000000000001, now(), null, null, '系统监控目录');
insert into sys_menu values(1761400000000000003, '系统工具', 0, 4, 'tool', null, '', 'N', 'Y', 'M', '0', '0', '', 'tool', '', '', 1761000000000000103, 1761100000000000001, now(), null, null, '系统工具目录');
insert into sys_menu values(1761400000000000005, '测试菜单', 0, 5, 'demo', null, '', 'N', 'Y', 'M', '0', '0', null, 'star', '', '', 1761000000000000103, 1761100000000000001, now(), null, null, '测试菜单');
-insert into sys_menu values(1761400000000000006, 'AI会话', 0, 6, 'ai/chat', 'ai/chat/index', '', 'N', 'Y', 'C', '0', '0', '', 'checkbox', '', '', 1761000000000000103, 1761100000000000001, now(), null, null, 'AI聊天菜单');
+insert into sys_menu values(1761400000000000006, 'AI会话', 0, 8, 'ai/chat', 'ai/chat/index', '', 'N', 'Y', 'C', '0', '0', '', 'checkbox', '', '', 1761000000000000103, 1761100000000000001, now(), null, null, 'AI聊天菜单');
insert into sys_menu values(1761400000000000004, 'PLUS官网', 0, 9, 'https://gitee.com/dromara/RuoYi-Vue-Plus', null, '', 'Y', 'Y', 'M', '0', '0', '', 'guide', '', '', 1761000000000000103, 1761100000000000001, now(), null, null, 'RuoYi-Vue-Plus官网地址');
-- 二级菜单
insert into sys_menu values(1761400000000000100, '用户管理', 1761400000000000001, 1, 'user', 'system/user/index', '', 'N', 'Y', 'C', '0', '0', 'system:user:list', 'user', '', '', 1761000000000000103, 1761100000000000001, now(), null, null, '用户管理菜单');
diff --git a/script/sql/ry_ai.sql b/script/sql/ry_ai.sql
new file mode 100644
index 000000000..adeec46d1
--- /dev/null
+++ b/script/sql/ry_ai.sql
@@ -0,0 +1,516 @@
+-- ============================================================
+-- Snail AI MySQL 全量建表脚本(仅 CREATE,无 ALTER)
+-- 使用:mysql -u user -p database < snail_ai_schema.sql
+-- ============================================================
+
+-- ============================================================
+-- Enterprise RAG Schema for MySQL
+-- ============================================================
+
+-- Knowledge Base
+CREATE TABLE snail_ai_rag
+(
+ id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(255) NOT NULL,
+ description TEXT,
+ icon VARCHAR(512),
+ embedding_model_id BIGINT(128) NOT NULL,
+ dimension_of_vector_model INT NOT NULL COMMENT '向量维度',
+ rerank_model_id BIGINT(128),
+ search_engine_instance_id BIGINT(128),
+ vector_store_instance_id BIGINT(128),
+ search_engine_enable TINYINT(1) DEFAULT 0,
+ delimiter VARCHAR(32) DEFAULT '\n\n',
+ rag_enhancement TEXT,
+ config TEXT DEFAULT NULL COMMENT 'RAG检索和问答的页面配置参数',
+ dedup_strategy TINYINT(1) NOT NULL DEFAULT 2 COMMENT '去重策略: 0=NONE 1=BY_NAME 2=BY_CONTENT 3=BY_NAME_OR_CONTENT',
+ dedup_action TINYINT(1) NOT NULL DEFAULT 0 COMMENT '冲突动作: 0=REJECT 1=SKIP 2=OVERWRITE',
+ upload_confirm TINYINT(1) NOT NULL DEFAULT 1 COMMENT '上传前二次确认: 0-关 1-开',
+ create_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ update_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_unicode_ci;
+
+-- RAG Documents
+CREATE TABLE snail_ai_rag_document
+(
+ id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ rag_id BIGINT NOT NULL,
+ name VARCHAR(255),
+ file_type VARCHAR(32),
+ source_type VARCHAR(32),
+ source_path VARCHAR(1024),
+ storage_path VARCHAR(1024),
+ storage_type VARCHAR(32) DEFAULT 'LOCAL',
+ file_size BIGINT DEFAULT 0,
+ content TEXT,
+ status TINYINT(1) DEFAULT 0 COMMENT '状态: 0-待处理 1-解析中 2-处理中 3-处理完成 4-处理失败',
+ error_msg TEXT,
+ chunk_count INT DEFAULT 0,
+ content_hash VARCHAR(64) DEFAULT NULL COMMENT '文件内容SHA-256哈希,用于去重',
+ resource_id BIGINT DEFAULT NULL COMMENT '关联资源库 snail_ai_resource.id',
+ create_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ update_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_unicode_ci;
+
+CREATE INDEX idx_rag_doc_rag ON snail_ai_rag_document (rag_id);
+CREATE INDEX idx_rag_content_hash ON snail_ai_rag_document (rag_id, content_hash);
+CREATE INDEX idx_rag_name ON snail_ai_rag_document (rag_id, name);
+CREATE INDEX idx_rag_doc_resource ON snail_ai_rag_document (resource_id);
+
+-- RAG Chunks
+CREATE TABLE snail_ai_rag_chunk
+(
+ id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ rag_id BIGINT NOT NULL,
+ document_id BIGINT NOT NULL,
+ paragraph_index INT,
+ chunk_index INT,
+ content TEXT,
+ token_count INT,
+ vector_id VARCHAR(128),
+ content_hash VARCHAR(64) DEFAULT NULL COMMENT 'chunk内容SHA-256,用于向量去重',
+ create_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ update_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_unicode_ci;
+
+CREATE INDEX idx_rag_chunk_rag ON snail_ai_rag_chunk (rag_id);
+CREATE INDEX idx_rag_chunk_document ON snail_ai_rag_chunk (document_id);
+CREATE INDEX idx_chunk_rag_hash ON snail_ai_rag_chunk (rag_id, content_hash);
+
+CREATE TABLE snail_ai_user
+(
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
+ role INT,
+ totals INT,
+ username VARCHAR(255),
+ email VARCHAR(64),
+ password VARCHAR(64) NOT NULL,
+ create_dt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ update_dt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ UNIQUE KEY uk_username (username)
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4;
+
+INSERT INTO snail_ai_user (id, role, username, email, password, create_dt, update_dt)
+VALUES (1, 2, 'admin', '', '094c883e17947ff795de8b22279d81c2600dfe85e2dba3fbf562423e883b07ca',
+ '2026-02-11 13:56:48.210429', '2026-02-11 13:56:48.210429');
+
+-- ============================================
+-- 1. AI 模型提供商表
+-- ============================================
+CREATE TABLE IF NOT EXISTS snail_ai_model_provider
+(
+ id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ provider_name VARCHAR(255) NOT NULL COMMENT '提供商名称',
+ provider_key VARCHAR(50) NOT NULL COMMENT '提供商标识符',
+ description TEXT COMMENT '提供商描述',
+ icon_url VARCHAR(500) COMMENT 'LOGO图标URL',
+ is_enabled TINYINT(1) DEFAULT 1 COMMENT '是否启用',
+ created_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ updated_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ UNIQUE KEY uk_provider_name (provider_name),
+ UNIQUE KEY uk_provider_key (provider_key)
+ ) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_unicode_ci COMMENT = 'AI模型提供商表';
+
+CREATE INDEX idx_provider_key ON snail_ai_model_provider (provider_key);
+CREATE INDEX idx_is_enabled ON snail_ai_model_provider (is_enabled);
+
+-- ============================================
+-- 2. AI模型配置表
+-- ============================================
+CREATE TABLE IF NOT EXISTS snail_ai_model_config
+(
+ id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ provider_id BIGINT NOT NULL COMMENT '提供商ID',
+ model_name VARCHAR(255) NOT NULL COMMENT '模型名称',
+ model_key VARCHAR(100) NOT NULL COMMENT '模型标识符',
+ model_type VARCHAR(50) NOT NULL COMMENT '模型类型(CHAT/EMBEDDING/RERANKER/IMAGE/SPEECH)',
+ description VARCHAR(1000) COMMENT '模型描述',
+ api_key VARCHAR(1000) COMMENT 'API密钥(加密存储)',
+ api_endpoint VARCHAR(500) COMMENT 'API端点URL',
+ config_json TEXT COMMENT '模型参数配置(JSON格式)',
+ owner_id BIGINT COMMENT '所有者ID(NULL=全局,具体值=用户ID)',
+ scope VARCHAR(20) NOT NULL DEFAULT 'GLOBAL' COMMENT '作用域(GLOBAL/PERSONAL)',
+ is_default TINYINT(1) DEFAULT 0 COMMENT '是否为默认模型',
+ is_enabled TINYINT(1) DEFAULT 1 COMMENT '是否启用',
+ created_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ updated_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ KEY fk_provider_id (provider_id)
+ ) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_unicode_ci COMMENT = 'AI模型配置表';
+
+CREATE INDEX idx_provider_model_type ON snail_ai_model_config (provider_id, model_type);
+CREATE INDEX idx_model_type_enabled ON snail_ai_model_config (model_type, is_enabled);
+CREATE INDEX idx_owner_id ON snail_ai_model_config (owner_id);
+CREATE INDEX idx_is_default ON snail_ai_model_config (is_default);
+CREATE INDEX idx_scope ON snail_ai_model_config (scope);
+CREATE INDEX idx_model_key ON snail_ai_model_config (model_key);
+
+-- ============================================
+-- 3. 模型使用统计表
+-- ============================================
+CREATE TABLE IF NOT EXISTS snail_ai_model_usage_stat
+(
+ id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ model_id BIGINT NOT NULL COMMENT '模型ID',
+ user_id BIGINT NOT NULL COMMENT '用户ID',
+ total_calls BIGINT DEFAULT 0 COMMENT '总调用次数',
+ success_calls BIGINT DEFAULT 0 COMMENT '成功调用次数',
+ failed_calls BIGINT DEFAULT 0 COMMENT '失败调用次数',
+ total_tokens_used BIGINT DEFAULT 0 COMMENT '总Token使用量',
+ total_cost DECIMAL(18, 8) DEFAULT 0 COMMENT '总费用',
+ avg_response_time BIGINT DEFAULT 0 COMMENT '平均响应时间(毫秒)',
+ last_used_dt TIMESTAMP NULL DEFAULT NULL COMMENT '最后使用时间',
+ created_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ updated_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ UNIQUE KEY unique_model_user (model_id, user_id),
+ KEY fk_stat_model_id (model_id)
+ ) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_unicode_ci COMMENT = '模型使用统计表';
+
+CREATE INDEX idx_model_id ON snail_ai_model_usage_stat (model_id);
+CREATE INDEX idx_user_id ON snail_ai_model_usage_stat (user_id);
+CREATE INDEX idx_last_used_dt ON snail_ai_model_usage_stat (last_used_dt);
+
+-- ============================================
+-- 初始化数据 (可选)
+-- ============================================
+-- 插入常见的AI提供商(重复 provider_key 则忽略)
+INSERT IGNORE INTO snail_ai_model_provider (provider_name, provider_key, description, is_enabled)
+VALUES ('OpenAI', 'openai', 'OpenAI官方模型 (GPT-4, GPT-3.5等)', 1),
+ ('Claude', 'claude', 'Anthropic Claude模型', 1),
+ ('Ollama', 'ollama', '本地开源模型 (Llama, Mistral等)', 1),
+ ('Google Gemini', 'gemini', 'Google Gemini模型', 1);
+
+-- ============================================
+-- 智能体相关表
+-- ============================================
+
+-- 智能体主表
+CREATE TABLE IF NOT EXISTS snail_ai_agent
+(
+ id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(255) NOT NULL COMMENT '智能体名称',
+ description TEXT COMMENT '智能体描述',
+ avatar VARCHAR(512) COMMENT '头像URL',
+ instruction TEXT COMMENT '系统指令(System Prompt)',
+ greeting TEXT COMMENT '欢迎语',
+ preset_questions TEXT COMMENT '预设问题列表(JSON数组字符串)',
+ chat_model_id BIGINT COMMENT '关联的对话模型ID',
+ memory_enabled TINYINT(1) DEFAULT 0 COMMENT '是否启用记忆库',
+ mcp_enabled TINYINT(1) DEFAULT 0 COMMENT '是否启用MCP',
+ skill_enabled TINYINT(1) DEFAULT 0 COMMENT '是否启用Skill',
+ web_search_enabled TINYINT(1) DEFAULT 0 COMMENT '是否启用联网搜索',
+ rag_enabled TINYINT(1) DEFAULT 0 COMMENT '是否启用RAG',
+ rag_id BIGINT NULL COMMENT '绑定的RAG ID',
+ short_term_memory_size INT DEFAULT 20 COMMENT '短期记忆滑动窗口保留条数',
+ creator_id BIGINT COMMENT '创建者用户ID',
+ is_featured TINYINT(1) DEFAULT 0 COMMENT '是否精选',
+ view_count INT DEFAULT 0 COMMENT '浏览次数',
+ status TINYINT DEFAULT 1 COMMENT '状态: 1-活跃 2-非活跃 3-已废弃 4-已禁用',
+ config TEXT COMMENT '扩展配置(预留)',
+ app_id VARCHAR(128) NULL COMMENT '关联应用ID(NULL=本地执行)',
+ create_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ update_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+ ) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_unicode_ci COMMENT = '智能体表';
+
+CREATE INDEX idx_agent_creator ON snail_ai_agent (creator_id);
+CREATE INDEX idx_agent_featured ON snail_ai_agent (is_featured);
+
+-- 智能体对话表
+CREATE TABLE IF NOT EXISTS snail_ai_agent_conversation
+(
+ id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ agent_id BIGINT NOT NULL COMMENT '智能体ID',
+ user_id BIGINT NOT NULL COMMENT '用户ID',
+ conversation_id VARCHAR(64) NOT NULL COMMENT '对话ID(UUID)',
+ title VARCHAR(255) COMMENT '对话标题',
+ create_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ update_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ UNIQUE KEY uk_conv_id (conversation_id)
+ ) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_unicode_ci COMMENT = '智能体对话表';
+
+CREATE INDEX idx_agent_conv_agent ON snail_ai_agent_conversation (agent_id);
+CREATE INDEX idx_agent_conv_user ON snail_ai_agent_conversation (user_id);
+
+-- 智能体对话消息记录表
+CREATE TABLE IF NOT EXISTS snail_ai_agent_conversation_record
+(
+ id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ agent_id BIGINT NOT NULL COMMENT '智能体ID',
+ conversation_id VARCHAR(64) NOT NULL COMMENT '对话ID',
+ user_id BIGINT NOT NULL COMMENT '用户ID',
+ role VARCHAR(16) DEFAULT 'user' COMMENT 'user/assistant',
+ content TEXT COMMENT '消息内容',
+ thinking TEXT COMMENT '思考过程(仅assistant)',
+ status INT DEFAULT 1 COMMENT '1=成功,2=失败,3=进行中',
+ token_count INT DEFAULT 0 COMMENT 'Token数',
+ create_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+ ) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_unicode_ci COMMENT = '智能体对话消息记录';
+
+CREATE INDEX idx_agent_rec_conv ON snail_ai_agent_conversation_record (conversation_id);
+
+-- 智能体使用统计表
+CREATE TABLE IF NOT EXISTS snail_ai_agent_usage_stat
+(
+ id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ agent_id BIGINT NOT NULL COMMENT '智能体ID',
+ user_id BIGINT NOT NULL COMMENT '用户ID',
+ user_name VARCHAR(255) COMMENT '用户名',
+ department VARCHAR(255) DEFAULT '' COMMENT '部门',
+ message_count INT DEFAULT 0 COMMENT '消息条数',
+ conversation_count INT DEFAULT 0 COMMENT '对话轮次',
+ stat_date DATE NOT NULL COMMENT '统计日期',
+ create_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ update_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ UNIQUE KEY uk_agent_user_date (agent_id, user_id, stat_date)
+ ) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_unicode_ci COMMENT = '智能体使用统计';
+
+CREATE INDEX idx_usage_agent ON snail_ai_agent_usage_stat (agent_id);
+CREATE INDEX idx_usage_date ON snail_ai_agent_usage_stat (stat_date);
+
+-- ============================================
+-- MCP 服务管理
+-- ============================================
+
+-- MCP 服务配置表
+CREATE TABLE IF NOT EXISTS snail_ai_mcp_server
+(
+ id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(255) NOT NULL COMMENT 'MCP服务名称',
+ description TEXT COMMENT 'MCP服务描述',
+ transport_type TINYINT(1) DEFAULT 1 COMMENT '传输类型: 1-SSE 2-Streamable HTTP 3-Stdio',
+ base_uri VARCHAR(1024) COMMENT '服务基础地址(SSE/Streamable HTTP时使用)',
+ endpoint VARCHAR(1024) COMMENT '端点路径(SSE/Streamable HTTP时可选)',
+ command VARCHAR(1024) COMMENT 'Stdio命令(Stdio时必填)',
+ args TEXT COMMENT 'Stdio命令参数(JSON数组)',
+ env_vars TEXT COMMENT 'Stdio环境变量(JSON对象)',
+ version VARCHAR(32) DEFAULT '1.0.0' COMMENT '版本',
+ auth_type TINYINT(1) DEFAULT 0 COMMENT '认证方式: 0-无需认证 1-API Key 2-OAuth 3-Basic Auth',
+ auth_config TEXT COMMENT '认证配置(JSON)',
+ status TINYINT(1) DEFAULT 0 COMMENT '状态: 0-未连接 1-已连接 2-异常',
+ capabilities TEXT COMMENT '能力列表(JSON数组)',
+ last_connect_dt TIMESTAMP NULL COMMENT '最后连接时间',
+ creator_id BIGINT COMMENT '创建者用户ID',
+ create_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ update_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+ ) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_unicode_ci COMMENT = 'MCP服务配置表';
+
+CREATE INDEX idx_mcp_server_creator ON snail_ai_mcp_server (creator_id);
+CREATE INDEX idx_mcp_server_status ON snail_ai_mcp_server (status);
+
+-- 智能体与MCP服务关联表(多对多)
+CREATE TABLE IF NOT EXISTS snail_ai_agent_mcp_server
+(
+ id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ agent_id BIGINT NOT NULL COMMENT '智能体ID',
+ mcp_server_id BIGINT NOT NULL COMMENT 'MCP服务ID',
+ create_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ UNIQUE KEY uk_agent_mcp (agent_id, mcp_server_id)
+ ) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_unicode_ci COMMENT = '智能体MCP服务关联表';
+
+CREATE INDEX idx_agent_mcp_agent ON snail_ai_agent_mcp_server (agent_id);
+CREATE INDEX idx_agent_mcp_server ON snail_ai_agent_mcp_server (mcp_server_id);
+
+-- 用户订阅的智能体(多对多)
+CREATE TABLE IF NOT EXISTS snail_ai_user_agent
+(
+ id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id BIGINT NOT NULL COMMENT '用户ID',
+ agent_id BIGINT NOT NULL COMMENT '智能体ID',
+ create_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ UNIQUE KEY uk_user_agent (user_id, agent_id)
+ ) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_unicode_ci COMMENT = '用户订阅的智能体';
+
+CREATE INDEX idx_user_agent_user ON snail_ai_user_agent (user_id);
+
+-- ============================================
+-- Skill 技能包管理
+-- ============================================
+
+-- Skill 技能包表
+CREATE TABLE IF NOT EXISTS snail_ai_skill
+(
+ id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(255) NOT NULL COMMENT 'Skill名称(从SKILL.md解析)',
+ description TEXT COMMENT 'Skill描述(从SKILL.md解析)',
+ file_name VARCHAR(255) COMMENT '上传的zip文件名',
+ file_path VARCHAR(1024) COMMENT '解压后存储路径',
+ file_size BIGINT DEFAULT 0 COMMENT '文件大小(字节)',
+ skill_content LONGTEXT COMMENT 'SKILL.md正文内容(去除frontmatter)',
+ storage_path VARCHAR(500) DEFAULT NULL COMMENT '对象存储相对路径前缀(如 skills/123/)',
+ version BIGINT DEFAULT 0 COMMENT '版本号,文件变更时自增,用于缓存一致性校验',
+ has_files TINYINT(1) DEFAULT 0 COMMENT '是否包含支撑文件(0=仅SKILL.md,1=有scripts/references等)',
+ creator_id BIGINT COMMENT '创建者用户ID',
+ create_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ update_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+ ) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_unicode_ci COMMENT = 'Skill技能包表';
+
+CREATE INDEX idx_skill_creator ON snail_ai_skill (creator_id);
+
+-- Skill 支撑文件内容表
+CREATE TABLE IF NOT EXISTS snail_ai_skill_file
+(
+ id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ skill_id BIGINT NOT NULL COMMENT 'Skill ID',
+ file_path VARCHAR(255) NOT NULL COMMENT '文件相对路径',
+ content LONGTEXT NOT NULL COMMENT '文件内容',
+ file_size INT NOT NULL COMMENT '文件大小(字节)',
+ encoding VARCHAR(50) DEFAULT 'utf-8' COMMENT '编码方式',
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ UNIQUE KEY uk_skill_path (skill_id, file_path) COMMENT '同一Skill不能有重复的文件路径',
+ KEY idx_skill_id (skill_id) COMMENT '技能ID索引'
+ ) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_unicode_ci COMMENT = 'Skill支撑文件内容表';
+
+-- 智能体与Skill关联表(多对多)
+CREATE TABLE IF NOT EXISTS snail_ai_agent_skill
+(
+ id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ agent_id BIGINT NOT NULL COMMENT '智能体ID',
+ skill_id BIGINT NOT NULL COMMENT 'Skill ID',
+ create_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ UNIQUE KEY uk_agent_skill (agent_id, skill_id)
+ ) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_unicode_ci COMMENT = '智能体Skill关联表';
+
+CREATE INDEX idx_agent_skill_agent ON snail_ai_agent_skill (agent_id);
+CREATE INDEX idx_agent_skill_skill ON snail_ai_agent_skill (skill_id);
+
+-- ============================================================
+CREATE TABLE IF NOT EXISTS snail_ai_store_instance
+(
+ id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(128) NOT NULL COMMENT '实例名称',
+ category TINYINT(1) NOT NULL COMMENT '分类: 1-向量库 2-搜索引擎',
+ type TINYINT(1) NOT NULL COMMENT '类型: 1-PG_VECTOR 2-MILVUS 3-ELASTICSEARCH 4-PG_FULLTEXT',
+ config TEXT DEFAULT NULL COMMENT '连接参数 JSON',
+ status TINYINT(1) DEFAULT 1 COMMENT '状态: 0-停用 1-启用',
+ is_default TINYINT(1) DEFAULT 0 COMMENT '是否为该 category 下默认实例',
+ create_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ update_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+ ) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_unicode_ci COMMENT = '存储实例';
+
+CREATE INDEX idx_store_instance_category ON snail_ai_store_instance (category);
+CREATE INDEX idx_store_instance_type ON snail_ai_store_instance (type);
+
+-- ============================================================
+-- 记忆系统(配置 / 主表 / 历史 / 摘要 / 统计 / 提取进度)
+-- 依赖:snail_ai_store_instance(conversation_memory 外键)
+-- ============================================================
+
+-- 客户端应用
+-- ----------------------------
+CREATE TABLE IF NOT EXISTS snail_ai_app
+(
+ id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ app_id VARCHAR(128) NOT NULL COMMENT '应用唯一标识',
+ app_name VARCHAR(255) NOT NULL COMMENT '应用名称',
+ description VARCHAR(512) COMMENT '应用描述',
+ token VARCHAR(128) NOT NULL COMMENT '通信认证令牌',
+ route_strategy VARCHAR(32) DEFAULT 'LEAST_LOAD' COMMENT '路由策略',
+ status TINYINT(1) DEFAULT 1 COMMENT '1=启用, 0=停用',
+ create_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ update_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ UNIQUE KEY uk_app_id (app_id)
+ ) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_unicode_ci COMMENT = '客户端应用';
+
+INSERT INTO snail_ai_app VALUES (1, '1', 'demo', '', 'SAI_7c557fc05a304b0482a65ca67afdb0b8', 'LEAST_LOAD', 1, '2026-05-26 12:31:17', '2026-05-26 12:31:17');
+
+-- ----------------------------
+-- AI客户端实例节点
+-- ----------------------------
+CREATE TABLE IF NOT EXISTS snail_ai_client_node
+(
+ id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ app_id VARCHAR(128) NOT NULL COMMENT '所属应用ID',
+ host_id VARCHAR(128) NOT NULL COMMENT '客户端实例唯一标识',
+ host_ip VARCHAR(64) NOT NULL COMMENT '客户端IP',
+ grpc_port INT NOT NULL COMMENT '客户端gRPC端口',
+ max_concurrent INT DEFAULT 10 COMMENT '最大并发对话数',
+ active_chats INT DEFAULT 0 COMMENT '当前活跃对话数',
+ supported_providers TEXT COMMENT '支持的模型提供商(JSON数组)',
+ labels TEXT COMMENT '路由标签',
+ expire_dt DATETIME NOT NULL COMMENT '过期时间(心跳更新)',
+ create_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ update_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ UNIQUE KEY uk_client_node (app_id, host_id),
+ INDEX idx_app_expire (app_id, expire_dt)
+ ) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_unicode_ci COMMENT = 'AI客户端实例节点';
+
+-- OpenAPI 外部用户映射表
+CREATE TABLE snail_ai_openapi_user
+(
+ id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ app_id VARCHAR(128) NOT NULL COMMENT '关联 snail_ai_app.app_id',
+ open_id VARCHAR(64) NOT NULL COMMENT '平台分配的唯一标识(UUID)',
+ platform_user_id BIGINT NOT NULL COMMENT '关联 snail_ai_user.id,注册时自动创建',
+ external_id VARCHAR(256) DEFAULT NULL COMMENT '外部系统的用户标识(可选,幂等用)',
+ nickname VARCHAR(128) DEFAULT NULL COMMENT '外部用户昵称',
+ create_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ update_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ UNIQUE KEY uk_app_open (app_id, open_id),
+ UNIQUE KEY uk_app_external (app_id, external_id),
+ INDEX idx_open_id (open_id),
+ INDEX idx_platform_user (platform_user_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
+ COMMENT='OpenAPI 外部用户映射表';
+
+-- ----------------------------
+-- 通用资源存储
+-- ----------------------------
+CREATE TABLE IF NOT EXISTS snail_ai_resource
+(
+ id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ storage_key VARCHAR(512) NOT NULL COMMENT '存储键(相对路径或对象Key)',
+ original_name VARCHAR(255) NOT NULL COMMENT '原始文件名',
+ file_size BIGINT DEFAULT 0 COMMENT '文件大小(bytes)',
+ mime_type VARCHAR(128) COMMENT 'MIME类型',
+ storage_type VARCHAR(32) NOT NULL DEFAULT 'LOCAL' COMMENT '存储类型: LOCAL/MINIO',
+ access_url VARCHAR(1024) COMMENT '访问URL',
+ biz_type VARCHAR(64) NOT NULL DEFAULT 'GENERAL' COMMENT '业务类型: AVATAR/ATTACHMENT/DOCUMENT/GENERAL',
+ biz_id BIGINT COMMENT '关联业务ID',
+ creator_id BIGINT COMMENT '上传者ID',
+ create_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ update_dt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ UNIQUE KEY uk_storage_key (storage_key),
+ INDEX idx_biz (biz_type, biz_id),
+ INDEX idx_creator (creator_id)
+ ) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_unicode_ci COMMENT = '通用资源存储';
diff --git a/script/sql/ry_vue.sql b/script/sql/ry_vue.sql
index e6b2d3dda..246ae1152 100644
--- a/script/sql/ry_vue.sql
+++ b/script/sql/ry_vue.sql
@@ -212,7 +212,7 @@ insert into sys_menu values(1761400000000000001, '系统管理', 0, 1, 'system',
insert into sys_menu values(1761400000000000002, '系统监控', 0, 3, 'monitor', null, '', 'N', 'Y', 'M', '0', '0', '', 'monitor', '', '', 1761000000000000103, 1761100000000000001, sysdate(), null, null, '系统监控目录');
insert into sys_menu values(1761400000000000003, '系统工具', 0, 4, 'tool', null, '', 'N', 'Y', 'M', '0', '0', '', 'tool', '', '', 1761000000000000103, 1761100000000000001, sysdate(), null, null, '系统工具目录');
insert into sys_menu values(1761400000000000005, '测试菜单', 0, 5, 'demo', null, '', 'N', 'Y', 'M', '0', '0', '', 'star', '', '', 1761000000000000103, 1761100000000000001, sysdate(), null, null, '测试菜单');
-insert into sys_menu values(1761400000000000006, 'AI会话', 0, 6, 'ai/chat', 'ai/chat/index', '', 'N', 'Y', 'C', '0', '0', '', 'checkbox', '', '', 1761000000000000103, 1761100000000000001, sysdate(), null, null, 'AI聊天菜单');
+insert into sys_menu values(1761400000000000008, 'AI会话', 0, 8, 'ai/chat', 'ai/chat/index', '', 'N', 'Y', 'C', '0', '0', '', 'checkbox', '', '', 1761000000000000103, 1761100000000000001, sysdate(), null, null, 'AI聊天菜单');
insert into sys_menu values(1761400000000000004, 'PLUS官网', 0, 9, 'https://gitee.com/dromara/RuoYi-Vue-Plus', null, '', 'Y', 'Y', 'M', '0', '0', '', 'guide', '', '', 1761000000000000103, 1761100000000000001, sysdate(), null, null, 'RuoYi-Vue-Plus官网地址');
-- 二级菜单
insert into sys_menu values(1761400000000000100, '用户管理', 1761400000000000001, 1, 'user', 'system/user/index', '', 'N', 'Y', 'C', '0', '0', 'system:user:list', 'user', '', '', 1761000000000000103, 1761100000000000001, sysdate(), null, null, '用户管理菜单');
diff --git a/script/sql/sqlserver/sqlserver_ry_vue.sql b/script/sql/sqlserver/sqlserver_ry_vue.sql
index 12e3eb02c..6516f298e 100644
--- a/script/sql/sqlserver/sqlserver_ry_vue.sql
+++ b/script/sql/sqlserver/sqlserver_ry_vue.sql
@@ -1378,7 +1378,7 @@ insert into sys_menu values(1761400000000000003, N'系统工具', 0, 4, N'tool',
GO
insert into sys_menu values(1761400000000000005, N'测试菜单', 0, 5, N'demo', NULL, N'', N'N', N'Y', N'M', N'0', N'0', NULL, N'star', N'', N'', 1761000000000000103, 1761100000000000001, getdate(), NULL, NULL, N'');
GO
-insert into sys_menu values(1761400000000000006, N'AI会话', 0, 6, N'ai/chat', N'ai/chat/index', N'', N'N', N'Y', N'C', N'0', N'0', N'', N'checkbox', N'', N'', 1761000000000000103, 1761100000000000001, getdate(), NULL, NULL, N'AI聊天菜单');
+insert into sys_menu values(1761400000000000006, N'AI会话', 0, 8, N'ai/chat', N'ai/chat/index', N'', N'N', N'Y', N'C', N'0', N'0', N'', N'checkbox', N'', N'', 1761000000000000103, 1761100000000000001, getdate(), NULL, NULL, N'AI聊天菜单');
GO
insert into sys_menu values(1761400000000000004, N'PLUS官网', 0, 9, N'https://gitee.com/dromara/RuoYi-Vue-Plus', null, N'', N'Y', N'Y', N'M', N'0', N'0', N'', N'guide', N'', N'', 1761000000000000103, 1761100000000000001, getdate(), null, null, N'RuoYi-Vue-Plus官网地址');
GO