wangtao 3 роки тому
коміт
bbc08f38d2
23 змінених файлів з 3004 додано та 0 видалено
  1. 139 0
      pom.xml
  2. 22 0
      src/main/java/com/ydd/gateway/GatewayApplication.java
  3. 26 0
      src/main/java/com/ydd/gateway/config/GatewayConfig.java
  4. 33 0
      src/main/java/com/ydd/gateway/config/properties/IgnoreWhiteProperties.java
  5. 48 0
      src/main/java/com/ydd/gateway/config/properties/XssProperties.java
  6. 128 0
      src/main/java/com/ydd/gateway/filter/AuthFilter.java
  7. 139 0
      src/main/java/com/ydd/gateway/filter/AuthorizeGatewayFilterFactory.java
  8. 65 0
      src/main/java/com/ydd/gateway/filter/BlackListUrlFilter.java
  9. 105 0
      src/main/java/com/ydd/gateway/filter/CacheRequestFilter.java
  10. 39 0
      src/main/java/com/ydd/gateway/filter/ResponseObject.java
  11. 120 0
      src/main/java/com/ydd/gateway/filter/XssFilter.java
  12. 56 0
      src/main/java/com/ydd/gateway/handler/GatewayExceptionHandler.java
  13. 41 0
      src/main/java/com/ydd/gateway/handler/SentinelFallbackHandler.java
  14. 119 0
      src/main/java/com/ydd/gateway/service/FilterService.java
  15. 85 0
      src/main/java/com/ydd/gateway/util/CharsetKit.java
  16. 1012 0
      src/main/java/com/ydd/gateway/util/Convert.java
  17. 107 0
      src/main/java/com/ydd/gateway/util/Md5Util.java
  18. 90 0
      src/main/java/com/ydd/gateway/util/StrFormatter.java
  19. 480 0
      src/main/java/com/ydd/gateway/util/StringUtils.java
  20. 26 0
      src/main/resources/application.yml
  21. 10 0
      src/main/resources/banner.txt
  22. 40 0
      src/main/resources/bootstrap.yml
  23. 74 0
      src/main/resources/logback.xml

+ 139 - 0
pom.xml

