update 集成 snail-ai-server
update 增加 ruoyi-ai服务
This commit is contained in:
@@ -0,0 +1,12 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="ruoyi-ai" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
|
||||||
|
<deployment type="dockerfile">
|
||||||
|
<settings>
|
||||||
|
<option name="imageTag" value="ruoyi/ruoyi-ai:2.5.3" />
|
||||||
|
<option name="buildOnly" value="true" />
|
||||||
|
<option name="sourceFilePath" value="ruoyi-modules/ruoyi-ai/Dockerfile" />
|
||||||
|
</settings>
|
||||||
|
</deployment>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="ruoyi-snailai-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
|
||||||
|
<deployment type="dockerfile">
|
||||||
|
<settings>
|
||||||
|
<option name="imageTag" value="ruoyi/ruoyi-snailai-server:2.5.3" />
|
||||||
|
<option name="buildOnly" value="true" />
|
||||||
|
<option name="sourceFilePath" value="ruoyi-visual/ruoyi-snailai-server/Dockerfile" />
|
||||||
|
</settings>
|
||||||
|
</deployment>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
@@ -34,6 +34,7 @@
|
|||||||
<redisson.version>4.4.0</redisson.version>
|
<redisson.version>4.4.0</redisson.version>
|
||||||
<lock4j.version>2.2.7</lock4j.version>
|
<lock4j.version>2.2.7</lock4j.version>
|
||||||
<snailjob.version>2.0.0</snailjob.version>
|
<snailjob.version>2.0.0</snailjob.version>
|
||||||
|
<snailai.version>0.0.3</snailai.version>
|
||||||
<satoken.version>1.45.0</satoken.version>
|
<satoken.version>1.45.0</satoken.version>
|
||||||
<lombok.version>1.18.42</lombok.version>
|
<lombok.version>1.18.42</lombok.version>
|
||||||
<logstash.version>7.4</logstash.version>
|
<logstash.version>7.4</logstash.version>
|
||||||
@@ -61,7 +62,7 @@
|
|||||||
<!-- mq配置 -->
|
<!-- mq配置 -->
|
||||||
<rocketmq.version>2.3.4</rocketmq.version>
|
<rocketmq.version>2.3.4</rocketmq.version>
|
||||||
<!-- Spring AI 2.0 预览版,正式版发布后仅需调整此版本号 -->
|
<!-- Spring AI 2.0 预览版,正式版发布后仅需调整此版本号 -->
|
||||||
<spring-ai.version>2.0.0-M6</spring-ai.version>
|
<spring-ai.version>2.0.0-M8</spring-ai.version>
|
||||||
|
|
||||||
<!-- 插件版本 -->
|
<!-- 插件版本 -->
|
||||||
<maven-compiler-plugin.version>3.14.0</maven-compiler-plugin.version>
|
<maven-compiler-plugin.version>3.14.0</maven-compiler-plugin.version>
|
||||||
@@ -278,6 +279,19 @@
|
|||||||
<version>${snailjob.version}</version>
|
<version>${snailjob.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Snail AI Agent 启动器 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aizuda</groupId>
|
||||||
|
<artifactId>snail-ai-agent-starter</artifactId>
|
||||||
|
<version>${snailai.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- Snail AI OpenAPI 启动器 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aizuda</groupId>
|
||||||
|
<artifactId>snail-ai-openapi-starter</artifactId>
|
||||||
|
<version>${snailai.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- 加密包引入 -->
|
<!-- 加密包引入 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.bouncycastle</groupId>
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
<module>ruoyi-common-social</module>
|
<module>ruoyi-common-social</module>
|
||||||
<module>ruoyi-common-nacos</module>
|
<module>ruoyi-common-nacos</module>
|
||||||
<module>ruoyi-common-bus</module>
|
<module>ruoyi-common-bus</module>
|
||||||
|
<module>ruoyi-common-ai</module>
|
||||||
<module>ruoyi-common-mqtt</module>
|
<module>ruoyi-common-mqtt</module>
|
||||||
<module>ruoyi-common-mcp</module>
|
<module>ruoyi-common-mcp</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<groupId>org.dromara</groupId>
|
||||||
|
<artifactId>ruoyi-common</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>ruoyi-common-ai</artifactId>
|
||||||
|
|
||||||
|
<description>
|
||||||
|
ruoyi-common-ai AI公共模块
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<!-- 核心模块 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.dromara</groupId>
|
||||||
|
<artifactId>ruoyi-common-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Snail AI Agent 启动器 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aizuda</groupId>
|
||||||
|
<artifactId>snail-ai-agent-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Snail AI OpenAPI 启动器 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aizuda</groupId>
|
||||||
|
<artifactId>snail-ai-openapi-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
+16
@@ -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 {
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
org.dromara.common.ai.config.SnailAiConfig
|
||||||
@@ -223,6 +223,13 @@
|
|||||||
<version>${revision}</version>
|
<version>${revision}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- AI模块 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.dromara</groupId>
|
||||||
|
<artifactId>ruoyi-common-ai</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.dromara</groupId>
|
<groupId>org.dromara</groupId>
|
||||||
<artifactId>ruoyi-common-mqtt</artifactId>
|
<artifactId>ruoyi-common-mqtt</artifactId>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
<module>ruoyi-job</module>
|
<module>ruoyi-job</module>
|
||||||
<module>ruoyi-resource</module>
|
<module>ruoyi-resource</module>
|
||||||
<module>ruoyi-workflow</module>
|
<module>ruoyi-workflow</module>
|
||||||
|
<module>ruoyi-ai</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<artifactId>ruoyi-modules</artifactId>
|
<artifactId>ruoyi-modules</artifactId>
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
|
||||||
|
FROM bellsoft/liberica-openjdk-rocky:17.0.16-cds
|
||||||
|
#FROM bellsoft/liberica-openjdk-rocky:21.0.8-cds
|
||||||
|
#FROM findepi/graalvm:java17-native
|
||||||
|
|
||||||
|
LABEL maintainer="Lion Li"
|
||||||
|
|
||||||
|
RUN mkdir -p /ruoyi/ai/logs \
|
||||||
|
/ruoyi/skywalking/agent
|
||||||
|
|
||||||
|
WORKDIR /ruoyi/ai
|
||||||
|
|
||||||
|
ENV SERVER_PORT=9206 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS=""
|
||||||
|
|
||||||
|
EXPOSE ${SERVER_PORT}
|
||||||
|
EXPOSE 18889
|
||||||
|
|
||||||
|
ADD ./target/ruoyi-ai.jar ./app.jar
|
||||||
|
|
||||||
|
SHELL ["/bin/bash", "-c"]
|
||||||
|
|
||||||
|
ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -Dserver.port=${SERVER_PORT} \
|
||||||
|
#-Dskywalking.agent.service_name=ruoyi-ai \
|
||||||
|
#-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar \
|
||||||
|
-XX:+HeapDumpOnOutOfMemoryError -XX:+UseZGC ${JAVA_OPTS} \
|
||||||
|
-jar app.jar
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<groupId>org.dromara</groupId>
|
||||||
|
<artifactId>ruoyi-modules</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<artifactId>ruoyi-ai</artifactId>
|
||||||
|
|
||||||
|
<description>
|
||||||
|
ai模块
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<!-- Snail AI Agent 0.0.3 需要 protobuf 4.x,避免被 Dubbo 传递的 3.x 抢先选中 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.protobuf</groupId>
|
||||||
|
<artifactId>protobuf-java</artifactId>
|
||||||
|
<version>4.35.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 局部提升 ruoyi-ai 的 gRPC 运行时,与 Snail AI 0.0.3 保持一致 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.grpc</groupId>
|
||||||
|
<artifactId>grpc-protobuf</artifactId>
|
||||||
|
<version>1.81.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.grpc</groupId>
|
||||||
|
<artifactId>grpc-stub</artifactId>
|
||||||
|
<version>1.81.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.grpc</groupId>
|
||||||
|
<artifactId>grpc-netty-shaded</artifactId>
|
||||||
|
<version>1.81.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.dromara</groupId>
|
||||||
|
<artifactId>ruoyi-common-nacos</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.dromara</groupId>
|
||||||
|
<artifactId>ruoyi-common-ai</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.dromara</groupId>
|
||||||
|
<artifactId>ruoyi-common-doc</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.dromara</groupId>
|
||||||
|
<artifactId>ruoyi-common-satoken</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.dromara</groupId>
|
||||||
|
<artifactId>ruoyi-common-dubbo</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.dromara</groupId>
|
||||||
|
<artifactId>ruoyi-common-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.dromara</groupId>
|
||||||
|
<artifactId>ruoyi-common-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.dromara</groupId>
|
||||||
|
<artifactId>ruoyi-api-system</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<finalName>${project.artifactId}</finalName>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<version>${spring-boot.version}</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>repackage</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package org.dromara.ai;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI模块
|
||||||
|
*
|
||||||
|
* @author opensnail
|
||||||
|
*/
|
||||||
|
@SpringBootApplication
|
||||||
|
public class RuoYiAiApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication application = new SpringApplication(RuoYiAiApplication.class);
|
||||||
|
application.setApplicationStartup(new BufferingApplicationStartup(2048));
|
||||||
|
application.run(args);
|
||||||
|
System.out.println("(♥◠‿◠)ノ゙ AI模块启动成功 ლ(´ڡ`ლ)゙ ");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+364
@@ -0,0 +1,364 @@
|
|||||||
|
package org.dromara.ai.controller;
|
||||||
|
|
||||||
|
import com.aizuda.snail.ai.common.execption.SnailAiException;
|
||||||
|
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 jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import jakarta.validation.constraints.Min;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.dromara.common.core.domain.PageResult;
|
||||||
|
import org.dromara.common.core.domain.R;
|
||||||
|
import org.dromara.common.satoken.utils.LoginHelper;
|
||||||
|
import org.dromara.common.web.core.BaseController;
|
||||||
|
import org.dromara.system.api.model.LoginUser;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
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;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Snail AI OpenAPI 控制器
|
||||||
|
*
|
||||||
|
* @author opensnail
|
||||||
|
* @date 2026-04-25
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Validated
|
||||||
|
@RestController
|
||||||
|
@RequestMapping
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SnailAiController extends BaseController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Snail AI 成功状态码。
|
||||||
|
*/
|
||||||
|
private static final int SNAIL_AI_SUCCESS = 1;
|
||||||
|
/**
|
||||||
|
* SSE 超时时间。
|
||||||
|
*/
|
||||||
|
private static final long SSE_TIMEOUT = 300000L;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册当前登录用户并返回 OpenAPI 用户信息。
|
||||||
|
*/
|
||||||
|
@PostMapping("/user/register")
|
||||||
|
public R<OpenApiUserVO> registerCurrentUser() {
|
||||||
|
return R.ok(ensureOpenApiUser());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询当前登录用户对应的 OpenAPI 用户信息。
|
||||||
|
*/
|
||||||
|
@GetMapping("/user")
|
||||||
|
public R<OpenApiUserVO> getUser() {
|
||||||
|
OpenApiUserQueryRequest request = new OpenApiUserQueryRequest();
|
||||||
|
request.setOpenId(ensureOpenId());
|
||||||
|
return toR(userClient.getUser(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询当前用户可访问的智能体列表。
|
||||||
|
*/
|
||||||
|
@GetMapping("/agents")
|
||||||
|
public R<List<OpenApiAgentVO>> listAgents() {
|
||||||
|
return toR(agentClient.listAgents());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据智能体 ID 查询智能体详情。
|
||||||
|
*/
|
||||||
|
@GetMapping("/agent/{agentId}")
|
||||||
|
public R<OpenApiAgentVO> getAgent(@NotNull(message = "智能体ID不能为空") @PathVariable Long agentId) {
|
||||||
|
OpenApiAgentIdentityRequest request = new OpenApiAgentIdentityRequest();
|
||||||
|
request.setAgentId(agentId);
|
||||||
|
return toR(agentClient.getAgent(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为指定智能体创建新会话。
|
||||||
|
*/
|
||||||
|
@PostMapping("/agent/{agentId}/conversation")
|
||||||
|
public R<OpenApiConversationVO> createConversation(
|
||||||
|
@NotNull(message = "智能体ID不能为空") @PathVariable Long agentId,
|
||||||
|
@RequestBody OpenApiCreateConversationRequest request) {
|
||||||
|
request.setAgentId(agentId);
|
||||||
|
request.setOpenId(ensureOpenId());
|
||||||
|
return toR(conversationClient.createConversation(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询指定智能体下的会话列表。
|
||||||
|
*/
|
||||||
|
@GetMapping("/agent/{agentId}/conversations")
|
||||||
|
public R<PageResult<OpenApiConversationVO>> listConversations(
|
||||||
|
@NotNull(message = "智能体ID不能为空") @PathVariable Long agentId,
|
||||||
|
@Min(value = 1, message = "页码不能小于1") @RequestParam(defaultValue = "1") int page,
|
||||||
|
@Min(value = 1, message = "每页条数不能小于1") @RequestParam(defaultValue = "10") int size) {
|
||||||
|
OpenApiConversationQueryRequest request = new OpenApiConversationQueryRequest();
|
||||||
|
request.setAgentId(agentId);
|
||||||
|
request.setOpenId(ensureOpenId());
|
||||||
|
request.setPage(page);
|
||||||
|
request.setSize(size);
|
||||||
|
return toPageR(conversationClient.listConversations(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询指定会话的消息历史。
|
||||||
|
*/
|
||||||
|
@GetMapping("/agent/{agentId}/conversation/{conversationId}/messages")
|
||||||
|
public R<List<OpenApiMessageVO>> getMessages(
|
||||||
|
@NotNull(message = "智能体ID不能为空") @PathVariable Long agentId,
|
||||||
|
@NotBlank(message = "会话ID不能为空") @PathVariable String conversationId) {
|
||||||
|
OpenApiConversationIdentityRequest request = new OpenApiConversationIdentityRequest();
|
||||||
|
request.setAgentId(agentId);
|
||||||
|
request.setConversationId(conversationId);
|
||||||
|
request.setOpenId(ensureOpenId());
|
||||||
|
return toR(conversationClient.getMessages(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除指定会话。
|
||||||
|
*/
|
||||||
|
@DeleteMapping("/agent/{agentId}/conversation/{conversationId}")
|
||||||
|
public R<Void> deleteConversation(
|
||||||
|
@NotNull(message = "智能体ID不能为空") @PathVariable Long agentId,
|
||||||
|
@NotBlank(message = "会话ID不能为空") @PathVariable String conversationId) {
|
||||||
|
OpenApiConversationIdentityRequest request = new OpenApiConversationIdentityRequest();
|
||||||
|
request.setAgentId(agentId);
|
||||||
|
request.setConversationId(conversationId);
|
||||||
|
request.setOpenId(ensureOpenId());
|
||||||
|
return toR(conversationClient.deleteConversation(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前聊天发送模式。
|
||||||
|
*/
|
||||||
|
@GetMapping("/chat/mode")
|
||||||
|
public R<Map<String, String>> getChatMode() {
|
||||||
|
String mode = "sync".equalsIgnoreCase(chatMode) ? "sync" : "stream";
|
||||||
|
return R.ok(Map.of("mode", mode));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步对话接口。
|
||||||
|
*/
|
||||||
|
@PostMapping("/agent/{agentId}/chat/sync")
|
||||||
|
public R<OpenApiChatSyncResponse> chatSync(
|
||||||
|
@NotNull(message = "智能体ID不能为空") @PathVariable Long agentId,
|
||||||
|
@RequestBody OpenApiChatRequest request) {
|
||||||
|
request.setAgentId(agentId);
|
||||||
|
request.setOpenId(ensureOpenId());
|
||||||
|
log.info("Sync chat request: agentId={}", agentId);
|
||||||
|
return toR(chatClient.chatSync(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流式对话接口,按 SSE 事件返回消息分片。
|
||||||
|
*/
|
||||||
|
@PostMapping(value = "/agent/{agentId}/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||||
|
public SseEmitter chatStream(
|
||||||
|
@NotNull(message = "智能体ID不能为空") @PathVariable Long agentId,
|
||||||
|
@RequestBody OpenApiChatRequest request,
|
||||||
|
HttpServletResponse response) {
|
||||||
|
prepareSseResponse(response);
|
||||||
|
SseEmitter emitter = new SseEmitter(SSE_TIMEOUT);
|
||||||
|
AtomicBoolean closed = new AtomicBoolean(false);
|
||||||
|
emitter.onTimeout(() -> {
|
||||||
|
if (!closed.get()) {
|
||||||
|
safeSend(emitter, closed, "error", "SSE stream timeout");
|
||||||
|
safeSend(emitter, closed, "done", "");
|
||||||
|
if (closed.compareAndSet(false, true)) {
|
||||||
|
emitter.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
emitter.onCompletion(() -> closed.set(true));
|
||||||
|
emitter.onError(error -> {
|
||||||
|
closed.set(true);
|
||||||
|
log.warn("SSE emitter error: {}", error.getMessage());
|
||||||
|
});
|
||||||
|
|
||||||
|
request.setAgentId(agentId);
|
||||||
|
request.setOpenId(ensureOpenId());
|
||||||
|
|
||||||
|
log.info("Stream chat request: agentId={}", agentId);
|
||||||
|
try {
|
||||||
|
chatClient.chatStream(request, new SseEventListener() {
|
||||||
|
/**
|
||||||
|
* 推送普通文本片段。
|
||||||
|
*
|
||||||
|
* @param text 文本片段
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onText(String text) {
|
||||||
|
safeSend(emitter, closed, "text", text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推送思考内容片段。
|
||||||
|
*
|
||||||
|
* @param thinking 思考内容
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onThinking(String thinking) {
|
||||||
|
safeSend(emitter, closed, "thinking", thinking);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理流式对话完成事件。
|
||||||
|
*
|
||||||
|
* @param data 完成数据
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onComplete(String data) {
|
||||||
|
if (!closed.get()) {
|
||||||
|
safeSend(emitter, closed, "done", data);
|
||||||
|
log.info("Stream chat completed");
|
||||||
|
if (closed.compareAndSet(false, true)) {
|
||||||
|
emitter.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理流式对话异常事件。
|
||||||
|
*
|
||||||
|
* @param errorMessage 错误信息
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onError(String errorMessage) {
|
||||||
|
log.error("Stream chat error: {}", errorMessage);
|
||||||
|
if (!closed.get()) {
|
||||||
|
safeSend(emitter, closed, "error", errorMessage);
|
||||||
|
safeSend(emitter, closed, "done", "");
|
||||||
|
if (closed.compareAndSet(false, true)) {
|
||||||
|
emitter.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Stream chat exception", e);
|
||||||
|
if (!closed.get()) {
|
||||||
|
safeSend(emitter, closed, "error", "stream exception: " + e.getMessage());
|
||||||
|
safeSend(emitter, closed, "done", "");
|
||||||
|
if (closed.compareAndSet(false, true)) {
|
||||||
|
emitter.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return emitter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换 Snail AI 普通响应为项目统一响应。
|
||||||
|
*/
|
||||||
|
private <T> R<T> toR(Result<T> result) {
|
||||||
|
if (result == null) {
|
||||||
|
return R.fail("Snail AI 服务返回为空");
|
||||||
|
}
|
||||||
|
if (result.getStatus() != SNAIL_AI_SUCCESS) {
|
||||||
|
return R.fail(result.getMessage());
|
||||||
|
}
|
||||||
|
return R.ok(result.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换 Snail AI 分页响应为项目统一分页响应。
|
||||||
|
*/
|
||||||
|
private <T> R<PageResult<T>> toPageR(com.aizuda.snail.ai.common.model.PageResult<List<T>> result) {
|
||||||
|
R<List<T>> response = toR(result);
|
||||||
|
if (R.isError(response)) {
|
||||||
|
return R.fail(response.getMsg());
|
||||||
|
}
|
||||||
|
return R.ok(PageResult.build(response.getData(), result.getTotal()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 输出一条 SSE 事件。
|
||||||
|
*/
|
||||||
|
private boolean safeSend(SseEmitter emitter, AtomicBoolean closed, String event, String data) {
|
||||||
|
if (closed.get()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
emitter.send(SseEmitter.event().name(event).data(data == null ? "" : data));
|
||||||
|
return true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
closed.set(true);
|
||||||
|
log.warn("SSE send failed, event={}", event, e);
|
||||||
|
emitter.completeWithError(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置 SSE 响应头,覆盖统一鉴权成功路径中的默认 JSON 响应类型。
|
||||||
|
*
|
||||||
|
* @param response 当前响应
|
||||||
|
*/
|
||||||
|
private void prepareSseResponse(HttpServletResponse response) {
|
||||||
|
response.setContentType(MediaType.TEXT_EVENT_STREAM_VALUE);
|
||||||
|
response.setCharacterEncoding("UTF-8");
|
||||||
|
response.setHeader("Cache-Control", "no-cache");
|
||||||
|
response.setHeader("X-Accel-Buffering", "no");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前登录用户对应的 openId,不存在时会自动注册。
|
||||||
|
*/
|
||||||
|
private String ensureOpenId() {
|
||||||
|
return ensureOpenApiUser().getOpenId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确保当前登录用户已注册为 OpenAPI 用户。
|
||||||
|
*/
|
||||||
|
private OpenApiUserVO ensureOpenApiUser() {
|
||||||
|
Long userId = LoginHelper.getUserId();
|
||||||
|
LoginUser loginUser = LoginHelper.getLoginUser();
|
||||||
|
if (loginUser == null || userId == null) {
|
||||||
|
throw new SnailAiException("当前登录用户为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenApiUserRegisterRequest registerRequest = new OpenApiUserRegisterRequest();
|
||||||
|
registerRequest.setExternalId(String.valueOf(userId));
|
||||||
|
registerRequest.setNickname(loginUser.getNickname());
|
||||||
|
Result<OpenApiUserVO> registerResult = userClient.register(registerRequest);
|
||||||
|
if (registerResult == null) {
|
||||||
|
throw new SnailAiException("注册 OpenAPI 用户失败,返回为空");
|
||||||
|
}
|
||||||
|
if (registerResult.getStatus() != SNAIL_AI_SUCCESS) {
|
||||||
|
throw new SnailAiException(registerResult.getMessage());
|
||||||
|
}
|
||||||
|
if (registerResult.getData() == null) {
|
||||||
|
throw new SnailAiException("注册 OpenAPI 用户失败,返回为空");
|
||||||
|
}
|
||||||
|
return registerResult.getData();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Tomcat
|
||||||
|
server:
|
||||||
|
port: 9206
|
||||||
|
|
||||||
|
# Spring
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
# 应用名称
|
||||||
|
name: ruoyi-ai
|
||||||
|
profiles:
|
||||||
|
# 环境配置
|
||||||
|
active: @profiles.active@
|
||||||
|
|
||||||
|
--- # nacos 配置
|
||||||
|
spring:
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
# nacos 服务地址
|
||||||
|
server-addr: @nacos.server@
|
||||||
|
username: @nacos.username@
|
||||||
|
password: @nacos.password@
|
||||||
|
discovery:
|
||||||
|
# 注册组
|
||||||
|
group: DEFAULT_GROUP
|
||||||
|
namespace: public
|
||||||
|
config:
|
||||||
|
# 配置组
|
||||||
|
group: DEFAULT_GROUP
|
||||||
|
namespace: public
|
||||||
|
config:
|
||||||
|
import:
|
||||||
|
- optional:nacos:application-common.yml
|
||||||
|
- optional:nacos:${spring.application.name}.yml
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration scan="true" scanPeriod="60 seconds" debug="false">
|
||||||
|
<!-- 日志存放路径 -->
|
||||||
|
<property name="log.path" value="logs/${project.artifactId}"/>
|
||||||
|
<!-- 日志输出格式 -->
|
||||||
|
<property name="console.log.pattern"
|
||||||
|
value="%cyan(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
|
||||||
|
|
||||||
|
<!-- 控制台输出 -->
|
||||||
|
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>${console.log.pattern}</pattern>
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<include resource="logback-common.xml" />
|
||||||
|
|
||||||
|
<include resource="logback-logstash.xml" />
|
||||||
|
|
||||||
|
<!--系统操作日志-->
|
||||||
|
<root level="info">
|
||||||
|
<appender-ref ref="console"/>
|
||||||
|
</root>
|
||||||
|
</configuration>
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
<module>ruoyi-monitor</module>
|
<module>ruoyi-monitor</module>
|
||||||
|
<module>ruoyi-snailai-server</module>
|
||||||
<module>ruoyi-snailjob-server</module>
|
<module>ruoyi-snailjob-server</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<groupId>org.dromara</groupId>
|
||||||
|
<artifactId>ruoyi-visual</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>ruoyi-snailai-server</artifactId>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- Snail AI 服务端 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aizuda</groupId>
|
||||||
|
<artifactId>snail-ai-starter</artifactId>
|
||||||
|
<version>${snailai.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Boot Admin 客户端 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>de.codecentric</groupId>
|
||||||
|
<artifactId>spring-boot-admin-starter-client</artifactId>
|
||||||
|
<version>${spring-boot-admin.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.dromara</groupId>
|
||||||
|
<artifactId>ruoyi-common-nacos</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<finalName>${project.artifactId}</finalName>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<version>${spring-boot.version}</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>repackage</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
+97
@@ -0,0 +1,97 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actuator Basic Auth 认证过滤器。
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
public class ActuatorAuthFilter implements Filter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 认证用户名。
|
||||||
|
*/
|
||||||
|
private final String username;
|
||||||
|
/**
|
||||||
|
* 认证密码。
|
||||||
|
*/
|
||||||
|
private final String password;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 Actuator 认证过滤器。
|
||||||
|
*
|
||||||
|
* @param username 认证用户名
|
||||||
|
* @param password 认证密码
|
||||||
|
*/
|
||||||
|
public ActuatorAuthFilter(String username, String password) {
|
||||||
|
this.username = username;
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验 Actuator Basic Auth 请求。
|
||||||
|
*
|
||||||
|
* @param servletRequest 原始请求
|
||||||
|
* @param servletResponse 原始响应
|
||||||
|
* @param filterChain 过滤器链
|
||||||
|
* @throws IOException IO 异常
|
||||||
|
* @throws ServletException Servlet 异常
|
||||||
|
*/
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化过滤器。
|
||||||
|
*
|
||||||
|
* @param filterConfig 过滤器配置
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void init(FilterConfig filterConfig) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁过滤器。
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
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.cloud.nacos.discovery.metadata.username}")
|
||||||
|
private String username;
|
||||||
|
/**
|
||||||
|
* 认证密码。
|
||||||
|
*/
|
||||||
|
@Value("${spring.cloud.nacos.discovery.metadata.userpassword}")
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册 Actuator Basic Auth 过滤器。
|
||||||
|
*
|
||||||
|
* @return Actuator 过滤器注册对象
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public FilterRegistrationBean<ActuatorAuthFilter> actuatorFilterRegistrationBean() {
|
||||||
|
FilterRegistrationBean<ActuatorAuthFilter> registrationBean = new FilterRegistrationBean<>();
|
||||||
|
registrationBean.setFilter(new ActuatorAuthFilter(username, password));
|
||||||
|
registrationBean.addUrlPatterns("/actuator", "/actuator/*");
|
||||||
|
return registrationBean;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
package org.dromara.snailai;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Snail AI Server 启动程序
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
* @date 2026-05-26
|
||||||
|
*/
|
||||||
|
@SpringBootApplication
|
||||||
|
public class SnailAiServerApplication {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Snail AI 服务启动入口。
|
||||||
|
*
|
||||||
|
* @param args 启动参数
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
com.aizuda.snail.ai.starter.SnailAiSpringbootApplication.main(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
server:
|
||||||
|
port: 8900
|
||||||
|
servlet:
|
||||||
|
context-path: /snail-ai
|
||||||
|
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: ruoyi-snailai-server
|
||||||
|
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/${spring.application.name}/console.log
|
||||||
|
|
||||||
|
--- # nacos 配置
|
||||||
|
spring:
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
# nacos 服务地址
|
||||||
|
server-addr: @nacos.server@
|
||||||
|
username: @nacos.username@
|
||||||
|
password: @nacos.password@
|
||||||
|
discovery:
|
||||||
|
# 注册组
|
||||||
|
group: DEFAULT_GROUP
|
||||||
|
namespace: public
|
||||||
|
config:
|
||||||
|
# 配置组
|
||||||
|
group: DEFAULT_GROUP
|
||||||
|
namespace: public
|
||||||
|
config:
|
||||||
|
import:
|
||||||
|
- optional:nacos:datasource.yml
|
||||||
|
- optional:nacos:${spring.application.name}.yml
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
Application Version: ${revision}
|
||||||
|
Spring Boot Version: ${spring-boot.version}
|
||||||
|
_ _ _
|
||||||
|
(_) | (_)
|
||||||
|
___ _ __ __ _ _| |______ __ _ _
|
||||||
|
/ __| '_ \ / _` | | |______/ _` | |
|
||||||
|
\__ \ | | | (_| | | | | (_| | |
|
||||||
|
|___/_| |_|\__,_|_|_| \__,_|_|
|
||||||
|
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration>
|
||||||
|
<property name="log.path" value="./logs/ruoyi-snailai-server" />
|
||||||
|
<property name="console.log.pattern"
|
||||||
|
value="%cyan(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
|
||||||
|
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"/>
|
||||||
|
|
||||||
|
<!-- 控制台输出 -->
|
||||||
|
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>${console.log.pattern}</pattern>
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- 控制台输出 -->
|
||||||
|
<appender name="file_console" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${log.path}/console.log</file>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<!-- 日志文件名格式 -->
|
||||||
|
<fileNamePattern>${log.path}/console.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||||
|
<!-- 日志最大 1天 -->
|
||||||
|
<maxHistory>1</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<pattern>${log.pattern}</pattern>
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
</encoder>
|
||||||
|
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||||
|
<!-- 过滤的级别 -->
|
||||||
|
<level>INFO</level>
|
||||||
|
</filter>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${log.path}/info.log</file>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<FileNamePattern>${log.path}/info.%d{yyyy-MM-dd}.log</FileNamePattern>
|
||||||
|
<MaxHistory>60</MaxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<pattern>${log.pattern}</pattern>
|
||||||
|
</encoder>
|
||||||
|
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||||
|
<level>INFO</level>
|
||||||
|
<onMatch>ACCEPT</onMatch>
|
||||||
|
<onMismatch>DENY</onMismatch>
|
||||||
|
</filter>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${log.path}/error.log</file>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<FileNamePattern>${log.path}/error.%d{yyyy-MM-dd}.log
|
||||||
|
</FileNamePattern>
|
||||||
|
<MaxHistory>60</MaxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<pattern>${log.pattern}</pattern>
|
||||||
|
</encoder>
|
||||||
|
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||||
|
<level>ERROR</level>
|
||||||
|
<onMatch>ACCEPT</onMatch>
|
||||||
|
<onMismatch>DENY</onMismatch>
|
||||||
|
</filter>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name ="async_info" class= "ch.qos.logback.classic.AsyncAppender">
|
||||||
|
<discardingThreshold >100</discardingThreshold>
|
||||||
|
<queueSize>1024</queueSize>
|
||||||
|
<appender-ref ref ="file_info"/>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name ="async_error" class= "ch.qos.logback.classic.AsyncAppender">
|
||||||
|
<discardingThreshold >100</discardingThreshold>
|
||||||
|
<queueSize>1024</queueSize>
|
||||||
|
<appender-ref ref ="file_error"/>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- 控制台输出日志级别 -->
|
||||||
|
<root level="info">
|
||||||
|
<appender-ref ref="console" />
|
||||||
|
<appender-ref ref="file_console" />
|
||||||
|
<appender-ref ref="async_info" />
|
||||||
|
<appender-ref ref="async_error" />
|
||||||
|
</root>
|
||||||
|
</configuration>
|
||||||
@@ -13,6 +13,10 @@ datasource:
|
|||||||
url: jdbc:mysql://localhost:3306/ry-job?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
|
url: jdbc:mysql://localhost:3306/ry-job?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
|
||||||
username: root
|
username: root
|
||||||
password: password
|
password: password
|
||||||
|
ai:
|
||||||
|
url: jdbc:mysql://localhost:3306/ry-ai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
|
||||||
|
username: root
|
||||||
|
password: password
|
||||||
workflow:
|
workflow:
|
||||||
url: jdbc:mysql://localhost:3306/ry-workflow?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
|
url: jdbc:mysql://localhost:3306/ry-workflow?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
|
||||||
username: root
|
username: root
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
snail-ai:
|
||||||
|
# 启用客户端模式
|
||||||
|
enabled: true
|
||||||
|
# 聊天发送模式: stream(流式) / sync(同步)
|
||||||
|
chat-mode: stream
|
||||||
|
# ==================== Server 连接 ====================
|
||||||
|
# Server 端 gRPC 地址(即 ruoyi-snailai-server 的 snail-ai.server.grpc-port)
|
||||||
|
server-host: 127.0.0.1
|
||||||
|
server-port: 18888
|
||||||
|
# ==================== 客户端配置 ====================
|
||||||
|
# 应用 ID(在 Server「应用管理」页面创建后获取)
|
||||||
|
app-id: 1
|
||||||
|
# 认证令牌(在 Server「应用管理」页面创建时自动生成)
|
||||||
|
token: SAI_8f1c789e35454ebdabce0b739963da9a
|
||||||
|
# 本客户端 gRPC 端口(Server 通过此端口分发 Chat 请求)
|
||||||
|
port: 18889
|
||||||
|
# 客户端 IP 指定,留空默认自动获取本地 IP
|
||||||
|
host:
|
||||||
|
# 最大并发对话数
|
||||||
|
max-concurrent-chats: 10
|
||||||
|
# Skill 文件临时目录
|
||||||
|
skill-temp-dir: /tmp/snail-ai-agent/skills
|
||||||
|
# Server 回调配置
|
||||||
|
server-callback:
|
||||||
|
# Server 回调超时时间(毫秒)
|
||||||
|
timeout: 5000
|
||||||
|
# Server 回调重试次数
|
||||||
|
retry-times: 3
|
||||||
|
# Server 回调重试间隔(毫秒)
|
||||||
|
retry-interval: 1000
|
||||||
|
# ==================== OpenAPI Client 配置 ====================
|
||||||
|
openapi:
|
||||||
|
# 启用 OpenAPI Client
|
||||||
|
enabled: true
|
||||||
|
# OpenAPI 服务端地址,不配置则取 snail-ai.server-host
|
||||||
|
server-host:
|
||||||
|
# Server HTTP 端口
|
||||||
|
web-port: 8900
|
||||||
|
# 是否使用 HTTPS
|
||||||
|
https: false
|
||||||
|
# API 路径前缀
|
||||||
|
prefix: snail-ai
|
||||||
|
# 超时配置(毫秒)
|
||||||
|
connect-timeout-ms: 5000
|
||||||
|
read-timeout-ms: 60000
|
||||||
|
chat-timeout-ms: 300000
|
||||||
@@ -56,6 +56,11 @@ spring:
|
|||||||
- Path=/resource/**
|
- Path=/resource/**
|
||||||
filters:
|
filters:
|
||||||
- StripPrefix=1
|
- StripPrefix=1
|
||||||
|
# AI服务
|
||||||
|
- id: ruoyi-ai
|
||||||
|
uri: lb://ruoyi-ai
|
||||||
|
predicates:
|
||||||
|
- Path=/snail-ai/**
|
||||||
# workflow服务
|
# workflow服务
|
||||||
- id: ruoyi-workflow
|
- id: ruoyi-workflow
|
||||||
uri: lb://ruoyi-workflow
|
uri: lb://ruoyi-workflow
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
type: com.zaxxer.hikari.HikariDataSource
|
||||||
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
|
url: ${datasource.ai.url}
|
||||||
|
username: ${datasource.ai.username}
|
||||||
|
password: ${datasource.ai.password}
|
||||||
|
hikari:
|
||||||
|
connection-timeout: 30000
|
||||||
|
validation-timeout: 5000
|
||||||
|
minimum-idle: 10
|
||||||
|
maximum-pool-size: 20
|
||||||
|
idle-timeout: 600000
|
||||||
|
max-lifetime: 900000
|
||||||
|
keepaliveTime: 30000
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
discovery:
|
||||||
|
metadata:
|
||||||
|
# 解决服务有 context-path 无法监控问题
|
||||||
|
management.context-path: ${server.servlet.context-path}/actuator
|
||||||
|
# 监控账号密码
|
||||||
|
username: ruoyi
|
||||||
|
userpassword: 123456
|
||||||
|
|
||||||
|
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
|
||||||
@@ -165,6 +165,21 @@ services:
|
|||||||
privileged: true
|
privileged: true
|
||||||
network_mode: "host"
|
network_mode: "host"
|
||||||
|
|
||||||
|
ruoyi-snailai-server:
|
||||||
|
image: ruoyi/ruoyi-snailai-server:2.5.3
|
||||||
|
container_name: ruoyi-snailai-server
|
||||||
|
environment:
|
||||||
|
# 时区上海
|
||||||
|
TZ: Asia/Shanghai
|
||||||
|
ports:
|
||||||
|
- "8900:8900"
|
||||||
|
- "18888:18888"
|
||||||
|
volumes:
|
||||||
|
- /docker/snailai/logs/:/ruoyi/snailai/logs
|
||||||
|
- /docker/snailai/upload/:/ruoyi/snailai/upload
|
||||||
|
privileged: true
|
||||||
|
network_mode: "host"
|
||||||
|
|
||||||
ruoyi-gateway:
|
ruoyi-gateway:
|
||||||
image: ruoyi/ruoyi-gateway:2.5.3
|
image: ruoyi/ruoyi-gateway:2.5.3
|
||||||
container_name: ruoyi-gateway
|
container_name: ruoyi-gateway
|
||||||
@@ -279,6 +294,25 @@ services:
|
|||||||
privileged: true
|
privileged: true
|
||||||
network_mode: "host"
|
network_mode: "host"
|
||||||
|
|
||||||
|
ruoyi-ai:
|
||||||
|
image: ruoyi/ruoyi-ai:2.5.3
|
||||||
|
container_name: ruoyi-ai
|
||||||
|
environment:
|
||||||
|
# 时区上海
|
||||||
|
TZ: Asia/Shanghai
|
||||||
|
ports:
|
||||||
|
- "9206:9206"
|
||||||
|
- "18889:18889"
|
||||||
|
volumes:
|
||||||
|
# 配置文件
|
||||||
|
- /docker/ruoyi-ai/logs/:/ruoyi/ai/logs
|
||||||
|
# skywalking 探针
|
||||||
|
- /docker/skywalking/agent/:/ruoyi/skywalking/agent
|
||||||
|
# Snail AI Skill 临时目录
|
||||||
|
- /docker/snailai/agent/skills/:/tmp/snail-ai-agent/skills
|
||||||
|
privileged: true
|
||||||
|
network_mode: "host"
|
||||||
|
|
||||||
|
|
||||||
#################################################################################################
|
#################################################################################################
|
||||||
#################################### 以下为扩展根据需求搭建 #########################################
|
#################################### 以下为扩展根据需求搭建 #########################################
|
||||||
|
|||||||
@@ -0,0 +1,518 @@
|
|||||||
|
-- ============================================================
|
||||||
|
-- Snail AI MySQL 全量建表脚本(仅 CREATE,无 ALTER)
|
||||||
|
-- 使用:mysql -u user -p database < snail_ai_schema.sql
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Enterprise RAG Schema for MySQL
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- Knowledge Base
|
||||||
|
CREATE TABLE sai_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 sai_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 '关联资源库 sai_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 sai_rag_document (rag_id);
|
||||||
|
CREATE INDEX idx_rag_content_hash ON sai_rag_document (rag_id, content_hash);
|
||||||
|
CREATE INDEX idx_rag_name ON sai_rag_document (rag_id, name);
|
||||||
|
CREATE INDEX idx_rag_doc_resource ON sai_rag_document (resource_id);
|
||||||
|
|
||||||
|
-- RAG Chunks
|
||||||
|
CREATE TABLE sai_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 sai_rag_chunk (rag_id);
|
||||||
|
CREATE INDEX idx_rag_chunk_document ON sai_rag_chunk (document_id);
|
||||||
|
CREATE INDEX idx_chunk_rag_hash ON sai_rag_chunk (rag_id, content_hash);
|
||||||
|
|
||||||
|
CREATE TABLE sai_user
|
||||||
|
(
|
||||||
|
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
role INT,
|
||||||
|
totals INT,
|
||||||
|
username VARCHAR(255),
|
||||||
|
email VARCHAR(64),
|
||||||
|
password VARCHAR(255) 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;
|
||||||
|
|
||||||
|
-- 默认管理员:admin / admin123
|
||||||
|
INSERT INTO sai_user (id, role, username, email, password, create_dt, update_dt)
|
||||||
|
VALUES (1, 2, 'admin', '', 'pbkdf2$120000$c25haWwtYWktYWRtaW4tMQ==$kakglT/wYKOgv/77Ah1stie58d/JbY2nGgq5DwgUBw4=',
|
||||||
|
'2026-02-11 13:56:48.210429', '2026-02-11 13:56:48.210429');
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- 1. AI 模型提供商表
|
||||||
|
-- ============================================
|
||||||
|
CREATE TABLE IF NOT EXISTS sai_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 sai_model_provider (provider_key);
|
||||||
|
CREATE INDEX idx_is_enabled ON sai_model_provider (is_enabled);
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- 2. AI模型配置表
|
||||||
|
-- ============================================
|
||||||
|
CREATE TABLE IF NOT EXISTS sai_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 sai_model_config (provider_id, model_type);
|
||||||
|
CREATE INDEX idx_model_type_enabled ON sai_model_config (model_type, is_enabled);
|
||||||
|
CREATE INDEX idx_owner_id ON sai_model_config (owner_id);
|
||||||
|
CREATE INDEX idx_is_default ON sai_model_config (is_default);
|
||||||
|
CREATE INDEX idx_scope ON sai_model_config (scope);
|
||||||
|
CREATE INDEX idx_model_key ON sai_model_config (model_key);
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- 3. 模型使用统计表
|
||||||
|
-- ============================================
|
||||||
|
CREATE TABLE IF NOT EXISTS sai_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 sai_model_usage_stat (model_id);
|
||||||
|
CREATE INDEX idx_user_id ON sai_model_usage_stat (user_id);
|
||||||
|
CREATE INDEX idx_last_used_dt ON sai_model_usage_stat (last_used_dt);
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- 初始化数据 (可选)
|
||||||
|
-- ============================================
|
||||||
|
-- 插入常见的AI提供商(重复 provider_key 则忽略)
|
||||||
|
INSERT IGNORE INTO sai_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),
|
||||||
|
('阿里云百炼', 'qwen', '阿里云百炼 OpenAI 兼容模型 (Qwen等)', 1),
|
||||||
|
('DeepSeek', 'deepseek', 'DeepSeek OpenAI 兼容模型', 1),
|
||||||
|
('智谱AI', 'zhipu', '智谱AI OpenAI 兼容模型 (GLM等)', 1);
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- 智能体相关表
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
-- 智能体主表
|
||||||
|
CREATE TABLE IF NOT EXISTS sai_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 sai_agent (creator_id);
|
||||||
|
CREATE INDEX idx_agent_featured ON sai_agent (is_featured);
|
||||||
|
|
||||||
|
-- 智能体对话表
|
||||||
|
CREATE TABLE IF NOT EXISTS sai_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 sai_agent_conversation (agent_id);
|
||||||
|
CREATE INDEX idx_agent_conv_user ON sai_agent_conversation (user_id);
|
||||||
|
|
||||||
|
-- 智能体对话消息记录表
|
||||||
|
CREATE TABLE IF NOT EXISTS sai_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 sai_agent_conversation_record (conversation_id);
|
||||||
|
|
||||||
|
-- 智能体使用统计表
|
||||||
|
CREATE TABLE IF NOT EXISTS sai_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 sai_agent_usage_stat (agent_id);
|
||||||
|
CREATE INDEX idx_usage_date ON sai_agent_usage_stat (stat_date);
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- MCP 服务管理
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
-- MCP 服务配置表
|
||||||
|
CREATE TABLE IF NOT EXISTS sai_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 sai_mcp_server (creator_id);
|
||||||
|
CREATE INDEX idx_mcp_server_status ON sai_mcp_server (status);
|
||||||
|
|
||||||
|
-- 智能体与MCP服务关联表(多对多)
|
||||||
|
CREATE TABLE IF NOT EXISTS sai_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 sai_agent_mcp_server (agent_id);
|
||||||
|
CREATE INDEX idx_agent_mcp_server ON sai_agent_mcp_server (mcp_server_id);
|
||||||
|
|
||||||
|
-- 用户订阅的智能体(多对多)
|
||||||
|
CREATE TABLE IF NOT EXISTS sai_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 sai_user_agent (user_id);
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- Skill 技能包管理
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
-- Skill 技能包表
|
||||||
|
CREATE TABLE IF NOT EXISTS sai_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 sai_skill (creator_id);
|
||||||
|
|
||||||
|
-- Skill 支撑文件内容表
|
||||||
|
CREATE TABLE IF NOT EXISTS sai_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 sai_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 sai_agent_skill (agent_id);
|
||||||
|
CREATE INDEX idx_agent_skill_skill ON sai_agent_skill (skill_id);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE IF NOT EXISTS sai_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 sai_store_instance (category);
|
||||||
|
CREATE INDEX idx_store_instance_type ON sai_store_instance (type);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 记忆系统(配置 / 主表 / 历史 / 摘要 / 统计 / 提取进度)
|
||||||
|
-- 依赖:sai_store_instance(sai_memory_conversation 外键)
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- 客户端应用
|
||||||
|
-- ----------------------------
|
||||||
|
CREATE TABLE IF NOT EXISTS sai_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 = '客户端应用';
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- AI客户端实例节点
|
||||||
|
-- ----------------------------
|
||||||
|
CREATE TABLE IF NOT EXISTS sai_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 sai_openapi_user
|
||||||
|
(
|
||||||
|
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
app_id VARCHAR(128) NOT NULL COMMENT '关联 sai_app.app_id',
|
||||||
|
open_id VARCHAR(64) NOT NULL COMMENT '平台分配的唯一标识(UUID)',
|
||||||
|
platform_user_id BIGINT NOT NULL COMMENT '关联 sai_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 sai_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 = '通用资源存储';
|
||||||
Reference in New Issue
Block a user