From 806d4b0d8639b3b04c166cc60a69eb6f31d4e6aa Mon Sep 17 00:00:00 2001
From: xuekang <914468783@qq.com>
Date: 星期五, 10 五月 2024 20:42:23 +0800
Subject: [PATCH] 初始化
---
ruoyi-gateway/src/main/java/org/dromara/gateway/RuoYiGatewayApplication.java | 23 +
ruoyi-gateway/src/main/java/org/dromara/gateway/handler/SentinelFallbackHandler.java | 36 +
ruoyi-gateway/src/main/java/org/dromara/gateway/filter/XssFilter.java | 100 ++++
ruoyi-gateway/src/main/java/org/dromara/gateway/config/properties/XssProperties.java | 31 +
ruoyi-gateway/src/main/java/org/dromara/gateway/filter/ForwardAuthFilter.java | 41 +
ruoyi-gateway/src/main/java/org/dromara/gateway/config/properties/CustomGatewayProperties.java | 24 +
ruoyi-gateway/src/main/java/org/dromara/gateway/config/GatewayConfig.java | 21 +
ruoyi-gateway/Dockerfile | 22 +
ruoyi-gateway/src/main/resources/logback-plus.xml | 114 +++++
ruoyi-gateway/src/main/java/org/dromara/gateway/config/properties/ApiDecryptProperties.java | 28 +
ruoyi-gateway/src/main/java/org/dromara/gateway/filter/BlackListUrlFilter.java | 58 ++
ruoyi-gateway/src/main/java/org/dromara/gateway/filter/GlobalCorsFilter.java | 58 ++
ruoyi-gateway/src/main/java/org/dromara/gateway/filter/GlobalLogFilter.java | 81 +++
ruoyi-gateway/src/main/java/org/dromara/gateway/handler/GatewayExceptionHandler.java | 46 ++
ruoyi-gateway/src/main/java/org/dromara/gateway/filter/GlobalCacheRequestFilter.java | 38 +
ruoyi-gateway/pom.xml | 154 +++++++
ruoyi-gateway/src/main/java/org/dromara/gateway/filter/AuthFilter.java | 68 +++
ruoyi-gateway/src/main/resources/application.yml | 33 +
ruoyi-gateway/src/main/resources/banner.txt | 10
ruoyi-gateway/src/main/java/org/dromara/gateway/config/properties/IgnoreWhiteProperties.java | 29 +
ruoyi-gateway/src/main/java/org/dromara/gateway/filter/GlobalI18nFilter.java | 41 +
ruoyi-gateway/src/main/java/org/dromara/gateway/utils/WebFluxUtils.java | 150 +++++++
22 files changed, 1,206 insertions(+), 0 deletions(-)
diff --git a/ruoyi-gateway/Dockerfile b/ruoyi-gateway/Dockerfile
new file mode 100644
index 0000000..ffaec9f
--- /dev/null
+++ b/ruoyi-gateway/Dockerfile
@@ -0,0 +1,22 @@
+#FROM findepi/graalvm:java17-native
+FROM openjdk:17.0.2-oraclelinux8
+
+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.jar ./app.jar
+
+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 \
+ -jar app.jar \
+ -XX:+HeapDumpOnOutOfMemoryError -Xlog:gc*,:time,tags,level -XX:+UseZGC ${JAVA_OPTS}
diff --git a/ruoyi-gateway/pom.xml b/ruoyi-gateway/pom.xml
new file mode 100644
index 0000000..71e544c
--- /dev/null
+++ b/ruoyi-gateway/pom.xml
@@ -0,0 +1,154 @@
+<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</artifactId>
+
+ <description>
+ ruoyi-gateway缃戝叧妯″潡
+ </description>
+
+ <dependencies>
+
+ <!-- SpringCloud Gateway -->
+ <dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-starter-gateway</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>
+
+ <!-- SpringCloud Alibaba Nacos -->
+ <dependency>
+ <groupId>com.alibaba.cloud</groupId>
+ <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
+ </dependency>
+
+ <!-- SpringCloud Alibaba Nacos Config -->
+ <dependency>
+ <groupId>com.alibaba.cloud</groupId>
+ <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
+ </dependency>
+
+ <!-- SpringCloud Alibaba Sentinel Gateway -->
+ <dependency>
+ <groupId>com.alibaba.cloud</groupId>
+ <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
+ </dependency>
+
+ <!-- SpringBoot Actuator -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-actuator</artifactId>
+ </dependency>
+
+ <!-- SpringCloud Loadbalancer -->
+ <dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-loadbalancer</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>cn.hutool</groupId>
+ <artifactId>hutool-http</artifactId>
+ </dependency>
+
+ <!-- Sa-Token 鏉冮檺璁よ瘉锛圧eactor鍝嶅簲寮忛泦鎴愶級, 鍦ㄧ嚎鏂囨。锛歨ttp://sa-token.dev33.cn/ -->
+ <dependency>
+ <groupId>cn.dev33</groupId>
+ <artifactId>sa-token-reactor-spring-boot3-starter</artifactId>
+ <version>${satoken.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-sentinel</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>com.alibaba.csp</groupId>
+ <artifactId>sentinel-apache-dubbo3-adapter</artifactId>
+ </exclusion>
+ </exclusions>
+ </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>
+ <exclusions>
+ <exclusion>
+ <groupId>org.dromara</groupId>
+ <artifactId>ruoyi-common-mybatis</artifactId>
+ </exclusion>
+ </exclusions>
+ </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>
diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/RuoYiGatewayApplication.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/RuoYiGatewayApplication.java
new file mode 100644
index 0000000..4f64453
--- /dev/null
+++ b/ruoyi-gateway/src/main/java/org/dromara/gateway/RuoYiGatewayApplication.java
@@ -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 ruoyi
+ */
+@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
+public class RuoYiGatewayApplication {
+ public static void main(String[] args) {
+ // 鏍囪 sentinel 绫诲瀷涓� 缃戝叧
+ System.setProperty("csp.sentinel.app.type", "1");
+ SpringApplication application = new SpringApplication(RuoYiGatewayApplication.class);
+ application.setApplicationStartup(new BufferingApplicationStartup(2048));
+ application.run(args);
+ System.out.println("(鈾モ棤鈥库棤)锞夛緸 缃戝叧鍚姩鎴愬姛 醿�(麓凇`醿�)锞� ");
+ }
+}
diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/config/GatewayConfig.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/config/GatewayConfig.java
new file mode 100644
index 0000000..d3b40dd
--- /dev/null
+++ b/ruoyi-gateway/src/main/java/org/dromara/gateway/config/GatewayConfig.java
@@ -0,0 +1,21 @@
+package org.dromara.gateway.config;
+
+import org.dromara.gateway.handler.SentinelFallbackHandler;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+
+/**
+ * 缃戝叧闄愭祦閰嶇疆
+ *
+ * @author ruoyi
+ */
+@Configuration
+public class GatewayConfig {
+ @Bean
+ @Order(Ordered.HIGHEST_PRECEDENCE)
+ public SentinelFallbackHandler sentinelGatewayExceptionHandler() {
+ return new SentinelFallbackHandler();
+ }
+}
diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/config/properties/ApiDecryptProperties.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/config/properties/ApiDecryptProperties.java
new file mode 100644
index 0000000..c038bd4
--- /dev/null
+++ b/ruoyi-gateway/src/main/java/org/dromara/gateway/config/properties/ApiDecryptProperties.java
@@ -0,0 +1,28 @@
+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瑙e瘑灞炴�ч厤缃被
+ * @author wdhcr
+ */
+@Data
+@Component
+@RefreshScope
+@ConfigurationProperties(prefix = "api-decrypt")
+public class ApiDecryptProperties {
+
+ /**
+ * 鍔犲瘑寮�鍏�
+ */
+ private Boolean enabled;
+
+ /**
+ * 澶撮儴鏍囪瘑
+ */
+ private String headerFlag;
+
+}
diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/config/properties/CustomGatewayProperties.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/config/properties/CustomGatewayProperties.java
new file mode 100644
index 0000000..5996030
--- /dev/null
+++ b/ruoyi-gateway/src/main/java/org/dromara/gateway/config/properties/CustomGatewayProperties.java
@@ -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;
+
+/**
+ * 鑷畾涔塯ateway鍙傛暟閰嶇疆
+ *
+ * @author Lion Li
+ */
+@Data
+@Configuration
+@RefreshScope
+@ConfigurationProperties(prefix = "spring.cloud.gateway")
+public class CustomGatewayProperties {
+
+ /**
+ * 璇锋眰鏃ュ織
+ */
+ private Boolean requestLog;
+
+}
diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/config/properties/IgnoreWhiteProperties.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/config/properties/IgnoreWhiteProperties.java
new file mode 100644
index 0000000..bf27ed6
--- /dev/null
+++ b/ruoyi-gateway/src/main/java/org/dromara/gateway/config/properties/IgnoreWhiteProperties.java
@@ -0,0 +1,29 @@
+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<>();
+
+}
diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/config/properties/XssProperties.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/config/properties/XssProperties.java
new file mode 100644
index 0000000..d75b024
--- /dev/null
+++ b/ruoyi-gateway/src/main/java/org/dromara/gateway/config/properties/XssProperties.java
@@ -0,0 +1,31 @@
+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;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * XSS璺ㄧ珯鑴氭湰閰嶇疆
+ *
+ * @author ruoyi
+ */
+@Data
+@Configuration
+@RefreshScope
+@ConfigurationProperties(prefix = "security.xss")
+public class XssProperties {
+ /**
+ * Xss寮�鍏�
+ */
+ private Boolean enabled;
+
+ /**
+ * 鎺掗櫎璺緞
+ */
+ private List<String> excludeUrls = new ArrayList<>();
+
+}
diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/AuthFilter.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/AuthFilter.java
new file mode 100644
index 0000000..943b0ba
--- /dev/null
+++ b/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/AuthFilter.java
@@ -0,0 +1,68 @@
+package org.dromara.gateway.filter;
+
+import cn.dev33.satoken.exception.NotLoginException;
+import cn.dev33.satoken.reactor.context.SaReactorSyncHolder;
+import cn.dev33.satoken.reactor.filter.SaReactorFilter;
+import cn.dev33.satoken.router.SaRouter;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.util.SaResult;
+import org.dromara.common.core.constant.HttpStatus;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.gateway.config.properties.IgnoreWhiteProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+
+/**
+ * [Sa-Token 鏉冮檺璁よ瘉] 鎷︽埅鍣�
+ *
+ * @author Lion Li
+ */
+@Configuration
+public class AuthFilter {
+
+ /**
+ * 娉ㄥ唽 Sa-Token 鍏ㄥ眬杩囨护鍣�
+ */
+ @Bean
+ public SaReactorFilter getSaReactorFilter(IgnoreWhiteProperties ignoreWhite) {
+ return new SaReactorFilter()
+ // 鎷︽埅鍦板潃
+ .addInclude("/**")
+ .addExclude("/favicon.ico", "/actuator/**")
+ // 閴存潈鏂规硶锛氭瘡娆¤闂繘鍏�
+ .setAuth(obj -> {
+ // 鐧诲綍鏍¢獙 -- 鎷︽埅鎵�鏈夎矾鐢�
+ SaRouter.match("/**")
+ .notMatch(ignoreWhite.getWhites())
+ .check(r -> {
+ // 妫�鏌ユ槸鍚︾櫥褰� 鏄惁鏈塼oken
+ StpUtil.checkLogin();
+
+ // 妫�鏌� header 涓� param 閲岀殑 clientid 涓� token 閲岀殑鏄惁涓�鑷�
+ ServerHttpRequest request = SaReactorSyncHolder.getContext().getRequest();
+ String headerCid = request.getHeaders().getFirst(LoginHelper.CLIENT_KEY);
+ String paramCid = request.getQueryParams().getFirst(LoginHelper.CLIENT_KEY);
+ String clientId = StpUtil.getExtra(LoginHelper.CLIENT_KEY).toString();
+ if (!StringUtils.equalsAny(clientId, headerCid, paramCid)) {
+ // token 鏃犳晥
+ throw NotLoginException.newInstance(StpUtil.getLoginType(),
+ "-100", "瀹㈡埛绔疘D涓嶵oken涓嶅尮閰�",
+ StpUtil.getTokenValue());
+ }
+
+ // 鏈夋晥鐜囧奖鍝� 鐢ㄤ簬涓存椂娴嬭瘯
+ // if (log.isDebugEnabled()) {
+ // log.debug("鍓╀綑鏈夋晥鏃堕棿: {}", StpUtil.getTokenTimeout());
+ // log.debug("涓存椂鏈夋晥鏃堕棿: {}", StpUtil.getTokenActivityTimeout());
+ // }
+ });
+ }).setError(e -> {
+ if (e instanceof NotLoginException) {
+ return SaResult.error(e.getMessage()).setCode(HttpStatus.UNAUTHORIZED);
+ }
+ return SaResult.error("璁よ瘉澶辫触锛屾棤娉曡闂郴缁熻祫婧�").setCode(HttpStatus.UNAUTHORIZED);
+ });
+ }
+}
diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/BlackListUrlFilter.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/BlackListUrlFilter.java
new file mode 100644
index 0000000..177e93c
--- /dev/null
+++ b/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/BlackListUrlFilter.java
@@ -0,0 +1,58 @@
+package org.dromara.gateway.filter;
+
+import org.dromara.gateway.utils.WebFluxUtils;
+import org.springframework.cloud.gateway.filter.GatewayFilter;
+import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * 榛戝悕鍗曡繃婊ゅ櫒
+ *
+ * @author ruoyi
+ */
+@Component
+public class BlackListUrlFilter extends AbstractGatewayFilterFactory<BlackListUrlFilter.Config> {
+ @Override
+ public GatewayFilter apply(Config config) {
+ return (exchange, chain) -> {
+
+ String url = exchange.getRequest().getURI().getPath();
+ if (config.matchBlacklist(url)) {
+ return WebFluxUtils.webFluxResponseWriter(exchange.getResponse(), "璇锋眰鍦板潃涓嶅厑璁歌闂�");
+ }
+
+ return chain.filter(exchange);
+ };
+ }
+
+ public BlackListUrlFilter() {
+ super(Config.class);
+ }
+
+ public static class Config {
+ private List<String> blacklistUrl;
+
+ private List<Pattern> blacklistUrlPattern = new ArrayList<>();
+
+ public boolean matchBlacklist(String url) {
+ return !blacklistUrlPattern.isEmpty() && blacklistUrlPattern.stream().anyMatch(p -> p.matcher(url).find());
+ }
+
+ public List<String> getBlacklistUrl() {
+ return blacklistUrl;
+ }
+
+ public void setBlacklistUrl(List<String> blacklistUrl) {
+ this.blacklistUrl = blacklistUrl;
+ this.blacklistUrlPattern.clear();
+ this.blacklistUrl.forEach(url -> {
+ this.blacklistUrlPattern.add(Pattern.compile(url.replaceAll("\\*\\*", "(.*?)"), Pattern.CASE_INSENSITIVE));
+ });
+ }
+ }
+
+}
diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/ForwardAuthFilter.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/ForwardAuthFilter.java
new file mode 100644
index 0000000..fe0348b
--- /dev/null
+++ b/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/ForwardAuthFilter.java
@@ -0,0 +1,41 @@
+package org.dromara.gateway.filter;
+
+import cn.dev33.satoken.SaManager;
+import cn.dev33.satoken.same.SaSameUtil;
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.GlobalFilter;
+import org.springframework.core.Ordered;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+/**
+ * 杞彂璁よ瘉杩囨护鍣�(鍐呴儴鏈嶅姟澶栫綉闅旂)
+ *
+ * @author Lion Li
+ */
+@Component
+public class ForwardAuthFilter implements GlobalFilter, Ordered {
+ @Override
+ public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+ // 鏈紑鍚厤缃垯鐩存帴璺宠繃
+ if (!SaManager.getConfig().getCheckSameToken()) {
+ return chain.filter(exchange);
+ }
+ ServerHttpRequest newRequest = exchange
+ .getRequest()
+ .mutate()
+ // 涓鸿姹傝拷鍔� Same-Token 鍙傛暟
+ .header(SaSameUtil.SAME_TOKEN, SaSameUtil.getToken())
+ .build();
+ ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
+ return chain.filter(newExchange);
+ }
+
+ @Override
+ public int getOrder() {
+ return -100;
+ }
+}
+
diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/GlobalCacheRequestFilter.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/GlobalCacheRequestFilter.java
new file mode 100644
index 0000000..09e50ff
--- /dev/null
+++ b/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/GlobalCacheRequestFilter.java
@@ -0,0 +1,38 @@
+package org.dromara.gateway.filter;
+
+import org.dromara.gateway.utils.WebFluxUtils;
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.GlobalFilter;
+import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
+import org.springframework.core.Ordered;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+/**
+ * 鍏ㄥ眬缂撳瓨鑾峰彇body璇锋眰鏁版嵁锛堣В鍐虫祦涓嶈兘閲嶅璇诲彇闂锛�
+ *
+ * @author Lion Li
+ */
+@Component
+public class GlobalCacheRequestFilter implements GlobalFilter, Ordered {
+
+ @Override
+ public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+ // 鍙紦瀛榡son绫诲瀷璇锋眰
+ if (!WebFluxUtils.isJsonRequest(exchange)) {
+ return chain.filter(exchange);
+ }
+ return ServerWebExchangeUtils.cacheRequestBody(exchange, (serverHttpRequest) -> {
+ if (serverHttpRequest == exchange.getRequest()) {
+ return chain.filter(exchange);
+ }
+ return chain.filter(exchange.mutate().request(serverHttpRequest).build());
+ });
+ }
+
+ @Override
+ public int getOrder() {
+ return Ordered.HIGHEST_PRECEDENCE + 1;
+ }
+}
diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/GlobalCorsFilter.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/GlobalCorsFilter.java
new file mode 100644
index 0000000..9f990aa
--- /dev/null
+++ b/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/GlobalCorsFilter.java
@@ -0,0 +1,58 @@
+package org.dromara.gateway.filter;
+
+import org.springframework.core.Ordered;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.stereotype.Component;
+import org.springframework.web.cors.reactive.CorsUtils;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.WebFilter;
+import org.springframework.web.server.WebFilterChain;
+import reactor.core.publisher.Mono;
+
+
+/**
+ * 璺ㄥ煙閰嶇疆
+ *
+ * @author Lion Li
+ */
+@Component
+public class GlobalCorsFilter implements WebFilter, Ordered {
+
+ /**
+ * 杩欓噷涓烘敮鎸佺殑璇锋眰澶达紝濡傛灉鏈夎嚜瀹氫箟鐨刪eader瀛楁璇疯嚜宸辨坊鍔�
+ */
+ private static final String ALLOWED_HEADERS = "X-Requested-With, Content-Language, Content-Type, Authorization, clientid, credential, X-XSRF-TOKEN, isToken, token, Admin-Token, App-Token";
+ private static final String ALLOWED_METHODS = "GET,POST,PUT,DELETE,OPTIONS,HEAD";
+ private static final String ALLOWED_ORIGIN = "*";
+ private static final String ALLOWED_EXPOSE = "*";
+ private static final String MAX_AGE = "18000L";
+
+ @Override
+ public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
+ ServerHttpRequest request = exchange.getRequest();
+ if (CorsUtils.isCorsRequest(request)) {
+ ServerHttpResponse response = exchange.getResponse();
+ HttpHeaders headers = response.getHeaders();
+ headers.add("Access-Control-Allow-Headers", ALLOWED_HEADERS);
+ headers.add("Access-Control-Allow-Methods", ALLOWED_METHODS);
+ headers.add("Access-Control-Allow-Origin", ALLOWED_ORIGIN);
+ headers.add("Access-Control-Expose-Headers", ALLOWED_EXPOSE);
+ headers.add("Access-Control-Max-Age", MAX_AGE);
+ headers.add("Access-Control-Allow-Credentials", "true");
+ if (request.getMethod() == HttpMethod.OPTIONS) {
+ response.setStatusCode(HttpStatus.OK);
+ return Mono.empty();
+ }
+ }
+ return chain.filter(exchange);
+ }
+
+ @Override
+ public int getOrder() {
+ return Ordered.HIGHEST_PRECEDENCE;
+ }
+}
diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/GlobalI18nFilter.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/GlobalI18nFilter.java
new file mode 100644
index 0000000..bad65bf
--- /dev/null
+++ b/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/GlobalI18nFilter.java
@@ -0,0 +1,41 @@
+package org.dromara.gateway.filter;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.GlobalFilter;
+import org.springframework.context.i18n.LocaleContextHolder;
+import org.springframework.context.i18n.SimpleLocaleContext;
+import org.springframework.core.Ordered;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+import java.util.Locale;
+
+/**
+ * 鍏ㄥ眬鍥介檯鍖栧鐞�
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@Component
+public class GlobalI18nFilter implements GlobalFilter, Ordered {
+
+ @Override
+ public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+ String language = exchange.getRequest().getHeaders().getFirst("content-language");
+ Locale locale = Locale.getDefault();
+ if (language != null && language.length() > 0) {
+ String[] split = language.split("_");
+ locale = new Locale(split[0], split[1]);
+ }
+ LocaleContextHolder.setLocaleContext(new SimpleLocaleContext(locale), true);
+ return chain.filter(exchange);
+ }
+
+ @Override
+ public int getOrder() {
+ return Ordered.HIGHEST_PRECEDENCE;
+ }
+
+}
diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/GlobalLogFilter.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/GlobalLogFilter.java
new file mode 100644
index 0000000..2c77651
--- /dev/null
+++ b/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/GlobalLogFilter.java
@@ -0,0 +1,81 @@
+package org.dromara.gateway.filter;
+
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ObjectUtil;
+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.utils.WebFluxUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.GlobalFilter;
+import org.springframework.core.Ordered;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.stereotype.Component;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+/**
+ * 鍏ㄥ眬鏃ュ織杩囨护鍣�
+ * <p>
+ * 鐢ㄤ簬鎵撳嵃璇锋眰鎵ц鍙傛暟涓庡搷搴旀椂闂寸瓑绛�
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@Component
+public class GlobalLogFilter implements GlobalFilter, Ordered {
+
+ @Autowired
+ private CustomGatewayProperties customGatewayProperties;
+ @Autowired
+ private ApiDecryptProperties apiDecryptProperties;
+
+ private static final String START_TIME = "startTime";
+
+ @Override
+ public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+ if (!customGatewayProperties.getRequestLog()) {
+ return chain.filter(exchange);
+ }
+ ServerHttpRequest request = exchange.getRequest();
+ String path = WebFluxUtils.getOriginalRequestUrl(exchange);
+ String url = request.getMethod().name() + " " + path;
+
+ // 鎵撳嵃璇锋眰鍙傛暟
+ if (WebFluxUtils.isJsonRequest(exchange)) {
+ if (apiDecryptProperties.getEnabled()
+ && ObjectUtil.isNotNull(request.getHeaders().getFirst(apiDecryptProperties.getHeaderFlag()))) {
+ log.info("[PLUS]寮�濮嬭姹� => URL[{}],鍙傛暟绫诲瀷[encrypt]", url);
+ } else {
+ String jsonParam = WebFluxUtils.resolveBodyFromCacheRequest(exchange);
+ log.info("[PLUS]寮�濮嬭姹� => URL[{}],鍙傛暟绫诲瀷[json],鍙傛暟:[{}]", url, jsonParam);
+ }
+ } else {
+ MultiValueMap<String, String> parameterMap = request.getQueryParams();
+ if (MapUtil.isNotEmpty(parameterMap)) {
+ String parameters = JsonUtils.toJsonString(parameterMap);
+ log.info("[PLUS]寮�濮嬭姹� => URL[{}],鍙傛暟绫诲瀷[param],鍙傛暟:[{}]", url, parameters);
+ } else {
+ log.info("[PLUS]寮�濮嬭姹� => URL[{}],鏃犲弬鏁�", url);
+ }
+ }
+
+ exchange.getAttributes().put(START_TIME, System.currentTimeMillis());
+ return chain.filter(exchange).then(Mono.fromRunnable(() -> {
+ Long startTime = exchange.getAttribute(START_TIME);
+ if (startTime != null) {
+ long executeTime = (System.currentTimeMillis() - startTime);
+ log.info("[PLUS]缁撴潫璇锋眰 => URL[{}],鑰楁椂:[{}]姣", url, executeTime);
+ }
+ }));
+ }
+
+ @Override
+ public int getOrder() {
+ return Ordered.LOWEST_PRECEDENCE;
+ }
+
+}
diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/XssFilter.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/XssFilter.java
new file mode 100644
index 0000000..d501610
--- /dev/null
+++ b/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/XssFilter.java
@@ -0,0 +1,100 @@
+package org.dromara.gateway.filter;
+
+import cn.hutool.http.HtmlUtil;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.gateway.config.properties.XssProperties;
+import org.dromara.gateway.utils.WebFluxUtils;
+import io.netty.buffer.ByteBufAllocator;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.GlobalFilter;
+import org.springframework.core.Ordered;
+import org.springframework.core.io.buffer.*;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 璺ㄧ珯鑴氭湰杩囨护鍣�
+ *
+ * @author ruoyi
+ */
+@Component
+@ConditionalOnProperty(value = "security.xss.enabled", havingValue = "true")
+public class XssFilter implements GlobalFilter, Ordered {
+ // 璺ㄧ珯鑴氭湰鐨� xss 閰嶇疆锛宯acos鑷娣诲姞
+ @Autowired
+ private XssProperties xss;
+
+ @Override
+ public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+ ServerHttpRequest request = exchange.getRequest();
+ // GET DELETE 涓嶈繃婊�
+ HttpMethod method = request.getMethod();
+ if (method == null || method == HttpMethod.GET || method == HttpMethod.DELETE) {
+ return chain.filter(exchange);
+ }
+ // 闈瀓son绫诲瀷锛屼笉杩囨护
+ if (!WebFluxUtils.isJsonRequest(exchange)) {
+ return chain.filter(exchange);
+ }
+ // excludeUrls 涓嶈繃婊�
+ String url = request.getURI().getPath();
+ if (StringUtils.matches(url, xss.getExcludeUrls())) {
+ return chain.filter(exchange);
+ }
+ ServerHttpRequestDecorator httpRequestDecorator = requestDecorator(exchange);
+ return chain.filter(exchange.mutate().request(httpRequestDecorator).build());
+
+ }
+
+ private ServerHttpRequestDecorator requestDecorator(ServerWebExchange exchange) {
+ ServerHttpRequestDecorator serverHttpRequestDecorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
+ @Override
+ public Flux<DataBuffer> getBody() {
+ Flux<DataBuffer> body = super.getBody();
+ return body.buffer().map(dataBuffers -> {
+ DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
+ DataBuffer join = dataBufferFactory.join(dataBuffers);
+ byte[] content = new byte[join.readableByteCount()];
+ join.read(content);
+ DataBufferUtils.release(join);
+ String bodyStr = new String(content, StandardCharsets.UTF_8);
+ // 闃瞲ss鏀诲嚮杩囨护
+ bodyStr = HtmlUtil.cleanHtmlTag(bodyStr);
+ // 杞垚瀛楄妭
+ byte[] bytes = bodyStr.getBytes();
+ NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
+ DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
+ buffer.write(bytes);
+ return buffer;
+ });
+ }
+
+ @Override
+ public HttpHeaders getHeaders() {
+ HttpHeaders httpHeaders = new HttpHeaders();
+ httpHeaders.putAll(super.getHeaders());
+ // 鐢变簬淇敼浜嗚姹備綋鐨刡ody锛屽鑷碿ontent-length闀垮害涓嶇‘瀹氾紝鍥犳闇�瑕佸垹闄ゅ師鍏堢殑content-length
+ httpHeaders.remove(HttpHeaders.CONTENT_LENGTH);
+ httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
+ return httpHeaders;
+ }
+
+ };
+ return serverHttpRequestDecorator;
+ }
+
+ @Override
+ public int getOrder() {
+ return Ordered.HIGHEST_PRECEDENCE;
+ }
+}
diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/handler/GatewayExceptionHandler.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/handler/GatewayExceptionHandler.java
new file mode 100644
index 0000000..e89538e
--- /dev/null
+++ b/ruoyi-gateway/src/main/java/org/dromara/gateway/handler/GatewayExceptionHandler.java
@@ -0,0 +1,46 @@
+package org.dromara.gateway.handler;
+
+import org.dromara.gateway.utils.WebFluxUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
+import org.springframework.cloud.gateway.support.NotFoundException;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.web.server.ResponseStatusException;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+/**
+ * 缃戝叧缁熶竴寮傚父澶勭悊
+ *
+ * @author ruoyi
+ */
+@Slf4j
+@Order(-1)
+@Configuration
+public class GatewayExceptionHandler implements ErrorWebExceptionHandler {
+
+ @Override
+ public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
+ ServerHttpResponse response = exchange.getResponse();
+
+ if (exchange.getResponse().isCommitted()) {
+ return Mono.error(ex);
+ }
+
+ String msg;
+
+ if (ex instanceof NotFoundException) {
+ msg = "鏈嶅姟鏈壘鍒�";
+ } else if (ex instanceof ResponseStatusException responseStatusException) {
+ msg = responseStatusException.getMessage();
+ } else {
+ msg = "鍐呴儴鏈嶅姟鍣ㄩ敊璇�";
+ }
+
+ log.error("[缃戝叧寮傚父澶勭悊]璇锋眰璺緞:{},寮傚父淇℃伅:{}", exchange.getRequest().getPath(), ex.getMessage());
+
+ return WebFluxUtils.webFluxResponseWriter(response, msg);
+ }
+}
diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/handler/SentinelFallbackHandler.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/handler/SentinelFallbackHandler.java
new file mode 100644
index 0000000..0aec088
--- /dev/null
+++ b/ruoyi-gateway/src/main/java/org/dromara/gateway/handler/SentinelFallbackHandler.java
@@ -0,0 +1,36 @@
+package org.dromara.gateway.handler;
+
+import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
+import com.alibaba.csp.sentinel.slots.block.BlockException;
+import org.dromara.gateway.utils.WebFluxUtils;
+import org.springframework.web.reactive.function.server.ServerResponse;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.WebExceptionHandler;
+import reactor.core.publisher.Mono;
+
+/**
+ * 鑷畾涔夐檺娴佸紓甯稿鐞�
+ *
+ * @author ruoyi
+ */
+public class SentinelFallbackHandler implements WebExceptionHandler {
+ private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange) {
+ return WebFluxUtils.webFluxResponseWriter(exchange.getResponse(), "璇锋眰瓒呰繃鏈�澶ф暟锛岃绋嶅�欏啀璇�");
+ }
+
+ @Override
+ public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
+ ex.printStackTrace();
+ if (exchange.getResponse().isCommitted()) {
+ return Mono.error(ex);
+ }
+ if (!BlockException.isBlockException(ex)) {
+ return Mono.error(ex);
+ }
+ return handleBlockedRequest(exchange, ex).flatMap(response -> writeResponse(response, exchange));
+ }
+
+ private Mono<ServerResponse> handleBlockedRequest(ServerWebExchange exchange, Throwable throwable) {
+ return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable);
+ }
+}
diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/utils/WebFluxUtils.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/utils/WebFluxUtils.java
new file mode 100644
index 0000000..278050a
--- /dev/null
+++ b/ruoyi-gateway/src/main/java/org/dromara/gateway/utils/WebFluxUtils.java
@@ -0,0 +1,150 @@
+package org.dromara.gateway.utils;
+
+import cn.hutool.core.util.ObjectUtil;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.gateway.filter.GlobalCacheRequestFilter;
+import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.core.io.buffer.DataBufferUtils;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.util.UriComponentsBuilder;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.LinkedHashSet;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
+
+import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR;
+
+/**
+ * WebFlux 宸ュ叿绫�
+ *
+ * @author Lion Li
+ */
+public class WebFluxUtils {
+
+ /**
+ * 鑾峰彇鍘熻姹傝矾寰�
+ */
+ public static String getOriginalRequestUrl(ServerWebExchange exchange) {
+ ServerHttpRequest request = exchange.getRequest();
+ LinkedHashSet<URI> uris = exchange.getAttributeOrDefault(GATEWAY_ORIGINAL_REQUEST_URL_ATTR, new LinkedHashSet<>());
+ URI requestUri = uris.stream().findFirst().orElse(request.getURI());
+ return UriComponentsBuilder.fromPath(requestUri.getRawPath()).build().toUriString();
+ }
+
+ /**
+ * 鏄惁鏄疛son璇锋眰
+ *
+ * @param exchange HTTP璇锋眰
+ */
+ public static boolean isJsonRequest(ServerWebExchange exchange) {
+ String header = exchange.getRequest().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
+ return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE);
+ }
+
+ /**
+ * 璇诲彇request鍐呯殑body
+ *
+ * 娉ㄦ剰涓�涓猺equest鍙兘璇诲彇涓�娆� 璇诲彇涔嬪悗闇�瑕侀噸鏂板寘瑁�
+ */
+ public static String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) {
+ // 鑾峰彇璇锋眰浣�
+ Flux<DataBuffer> body = serverHttpRequest.getBody();
+ AtomicReference<String> bodyRef = new AtomicReference<>();
+ body.subscribe(buffer -> {
+ try (DataBuffer.ByteBufferIterator iterator = buffer.readableByteBuffers()) {
+ CharBuffer charBuffer = StandardCharsets.UTF_8.decode(iterator.next());
+ DataBufferUtils.release(buffer);
+ bodyRef.set(charBuffer.toString());
+ }
+ });
+ return bodyRef.get();
+ }
+
+ /**
+ * 浠庣紦瀛樹腑璇诲彇request鍐呯殑body
+ *
+ * 娉ㄦ剰瑕佹眰缁忚繃 {@link ServerWebExchangeUtils#cacheRequestBody(ServerWebExchange, Function)} 姝ゆ柟娉曞垱寤虹紦瀛�
+ * 妗嗘灦鍐呭凡缁忎娇鐢� {@link GlobalCacheRequestFilter} 鍏ㄥ眬鍒涘缓浜哹ody缂撳瓨
+ *
+ * @return body
+ */
+ public static String resolveBodyFromCacheRequest(ServerWebExchange exchange) {
+ Object obj = exchange.getAttributes().get(ServerWebExchangeUtils.CACHED_REQUEST_BODY_ATTR);
+ if (ObjectUtil.isNull(obj)) {
+ return null;
+ }
+ DataBuffer buffer = (DataBuffer) obj;
+ try (DataBuffer.ByteBufferIterator iterator = buffer.readableByteBuffers()) {
+ CharBuffer charBuffer = StandardCharsets.UTF_8.decode(iterator.next());
+ return charBuffer.toString();
+ }
+ }
+
+ /**
+ * 璁剧疆webflux妯″瀷鍝嶅簲
+ *
+ * @param response ServerHttpResponse
+ * @param value 鍝嶅簲鍐呭
+ * @return Mono<Void>
+ */
+ public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, Object value) {
+ return webFluxResponseWriter(response, HttpStatus.OK, value, R.FAIL);
+ }
+
+ /**
+ * 璁剧疆webflux妯″瀷鍝嶅簲
+ *
+ * @param response ServerHttpResponse
+ * @param code 鍝嶅簲鐘舵�佺爜
+ * @param value 鍝嶅簲鍐呭
+ * @return Mono<Void>
+ */
+ public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, Object value, int code) {
+ return webFluxResponseWriter(response, HttpStatus.OK, value, code);
+ }
+
+ /**
+ * 璁剧疆webflux妯″瀷鍝嶅簲
+ *
+ * @param response ServerHttpResponse
+ * @param status http鐘舵�佺爜
+ * @param code 鍝嶅簲鐘舵�佺爜
+ * @param value 鍝嶅簲鍐呭
+ * @return Mono<Void>
+ */
+ public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, HttpStatus status, Object value, int code) {
+ return webFluxResponseWriter(response, MediaType.APPLICATION_JSON_VALUE, status, value, code);
+ }
+
+ /**
+ * 璁剧疆webflux妯″瀷鍝嶅簲
+ *
+ * @param response ServerHttpResponse
+ * @param contentType content-type
+ * @param status http鐘舵�佺爜
+ * @param code 鍝嶅簲鐘舵�佺爜
+ * @param value 鍝嶅簲鍐呭
+ * @return Mono<Void>
+ */
+ public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, String contentType, HttpStatus status, Object value, int code) {
+ response.setStatusCode(status);
+ response.getHeaders().add(HttpHeaders.CONTENT_TYPE, contentType);
+ R<?> result = R.fail(code, value.toString());
+ DataBuffer dataBuffer = response.bufferFactory().wrap(JsonUtils.toJsonString(result).getBytes());
+ return response.writeWith(Mono.just(dataBuffer));
+ }
+}
diff --git a/ruoyi-gateway/src/main/resources/application.yml b/ruoyi-gateway/src/main/resources/application.yml
new file mode 100644
index 0000000..08bd112
--- /dev/null
+++ b/ruoyi-gateway/src/main/resources/application.yml
@@ -0,0 +1,33 @@
+# Tomcat
+server:
+ port: 8080
+ servlet:
+ context-path: /
+
+# Spring
+spring:
+ application:
+ # 搴旂敤鍚嶇О
+ name: ruoyi-gateway
+ profiles:
+ # 鐜閰嶇疆
+ active: @profiles.active@
+
+--- # nacos 閰嶇疆
+spring:
+ cloud:
+ nacos:
+ # nacos 鏈嶅姟鍦板潃
+ server-addr: @nacos.server@
+ 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
diff --git a/ruoyi-gateway/src/main/resources/banner.txt b/ruoyi-gateway/src/main/resources/banner.txt
new file mode 100644
index 0000000..ceced29
--- /dev/null
+++ b/ruoyi-gateway/src/main/resources/banner.txt
@@ -0,0 +1,10 @@
+Spring Boot Version: ${spring-boot.version}
+Spring Application Name: ${spring.application.name}
+ _ _
+ (_) | |
+ _ __ _ _ ___ _ _ _ ______ __ _ __ _ | |_ ___ __ __ __ _ _ _
+| '__|| | | | / _ \ | | | || ||______| / _` | / _` || __| / _ \\ \ /\ / / / _` || | | |
+| | | |_| || (_) || |_| || | | (_| || (_| || |_ | __/ \ V V / | (_| || |_| |
+|_| \__,_| \___/ \__, ||_| \__, | \__,_| \__| \___| \_/\_/ \__,_| \__, |
+ __/ | __/ | __/ |
+ |___/ |___/ |___/
\ No newline at end of file
diff --git a/ruoyi-gateway/src/main/resources/logback-plus.xml b/ruoyi-gateway/src/main/resources/logback-plus.xml
new file mode 100644
index 0000000..4d66014
--- /dev/null
+++ b/ruoyi-gateway/src/main/resources/logback-plus.xml
@@ -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="%red(%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銆丏EBUG銆両NFO绾у埆鐨勬棩蹇� -->
+ <discardingThreshold>0</discardingThreshold>
+ <!-- 鏇存敼榛樿鐨勯槦鍒楃殑娣卞害,璇ュ�间細褰卞搷鎬ц兘.榛樿鍊间负256 -->
+ <queueSize>512</queueSize>
+ <!-- 娣诲姞闄勫姞鐨刟ppender,鏈�澶氬彧鑳芥坊鍔犱竴涓� -->
+ <appender-ref ref="file_info"/>
+ </appender>
+
+ <!-- error寮傛杈撳嚭 -->
+ <appender name="async_error" class="ch.qos.logback.classic.AsyncAppender">
+ <!-- 涓嶄涪澶辨棩蹇�.榛樿鐨�,濡傛灉闃熷垪鐨�80%宸叉弧,鍒欎細涓㈠純TRACT銆丏EBUG銆両NFO绾у埆鐨勬棩蹇� -->
+ <discardingThreshold>0</discardingThreshold>
+ <!-- 鏇存敼榛樿鐨勯槦鍒楃殑娣卞害,璇ュ�间細褰卞搷鎬ц兘.榛樿鍊间负256 -->
+ <queueSize>512</queueSize>
+ <!-- 娣诲姞闄勫姞鐨刟ppender,鏈�澶氬彧鑳芥坊鍔犱竴涓� -->
+ <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>
--
Gitblit v1.9.1