@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>com.ydd</groupId>
+    <artifactId>lb-gateway</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <packaging>jar</packaging>
+    <description>
+        lb-gateway网关模块
+    </description>
+    <properties>
+        <spring-boot.version>2.5.5</spring-boot.version>
+        <spring-cloud.version>2020.0.4</spring-cloud.version>
+        <spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version>
+        <alibaba.nacos.version>2.0.3</alibaba.nacos.version>
+    </properties>
+    <dependencyManagement>
+        <dependencies>
+            <!-- SpringCloud 微服务 -->
+            <dependency>
+                <groupId>org.springframework.cloud</groupId>
+                <artifactId>spring-cloud-dependencies</artifactId>
+                <version>${spring-cloud.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <!-- SpringCloud Alibaba 微服务 -->
+            <dependency>
+                <groupId>com.alibaba.cloud</groupId>
+                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
+                <version>${spring-cloud-alibaba.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+
+            <!-- Alibaba Nacos 配置 -->
+            <dependency>
+                <groupId>com.alibaba.nacos</groupId>
+                <artifactId>nacos-client</artifactId>
+                <version>${alibaba.nacos.version}</version>
+            </dependency>
+            <!-- SpringBoot的依赖配置-->
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring-boot.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+    <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-bootstrap</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>
+
+        <!--&lt;!&ndash; SpringCloud Alibaba Sentinel &ndash;&gt;-->
+        <!--<dependency>-->
+            <!--<groupId>com.alibaba.cloud</groupId>-->
+            <!--<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>-->
+        <!--</dependency>-->
+
+        <!--&lt;!&ndash; SpringCloud Alibaba Sentinel Gateway &ndash;&gt;-->
+        <!--<dependency>-->
+            <!--<groupId>com.alibaba.cloud</groupId>-->
+            <!--<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>-->
+        <!--</dependency>-->
+
+        <!-- Sentinel Datasource Nacos -->
+        <dependency>
+            <groupId>com.alibaba.csp</groupId>
+            <artifactId>sentinel-datasource-nacos</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>1.2.75</version>
+        </dependency>
+        <!--常用工具类 -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.11</version>
+        </dependency>
+        <!--<dependency>-->
+            <!--<groupId>com.ydd</groupId>-->
+            <!--<artifactId>lb-common</artifactId>-->
+            <!--<version>1.0</version>-->
+        <!--</dependency>-->
+
+    </dependencies>
+
+    <build>
+        <finalName>${project.artifactId}</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>8</source>
+                    <target>8</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 22 - 0
src/main/java/com/ydd/gateway/GatewayApplication.java

@@ -0,0 +1,22 @@
+package com.ydd.gateway;
+
+import com.alibaba.nacos.api.config.annotation.NacosProperty;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+
+/**
+ * 网关启动程序
+ * 
+ * @author
+ */
+@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
+@EnableDiscoveryClient
+public class GatewayApplication
+{
+    public static void main(String[] args)
+    {
+        SpringApplication.run(GatewayApplication.class, args);
+    }
+}

+ 26 - 0
src/main/java/com/ydd/gateway/config/GatewayConfig.java

@@ -0,0 +1,26 @@
+package com.ydd.gateway.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 网关限流配置
+ *
+        *
+        */
+@Configuration
+public class GatewayConfig
+{
+    public static String NACOS_SERVER_ADDR;
+
+    @Value("${waimai.meituan.developerId}")
+    public void setNacosServerAddr(String nacosServerAddr){
+        NACOS_SERVER_ADDR = nacosServerAddr;
+    }
+//    @Bean
+//    @Order(Ordered.HIGHEST_PRECEDENCE)
+//    public SentinelFallbackHandler sentinelGatewayExceptionHandler()
+//    {
+//        return new SentinelFallbackHandler();
+//    }
+}

+ 33 - 0
src/main/java/com/ydd/gateway/config/properties/IgnoreWhiteProperties.java

@@ -0,0 +1,33 @@
+package com.ydd.gateway.config.properties;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 放行白名单配置
+ * 
+ * @author ruoyi
+ */
+@Configuration
+@RefreshScope
+@ConfigurationProperties(prefix = "security.ignore")
+public class IgnoreWhiteProperties
+{
+    /**
+     * 放行白名单配置,网关不校验此处的白名单
+     */
+    private List<String> whites = new ArrayList<>();
+
+    public List<String> getWhites()
+    {
+        return whites;
+    }
+
+    public void setWhites(List<String> whites)
+    {
+        this.whites = whites;
+    }
+}

+ 48 - 0
src/main/java/com/ydd/gateway/config/properties/XssProperties.java

@@ -0,0 +1,48 @@
+package com.ydd.gateway.config.properties;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * XSS跨站脚本配置
+ * 
+ * @author ruoyi
+ */
+@Configuration
+@RefreshScope
+@ConfigurationProperties(prefix = "security.xss")
+public class XssProperties
+{
+    /**
+     * Xss开关
+     */
+    private Boolean enabled;
+
+    /**
+     * 排除路径
+     */
+    private List<String> excludeUrls = new ArrayList<>();
+
+    public Boolean getEnabled()
+    {
+        return enabled;
+    }
+
+    public void setEnabled(Boolean enabled)
+    {
+        this.enabled = enabled;
+    }
+
+    public List<String> getExcludeUrls()
+    {
+        return excludeUrls;
+    }
+
+    public void setExcludeUrls(List<String> excludeUrls)
+    {
+        this.excludeUrls = excludeUrls;
+    }
+}

+ 128 - 0
src/main/java/com/ydd/gateway/filter/AuthFilter.java

@@ -0,0 +1,128 @@
+//package com.ydd.gateway.filter;
+//
+//import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//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.web.server.ServerWebExchange;
+//import com.ydd.gateway.config.properties.IgnoreWhiteProperties;
+//import io.jsonwebtoken.Claims;
+//import reactor.core.publisher.Mono;
+//
+///**
+// * 网关鉴权
+// *
+// * @author ruoyi
+// */
+//@Component
+//public class AuthFilter implements GlobalFilter, Ordered
+//{
+//    private static final Logger log = LoggerFactory.getLogger(AuthFilter.class);
+//
+//    // 排除过滤的 uri 地址,nacos自行添加
+//    @Autowired
+//    private IgnoreWhiteProperties ignoreWhite;
+//
+//    @Autowired
+//    private RedisService redisService;
+//
+//
+//    @Override
+//    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
+//    {
+//        ServerHttpRequest request = exchange.getRequest();
+//        ServerHttpRequest.Builder mutate = request.mutate();
+//
+//        String url = request.getURI().getPath();
+//        // 跳过不需要验证的路径
+//        if (StringUtils.matches(url, ignoreWhite.getWhites()))
+//        {
+//            return chain.filter(exchange);
+//        }
+//        String token = getToken(request);
+//        if (StringUtils.isEmpty(token))
+//        {
+//            return unauthorizedResponse(exchange, "令牌不能为空");
+//        }
+//        Claims claims = JwtUtils.parseToken(token);
+//        if (claims == null)
+//        {
+//            return unauthorizedResponse(exchange, "令牌已过期或验证不正确!");
+//        }
+//        String userkey = JwtUtils.getUserKey(claims);
+//        boolean islogin = redisService.hasKey(getTokenKey(userkey));
+//        if (!islogin)
+//        {
+//            return unauthorizedResponse(exchange, "登录状态已过期");
+//        }
+//        String userid = JwtUtils.getUserId(claims);
+//        String username = JwtUtils.getUserName(claims);
+//        if (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username))
+//        {
+//            return unauthorizedResponse(exchange, "令牌验证失败");
+//        }
+//
+//        // 设置用户信息到请求
+//        addHeader(mutate, SecurityConstants.USER_KEY, userkey);
+//        addHeader(mutate, SecurityConstants.DETAILS_USER_ID, userid);
+//        addHeader(mutate, SecurityConstants.DETAILS_USERNAME, username);
+//        // 内部请求来源参数清除
+//        removeHeader(mutate, SecurityConstants.FROM_SOURCE);
+//        return chain.filter(exchange.mutate().request(mutate.build()).build());
+//    }
+//
+//    private void addHeader(ServerHttpRequest.Builder mutate, String name, Object value)
+//    {
+//        if (value == null)
+//        {
+//            return;
+//        }
+//        String valueStr = value.toString();
+//        String valueEncode = ServletUtils.urlEncode(valueStr);
+//        mutate.header(name, valueEncode);
+//    }
+//
+//    private void removeHeader(ServerHttpRequest.Builder mutate, String name)
+//    {
+//        mutate.headers(httpHeaders -> httpHeaders.remove(name)).build();
+//    }
+//
+//    private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg)
+//    {
+//        log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath());
+//        return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, HttpStatus.UNAUTHORIZED);
+//    }
+//
+//    /**
+//     * 获取缓存key
+//     */
+//    private String getTokenKey(String token)
+//    {
+//        return CacheConstants.LOGIN_TOKEN_KEY + token;
+//    }
+//
+//    /**
+//     * 获取请求token
+//     */
+//    private String getToken(ServerHttpRequest request)
+//    {
+//        String token = request.getHeaders().getFirst(TokenConstants.AUTHENTICATION);
+//        // 如果前端设置了令牌前缀,则裁剪掉前缀
+//        if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX))
+//        {
+//            token = token.replaceFirst(TokenConstants.PREFIX, StringUtils.EMPTY);
+//        }
+//        return token;
+//    }
+//
+//    @Override
+//    public int getOrder()
+//    {
+//        return -200;
+//    }
+//}

+ 139 - 0
src/main/java/com/ydd/gateway/filter/AuthorizeGatewayFilterFactory.java

@@ -0,0 +1,139 @@
+package com.ydd.gateway.filter;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.serializer.SerializerFeature;
+import com.ydd.gateway.config.GatewayConfig;
+import com.ydd.gateway.service.FilterService;
+import com.ydd.gateway.util.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cloud.gateway.filter.GatewayFilter;
+import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.stereotype.Component;
+import reactor.core.publisher.Mono;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.TreeMap;
+
+/**
+ @author Administrator
+ *
+ */
+@Component
+public class AuthorizeGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthorizeGatewayFilterFactory.Config> {
+
+    private static final Log logger = LogFactory.getLog(AuthorizeGatewayFilterFactory.class);
+
+//	private static final String AUTHORIZE_TOKEN = "clientId";
+
+    @Autowired
+    FilterService filterService;
+
+    public AuthorizeGatewayFilterFactory() {
+        super(Config.class);
+        logger.info("Loaded GatewayFilterFactory [Authorize]");
+    }
+
+    @Override
+    public List<String> shortcutFieldOrder() {
+        return Arrays.asList("enabled");
+    }
+
+    // url白名单
+    public static final String URL = "/**/**,/doc.html/**";
+//    @Value("${waimai.meituan.developerId}")
+//    public String aa ;
+    @Override
+    public GatewayFilter apply(AuthorizeGatewayFilterFactory.Config config) {
+        return (exchange, chain) -> {
+            if (!config.isEnabled()) {
+                return chain.filter(exchange);
+            }
+
+            ServerHttpRequest request = exchange.getRequest();
+            ServerHttpResponse response = exchange.getResponse();
+            //	response.getHeaders().add("Content-Type", "text/plain;charset=UTF-8");
+
+            // url白名单
+            String url = request.getURI().getPath();
+            logger.info(GatewayConfig.NACOS_SERVER_ADDR);
+            String path = request.getPath().pathWithinApplication().value();
+			if (URL.contains(path.substring(path.indexOf("/", 1), path.lastIndexOf("/")))) {
+				return chain.filter(exchange);
+			}
+
+            HttpHeaders headers = request.getHeaders();
+//			String remoteIP = headers.getFirst("x-real-ip");
+//			if (!StringUtils.hasText(remoteIP)) {
+//				remoteIP = request.getRemoteAddress().getHostName();
+//			}
+
+            // 请求信息打印
+            logInfo(request, headers);
+            TreeMap<String, String> params = new TreeMap<>();
+            String signStr = headers.getFirst("sign");
+
+            String sourceType = headers.getFirst("sourceType");
+            String time = headers.getFirst("time");// 日期 yyyymmddHHmiss
+            String nonce = headers.getFirst("nonce");// 随机数
+            if (StringUtils.hasText(sourceType)) {
+                params.put("sourceType", sourceType);
+            }
+
+            params.put("time", time);
+            params.put("nonce", nonce);
+
+            // 签名校验
+            Mono<DataBuffer> mono = filterService.validateSign(params, signStr, response);
+            if (mono != null) {
+                return response.writeWith(mono);
+            }
+
+
+            return chain.filter(exchange);
+        };
+    }
+
+    public static class Config {
+
+        // 控制是否开启认证
+        private boolean enabled;
+
+        public Config() {
+        }
+
+        public boolean isEnabled() {
+            return enabled;
+        }
+
+        public void setEnabled(boolean enabled) {
+            this.enabled = enabled;
+        }
+
+    }
+
+    public void logInfo(ServerHttpRequest request, HttpHeaders headers) {
+
+        String remoteIP = headers.getFirst("x-real-ip");
+        if (!StringUtils.hasText(remoteIP)) {
+            remoteIP = request.getRemoteAddress().getHostName();
+        }
+        String method = request.getMethodValue();
+        String reqParams = JSON.toJSONString(request.getQueryParams(), SerializerFeature.DisableCircularReferenceDetect,
+                SerializerFeature.WriteMapNullValue);
+        String path = request.getPath().pathWithinApplication().value();
+        logger.info("请求方的IP:" + remoteIP);
+        logger.info("请求的接口:" + path);
+        logger.info("请求的类型:" + method);
+        logger.info("请求的参数:" + reqParams);
+
+    }
+
+
+}

+ 65 - 0
src/main/java/com/ydd/gateway/filter/BlackListUrlFilter.java

@@ -0,0 +1,65 @@
+package com.ydd.gateway.filter;
+
+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 ServletUtils.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() ? false : blacklistUrlPattern.stream().filter(p -> p.matcher(url).find()).findAny().isPresent();
+        }
+
+        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));
+            });
+        }
+    }
+
+}

