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