mirror of
https://gitee.com/dromara/RuoYi-Cloud-Plus.git
synced 2026-04-23 02:48:34 +08:00
add 新增 gateway Servlet MVC版本(建议在虚拟线程下使用)
This commit is contained in:
1
pom.xml
1
pom.xml
@@ -388,6 +388,7 @@
|
||||
<modules>
|
||||
<module>ruoyi-auth</module>
|
||||
<module>ruoyi-gateway</module>
|
||||
<module>ruoyi-gateway-mvc</module>
|
||||
<module>ruoyi-visual</module>
|
||||
<module>ruoyi-modules</module>
|
||||
<module>ruoyi-api</module>
|
||||
|
||||
26
ruoyi-gateway-mvc/Dockerfile
Normal file
26
ruoyi-gateway-mvc/Dockerfile
Normal file
@@ -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/gateway/logs \
|
||||
/ruoyi/gateway/temp \
|
||||
/ruoyi/skywalking/agent
|
||||
|
||||
WORKDIR /ruoyi/gateway
|
||||
|
||||
ENV SERVER_PORT=8080 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS=""
|
||||
|
||||
EXPOSE ${SERVER_PORT}
|
||||
|
||||
ADD ./target/ruoyi-gateway-mvc.jar ./app.jar
|
||||
|
||||
SHELL ["/bin/bash", "-c"]
|
||||
|
||||
ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -Dserver.port=${SERVER_PORT} \
|
||||
#-Dskywalking.agent.service_name=ruoyi-gateway \
|
||||
#-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar \
|
||||
-XX:+HeapDumpOnOutOfMemoryError -XX:+UseZGC ${JAVA_OPTS} \
|
||||
-jar app.jar
|
||||
7
ruoyi-gateway-mvc/README.md
Normal file
7
ruoyi-gateway-mvc/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
此服务为 spring-cloud-gateway 的 Servlet MVC 版本
|
||||
|
||||
建议在JDK21启动虚拟线程的基础上使用 在JDK17下使用性能比原版差很多
|
||||
|
||||
使用教程 将 ruoyi-gateway-mvc.yml 配置文件上传到 nacos 正常启动服务 与原先版本一样使用
|
||||
|
||||
注意 原版与此版只能选择一个使用 不允许两个同时使用
|
||||
134
ruoyi-gateway-mvc/pom.xml
Normal file
134
ruoyi-gateway-mvc/pom.xml
Normal file
@@ -0,0 +1,134 @@
|
||||
<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-cloud-plus</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>ruoyi-gateway-mvc</artifactId>
|
||||
|
||||
<description>
|
||||
ruoyi-gateway-mvc网关模块
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- SpringCloud Gateway -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-gateway-server-webmvc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- SpringBoot Web容器 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- web 容器使用 undertow 性能更强 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-undertow</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-nacos</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- SpringBoot Actuator -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-http</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 权限认证 -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot3-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-satoken</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- RuoYi Common Redis-->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-tenant</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 自定义负载均衡(多团队开发使用) -->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.dromara</groupId>-->
|
||||
<!-- <artifactId>ruoyi-common-loadbalancer</artifactId>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<!-- ELK 日志收集 -->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.dromara</groupId>-->
|
||||
<!-- <artifactId>ruoyi-common-logstash</artifactId>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<!-- skywalking 日志收集 -->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.dromara</groupId>-->
|
||||
<!-- <artifactId>ruoyi-common-skylog</artifactId>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<!-- prometheus 监控 -->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.dromara</groupId>-->
|
||||
<!-- <artifactId>ruoyi-common-prometheus</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,23 @@
|
||||
package org.dromara.gateway;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
|
||||
|
||||
/**
|
||||
* 网关启动程序
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
|
||||
public class RuoYiGatewayMvcApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication application = new SpringApplication(RuoYiGatewayMvcApplication.class);
|
||||
application.setApplicationStartup(new BufferingApplicationStartup(2048));
|
||||
application.run(args);
|
||||
System.out.println("(♥◠‿◠)ノ゙ MVC网关启动成功 ლ(´ڡ`ლ)゙ ");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package org.dromara.gateway.config;
|
||||
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
import org.springframework.web.servlet.LocaleResolver;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import org.springframework.core.Ordered;
|
||||
|
||||
/**
|
||||
* 网关配置
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Configuration
|
||||
public class GatewayConfig {
|
||||
|
||||
@Bean
|
||||
public LocaleResolver localeResolver() {
|
||||
return new LocaleResolver() {
|
||||
@Override
|
||||
public Locale resolveLocale(HttpServletRequest request) {
|
||||
String language = request.getHeader("content-language");
|
||||
Locale locale = Locale.getDefault();
|
||||
if (language == null || language.isBlank()) {
|
||||
return locale;
|
||||
}
|
||||
String[] split = language.split("_");
|
||||
if (split.length == 2) {
|
||||
return new Locale(split[0], split[1]);
|
||||
}
|
||||
return Locale.forLanguageTag(language.replace('_', '-'));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
|
||||
// noop
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FilterRegistrationBean<CorsFilter> corsFilterRegistrationBean() {
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
config.setAllowedOriginPatterns(List.of("*"));
|
||||
config.setAllowCredentials(true);
|
||||
config.setAllowedHeaders(List.of(
|
||||
"X-Requested-With", "Content-Language", "Content-Type",
|
||||
"Authorization", "clientid", "credential", "X-XSRF-TOKEN",
|
||||
"isToken", "token", "Admin-Token", "App-Token", "Encrypt-Key", "isEncrypt"
|
||||
));
|
||||
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD"));
|
||||
config.setExposedHeaders(List.of("*"));
|
||||
config.setMaxAge(18000L);
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
|
||||
FilterRegistrationBean<CorsFilter> registration = new FilterRegistrationBean<>(new CorsFilter(source));
|
||||
registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
|
||||
return registration;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package org.dromara.gateway.config;
|
||||
|
||||
import io.undertow.UndertowOptions;
|
||||
import io.undertow.server.DefaultByteBufferPool;
|
||||
import io.undertow.server.handlers.DisallowedMethodsHandler;
|
||||
import io.undertow.util.HttpString;
|
||||
import io.undertow.websockets.jsr.WebSocketDeploymentInfo;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
|
||||
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.task.VirtualThreadTaskExecutor;
|
||||
|
||||
/**
|
||||
* Undertow 自定义配置
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Configuration
|
||||
public class UndertowConfig implements WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
|
||||
|
||||
@Autowired
|
||||
private ServerProperties serverProperties;
|
||||
|
||||
/**
|
||||
* 自定义 Undertow 配置
|
||||
* <p>
|
||||
* 主要配置内容包括:
|
||||
* 1. 配置 WebSocket 部署信息
|
||||
* 2. 在虚拟线程模式下使用虚拟线程池
|
||||
* 3. 禁用不安全的 HTTP 方法,如 CONNECT、TRACE、TRACK
|
||||
* </p>
|
||||
*
|
||||
* @param factory Undertow 的 Web 服务器工厂
|
||||
*/
|
||||
@Override
|
||||
public void customize(UndertowServletWebServerFactory factory) {
|
||||
long bytes = serverProperties.getUndertow().getMaxHttpPostSize().toBytes();
|
||||
factory.addBuilderCustomizers(builder -> {
|
||||
builder.setServerOption(UndertowOptions.MULTIPART_MAX_ENTITY_SIZE, bytes);
|
||||
});
|
||||
|
||||
// 默认不直接分配内存 如果项目中使用了 websocket 建议直接分配
|
||||
factory.addDeploymentInfoCustomizers(deploymentInfo -> {
|
||||
// 配置 WebSocket 部署信息,设置 WebSocket 使用的缓冲区池
|
||||
WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo();
|
||||
webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(true, 1024));
|
||||
deploymentInfo.addServletContextAttribute("io.undertow.websockets.jsr.WebSocketDeploymentInfo", webSocketDeploymentInfo);
|
||||
|
||||
// 如果启用了虚拟线程,配置 Undertow 使用虚拟线程池
|
||||
if (SpringUtils.isVirtual()) {
|
||||
// 创建虚拟线程池,线程池前缀为 "undertow-"
|
||||
VirtualThreadTaskExecutor executor = new VirtualThreadTaskExecutor("undertow-");
|
||||
// 设置虚拟线程池为执行器和异步执行器
|
||||
deploymentInfo.setExecutor(executor);
|
||||
deploymentInfo.setAsyncExecutor(executor);
|
||||
}
|
||||
|
||||
// 配置禁止某些不安全的 HTTP 方法(如 CONNECT、TRACE、TRACK)
|
||||
deploymentInfo.addInitialHandlerChainWrapper(handler -> {
|
||||
// 禁止三个方法 CONNECT/TRACE/TRACK 也是不安全的 避免爬虫骚扰
|
||||
HttpString[] disallowedHttpMethods = {
|
||||
HttpString.tryFromString("CONNECT"),
|
||||
HttpString.tryFromString("TRACE"),
|
||||
HttpString.tryFromString("TRACK")
|
||||
};
|
||||
// 使用 DisallowedMethodsHandler 拦截并拒绝这些方法的请求
|
||||
return new DisallowedMethodsHandler(handler, disallowedHttpMethods);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.dromara.gateway.config.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* api解密属性配置类
|
||||
*
|
||||
* @author wdhcr
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@RefreshScope
|
||||
@ConfigurationProperties(prefix = "api-decrypt")
|
||||
public class ApiDecryptProperties {
|
||||
|
||||
/**
|
||||
* 加密开关
|
||||
*/
|
||||
private Boolean enabled;
|
||||
|
||||
/**
|
||||
* 头部标识
|
||||
*/
|
||||
private String headerFlag;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.dromara.gateway.config.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 自定义gateway参数配置
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@RefreshScope
|
||||
@ConfigurationProperties(prefix = "spring.cloud.gateway")
|
||||
public class CustomGatewayProperties {
|
||||
|
||||
/**
|
||||
* 请求日志
|
||||
*/
|
||||
private Boolean requestLog;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.dromara.gateway.config.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 放行白名单配置
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@Configuration
|
||||
@RefreshScope
|
||||
@ConfigurationProperties(prefix = "security.ignore")
|
||||
public class IgnoreWhiteProperties {
|
||||
|
||||
/**
|
||||
* 放行白名单配置,网关不校验此处的白名单
|
||||
*/
|
||||
private List<String> whites = new ArrayList<>();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package org.dromara.gateway.filter;
|
||||
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import cn.dev33.satoken.filter.SaServletFilter;
|
||||
import cn.dev33.satoken.httpauth.basic.SaHttpBasicUtil;
|
||||
import cn.dev33.satoken.interceptor.SaInterceptor;
|
||||
import cn.dev33.satoken.router.SaRouter;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import cn.dev33.satoken.util.SaTokenConsts;
|
||||
import org.dromara.common.core.constant.HttpStatus;
|
||||
import org.dromara.common.core.utils.ServletUtils;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.gateway.config.properties.IgnoreWhiteProperties;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* [Sa-Token 权限认证] 拦截器配置
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Configuration
|
||||
public class AuthFilter implements WebMvcConfigurer {
|
||||
|
||||
private final IgnoreWhiteProperties ignoreWhite;
|
||||
|
||||
public AuthFilter(IgnoreWhiteProperties ignoreWhite) {
|
||||
this.ignoreWhite = ignoreWhite;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(new SaInterceptor(handler -> SaRouter.match("/**")
|
||||
.notMatch(ignoreWhite.getWhites())
|
||||
.check(() -> {
|
||||
HttpServletRequest request = ServletUtils.getRequest();
|
||||
HttpServletResponse response = ServletUtils.getResponse();
|
||||
if (response != null) {
|
||||
response.setContentType(SaTokenConsts.CONTENT_TYPE_APPLICATION_JSON);
|
||||
}
|
||||
|
||||
StpUtil.checkLogin();
|
||||
|
||||
String headerCid = request.getHeader(LoginHelper.CLIENT_KEY);
|
||||
String paramCid = ServletUtils.getParameter(LoginHelper.CLIENT_KEY);
|
||||
Object extra = StpUtil.getExtra(LoginHelper.CLIENT_KEY);
|
||||
String clientId = extra == null ? null : extra.toString();
|
||||
if (!StringUtils.equalsAny(clientId, headerCid, paramCid)) {
|
||||
throw NotLoginException.newInstance(StpUtil.getLoginType(),
|
||||
"-100", "客户端ID与Token不匹配",
|
||||
StpUtil.getTokenValue());
|
||||
}
|
||||
})))
|
||||
.addPathPatterns("/**")
|
||||
.excludePathPatterns("/favicon.ico", "/actuator", "/actuator/**", "/resource/sse");
|
||||
}
|
||||
|
||||
/**
|
||||
* 为 actuator 健康检查接口配置 Basic Auth 鉴权过滤器。
|
||||
*
|
||||
* @return Sa-Token Servlet 过滤器
|
||||
*/
|
||||
@Bean
|
||||
public SaServletFilter getSaServletFilter() {
|
||||
String username = SpringUtils.getProperty("spring.cloud.nacos.discovery.metadata.username");
|
||||
String password = SpringUtils.getProperty("spring.cloud.nacos.discovery.metadata.userpassword");
|
||||
return new SaServletFilter()
|
||||
.addInclude("/actuator", "/actuator/**")
|
||||
.setAuth(obj -> {
|
||||
SaHttpBasicUtil.check(username + ":" + password);
|
||||
})
|
||||
.setError(e -> {
|
||||
HttpServletResponse response = ServletUtils.getResponse();
|
||||
response.setContentType(SaTokenConsts.CONTENT_TYPE_APPLICATION_JSON);
|
||||
return SaResult.error(e.getMessage()).setCode(HttpStatus.UNAUTHORIZED);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package org.dromara.gateway.filter;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.same.SaSameUtil;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.dromara.gateway.filter.support.MutableHttpServletRequest;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 转发请求头过滤器:
|
||||
* 1. 动态透传 X-Forwarded-Prefix
|
||||
* 2. 转发内部 same-token
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Component
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE + 15)
|
||||
public class ForwardAuthFilter extends OncePerRequestFilter {
|
||||
|
||||
@Override
|
||||
protected boolean shouldNotFilter(HttpServletRequest request) {
|
||||
return request.getRequestURI().startsWith("/actuator");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
MutableHttpServletRequest newRequest = null;
|
||||
|
||||
String forwardedPrefix = resolveForwardedPrefix(request);
|
||||
if (forwardedPrefix != null) {
|
||||
newRequest = getOrCreateMutableRequest(request, newRequest);
|
||||
newRequest.putHeader("X-Forwarded-Prefix", forwardedPrefix);
|
||||
}
|
||||
|
||||
if (SaManager.getConfig().getCheckSameToken()) {
|
||||
newRequest = getOrCreateMutableRequest(request, newRequest);
|
||||
newRequest.putHeader(SaSameUtil.SAME_TOKEN, SaSameUtil.getToken());
|
||||
}
|
||||
|
||||
filterChain.doFilter(newRequest == null ? request : newRequest, response);
|
||||
}
|
||||
|
||||
private String resolveForwardedPrefix(HttpServletRequest request) {
|
||||
String[] pathSegments = StringUtils.tokenizeToStringArray(request.getRequestURI(), "/");
|
||||
if (pathSegments.length == 0) {
|
||||
return null;
|
||||
}
|
||||
return "/" + pathSegments[0];
|
||||
}
|
||||
|
||||
private MutableHttpServletRequest getOrCreateMutableRequest(HttpServletRequest request,
|
||||
MutableHttpServletRequest currentRequest) {
|
||||
return currentRequest != null ? currentRequest : new MutableHttpServletRequest(request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package org.dromara.gateway.filter;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.constant.SystemConstants;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.json.utils.JsonUtils;
|
||||
import org.dromara.gateway.config.properties.ApiDecryptProperties;
|
||||
import org.dromara.gateway.config.properties.CustomGatewayProperties;
|
||||
import org.dromara.gateway.filter.support.CachedBodyHttpServletRequest;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 全局日志过滤器
|
||||
* <p>
|
||||
* 用于打印请求执行参数与响应时间等等
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@Order(Ordered.LOWEST_PRECEDENCE - 10)
|
||||
public class GlobalLogFilter extends OncePerRequestFilter {
|
||||
|
||||
private final CustomGatewayProperties customGatewayProperties;
|
||||
private final ApiDecryptProperties apiDecryptProperties;
|
||||
|
||||
public GlobalLogFilter(CustomGatewayProperties customGatewayProperties, ApiDecryptProperties apiDecryptProperties) {
|
||||
this.customGatewayProperties = customGatewayProperties;
|
||||
this.apiDecryptProperties = apiDecryptProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldNotFilter(HttpServletRequest request) {
|
||||
return !Boolean.TRUE.equals(customGatewayProperties.getRequestLog());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
HttpServletRequest requestToUse = request;
|
||||
if (isJsonRequest(request) && !(request instanceof CachedBodyHttpServletRequest)) {
|
||||
requestToUse = new CachedBodyHttpServletRequest(request);
|
||||
}
|
||||
|
||||
String url = requestToUse.getMethod() + " " + requestToUse.getRequestURI();
|
||||
logRequest(requestToUse, url);
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
try {
|
||||
filterChain.doFilter(requestToUse, response);
|
||||
} finally {
|
||||
log.info("[PLUS]结束请求 => URL[{}],耗时:[{}]毫秒", url, System.currentTimeMillis() - startTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void logRequest(HttpServletRequest request, String url) {
|
||||
if (isJsonRequest(request)) {
|
||||
if (Boolean.TRUE.equals(apiDecryptProperties.getEnabled())
|
||||
&& ObjectUtil.isNotNull(request.getHeader(apiDecryptProperties.getHeaderFlag()))) {
|
||||
log.info("[PLUS]开始请求 => URL[{}],参数类型[encrypt]", url);
|
||||
return;
|
||||
}
|
||||
String jsonParam = resolveBody(request);
|
||||
if (StringUtils.isNotBlank(jsonParam)) {
|
||||
jsonParam = removeSensitiveFields(jsonParam);
|
||||
}
|
||||
log.info("[PLUS]开始请求 => URL[{}],参数类型[json],参数:[{}]", url, jsonParam);
|
||||
return;
|
||||
}
|
||||
|
||||
MultiValueMap<String, String> parameterMap = UriComponentsBuilder.newInstance()
|
||||
.query(request.getQueryString())
|
||||
.build()
|
||||
.getQueryParams();
|
||||
if (MapUtil.isNotEmpty(parameterMap)) {
|
||||
LinkedMultiValueMap<String, String> map = new LinkedMultiValueMap<>(parameterMap);
|
||||
MapUtil.removeAny(map, SystemConstants.EXCLUDE_PROPERTIES);
|
||||
log.info("[PLUS]开始请求 => URL[{}],参数类型[param],参数:[{}]", url, JsonUtils.toJsonString(map));
|
||||
} else {
|
||||
log.info("[PLUS]开始请求 => URL[{}],无参数", url);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isJsonRequest(HttpServletRequest request) {
|
||||
return StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE);
|
||||
}
|
||||
|
||||
private String resolveBody(HttpServletRequest request) {
|
||||
if (request instanceof CachedBodyHttpServletRequest cachedRequest) {
|
||||
return cachedRequest.getCachedBodyAsString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String removeSensitiveFields(String jsonParam) {
|
||||
try {
|
||||
ObjectMapper objectMapper = JsonUtils.getObjectMapper();
|
||||
JsonNode rootNode = objectMapper.readTree(jsonParam);
|
||||
removeSensitiveFields(rootNode, SystemConstants.EXCLUDE_PROPERTIES);
|
||||
return rootNode.toString();
|
||||
} catch (Exception ex) {
|
||||
return jsonParam;
|
||||
}
|
||||
}
|
||||
|
||||
private void removeSensitiveFields(JsonNode node, String[] excludeProperties) {
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
if (node.isObject()) {
|
||||
ObjectNode objectNode = (ObjectNode) node;
|
||||
Set<String> fieldsToRemove = new HashSet<>();
|
||||
objectNode.fieldNames().forEachRemaining(fieldName -> {
|
||||
if (ArrayUtil.contains(excludeProperties, fieldName)) {
|
||||
fieldsToRemove.add(fieldName);
|
||||
}
|
||||
});
|
||||
fieldsToRemove.forEach(objectNode::remove);
|
||||
objectNode.elements().forEachRemaining(child -> removeSensitiveFields(child, excludeProperties));
|
||||
} else if (node.isArray()) {
|
||||
ArrayNode arrayNode = (ArrayNode) node;
|
||||
for (JsonNode child : arrayNode) {
|
||||
removeSensitiveFields(child, excludeProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.dromara.gateway.filter.support;
|
||||
|
||||
import jakarta.servlet.ReadListener;
|
||||
import jakarta.servlet.ServletInputStream;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* 可重复读取请求体的包装类
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
|
||||
|
||||
private final byte[] cachedBody;
|
||||
|
||||
public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
|
||||
super(request);
|
||||
this.cachedBody = StreamUtils.copyToByteArray(request.getInputStream());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletInputStream getInputStream() {
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(cachedBody);
|
||||
return new ServletInputStream() {
|
||||
@Override
|
||||
public int read() {
|
||||
return inputStream.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFinished() {
|
||||
return inputStream.available() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReady() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadListener(ReadListener listener) {
|
||||
// noop
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedReader getReader() {
|
||||
Charset charset = getCharacterEncoding() == null
|
||||
? StandardCharsets.UTF_8
|
||||
: Charset.forName(getCharacterEncoding());
|
||||
return new BufferedReader(new InputStreamReader(getInputStream(), charset));
|
||||
}
|
||||
|
||||
public String getCachedBodyAsString() {
|
||||
Charset charset = getCharacterEncoding() == null
|
||||
? StandardCharsets.UTF_8
|
||||
: Charset.forName(getCharacterEncoding());
|
||||
return new String(cachedBody, charset);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package org.dromara.gateway.filter.support;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* 可追加请求头的包装类
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
public class MutableHttpServletRequest extends HttpServletRequestWrapper {
|
||||
|
||||
private final Map<String, String> customHeaders = new LinkedHashMap<>();
|
||||
|
||||
public MutableHttpServletRequest(HttpServletRequest request) {
|
||||
super(request);
|
||||
}
|
||||
|
||||
public void putHeader(String name, String value) {
|
||||
customHeaders.put(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader(String name) {
|
||||
String value = customHeaders.get(name);
|
||||
return value != null ? value : super.getHeader(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getHeaders(String name) {
|
||||
String value = customHeaders.get(name);
|
||||
if (value != null) {
|
||||
return Collections.enumeration(Collections.singletonList(value));
|
||||
}
|
||||
return super.getHeaders(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getHeaderNames() {
|
||||
Set<String> names = new LinkedHashSet<>();
|
||||
Enumeration<String> headerNames = super.getHeaderNames();
|
||||
while (headerNames.hasMoreElements()) {
|
||||
names.add(headerNames.nextElement());
|
||||
}
|
||||
names.addAll(customHeaders.keySet());
|
||||
return new Vector<>(names).elements();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.dromara.gateway.handler;
|
||||
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.constant.HttpStatus;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
/**
|
||||
* 网关统一异常处理
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
public class GatewayExceptionHandler {
|
||||
|
||||
@ExceptionHandler(NotLoginException.class)
|
||||
public R<Void> handleNotLogin(HttpServletRequest request, NotLoginException ex) {
|
||||
log.warn("[网关认证失败]请求路径:{},异常信息:{}", request.getRequestURI(), ex.getMessage());
|
||||
return R.fail(HttpStatus.UNAUTHORIZED, ex.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(Throwable.class)
|
||||
public R<Void> handle(HttpServletRequest request, Throwable ex) {
|
||||
String msg;
|
||||
if ("NotFoundException".equals(ex.getClass().getSimpleName())) {
|
||||
msg = "服务未找到";
|
||||
} else if (ex instanceof ResponseStatusException responseStatusException) {
|
||||
msg = responseStatusException.getMessage();
|
||||
} else {
|
||||
msg = "内部服务器错误";
|
||||
}
|
||||
|
||||
log.error("[网关异常处理]请求路径:{},异常信息:{}", request.getRequestURI(), ex.getMessage(), ex);
|
||||
return R.fail(msg);
|
||||
}
|
||||
}
|
||||
35
ruoyi-gateway-mvc/src/main/resources/application.yml
Normal file
35
ruoyi-gateway-mvc/src/main/resources/application.yml
Normal file
@@ -0,0 +1,35 @@
|
||||
# Tomcat
|
||||
server:
|
||||
port: 8080
|
||||
servlet:
|
||||
context-path: /
|
||||
|
||||
# Spring
|
||||
spring:
|
||||
application:
|
||||
# 应用名称
|
||||
name: ruoyi-gateway-mvc
|
||||
profiles:
|
||||
# 环境配置
|
||||
active: @profiles.active@
|
||||
|
||||
--- # nacos 配置
|
||||
spring:
|
||||
cloud:
|
||||
nacos:
|
||||
# nacos 服务地址
|
||||
server-addr: @nacos.server@
|
||||
username: @nacos.username@
|
||||
password: @nacos.password@
|
||||
discovery:
|
||||
# 注册组
|
||||
group: @nacos.discovery.group@
|
||||
namespace: ${spring.profiles.active}
|
||||
config:
|
||||
# 配置组
|
||||
group: @nacos.config.group@
|
||||
namespace: ${spring.profiles.active}
|
||||
config:
|
||||
import:
|
||||
- optional:nacos:application-common.yml
|
||||
- optional:nacos:${spring.application.name}.yml
|
||||
10
ruoyi-gateway-mvc/src/main/resources/banner.txt
Normal file
10
ruoyi-gateway-mvc/src/main/resources/banner.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
Spring Boot Version: ${spring-boot.version}
|
||||
Spring Application Name: ${spring.application.name}
|
||||
_ _
|
||||
(_) | |
|
||||
_ __ _ _ ___ _ _ _ ______ __ _ __ _ | |_ ___ __ __ __ _ _ _
|
||||
| '__|| | | | / _ \ | | | || ||______| / _` | / _` || __| / _ \\ \ /\ / / / _` || | | |
|
||||
| | | |_| || (_) || |_| || | | (_| || (_| || |_ | __/ \ V V / | (_| || |_| |
|
||||
|_| \__,_| \___/ \__, ||_| \__, | \__,_| \__| \___| \_/\_/ \__,_| \__, |
|
||||
__/ | __/ | __/ |
|
||||
|___/ |___/ |___/
|
||||
114
ruoyi-gateway-mvc/src/main/resources/logback-plus.xml
Normal file
114
ruoyi-gateway-mvc/src/main/resources/logback-plus.xml
Normal file
@@ -0,0 +1,114 @@
|
||||
<?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"/>
|
||||
<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>
|
||||
<!-- 日志最大的历史 60天 -->
|
||||
<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>
|
||||
<!-- 日志最大的历史 60天 -->
|
||||
<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>
|
||||
|
||||
<!-- info异步输出 -->
|
||||
<appender name="async_info" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
|
||||
<discardingThreshold>0</discardingThreshold>
|
||||
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
|
||||
<queueSize>512</queueSize>
|
||||
<!-- 添加附加的appender,最多只能添加一个 -->
|
||||
<appender-ref ref="file_info"/>
|
||||
</appender>
|
||||
|
||||
<!-- error异步输出 -->
|
||||
<appender name="async_error" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
|
||||
<discardingThreshold>0</discardingThreshold>
|
||||
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
|
||||
<queueSize>512</queueSize>
|
||||
<!-- 添加附加的appender,最多只能添加一个 -->
|
||||
<appender-ref ref="file_error"/>
|
||||
</appender>
|
||||
|
||||
<include resource="logback-logstash.xml" />
|
||||
|
||||
<!-- 开启 skywalking 日志收集 -->
|
||||
<include resource="logback-skylog.xml" />
|
||||
|
||||
<!--系统操作日志-->
|
||||
<root level="info">
|
||||
<appender-ref ref="console"/>
|
||||
<appender-ref ref="async_info"/>
|
||||
<appender-ref ref="async_error"/>
|
||||
<appender-ref ref="file_console"/>
|
||||
</root>
|
||||
</configuration>
|
||||
84
script/config/nacos/ruoyi-gateway-mvc.yml
Normal file
84
script/config/nacos/ruoyi-gateway-mvc.yml
Normal file
@@ -0,0 +1,84 @@
|
||||
# 安全配置
|
||||
security:
|
||||
# 不校验白名单
|
||||
ignore:
|
||||
whites:
|
||||
- /auth/code
|
||||
- /auth/logout
|
||||
- /auth/login
|
||||
- /auth/binding/*
|
||||
- /auth/register
|
||||
- /auth/tenant/list
|
||||
- /resource/sms/code
|
||||
- /resource/sse/close
|
||||
- /*/v3/api-docs
|
||||
- /*/error
|
||||
- /csrf
|
||||
- /warm-flow-ui/**
|
||||
|
||||
spring:
|
||||
cloud:
|
||||
gateway:
|
||||
# 打印请求日志(自定义)
|
||||
requestLog: true
|
||||
server:
|
||||
webmvc:
|
||||
discovery:
|
||||
locator:
|
||||
lowerCaseServiceId: true
|
||||
enabled: true
|
||||
routes:
|
||||
# 认证中心
|
||||
- id: ruoyi-auth
|
||||
uri: lb://ruoyi-auth
|
||||
predicates:
|
||||
- Path=/auth/**
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
# 代码生成
|
||||
- id: ruoyi-gen
|
||||
uri: lb://ruoyi-gen
|
||||
predicates:
|
||||
- Path=/tool/**
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
# 系统模块
|
||||
- id: ruoyi-system
|
||||
uri: lb://ruoyi-system
|
||||
predicates:
|
||||
- Path=/system/**,/monitor/**
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
# 资源服务
|
||||
- id: ruoyi-resource
|
||||
uri: lb://ruoyi-resource
|
||||
predicates:
|
||||
- Path=/resource/**
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
# workflow服务
|
||||
- id: ruoyi-workflow
|
||||
uri: lb://ruoyi-workflow
|
||||
predicates:
|
||||
- Path=/workflow/**
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
# warm-flow服务
|
||||
- id: warm-flow
|
||||
uri: lb://ruoyi-workflow
|
||||
predicates:
|
||||
- Path=/warm-flow-ui/**,/warm-flow/**
|
||||
# 演示服务
|
||||
- id: ruoyi-demo
|
||||
uri: lb://ruoyi-demo
|
||||
predicates:
|
||||
- Path=/demo/**
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
# MQ演示服务
|
||||
- id: ruoyi-test-mq
|
||||
uri: lb://ruoyi-test-mq
|
||||
predicates:
|
||||
- Path=/test-mq/**
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
Reference in New Issue
Block a user