+ 105 - 0
src/main/java/com/ydd/gateway/filter/CacheRequestFilter.java

@@ -0,0 +1,105 @@
+package com.ydd.gateway.filter;
+
+import java.util.Collections;
+import java.util.List;
+import org.springframework.cloud.gateway.filter.GatewayFilter;
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.OrderedGatewayFilter;
+import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.core.io.buffer.DataBufferFactory;
+import org.springframework.core.io.buffer.DataBufferUtils;
+import org.springframework.http.HttpMethod;
+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;
+
+/**
+ * 获取body请求数据(解决流不能重复读取问题)
+ * 
+ * @author ruoyi
+ */
+//@Component
+public class CacheRequestFilter extends AbstractGatewayFilterFactory<CacheRequestFilter.Config>
+{
+    public CacheRequestFilter()
+    {
+        super(Config.class);
+    }
+
+    @Override
+    public String name()
+    {
+        return "CacheRequestFilter";
+    }
+
+    @Override
+    public GatewayFilter apply(Config config)
+    {
+        CacheRequestGatewayFilter cacheRequestGatewayFilter = new CacheRequestGatewayFilter();
+        Integer order = config.getOrder();
+        if (order == null)
+        {
+            return cacheRequestGatewayFilter;
+        }
+        return new OrderedGatewayFilter(cacheRequestGatewayFilter, order);
+    }
+
+    public static class CacheRequestGatewayFilter implements GatewayFilter
+    {
+        @Override
+        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
+        {
+            // GET DELETE 不过滤
+            HttpMethod method = exchange.getRequest().getMethod();
+            if (method == null || method.matches("GET") || method.matches("DELETE"))
+            {
+                return chain.filter(exchange);
+            }
+            return DataBufferUtils.join(exchange.getRequest().getBody()).map(dataBuffer -> {
+                byte[] bytes = new byte[dataBuffer.readableByteCount()];
+                dataBuffer.read(bytes);
+                DataBufferUtils.release(dataBuffer);
+                return bytes;
+            }).defaultIfEmpty(new byte[0]).flatMap(bytes -> {
+                DataBufferFactory dataBufferFactory = exchange.getResponse().bufferFactory();
+                ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(exchange.getRequest())
+                {
+                    @Override
+                    public Flux<DataBuffer> getBody()
+                    {
+                        if (bytes.length > 0)
+                        {
+                            return Flux.just(dataBufferFactory.wrap(bytes));
+                        }
+                        return Flux.empty();
+                    }
+                };
+                return chain.filter(exchange.mutate().request(decorator).build());
+            });
+        }
+    }
+
+    @Override
+    public List<String> shortcutFieldOrder()
+    {
+        return Collections.singletonList("order");
+    }
+
+    static class Config
+    {
+        private Integer order;
+
+        public Integer getOrder()
+        {
+            return order;
+        }
+
+        public void setOrder(Integer order)
+        {
+            this.order = order;
+        }
+    }
+}

