update 集成 snail-ai-server
update 增加 ruoyi-ai服务
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user