update 集成 snail-ai-server

update 增加 ruoyi-ai服务
This commit is contained in:
疯狂的狮子Li
2026-06-02 14:11:32 +08:00
parent f19d567b6e
commit 629e6a4551
30 changed files with 1754 additions and 1 deletions
@@ -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
+53
View File
@@ -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>
@@ -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() {
}
}
@@ -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;
}
}
@@ -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>