+ 39 - 0
src/main/java/com/ydd/gateway/filter/ResponseObject.java

@@ -0,0 +1,39 @@
+package com.ydd.gateway.filter;
+
+public class ResponseObject {
+
+	public String result;
+
+	private String msg;
+
+	private String uri;
+
+	public ResponseObject() {
+
+	}
+
+	public String getResult() {
+		return result;
+	}
+
+	public void setResult(String result) {
+		this.result = result;
+	}
+
+	public String getMsg() {
+		return msg;
+	}
+
+	public void setMsg(String msg) {
+		this.msg = msg;
+	}
+
+	public String getUri() {
+		return uri;
+	}
+
+	public void setUri(String uri) {
+		this.uri = uri;
+	}
+
+}

+ 120 - 0
src/main/java/com/ydd/gateway/filter/XssFilter.java

@@ -0,0 +1,120 @@
+//package com.ydd.gateway.filter;
+//
+//import java.nio.charset.StandardCharsets;
+//
+//
+//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.DataBuffer;
+//import org.springframework.core.io.buffer.DataBufferUtils;
+//import org.springframework.core.io.buffer.NettyDataBufferFactory;
+//import org.springframework.http.HttpHeaders;
+//import org.springframework.http.HttpMethod;
+//import org.springframework.http.MediaType;
+//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 com.ydd.gateway.config.properties.XssProperties;
+//import io.netty.buffer.ByteBufAllocator;
+//import reactor.core.publisher.Flux;
+//import reactor.core.publisher.Mono;
+//
+///**
+// * 跨站脚本过滤器
+// *
+// * @author ruoyi
+// */
+//@Component
+//@ConditionalOnProperty(value = "security.xss.enabled", havingValue = "true")
+//public class XssFilter implements GlobalFilter, Ordered
+//{
+//    // 跨站脚本的 xss 配置,nacos自行添加
+//    @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.matches("GET") || method.matches("DELETE"))
+//        {
+//            return chain.filter(exchange);
+//        }
+//        // 非json类型,不过滤
+//        if (!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.map(dataBuffer -> {
+//                    byte[] content = new byte[dataBuffer.readableByteCount()];
+//                    dataBuffer.read(content);
+//                    DataBufferUtils.release(dataBuffer);
+//                    String bodyStr = new String(content, StandardCharsets.UTF_8);
+//                    // 防xss攻击过滤
+//                    bodyStr = EscapeUtil.clean(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());
+//                // 由于修改了请求体的body,导致content-length长度不确定,因此需要删除原先的content-length
+//                httpHeaders.remove(HttpHeaders.CONTENT_LENGTH);
+//                httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
+//                return httpHeaders;
+//            }
+//
+//        };
+//        return serverHttpRequestDecorator;
+//    }
+//
+//    /**
+//     * 是否是Json请求
+//     *
+//     * @param exchange
+//     */
+//    public boolean isJsonRequest(ServerWebExchange exchange)
+//    {
+//        String header = exchange.getRequest().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
+//        return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE);
+//    }
+//
+//    @Override
+//    public int getOrder()
+//    {
+//        return -100;
+//    }
+//}

+ 56 - 0
src/main/java/com/ydd/gateway/handler/GatewayExceptionHandler.java

@@ -0,0 +1,56 @@
+//package com.ydd.gateway.handler;
+//
+//import com.ydd.common.utils.ServletUtils;
+//import org.springframework.cloud.gateway.support.NotFoundException;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
+//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
+// */
+//@Order(-1)
+//@Configuration
+//public class GatewayExceptionHandler implements ErrorWebExceptionHandler
+//{
+//    private static final Logger log = LoggerFactory.getLogger(GatewayExceptionHandler.class);
+//
+//    @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 responseStatusException = (ResponseStatusException) ex;
+//            msg = responseStatusException.getMessage();
+//        }
+//        else
+//        {
+//            msg = "内部服务器错误";
+//        }
+//
+//        log.error("[网关异常处理]请求路径:{},异常信息:{}", exchange.getRequest().getPath(), ex.getMessage());
+//
+//        return ServletUtils.webFluxResponseWriter(response, msg);
+//    }
+//}

+ 41 - 0
src/main/java/com/ydd/gateway/handler/SentinelFallbackHandler.java

@@ -0,0 +1,41 @@
+//package com.ydd.gateway.handler;
+//
+//import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
+//import com.alibaba.csp.sentinel.slots.block.BlockException;
+//import com.ydd.common.utils.ServletUtils;
+//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 ServletUtils.webFluxResponseWriter(exchange.getResponse(), "请求超过最大数,请稍候再试");
+//    }
+//
+//    @Override
+//    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex)
+//    {
+//        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);
+//    }
+//}

+ 119 - 0
src/main/java/com/ydd/gateway/service/FilterService.java

