update snail-ai 0.0.5

This commit is contained in:
疯狂的狮子Li
2026-06-17 09:15:35 +08:00
parent 5a74eb85c3
commit f508ed5c57
13 changed files with 215 additions and 343 deletions
+11 -5
View File
@@ -33,7 +33,7 @@
<lock4j.version>2.2.7</lock4j.version>
<dynamic-ds.version>4.5.0</dynamic-ds.version>
<snailjob.version>2.0.0</snailjob.version>
<snailai.version>0.0.4</snailai.version>
<snailai.version>0.0.5</snailai.version>
<mapstruct-plus.version>1.5.0</mapstruct-plus.version>
<mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
<lombok.version>1.18.42</lombok.version>
@@ -55,8 +55,7 @@
<mica-mqtt.version>2.6.5</mica-mqtt.version>
<easy-es.version>3.0.2</easy-es.version>
<elasticsearch-client.version>7.17.28</elasticsearch-client.version>
<!-- Spring AI 2.0 预览版,正式版发布后仅需调整此版本号 -->
<spring-ai.version>2.0.0-M8</spring-ai.version>
<spring-ai.version>2.0.0</spring-ai.version>
<!-- 插件版本 -->
<maven-jar-plugin.version>3.5.0</maven-jar-plugin.version>
@@ -310,10 +309,17 @@
<version>${snailjob.version}</version>
</dependency>
<!-- Snail AI Agent 启动器 -->
<!-- Snail AI Agent 聊天室 -->
<dependency>
<groupId>com.aizuda</groupId>
<artifactId>snail-ai-agent-starter</artifactId>
<artifactId>snail-ai-agent-chat-starter</artifactId>
<version>${snailai.version}</version>
</dependency>
<!-- Snail AI Agent 执行器 -->
<dependency>
<groupId>com.aizuda</groupId>
<artifactId>snail-ai-agent-executor-starter</artifactId>
<version>${snailai.version}</version>
</dependency>
@@ -32,8 +32,6 @@ snail-job:
snail-ai:
# 启用客户端模式
enabled: false
# 聊天发送模式: stream(流式) / sync(同步)
chat-mode: stream
# ==================== Server 连接 ====================
# Server 端 gRPC 地址(即 snail-ai-starter 的 snail-ai.server.grpc-port
server:
@@ -44,7 +42,7 @@ snail-ai:
# 应用 ID(在 Server「应用管理」页面创建后获取)
app-id: 1
# 认证令牌(在 Server「应用管理」页面创建时自动生成)
token: SAI_7c557fc05a304b0482a65ca67afdb0b8
token: SAI_566a6bfbc26e4998b4841cc927d50c5d
port: 18889
# Skill 文件临时目录
skill-temp-dir: /tmp/snail-ai-agent/skills
@@ -35,8 +35,6 @@ snail-job:
snail-ai:
# 启用客户端模式
enabled: false
# 聊天发送模式: stream(流式) / sync(同步)
chat-mode: stream
# ==================== Server 连接 ====================
# Server 端 gRPC 地址(即 snail-ai-starter 的 snail-ai.server.grpc-port
server:
@@ -47,7 +45,7 @@ snail-ai:
# 应用 ID(在 Server「应用管理」页面创建后获取)
app-id: 1
# 认证令牌(在 Server「应用管理」页面创建时自动生成)
token: SAI_7c557fc05a304b0482a65ca67afdb0b8
token: SAI_566a6bfbc26e4998b4841cc927d50c5d
port: 18889
# Skill 文件临时目录
skill-temp-dir: /tmp/snail-ai-agent/skills
@@ -109,6 +109,8 @@ security:
- /*/api-docs
- /*/api-docs/**
- /warm-flow-ui/config
- /snail-chat/**
- /api/snail/chat/**
# MyBatisPlus配置
# https://baomidou.com/config/
+8 -2
View File
@@ -23,10 +23,16 @@
<artifactId>ruoyi-common-core</artifactId>
</dependency>
<!-- Snail AI Agent 启动器 -->
<!-- Snail AI Agent 聊天室 -->
<dependency>
<groupId>com.aizuda</groupId>
<artifactId>snail-ai-agent-starter</artifactId>
<artifactId>snail-ai-agent-chat-starter</artifactId>
</dependency>
<!-- Snail AI Agent 执行器 -->
<dependency>
<groupId>com.aizuda</groupId>
<artifactId>snail-ai-agent-executor-starter</artifactId>
</dependency>
<!-- Snail AI OpenAPI 启动器 -->
@@ -0,0 +1,124 @@
package org.dromara.common.ai.handler;
import com.aizuda.snail.ai.agent.chat.starter.SnailAiChatGatewayController;
import com.aizuda.snail.ai.common.execption.BaseSnailAiException;
import com.aizuda.snail.ai.common.execption.SnailAiAuthenticationException;
import com.aizuda.snail.ai.common.model.ModelCallException;
import com.aizuda.snail.ai.common.model.Result;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.HandlerMethodValidationException;
import java.util.Collection;
import java.util.stream.Collectors;
/**
* Snail AI Chat 网关异常处理器。
* <p>
* /api/snail/chat/** 接口由 Snail AI SDK 前端消费,响应结构需要保持 SDK Result(status/message/data)
* 不能落到 RuoYi 通用 R(code/msg/data) 格式。
*/
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE)
@RestControllerAdvice(assignableTypes = SnailAiChatGatewayController.class)
public class SnailAiChatExceptionHandler {
private static final int AUTHENTICATION_ERROR_STATUS = 5001;
@ExceptionHandler(SnailAiAuthenticationException.class)
public Result<Void> handleAuthenticationException(SnailAiAuthenticationException e) {
log.warn("Snail AI Chat authentication failed: {}", e.getMessage());
return Result.fail(AUTHENTICATION_ERROR_STATUS, defaultMessage(e, "认证失败,请重新登录"));
}
@ExceptionHandler(BaseSnailAiException.class)
public Result<Void> handleSnailAiException(BaseSnailAiException e) {
log.warn("Snail AI Chat request failed: {}", e.getMessage());
return Result.fail(defaultMessage(e, "AI 服务请求失败,请稍后再试"));
}
@ExceptionHandler(ModelCallException.class)
public Result<Void> handleModelCallException(ModelCallException e) {
log.warn("Snail AI Chat model call failed: {}", e.getMessage(), e);
return Result.fail(defaultMessage(e, "模型调用失败,请稍后再试"));
}
@ExceptionHandler({
BindException.class,
MethodArgumentNotValidException.class,
HandlerMethodValidationException.class
})
public Result<Void> handleValidationException(Exception e) {
log.warn("Snail AI Chat validation failed: {}", e.getMessage());
return Result.fail(validationMessage(e));
}
@ExceptionHandler(ConstraintViolationException.class)
public Result<Void> handleConstraintViolationException(ConstraintViolationException e) {
log.warn("Snail AI Chat constraint validation failed: {}", e.getMessage());
String message = e.getConstraintViolations().stream()
.map(ConstraintViolation::getMessage)
.filter(this::hasText)
.collect(Collectors.joining(", "));
return Result.fail(hasText(message) ? message : "请求参数校验失败");
}
@ExceptionHandler(HttpMessageNotReadableException.class)
public Result<Void> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
log.warn("Snail AI Chat request body parse failed: {}", e.getMessage());
return Result.fail("请求参数格式错误");
}
@ExceptionHandler(IllegalArgumentException.class)
public Result<Void> handleIllegalArgumentException(IllegalArgumentException e) {
log.warn("Snail AI Chat illegal argument: {}", e.getMessage());
return Result.fail(defaultMessage(e, "请求参数不合法"));
}
@ExceptionHandler(IllegalStateException.class)
public Result<Void> handleIllegalStateException(IllegalStateException e) {
log.warn("Snail AI Chat illegal state: {}", e.getMessage());
return Result.fail(defaultMessage(e, "AI 会话状态异常,请刷新后重试"));
}
@ExceptionHandler(Exception.class)
public Result<Void> handleException(Exception e) {
log.error("Snail AI Chat unexpected exception", e);
return Result.fail("AI 服务异常,请稍后再试");
}
private String validationMessage(Exception e) {
Collection<? extends MessageSourceResolvable> errors;
if (e instanceof BindException bindException) {
errors = bindException.getAllErrors();
} else if (e instanceof MethodArgumentNotValidException methodArgumentNotValidException) {
errors = methodArgumentNotValidException.getBindingResult().getAllErrors();
} else if (e instanceof HandlerMethodValidationException handlerMethodValidationException) {
errors = handlerMethodValidationException.getAllErrors();
} else {
return "请求参数校验失败";
}
String message = errors.stream()
.map(MessageSourceResolvable::getDefaultMessage)
.filter(this::hasText)
.collect(Collectors.joining(", "));
return hasText(message) ? message : "请求参数校验失败";
}
private String defaultMessage(Throwable e, String fallback) {
return hasText(e.getMessage()) ? e.getMessage() : fallback;
}
private boolean hasText(String value) {
return value != null && !value.isBlank();
}
}
@@ -2,33 +2,19 @@ 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.common.openapi.dto.OpenApiUserRegisterRequest;
import com.aizuda.snail.ai.common.openapi.dto.OpenApiUserVO;
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;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Snail AI OpenAPI 控制器
@@ -47,20 +33,7 @@ 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 用户信息。
@@ -70,272 +43,6 @@ public class SnailAiController extends BaseController {
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 用户。
*/
+26
View File
@@ -49,6 +49,10 @@ http {
server 127.0.0.1:8800;
}
upstream snailai-server {
server 127.0.0.1:8900;
}
server {
listen 80;
server_name localhost;
@@ -147,6 +151,28 @@ http {
proxy_pass http://snailjob-server/snail-job/;
}
location /snail-ai/ {
# 设置客户端请求头中的 Host 信息(保持原始 Host)
proxy_set_header Host $http_host;
# 获取客户端真实 IP
proxy_set_header X-Real-IP $remote_addr;
# 自定义头 REMOTE-HOST,记录客户端 IP
proxy_set_header REMOTE-HOST $remote_addr;
# 获取完整的客户端 IP 链(经过多级代理时)
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 设置后端响应超时时间(这里是 24 小时,适合长连接/SSE)
proxy_read_timeout 86400s;
# SSE (Server-Sent Events) 与 WebSocket 支持参数
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 禁用代理缓冲,直接传输给客户端
proxy_buffering off;
# 禁用代理缓存
proxy_cache off;
proxy_pass http://snailai-server/snail-ai/;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
+2
View File
@@ -375,6 +375,8 @@ insert into sys_menu values(1761400000000000117, 'Admin监控', 1761400000000000
insert into sys_menu values(1761400000000000118, '文件管理', 1761400000000000001, 10, 'oss', 'system/oss/index', '', 'N', 'Y', 'C', '0', '0', 'system:oss:list', 'upload', '', '', 1761000000000000103, 1761100000000000001, sysdate, null, null, '文件管理菜单');
-- snail-job server控制台
insert into sys_menu values(1761400000000000120, '任务调度中心', 1761400000000000002, 5, 'snailjob', 'monitor/snailjob/index', '', 'N', 'Y', 'C', '0', '0', 'monitor:snailjob:list', 'job', '', '', 1761000000000000103, 1761100000000000001, sysdate, null, null, 'snailjob控制台菜单');
-- snail-ai server控制台
insert into sys_menu values(1761400000000000121, 'AI控制台', 1761400000000000002, 7, 'snailai', 'monitor/snailai/index', '', 'N', 'Y', 'C', '0', '0', 'monitor:snailai:list', 'checkbox', '', '', 1761000000000000103, 1761100000000000001, sysdate, null, null, 'AI控制台菜单');
-- 三级菜单
insert into sys_menu values(1761400000000000500, '操作日志', 1761400000000000108, 1, 'operlog', 'monitor/operlog/index', '', 'N', 'Y', 'C', '0', '0', 'monitor:operlog:list', 'form', '', '', 1761000000000000103, 1761100000000000001, sysdate, null, null, '操作日志菜单');
+2
View File
@@ -372,6 +372,8 @@ insert into sys_menu values(1761400000000000117, 'Admin监控', 1761400000000000
insert into sys_menu values(1761400000000000118, '文件管理', 1761400000000000001, 10, 'oss', 'system/oss/index', '', 'N', 'Y', 'C', '0', '0', 'system:oss:list', 'upload', '', '', 1761000000000000103, 1761100000000000001, now(), null, null, '文件管理菜单');
-- snail-job server控制台
insert into sys_menu values(1761400000000000120, '任务调度中心', 1761400000000000002, 6, 'snailjob', 'monitor/snailjob/index', '', 'N', 'Y', 'C', '0', '0', 'monitor:snailjob:list', 'job', '', '', 1761000000000000103, 1761100000000000001, now(), null, null, 'SnailJob控制台菜单');
-- snail-ai server控制台
insert into sys_menu values(1761400000000000121, 'AI控制台', 1761400000000000002, 7, 'snailai', 'monitor/snailai/index', '', 'N', 'Y', 'C', '0', '0', 'monitor:snailai:list', 'checkbox', '', '', 1761000000000000103, 1761100000000000001, now(), null, null, 'AI控制台菜单');
-- 三级菜单
insert into sys_menu values(1761400000000000500, '操作日志', 1761400000000000108, 1, 'operlog', 'monitor/operlog/index', '', 'N', 'Y', 'C', '0', '0', 'monitor:operlog:list', 'form', '', '', 1761000000000000103, 1761100000000000001, now(), null, null, '操作日志菜单');
+29 -32
View File
@@ -97,11 +97,6 @@ CREATE TABLE sai_user
) 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 模型提供商表
-- ============================================
@@ -134,6 +129,7 @@ CREATE TABLE IF NOT EXISTS sai_model_config
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)',
adapter_key VARCHAR(100) COMMENT '底层协议适配器标识(openai-compatible/http等)',
description VARCHAR(1000) COMMENT '模型描述',
api_key VARCHAR(1000) COMMENT 'API密钥(加密存储)',
api_endpoint VARCHAR(500) COMMENT 'API端点URL',
@@ -156,9 +152,6 @@ 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);
INSERT INTO sai_model_config VALUES (1, 5, 'glm-5.1', 'glm-5.1', 'CHAT', '', 'xxx', 'https://dashscope.aliyuncs.com/compatible-mode/v1', '{\"frequencyPenalty\":0.0,\"maxTokens\":20000,\"presencePenalty\":0.0,\"stopSequences\":[],\"stream\":true,\"temperature\":0.7,\"timeoutMs\":300000,\"topK\":1,\"topP\":1.0}', NULL, 'GLOBAL', 1, 1, '2026-06-05 09:34:01', '2026-06-05 09:34:02');
-- ============================================
-- 3. 模型使用统计表
-- ============================================
@@ -186,19 +179,6 @@ 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);
-- ============================================
-- 智能体相关表
-- ============================================
@@ -237,9 +217,6 @@ CREATE TABLE IF NOT EXISTS sai_agent
CREATE INDEX idx_agent_creator ON sai_agent (creator_id);
CREATE INDEX idx_agent_featured ON sai_agent (is_featured);
INSERT INTO sai_agent VALUES (1, '智测先锋专家', '智测先锋专家是一款专注于软件测试与质量保障领域的智能助手。它能够高效生成覆盖全面的测试用例,深度分析Bug根因并提供修复建议,支持编写自动化测试脚本,以及解读复杂的测试报告。适用于软件开发周期的各个QA阶段,包括单元测试、接口测试、UI自动化及回归测试规划。其核心特点是逻辑严密、注重边界与异常场景,帮助团队大幅提升测试效率与软件质量。', NULL, '你是一位资深的软件测试与质量保障(QA)专家,名为“智测先锋专家”。\\n\\n【角色定位】你是开发团队的最后一道防线,致力于保障软件产品的卓越质量。\\n\\n【专业领域】精通黑盒与白盒测试、自动化测试框架(如Selenium、Pytest)、接口与性能测试、安全测试及CI/CD持续集成流程。\\n\\n【回答风格】逻辑严密、条理清晰、客观专业。善于使用结构化排版(如Markdown列表、代码块、表格)呈现测试用例和步骤,语言精炼,直击痛点。\\n\\n【行为指南】\\n1. 生成测试用例:必须覆盖正常流、异常流、边界值和兼容性等方面,确保测试的全面性与无遗漏。\\n2. 分析Bug根因:从代码逻辑、数据状态、环境配置等多维度推导,不仅给出修复建议,更要提供预防性的测试策略。\\n3. 编写自动化脚本:确保代码规范、包含必要注释与断言(Assert),并明确说明运行依赖与环境配置。\\n4. 需求澄清:若用户提问模糊,主动追问业务背景、技术栈等关键细节,拒绝给出宽泛且无实操价值的答案。\\n5. 风险预警:始终秉持质量第一理念,在解答中适时提示潜在的测试盲区与质量风险。', '你好!我是智测先锋专家,你的专属软件测试与质量保障顾问。无论是编写用例还是排查Bug,我都能为你提供专业支持!', '[\"如何为一个用户登录接口设计全面的测试用例?\",\"帮我分析这个空指针异常Bug的可能根因及修复建议。\",\"请提供一段Python的Pytest接口自动化测试脚本示例。\",\"怎样制定一个高效的回归测试策略?\"]', 2, 0, 0, 0, 0, 0, NULL, 1, 20, 1, 0, 1, 1, NULL, '1', '2026-06-05 09:34:50', '2026-06-05 09:34:51');
-- 智能体对话表
CREATE TABLE IF NOT EXISTS sai_agent_conversation
(
@@ -436,11 +413,6 @@ CREATE TABLE IF NOT EXISTS sai_store_instance
CREATE INDEX idx_store_instance_category ON sai_store_instance (category);
CREATE INDEX idx_store_instance_type ON sai_store_instance (type);
-- ============================================================
-- 记忆系统(配置 / 主表 / 历史 / 摘要 / 统计 / 提取进度)
-- 依赖:sai_store_instancesai_memory_conversation 外键)
-- ============================================================
-- 客户端应用
-- ----------------------------
CREATE TABLE IF NOT EXISTS sai_app
@@ -459,9 +431,6 @@ CREATE TABLE IF NOT EXISTS sai_app
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_unicode_ci COMMENT = '客户端应用';
INSERT INTO sai_app VALUES (1, '1', '测试', '', 'SAI_566a6bfbc26e4998b4841cc927d50c5d', 'LEAST_LOAD', 1, '2026-06-05 09:32:50', '2026-06-05 09:32:50');
-- ----------------------------
-- AI客户端实例节点
-- ----------------------------
@@ -503,6 +472,7 @@ CREATE TABLE sai_openapi_user
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='OpenAPI 外部用户映射表';
-- ----------------------------
-- 通用资源存储
-- ----------------------------
@@ -526,3 +496,30 @@ CREATE TABLE IF NOT EXISTS sai_resource
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_unicode_ci COMMENT = '通用资源存储';
-- ============================================
-- 初始化数据
-- ============================================
-- 默认管理员: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');
-- 插入常见的AI提供商
INSERT 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);
INSERT INTO sai_model_config VALUES (1, 5, 'glm-5.1', 'glm-5.1', 'CHAT', 'openai-compatible', '', 'xxx', 'https://dashscope.aliyuncs.com/compatible-mode/v1', '{\"frequencyPenalty\":0.0,\"maxTokens\":20000,\"presencePenalty\":0.0,\"stopSequences\":[],\"stream\":true,\"temperature\":0.7,\"timeoutMs\":300000,\"topK\":1,\"topP\":1.0}', NULL, 'GLOBAL', 1, 1, '2026-06-05 09:34:01', '2026-06-05 09:34:02');
INSERT INTO sai_agent VALUES (1, '智测先锋专家', '智测先锋专家是一款专注于软件测试与质量保障领域的智能助手。它能够高效生成覆盖全面的测试用例,深度分析Bug根因并提供修复建议,支持编写自动化测试脚本,以及解读复杂的测试报告。适用于软件开发周期的各个QA阶段,包括单元测试、接口测试、UI自动化及回归测试规划。其核心特点是逻辑严密、注重边界与异常场景,帮助团队大幅提升测试效率与软件质量。', NULL, '你是一位资深的软件测试与质量保障(QA)专家,名为“智测先锋专家”。\\n\\n【角色定位】你是开发团队的最后一道防线,致力于保障软件产品的卓越质量。\\n\\n【专业领域】精通黑盒与白盒测试、自动化测试框架(如Selenium、Pytest)、接口与性能测试、安全测试及CI/CD持续集成流程。\\n\\n【回答风格】逻辑严密、条理清晰、客观专业。善于使用结构化排版(如Markdown列表、代码块、表格)呈现测试用例和步骤,语言精炼,直击痛点。\\n\\n【行为指南】\\n1. 生成测试用例:必须覆盖正常流、异常流、边界值和兼容性等方面,确保测试的全面性与无遗漏。\\n2. 分析Bug根因:从代码逻辑、数据状态、环境配置等多维度推导,不仅给出修复建议,更要提供预防性的测试策略。\\n3. 编写自动化脚本:确保代码规范、包含必要注释与断言(Assert),并明确说明运行依赖与环境配置。\\n4. 需求澄清:若用户提问模糊,主动追问业务背景、技术栈等关键细节,拒绝给出宽泛且无实操价值的答案。\\n5. 风险预警:始终秉持质量第一理念,在解答中适时提示潜在的测试盲区与质量风险。', '你好!我是智测先锋专家,你的专属软件测试与质量保障顾问。无论是编写用例还是排查Bug,我都能为你提供专业支持!', '[\"如何为一个用户登录接口设计全面的测试用例?\",\"帮我分析这个空指针异常Bug的可能根因及修复建议。\",\"请提供一段Python的Pytest接口自动化测试脚本示例。\",\"怎样制定一个高效的回归测试策略?\"]', 2, 0, 0, 0, 0, 0, NULL, 1, 20, 1, 0, 1, 1, NULL, '1', '2026-06-05 09:34:50', '2026-06-05 09:34:51');
INSERT INTO sai_app VALUES (1, '1', '测试', '', 'SAI_566a6bfbc26e4998b4841cc927d50c5d', 'LEAST_LOAD', 1, '2026-06-05 09:32:50', '2026-06-05 09:32:50');
INSERT INTO sai_openapi_user VALUES (1, '1', '46ed53c6a20044c7bbd870848e80f92f', 1, '1', 'test', NOW(), NOW());
+2
View File
@@ -240,6 +240,8 @@ insert into sys_menu values(1761400000000000117, 'Admin监控', 1761400000000000
insert into sys_menu values(1761400000000000118, '文件管理', 1761400000000000001, 10, 'oss', 'system/oss/index', '', 'N', 'Y', 'C', '0', '0', 'system:oss:list', 'upload', '', '', 1761000000000000103, 1761100000000000001, sysdate(), null, null, '文件管理菜单');
-- snail-job server控制台
insert into sys_menu values(1761400000000000120, '任务调度中心', 1761400000000000002, 6, 'snailjob', 'monitor/snailjob/index', '', 'N', 'Y', 'C', '0', '0', 'monitor:snailjob:list', 'job', '', '', 1761000000000000103, 1761100000000000001, sysdate(), null, null, 'SnailJob控制台菜单');
-- snail-ai server控制台
insert into sys_menu values(1761400000000000121, 'AI控制台', 1761400000000000002, 7, 'snailai', 'monitor/snailai/index', '', 'N', 'Y', 'C', '0', '0', 'monitor:snailai:list', 'checkbox', '', '', 1761000000000000103, 1761100000000000001, sysdate(), null, null, 'AI控制台菜单');
-- 三级菜单
insert into sys_menu values(1761400000000000500, '操作日志', 1761400000000000108, 1, 'operlog', 'monitor/operlog/index', '', 'N', 'Y', 'C', '0', '0', 'monitor:operlog:list', 'form', '', '', 1761000000000000103, 1761100000000000001, sysdate(), null, null, '操作日志菜单');
@@ -1416,6 +1416,8 @@ insert into sys_menu values(1761400000000000118, N'文件管理', 17614000000000
GO
insert into sys_menu values(1761400000000000120, N'任务调度中心', 1761400000000000002, 5, N'snailjob', N'monitor/snailjob/index', N'', N'N', N'Y', N'C', N'0', N'0', N'monitor:snailjob:list', N'job', N'', N'', 1761000000000000103, 1761100000000000001, getdate(), NULL, NULL, N'SnailJob控制台菜单');
GO
insert into sys_menu values(1761400000000000121, N'AI控制台', 1761400000000000002, 7, N'snailai', N'monitor/snailai/index', N'', N'N', N'Y', N'C', N'0', N'0', N'monitor:snailai:list', N'checkbox', N'', N'', 1761000000000000103, 1761100000000000001, getdate(), NULL, NULL, N'AI控制台菜单');
GO
insert into sys_menu values(1761400000000000500, N'操作日志', 1761400000000000108, 1, N'operlog', N'monitor/operlog/index', N'', N'N', N'Y', N'C', N'0', N'0', N'monitor:operlog:list', N'form', N'', N'', 1761000000000000103, 1761100000000000001, getdate(), NULL, NULL, N'操作日志菜单');
GO
insert into sys_menu values(1761400000000000501, N'登录日志', 1761400000000000108, 2, N'logininfo', N'monitor/logininfo/index', N'', N'N', N'Y', N'C', N'0', N'0', N'monitor:logininfo:list', N'logininfo', N'', N'', 1761000000000000103, 1761100000000000001, getdate(), NULL, NULL, N'登录日志菜单');