@@ -0,0 +1,119 @@
+package com.ydd.gateway.service;
+
+import com.alibaba.fastjson.JSON;
+import com.ydd.gateway.filter.AuthorizeGatewayFilterFactory;
+import com.ydd.gateway.filter.ResponseObject;
+
+import com.ydd.gateway.util.Md5Util;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.stereotype.Component;
+import reactor.core.publisher.Mono;
+
+import java.nio.charset.StandardCharsets;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.TreeMap;
+
+@Component
+public class FilterService {
+
+	private static final Log logger = LogFactory.getLog(AuthorizeGatewayFilterFactory.class);
+
+	private static final String AUTHORIZE_TOKEN = "clientId";
+
+//	@Autowired
+//	RedisTemplate redisTemplate;
+	public Mono<DataBuffer> validateSign(TreeMap<String, String> params, String signStr, ServerHttpResponse response) {
+		if (StringUtils.isEmpty(signStr)) {
+			ResponseObject data = new ResponseObject();
+			data.setResult("error");
+			data.setMsg("非法请求");
+			byte[] datas = JSON.toJSONString(data).getBytes(StandardCharsets.UTF_8);
+			DataBuffer buffer = response.bufferFactory().wrap(datas);
+			response.setStatusCode(HttpStatus.BAD_REQUEST);
+			return Mono.just(buffer);
+		}
+
+		// logger.info("签名参数" + params.toString());
+		// logger.info("签名值" + signStr);
+		// logger.info("签名后的值" + Md5Util.sign(params));
+
+		// 查看传过来的日期与当前日期比较相差几分钟
+		long min = getMin(params.get("time"));
+		if (min >= 3) {// 暂时定相差三分钟为无效请求
+			logger.info("相差三分钟为无效请求");
+			ResponseObject data = new ResponseObject();
+			data.setResult("error");
+			data.setMsg("非法请求");
+			byte[] datas = JSON.toJSONString(data).getBytes(StandardCharsets.UTF_8);
+			DataBuffer buffer = response.bufferFactory().wrap(datas);
+			response.setStatusCode(HttpStatus.BAD_REQUEST);
+			return Mono.just(buffer);
+		}
+
+		boolean right = Md5Util.verifySign(params, signStr);
+		if (!right) {
+			ResponseObject data = new ResponseObject();
+			data.setResult("error");
+			data.setMsg("签名不合法");
+			byte[] datas = JSON.toJSONString(data).getBytes(StandardCharsets.UTF_8);
+			DataBuffer buffer = response.bufferFactory().wrap(datas);
+			response.setStatusCode(HttpStatus.UNAUTHORIZED);
+
+			return Mono.just(buffer);
+		}
+		return null;
+	}
+
+
+
+	/**
+	 * 比较与当前时间相差几分钟
+	 * 
+	 * @param time
+	 * @return
+	 * @throws Exception
+	 */
+	public long getMin(String time) {
+		long nd = 1000 * 24 * 60 * 60;
+
+		long nh = 1000 * 60 * 60;
+
+		long nm = 1000 * 60;
+
+		// long ns = 1000;
+
+		// 获得两个时间的毫秒时间差异
+		// String time = "20190321141020";
+		Calendar cal = Calendar.getInstance();
+		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
+		long diff = 0;
+		try {
+			diff = cal.getTimeInMillis() - sdf.parse(time).getTime();
+		}
+		catch (ParseException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+
+		// 计算差多少天
+		long day = diff / nd;
+
+		// 计算差多少小时
+		long hour = diff % nd / nh;
+
+		// 计算差多少分钟
+		long min = diff % nd % nh / nm;
+
+		// 计算差多少秒//输出结果
+		// long sec = diff % nd % nh % nm / ns;
+		return min;
+	}
+}

+ 85 - 0
src/main/java/com/ydd/gateway/util/CharsetKit.java

@@ -0,0 +1,85 @@
+package com.ydd.gateway.util;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 字符集工具类
+ * 
+ * @author ruoyi
+ */
+public class CharsetKit
+{
+    /** ISO-8859-1 */
+    public static final String ISO_8859_1 = "ISO-8859-1";
+    /** UTF-8 */
+    public static final String UTF_8 = "UTF-8";
+    /** GBK */
+    public static final String GBK = "GBK";
+
+    /** ISO-8859-1 */
+    public static final Charset CHARSET_ISO_8859_1 = Charset.forName(ISO_8859_1);
+    /** UTF-8 */
+    public static final Charset CHARSET_UTF_8 = Charset.forName(UTF_8);
+    /** GBK */
+    public static final Charset CHARSET_GBK = Charset.forName(GBK);
+
+    /**
+     * 转换为Charset对象
+     * 
+     * @param charset 字符集,为空则返回默认字符集
+     * @return Charset
+     */
+    public static Charset charset(String charset)
+    {
+        return StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset);
+    }
+
+    /**
+     * 转换字符串的字符集编码
+     * 
+     * @param source 字符串
+     * @param srcCharset 源字符集,默认ISO-8859-1
+     * @param destCharset 目标字符集,默认UTF-8
+     * @return 转换后的字符集
+     */
+    public static String convert(String source, String srcCharset, String destCharset)
+    {
+        return convert(source, Charset.forName(srcCharset), Charset.forName(destCharset));
+    }
+
+    /**
+     * 转换字符串的字符集编码
+     * 
+     * @param source 字符串
+     * @param srcCharset 源字符集,默认ISO-8859-1
+     * @param destCharset 目标字符集,默认UTF-8
+     * @return 转换后的字符集
+     */
+    public static String convert(String source, Charset srcCharset, Charset destCharset)
+    {
+        if (null == srcCharset)
+        {
+            srcCharset = StandardCharsets.ISO_8859_1;
+        }
+
+        if (null == destCharset)
+        {
+            destCharset = StandardCharsets.UTF_8;
+        }
+
+        if (StringUtils.isEmpty(source) || srcCharset.equals(destCharset))
+        {
+            return source;
+        }
+        return new String(source.getBytes(srcCharset), destCharset);
+    }
+
+    /**
+     * @return 系统字符集编码
+     */
+    public static String systemCharset()
+    {
+        return Charset.defaultCharset().name();
+    }
+}

Різницю між файлами не показано, бо вона завелика
+ 1012 - 0
src/main/java/com/ydd/gateway/util/Convert.java


+ 107 - 0
src/main/java/com/ydd/gateway/util/Md5Util.java

@@ -0,0 +1,107 @@
+package com.ydd.gateway.util;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+
+public class Md5Util {
+
+	private static final Log logger = LogFactory.getLog(Md5Util.class);
+
+	private static final String[] STRDIGITS = new String[] { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
+
+	public static final String APP_KEY = "BE53C102BA2176C77BCC29F8E4B7EEA9";
+
+	public static String md5(String string) {
+		byte[] hash;
+		try {
+			hash = MessageDigest.getInstance("MD5").digest(string.getBytes("UTF-8"));
+		}
+		catch (UnsupportedEncodingException e) {
+			throw new RuntimeException("UTF-8 is unsupported", e);
+		}
+		catch (NoSuchAlgorithmException e) {
+			throw new RuntimeException("MessageDigest不支持MD5Util", e);
+		}
+		StringBuilder hex = new StringBuilder(hash.length * 2);
+		for (byte b : hash) {
+			if ((b & 0xFF) < 0x10) {
+				hex.append("0");
+			}
+			hex.append(Integer.toHexString(b & 0xFF));
+		}
+		return hex.toString();
+	}
+
+	/**
+	 * md5签名 按参数名称升序,将参数值进行连接 签名
+	 *
+	 * @param
+	 * @param params
+	 * @return
+	 */
+	public static String sign(TreeMap<String, String> params) {
+		StringBuilder paramValues = new StringBuilder();
+		for (Entry<String, String> entry : params.entrySet()) {
+			paramValues.append(entry.getKey());
+			paramValues.append("=");
+			paramValues.append(entry.getValue());
+		}
+		paramValues.append(APP_KEY);
+		return md5(paramValues.toString()).toUpperCase();
+	}
+
+	/**
+	 * 请求参数签名验证
+	 *
+	 * @param
+	 * @param
+	 * @return true 验证通过 false 验证失败
+	 * @throws Exception
+	 */
+	public static boolean verifySign(TreeMap<String, String> params, String signStr) {
+		return sign(params).equals(signStr);
+	}
+
+	private static String byteToString(byte[] bByte) {
+		StringBuffer sBuffer = new StringBuffer();
+
+		for (int i = 0; i < bByte.length; ++i) {
+			sBuffer.append(byteToArrayString(bByte[i]));
+		}
+
+		return sBuffer.toString();
+	}
+
+	private static String byteToArrayString(byte bByte) {
+		int iRet = bByte;
+		if (bByte < 0) {
+			iRet = bByte + 256;
+		}
+
+		int iD1 = iRet / 16;
+		int iD2 = iRet % 16;
+		return STRDIGITS[iD1] + STRDIGITS[iD2];
+	}
+
+	public static String getMD5Code(String strObj, String charsetname) {
+		String resultString = null;
+
+		try {
+			new String(strObj);
+			MessageDigest md = MessageDigest.getInstance("MD5");
+			resultString = byteToString(md.digest(strObj.getBytes(charsetname)));
+		}
+		catch (Exception var4) {
+			var4.printStackTrace();
+		}
+
+		return resultString;
+	}
+}

+ 90 - 0
src/main/java/com/ydd/gateway/util/StrFormatter.java

@@ -0,0 +1,90 @@
+package com.ydd.gateway.util;
+
+/**
+ * 字符串格式化
+ *
+ * @author ruoyi
+ */
+public class StrFormatter
+{
+    public static final String EMPTY_JSON = "{}";
+    public static final char C_BACKSLASH = '\\';
+    public static final char C_DELIM_START = '{';
+    public static final char C_DELIM_END = '}';
+
+    /**
+     * 格式化字符串<br>
+     * 此方法只是简单将占位符 {} 按照顺序替换为参数<br>
+     * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可<br>
+     * 例:<br>
+     * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b<br>
+     * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a<br>
+     * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b<br>
+     *
+     * @param strPattern 字符串模板
+     * @param argArray 参数列表
+     * @return 结果
+     */
+    public static String format(final String strPattern, final Object... argArray)
+    {
+        if (StringUtils.isEmpty(strPattern) || StringUtils.isEmpty(argArray))
+        {
+            return strPattern;
+        }
+        final int strPatternLength = strPattern.length();
+
+        // 初始化定义好的长度以获得更好的性能
+        StringBuilder sbuf = new StringBuilder(strPatternLength + 50);
+
+        int handledPosition = 0;
+        int delimIndex;// 占位符所在位置
+        for (int argIndex = 0; argIndex < argArray.length; argIndex++)
+        {
+            delimIndex = strPattern.indexOf(EMPTY_JSON, handledPosition);
+            if (delimIndex == -1)
+            {
+                if (handledPosition == 0)
+                {
+                    return strPattern;
+                }
+                else
+                { // 字符串模板剩余部分不再包含占位符,加入剩余部分后返回结果
+                    sbuf.append(strPattern, handledPosition, strPatternLength);
+                    return sbuf.toString();
+                }
+            }
+            else
+            {
+                if (delimIndex > 0 && strPattern.charAt(delimIndex - 1) == C_BACKSLASH)
+                {
+                    if (delimIndex > 1 && strPattern.charAt(delimIndex - 2) == C_BACKSLASH)
+                    {
+                        // 转义符之前还有一个转义符,占位符依旧有效
+                        sbuf.append(strPattern, handledPosition, delimIndex - 1);
+                        sbuf.append(Convert.utf8Str(argArray[argIndex]));
+                        handledPosition = delimIndex + 2;
+                    }
+                    else
+                    {
+                        // 占位符被转义
+                        argIndex--;
+                        sbuf.append(strPattern, handledPosition, delimIndex - 1);
+                        sbuf.append(C_DELIM_START);
+                        handledPosition = delimIndex + 1;
+                    }
+                }
+                else
+                {
+                    // 正常占位符
+                    sbuf.append(strPattern, handledPosition, delimIndex);
+                    sbuf.append(Convert.utf8Str(argArray[argIndex]));
+                    handledPosition = delimIndex + 2;
+                }
+            }
+        }
+        // 加入最后一个占位符后所有的字符
+        sbuf.append(strPattern, handledPosition, strPattern.length());
+
+        return sbuf.toString();
+    }
+}

+ 480 - 0
src/main/java/com/ydd/gateway/util/StringUtils.java

@@ -0,0 +1,480 @@
+package com.ydd.gateway.util;
+
+import org.springframework.util.AntPathMatcher;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 字符串工具类
+ *
+ * @author ruoyi
+ */
+public class StringUtils extends org.apache.commons.lang3.StringUtils
+{
+    /** 空字符串 */
+    private static final String NULLSTR = "";
+
+    /** 下划线 */
+    private static final char SEPARATOR = '_';
+
+    /**
+     * 获取参数不为空值
+     *
+     * @param value defaultValue 要判断的value
+     * @return value 返回值
+     */
+    public static <T> T nvl(T value, T defaultValue)
+    {
+        return value != null ? value : defaultValue;
+    }
+
+    /**
+     * * 判断一个Collection是否为空, 包含List,Set,Queue
+     *
+     * @param coll 要判断的Collection
+     * @return true:为空 false:非空
+     */
+    public static boolean isEmpty(Collection<?> coll)
+    {
+        return isNull(coll) || coll.isEmpty();
+    }
+
+    /**
+     * * 判断一个Collection是否非空,包含List,Set,Queue
+     *
+     * @param coll 要判断的Collection
+     * @return true:非空 false:空
+     */
+    public static boolean isNotEmpty(Collection<?> coll)
+    {
+        return !isEmpty(coll);
+    }
+
+    /**
+     * * 判断一个对象数组是否为空
+     *
+     * @param objects 要判断的对象数组
+     ** @return true:为空 false:非空
+     */
+    public static boolean isEmpty(Object[] objects)
+    {
+        return isNull(objects) || (objects.length == 0);
+    }
+
+    /**
+     * * 判断一个对象数组是否非空
+     *
+     * @param objects 要判断的对象数组
+     * @return true:非空 false:空
+     */
+    public static boolean isNotEmpty(Object[] objects)
+    {
+        return !isEmpty(objects);
+    }
+
+    /**
+     * * 判断一个Map是否为空
+     *
+     * @param map 要判断的Map
+     * @return true:为空 false:非空
+     */
+    public static boolean isEmpty(Map<?, ?> map)
+    {
+        return isNull(map) || map.isEmpty();
+    }
+
+    /**
+     * * 判断一个Map是否为空
+     *
+     * @param map 要判断的Map
+     * @return true:非空 false:空
+     */
+    public static boolean isNotEmpty(Map<?, ?> map)
+    {
+        return !isEmpty(map);
+    }
+
+    /**
+     * * 判断一个字符串是否为空串
+     *
+     * @param str String
+     * @return true:为空 false:非空
+     */
+    public static boolean isEmpty(String str)
+    {
+        return isNull(str) || NULLSTR.equals(str.trim());
+    }
+
+    /**
+     * * 判断一个字符串是否为非空串
+     *
+     * @param str String
+     * @return true:非空串 false:空串
+     */
+    public static boolean isNotEmpty(String str)
+    {
+        return !isEmpty(str);
+    }
+
+    /**
+     * * 判断一个对象是否为空
+     *
+     * @param object Object
+     * @return true:为空 false:非空
+     */
+    public static boolean isNull(Object object)
+    {
+        return object == null;
+    }
+
+    /**
+     * * 判断一个对象是否非空
+     *
+     * @param object Object
+     * @return true:非空 false:空
+     */
+    public static boolean isNotNull(Object object)
+    {
+        return !isNull(object);
+    }
+
+    /**
+     * * 判断一个对象是否是数组类型(Java基本型别的数组)
+     *
+     * @param object 对象
+     * @return true:是数组 false:不是数组
+     */
+    public static boolean isArray(Object object)
+    {
+        return isNotNull(object) && object.getClass().isArray();
+    }
+
+    /**
+     * 去空格
+     */
+    public static String trim(String str)
+    {
+        return (str == null ? "" : str.trim());
+    }
+
+    /**
+     * 截取字符串
+     *
+     * @param str 字符串
+     * @param start 开始
+     * @return 结果
+     */
+    public static String substring(final String str, int start)
+    {
+        if (str == null)
+        {
+            return NULLSTR;
+        }
+
+        if (start < 0)
+        {
+            start = str.length() + start;
+        }
+
+        if (start < 0)
+        {
+            start = 0;
+        }
+        if (start > str.length())
+        {
+            return NULLSTR;
+        }
+
+        return str.substring(start);
+    }
+
+    /**
+     * 截取字符串
+     *
+     * @param str 字符串
+     * @param start 开始
+     * @param end 结束
+     * @return 结果
+     */
+    public static String substring(final String str, int start, int end)
+    {
+        if (str == null)
+        {
+            return NULLSTR;
+        }
+
+        if (end < 0)
+        {
+            end = str.length() + end;
+        }
+        if (start < 0)
+        {
+            start = str.length() + start;
+        }
+
+        if (end > str.length())
+        {
+            end = str.length();
+        }
+
+        if (start > end)
+        {
+            return NULLSTR;
+        }
+
+        if (start < 0)
+        {
+            start = 0;
+        }
+        if (end < 0)
+        {
+            end = 0;
+        }
+
+        return str.substring(start, end);
+    }
+
+    /**
+     * 判断是否为空,并且不是空白字符
+     *
+     * @param str 要判断的value
+     * @return 结果
+     */
+    public static boolean hasText(String str)
+    {
+        return (str != null && !str.isEmpty() && containsText(str));
+    }
+
+    private static boolean containsText(CharSequence str)
+    {
+        int strLen = str.length();
+        for (int i = 0; i < strLen; i++)
+        {
+            if (!Character.isWhitespace(str.charAt(i)))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 格式化文本, {} 表示占位符<br>
+     * 此方法只是简单将占位符 {} 按照顺序替换为参数<br>
+     * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可<br>
+     * 例:<br>
+     * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b<br>
+     * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a<br>
+     * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b<br>
+     *
+     * @param template 文本模板,被替换的部分用 {} 表示
+     * @param params 参数值
+     * @return 格式化后的文本
+     */
+    public static String format(String template, Object... params)
+    {
+        if (isEmpty(params) || isEmpty(template))
+        {
+            return template;
+        }
+        return StrFormatter.format(template, params);
+    }
+
+    /**
+     * 是否为http(s)://开头
+     *
+     * @param link 链接
+     * @return 结果
+     */
+    public static boolean ishttp(String link)
+    {
+        return StringUtils.startsWithAny(link,  "http://", "https://");
+    }
+
+    /**
+     * 驼峰转下划线命名
+     */
+    public static String toUnderScoreCase(String str)
+    {
+        if (str == null)
+        {
+            return null;
+        }
+        StringBuilder sb = new StringBuilder();
+        // 前置字符是否大写
+        boolean preCharIsUpperCase = true;
+        // 当前字符是否大写
+        boolean curreCharIsUpperCase = true;
+        // 下一字符是否大写
+        boolean nexteCharIsUpperCase = true;
+        for (int i = 0; i < str.length(); i++)
+        {
+            char c = str.charAt(i);
+            if (i > 0)
+            {
+                preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1));
+            }
+            else
+            {
+                preCharIsUpperCase = false;
+            }
+
+            curreCharIsUpperCase = Character.isUpperCase(c);
+
+            if (i < (str.length() - 1))
+            {
+                nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1));
+            }
+
+            if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase)
+            {
+                sb.append(SEPARATOR);
+            }
+            else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase)
+            {
+                sb.append(SEPARATOR);
+            }
+            sb.append(Character.toLowerCase(c));
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * 是否包含字符串
+     *
+     * @param str 验证字符串
+     * @param strs 字符串组
+     * @return 包含返回true
+     */
+    public static boolean inStringIgnoreCase(String str, String... strs)
+    {
+        if (str != null && strs != null)
+        {
+            for (String s : strs)
+            {
+                if (str.equalsIgnoreCase(trim(s)))
+                {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 例如:HELLO_WORLD->HelloWorld
+     *
+     * @param name 转换前的下划线大写方式命名的字符串
+     * @return 转换后的驼峰式命名的字符串
+     */
+    public static String convertToCamelCase(String name)
+    {
+        StringBuilder result = new StringBuilder();
+        // 快速检查
+        if (name == null || name.isEmpty())
+        {
+            // 没必要转换
+            return "";
+        }
+        else if (!name.contains("_"))
+        {
+            // 不含下划线,仅将首字母大写
+            return name.substring(0, 1).toUpperCase() + name.substring(1);
+        }
+        // 用下划线将原始字符串分割
+        String[] camels = name.split("_");
+        for (String camel : camels)
+        {
+            // 跳过原始字符串中开头、结尾的下换线或双重下划线
+            if (camel.isEmpty())
+            {
+                continue;
+            }
+            // 首字母大写
+            result.append(camel.substring(0, 1).toUpperCase());
+            result.append(camel.substring(1).toLowerCase());
+        }
+        return result.toString();
+    }
+
+    /**
+     * 驼峰式命名法 例如:user_name->userName
+     */
+    public static String toCamelCase(String s)
+    {
+        if (s == null)
+        {
+            return null;
+        }
+        s = s.toLowerCase();
+        StringBuilder sb = new StringBuilder(s.length());
+        boolean upperCase = false;
+        for (int i = 0; i < s.length(); i++)
+        {
+            char c = s.charAt(i);
+
+            if (c == SEPARATOR)
+            {
+                upperCase = true;
+            }
+            else if (upperCase)
+            {
+                sb.append(Character.toUpperCase(c));
+                upperCase = false;
+            }
+            else
+            {
+                sb.append(c);
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 查找指定字符串是否匹配指定字符串列表中的任意一个字符串
+     *
+     * @param str 指定字符串
+     * @param strs 需要检查的字符串数组
+     * @return 是否匹配
+     */
+    public static boolean matches(String str, List<String> strs)
+    {
+        if (isEmpty(str) || isEmpty(strs))
+        {
+            return false;
+        }
+        for (String pattern : strs)
+        {
+            if (isMatch(pattern, str))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 判断url是否与规则配置:
+     * ? 表示单个字符;
+     * * 表示一层路径内的任意字符串,不可跨层级;
+     * ** 表示任意层路径;
+     *
+     * @param pattern 匹配规则
+     * @param url 需要匹配的url
+     * @return
+     */
+    public static boolean isMatch(String pattern, String url)
+    {
+        AntPathMatcher matcher = new AntPathMatcher();
+        return matcher.match(pattern, url);
+    }
+
+    @SuppressWarnings("unchecked")
+    public static <T> T cast(Object obj)
+    {
+        return (T) obj;
+    }
+}

+ 26 - 0
src/main/resources/application.yml

@@ -0,0 +1,26 @@
+spring:
+  cloud:
+    gateway:
+      #default-filters:
+      #- Authorize=true
+      discovery:
+        locator:
+          enabled: false
+      routes:
+        - id: api-beta
+          #url: lb://API
+          uri: http://www.baidu.com/
+          predicates:
+            #匹配所有请求路径以
+            - Path=/**
+            - Header=citycode,0512
+          filters:
+            - Authorize=true
+        - id: api-release
+          #url: lb://API
+          uri:  http://www.2500sz.com/
+          predicates:
+            #匹配所有请求路径以
+            - Path=/**
+          filters:
+            - Authorize=true

+ 10 - 0
src/main/resources/banner.txt

@@ -0,0 +1,10 @@
+Spring Boot Version: ${spring-boot.version}
+Spring Application Name: ${spring.application.name}
+                            _                        _                                 
+                           (_)                      | |                                
+ _ __  _   _   ___   _   _  _  ______   __ _   __ _ | |_   ___ __      __  __ _  _   _ 
+| '__|| | | | / _ \ | | | || ||______| / _` | / _` || __| / _ \\ \ /\ / / / _` || | | |
+| |   | |_| || (_) || |_| || |        | (_| || (_| || |_ |  __/ \ V  V / | (_| || |_| |
+|_|    \__,_| \___/  \__, ||_|         \__, | \__,_| \__| \___|  \_/\_/   \__,_| \__, |
+                      __/ |             __/ |                                     __/ |
+                     |___/             |___/                                     |___/ 

+ 40 - 0
src/main/resources/bootstrap.yml

@@ -0,0 +1,40 @@
+# Tomcat
+server:
+  port: 8080
+
+# Spring
+spring:
+  application:
+    name: lb-gateway
+  profiles:
+    active: dev
+  cloud:
+    nacos:
+      discovery:
+      # 服务注册地址
+        server-addr: 153.37.175.42:8848
+      config:
+       # 配置中心地址
+        server-addr: 153.37.175.42:8848
+        file-extension: yml         # 配置文件格式
+        # 共享配置
+        shared-configs[0]:
+          data-id: application-dev.yaml
+          refresh: false
+#        shared-configs:
+#          - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
+            #    sentinel:
+#      # 取消控制台懒加载
+#      eager: true
+#      transport:
+#        # 控制台地址
+#        dashboard: 127.0.0.1:8718
+      # nacos配置持久化
+#      datasource:
+                            ##        ds1:
+                            ##          nacos:
+                            ##            server-addr: 153.37.175.42:8848
+                            ##            dataId: sentinel-ruoyi-gateway
+                            ##            groupId: DEFAULT_GROUP
+                            ##            data-type: json
+                            ##            rule-type: flow

+ 74 - 0
src/main/resources/logback.xml

@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration scan="true" scanPeriod="60 seconds" debug="false">
+    <!-- 日志存放路径 -->
+	<property name="log.path" value="logs/lb-gateway" />
+   <!-- 日志输出格式 -->
+	<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
+
+    <!-- 控制台输出 -->
+	<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+		<encoder>
+			<pattern>${log.pattern}</pattern>
+		</encoder>
+	</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>
+
+    <!-- 系统模块日志级别控制  -->
+	<logger name="com.ruoyi" level="info" />
+	<!-- Spring日志级别控制  -->
+	<logger name="org.springframework" level="warn" />
+
+	<root level="info">
+		<appender-ref ref="console" />
+	</root>
+	
+	<!--系统操作日志-->
+    <root level="info">
+        <appender-ref ref="file_info" />
+        <appender-ref ref="file_error" />
+    </root>
+</configuration>