reghao 2 лет назад
Родитель
Сommit
25f0be3b35

+ 157 - 0
dfs-gw/pom.xml

@@ -0,0 +1,157 @@
+<?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>cn.reghao.dfs</groupId>
+    <artifactId>dfs-gw</artifactId>
+    <version>1.0.0</version>
+
+    <properties>
+        <maven.compiler.source>11</maven.compiler.source>
+        <maven.compiler.target>11</maven.compiler.target>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>2.3.9.RELEASE</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <dependency>
+                <groupId>org.springframework.cloud</groupId>
+                <artifactId>spring-cloud-dependencies</artifactId>
+                <version>Hoxton.SR10</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.reghao.jutil</groupId>
+            <artifactId>tool</artifactId>
+            <version>1.0.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.18.6</version>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-core</artifactId>
+            <version>1.2.3</version>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+            <version>1.2.3</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-webflux</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-gateway</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.micrometer</groupId>
+            <artifactId>micrometer-registry-prometheus</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+            <version>0.9.1</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.xml.bind</groupId>
+            <artifactId>jaxb-api</artifactId>
+            <version>2.3.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.xml.bind</groupId>
+            <artifactId>jaxb-impl</artifactId>
+            <version>2.3.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.xml.bind</groupId>
+            <artifactId>jaxb-core</artifactId>
+            <version>2.3.0</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.activation</groupId>
+            <artifactId>activation</artifactId>
+            <version>1.1.1</version>
+        </dependency>
+    </dependencies>
+
+    <profiles>
+        <profile>
+            <id>dev</id>
+            <properties>
+                <profile.active>dev</profile.active>
+            </properties>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+        </profile>
+        <profile>
+            <id>test</id>
+            <properties>
+                <profile.active>test</profile.active>
+            </properties>
+        </profile>
+        <profile>
+            <id>prod</id>
+            <properties>
+                <profile.active>prod</profile.active>
+            </properties>
+        </profile>
+    </profiles>
+
+    <build>
+        <finalName>dfs-gw</finalName>
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+                <filtering>true</filtering>
+                <includes>
+                    <include>application.yml</include>
+                    <include>application-${profile.active}.yml</include>
+                    <include>*.xml</include>
+                </includes>
+            </resource>
+        </resources>
+
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>2.3.9.RELEASE</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 13 - 0
dfs-gw/src/main/java/cn/reghao/dfs/gw/DfsGwApplication.java

@@ -0,0 +1,13 @@
+package cn.reghao.dfs.gw;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+
+@SpringBootApplication
+@EnableDiscoveryClient
+public class DfsGwApplication {
+    public static void main(String[] args) {
+        SpringApplication.run(DfsGwApplication.class, args);
+    }
+}

+ 53 - 0
dfs-gw/src/main/java/cn/reghao/dfs/gw/config/CorsConfig.java

@@ -0,0 +1,53 @@
+package cn.reghao.dfs.gw.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+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.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 reghao
+ * @date 2019-11-22 16:59:28
+ */
+@Configuration
+public class CorsConfig {
+    private final String MAX_AGE = "18_000L";
+
+    @Bean
+    public WebFilter corsFilter() {
+        return (ServerWebExchange ctx, WebFilterChain chain) -> {
+            ServerHttpRequest request = ctx.getRequest();
+            if (CorsUtils.isCorsRequest(request)) {
+                HttpHeaders requestHeaders = request.getHeaders();
+                ServerHttpResponse response = ctx.getResponse();
+                HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();
+                HttpHeaders headers = response.getHeaders();
+                headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
+                headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders
+                        .getAccessControlRequestHeaders());
+                if (requestMethod != null) {
+                    headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
+                }
+                headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
+                headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");
+                headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE);
+                if (request.getMethod() == HttpMethod.OPTIONS) {
+                    response.setStatusCode(HttpStatus.OK);
+                    return Mono.empty();
+                }
+            }
+
+            return chain.filter(ctx);
+        };
+    }
+}

+ 59 - 0
dfs-gw/src/main/java/cn/reghao/dfs/gw/config/RouterConfig.java

@@ -0,0 +1,59 @@
+package cn.reghao.dfs.gw.config;
+
+import org.springframework.cloud.client.discovery.DiscoveryClient;
+import org.springframework.cloud.gateway.discovery.DiscoveryClientRouteDefinitionLocator;
+import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
+import org.springframework.cloud.gateway.route.RouteLocator;
+import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.reactive.function.BodyInserters;
+import org.springframework.web.reactive.function.server.RequestPredicates;
+import org.springframework.web.reactive.function.server.RouterFunction;
+import org.springframework.web.reactive.function.server.RouterFunctions;
+import org.springframework.web.reactive.function.server.ServerResponse;
+
+/**
+ * @author reghao
+ * @date 2019-11-19 09:21:48
+ */
+//@Configuration
+public class RouterConfig {
+    /**
+     * 路由过滤,向请求或响应添加首部,参数等
+     *
+     * @param
+     * @return
+     * @date 2019-11-19 上午9:30
+     */
+    @Bean
+    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
+        return builder.routes()
+                .route("path_route", r -> r.path("/reghao")
+                        .filters(f -> f.addResponseHeader("X-Reghao", "reghao"))
+                        .uri("http://cnblogs.com"))
+                .build();
+    }
+
+    /**
+     * 路由断言,根据请求的时间,路径,host 等进行相应的处理
+     *
+     * @param
+     * @return
+     * @date 2019-11-19 上午9:29
+     */
+    @Bean
+    public RouterFunction<ServerResponse> routerFunction() {
+        RouterFunction<ServerResponse> route = RouterFunctions.route(
+                RequestPredicates.path("/test"),
+                req -> ServerResponse.ok().body(BodyInserters.fromObject("test"))
+        );
+
+        return route;
+    }
+
+    @Bean
+    public RouteDefinitionLocator routeDefinitionLocator(DiscoveryClient discoveryClient) {
+        return new DiscoveryClientRouteDefinitionLocator(discoveryClient, null);
+    }
+}

+ 46 - 0
dfs-gw/src/main/java/cn/reghao/dfs/gw/filter/CorsResponseHeaderFilter.java

@@ -0,0 +1,46 @@
+package cn.reghao.dfs.gw.filter;
+
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.GlobalFilter;
+import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
+import org.springframework.core.Ordered;
+import org.springframework.http.HttpHeaders;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+import java.util.ArrayList;
+
+/**
+ * 处理响应中出现多个 Access-Control-Allow-Origin 的问题
+ * TODO 启用 CorsResponseHeaderFilter 会出现重复执行 filter 的情况, 暂时禁用, 使用配置代替
+ *
+ * @author reghao
+ * @date 2021-12-12 14:38:04
+ */
+//@Component("corsResponseHeaderFilter")
+public class CorsResponseHeaderFilter implements GlobalFilter, Ordered {
+    @Override
+    public int getOrder() {
+        // 指定此过滤器位于NettyWriteResponseFilter之后
+        // 即待处理完响应体后接着处理响应头
+        return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER + 1;
+    }
+
+    @Override
+    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+        return chain.filter(exchange).then(Mono.defer(() -> {
+            exchange.getResponse().getHeaders().entrySet().stream()
+                    .filter(kv -> (kv.getValue() != null && kv.getValue().size() > 1))
+                    .filter(kv -> (kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)
+                            || kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)))
+                    .forEach(kv -> {
+                        kv.setValue(new ArrayList<String>() {{
+                            add(kv.getValue().get(0));
+                        }});
+                    });
+
+            return chain.filter(exchange);
+        }));
+    }
+}

+ 87 - 0
dfs-gw/src/main/java/cn/reghao/dfs/gw/filter/JwtGlobalFilter.java

@@ -0,0 +1,87 @@
+package cn.reghao.dfs.gw.filter;
+
+import cn.reghao.jutil.tool.jwt.Jwt;
+import cn.reghao.jutil.tool.jwt.JwtPayload;
+import io.jsonwebtoken.ExpiredJwtException;
+import io.jsonwebtoken.JwtException;
+import lombok.extern.slf4j.Slf4j;
+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.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+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;
+import java.util.*;
+import java.util.function.Consumer;
+
+/**
+ * 检查 JWT token(若存在) 是否过期
+ *
+ * @author reghao
+ * @date 2021-07-28 16:50:50
+ */
+@Slf4j
+@Component
+public class JwtGlobalFilter implements GlobalFilter, Ordered {
+    @Override
+    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+        ServerHttpRequest request = exchange.getRequest();
+        String jwt = getJwtToken(request);
+        if (jwt != null) {
+            // TODO 采用响应式编程的格式进行编码
+            /*try {
+                JwtPayload jwtPayload = Jwt.parse(jwt);
+                String userId = jwtPayload.getUserId();
+                Consumer<HttpHeaders> headers = header -> {
+                    header.set("x-user-id", userId);
+                };
+                exchange.getRequest().mutate().headers(headers).build();
+                exchange.mutate().request(exchange.getRequest()).build();
+            } catch (JwtException jwtException) {
+                DataBuffer buffer;
+                if (jwtException instanceof ExpiredJwtException) {
+                    // token 过期
+                    //throw new CredentialsExpiredException("token is expired");
+                    exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
+                    String msg = "登录已过期, 请重新登录";
+                    byte[] bytes = msg.getBytes(StandardCharsets.UTF_8);
+                    buffer = exchange.getResponse().bufferFactory().wrap(bytes);
+                } else {
+                    // token 无效
+                    //throw new BadCredentialsException("token is invalid");
+                    exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
+                    byte[] bytes = "token is invalid".getBytes(StandardCharsets.UTF_8);
+                    buffer = exchange.getResponse().bufferFactory().wrap(bytes);
+                }
+
+                // 前端无法处理 302,后端返回 401,由前端重定向到认证页面
+                return exchange.getResponse().writeWith(Flux.just(buffer));
+            }*/
+        }
+
+        return chain.filter(exchange);
+    }
+
+    private String getJwtToken(ServerHttpRequest request) {
+        List<String> headerValues = request.getHeaders().get(Jwt.AUTH_HEADER);
+        if (headerValues != null && headerValues.size() == 1) {
+            String headerValue = headerValues.get(0);
+            if (headerValue != null && headerValue.startsWith(Jwt.JWT_PREFIX)) {
+                return headerValue.replace(Jwt.JWT_PREFIX, "");
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public int getOrder() {
+        return -1;
+    }
+}

+ 36 - 0
dfs-gw/src/main/java/cn/reghao/dfs/gw/filter/RateLimitByIpGatewayFilter.java

@@ -0,0 +1,36 @@
+package cn.reghao.dfs.gw.filter;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cloud.gateway.filter.GatewayFilter;
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.core.Ordered;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+/**
+ * 限流
+ *
+ * @author reghao
+ * @date 2019-11-21 15:34:14
+ */
+//@Slf4j
+//@Component
+public class RateLimitByIpGatewayFilter implements GatewayFilter, Ordered {
+    public RateLimitByIpGatewayFilter() {
+    }
+
+    @Override
+    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+        ServerHttpRequest request = exchange.getRequest();
+        ServerHttpResponse response = exchange.getResponse();
+        return chain.filter(exchange);
+    }
+
+    @Override
+    public int getOrder() {
+        return -1;
+    }
+}

+ 38 - 0
dfs-gw/src/main/java/cn/reghao/dfs/gw/filter/RequestLogGlobalFilter.java

@@ -0,0 +1,38 @@
+package cn.reghao.dfs.gw.filter;
+
+import lombok.extern.slf4j.Slf4j;
+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 reghao
+ * @date 2019-11-21 15:34:14
+ */
+@Slf4j
+//@Component
+public class RequestLogGlobalFilter implements GlobalFilter, Ordered {
+    public RequestLogGlobalFilter() {
+    }
+
+    @Override
+    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+        long start = System.currentTimeMillis();
+        ServerHttpRequest request = exchange.getRequest();
+        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
+            long costTime = System.currentTimeMillis()-start;
+            log.info("request costs {}ms", costTime);
+        }));
+    }
+
+    @Override
+    public int getOrder() {
+        return -1;
+    }
+}

+ 78 - 0
dfs-gw/src/main/java/cn/reghao/dfs/gw/handler/DynamicRouteHandler.java

@@ -0,0 +1,78 @@
+package cn.reghao.dfs.gw.handler;
+
+import org.springframework.cloud.gateway.filter.FilterDefinition;
+import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
+import org.springframework.cloud.gateway.route.RouteDefinition;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Service;
+import org.springframework.web.reactive.function.server.ServerRequest;
+import org.springframework.web.reactive.function.server.ServerResponse;
+import reactor.core.publisher.Mono;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 动态路由配置
+ *
+ * @author reghao
+ * @date 2019-11-19 10:57:19
+ */
+//@Service
+public class DynamicRouteHandler {
+    /**
+     * 注册服务
+     *
+     * @param
+     * @return
+     * @date 2019-11-19 上午10:58
+     */
+    public Mono<ServerResponse> registerService(ServerRequest request) {
+        String service = request.pathVariable("service");
+
+        List<PredicateDefinition> predDefs = new ArrayList<>();
+        PredicateDefinition predDef = new PredicateDefinition();
+        predDef.setName("Path");
+        predDef.addArg("pattern", "/api/");
+        predDefs.add(predDef);
+
+        List<FilterDefinition> filterDefs = new ArrayList<>();
+        FilterDefinition filterDef = new FilterDefinition();
+        filterDef.setName("StripPrefix");
+        filterDef.addArg("parts", "1");
+        filterDefs.add(filterDef);
+
+        URI uri = null;
+        try {
+            uri = new URI("lb://localhost");
+        } catch (URISyntaxException e) {
+            e.printStackTrace();
+        }
+
+        RouteDefinition routeDef = new RouteDefinition();
+        routeDef.setId("");
+        routeDef.setPredicates(predDefs);
+        routeDef.setFilters(filterDefs);
+        routeDef.setUri(uri);
+        routeDef.setOrder(0);
+
+        return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON_UTF8)
+                .body(Mono.just("register " + service), String.class);
+    }
+
+    /**
+     * 注销服务
+     *
+     * @param
+     * @return
+     * @date 2019-11-19 上午11:00
+     */
+    public Mono<ServerResponse> deregisterService(ServerRequest request) {
+        String service = request.pathVariable("service");
+
+        return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON_UTF8)
+                .body(Mono.just("deregister " + service), String.class);
+    }
+}

+ 18 - 0
dfs-gw/src/main/java/cn/reghao/dfs/gw/model/RequestLog.java

@@ -0,0 +1,18 @@
+package cn.reghao.dfs.gw.model;
+
+import lombok.Data;
+
+import java.net.InetSocketAddress;
+
+/**
+ * @author reghao
+ * @date 2020-04-26 13:47:16
+ */
+@Data
+public class RequestLog {
+    private InetSocketAddress remote;
+    private String uri;
+    private String method;
+    private String query;
+    private String body;
+}

+ 39 - 0
dfs-gw/src/main/java/cn/reghao/dfs/gw/route/DynamicServiceRoute.java

@@ -0,0 +1,39 @@
+package cn.reghao.dfs.gw.route;
+
+import cn.reghao.dfs.gw.handler.DynamicRouteHandler;
+import org.springframework.context.annotation.Bean;
+import org.springframework.web.reactive.function.server.RouterFunction;
+import org.springframework.web.reactive.function.server.ServerResponse;
+
+import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
+import static org.springframework.web.reactive.function.server.RequestPredicates.POST;
+import static org.springframework.web.reactive.function.server.RouterFunctions.route;
+
+/**
+ * 动态注册服务路由
+ *
+ * @author reghao
+ * @date 2019-11-19 10:53:01
+ */
+//@Configuration
+public class DynamicServiceRoute {
+    private final DynamicRouteHandler dynamicRouteHandler;
+
+    public DynamicServiceRoute(DynamicRouteHandler dynamicRouteHandler) {
+        this.dynamicRouteHandler = dynamicRouteHandler;
+    }
+
+    /**
+     * 注册和注销服务路由
+     *
+     * @param
+     * @return
+     * @date 2019-11-19 上午11:06
+     */
+    @Bean
+    public RouterFunction<ServerResponse> serviceRoute() {
+        return route(POST("/api/route/register/{service}"), req -> dynamicRouteHandler.registerService(req))
+                .andRoute(GET("/api/route/deregister/{service}"),
+                        req -> dynamicRouteHandler.deregisterService(req));
+    }
+}

+ 20 - 0
dfs-gw/src/main/java/cn/reghao/dfs/gw/route/StaticRoute.java

@@ -0,0 +1,20 @@
+package cn.reghao.dfs.gw.route;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.web.reactive.function.server.RouterFunction;
+import org.springframework.web.reactive.function.server.RouterFunctions;
+import org.springframework.web.reactive.function.server.ServerResponse;
+
+/**
+ * @author reghao
+ * @date 2019-11-19 15:43:03
+ */
+//@Configuration
+public class StaticRoute {
+    @Bean
+    public RouterFunction<ServerResponse> staticReoute() {
+        return RouterFunctions.resources("/**", new ClassPathResource("static"));
+    }
+}

+ 10 - 0
dfs-gw/src/main/resources/application-dev.yml

@@ -0,0 +1,10 @@
+spring:
+  cloud:
+    gateway:
+      routes:
+        - id: file-router
+          uri: http://127.0.0.1:8002
+          predicates:
+            - Path=/**
+          filters:
+            - StripPrefix=0

+ 10 - 0
dfs-gw/src/main/resources/application-test.yml

@@ -0,0 +1,10 @@
+spring:
+  cloud:
+    gateway:
+      routes:
+        - id: file-router
+          uri: http://127.0.0.1:8002
+          predicates:
+            - Path=/**
+          filters:
+            - StripPrefix=0

+ 7 - 0
dfs-gw/src/main/resources/application.yml

@@ -0,0 +1,7 @@
+server:
+  port: 8000
+spring:
+  application:
+    name: dfs-gw
+  profiles:
+    active: @profile.active@

+ 68 - 0
dfs-gw/src/main/resources/logback-spring.xml

@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<configuration>
+    <appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
+        <layout class="ch.qos.logback.classic.PatternLayout">
+            <pattern>
+                %d{HH:mm:ss.SSS} [%thread] %-5level %c %M %L - %msg%n
+            </pattern>
+        </layout>
+    </appender>
+
+    <!-- info 日志文件 -->
+    <appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>ERROR</level>
+            <onMatch>DENY</onMatch>
+            <onMismatch>ACCEPT</onMismatch>
+        </filter>
+        <encoder>
+            <pattern>
+                %d{HH:mm:ss.SSS} %-5level %c %M %L - %msg%n
+            </pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+        <!-- 滚动策略 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>
+                logs/info.%d.log
+            </fileNamePattern>
+        </rollingPolicy>
+    </appender>
+
+    <!-- error 日志文件 -->
+    <appender name="fileErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+            <level>ERROR</level>
+        </filter>
+        <encoder>
+            <pattern>
+                %d{HH:mm:ss.SSS} %-5level %c %M %L - %msg%n
+            </pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>
+                logs/error.%d.log
+            </fileNamePattern>
+        </rollingPolicy>
+    </appender>
+
+    <springProfile name="dev">
+        <root level="info">
+            <appender-ref ref="consoleLog"></appender-ref>
+        </root>
+    </springProfile>
+    <springProfile name="test">
+        <root level="info">
+            <appender-ref ref="fileInfoLog"></appender-ref>
+            <appender-ref ref="fileErrorLog"></appender-ref>
+        </root>
+    </springProfile>
+    <springProfile name="prod">
+        <root level="info">
+            <appender-ref ref="fileInfoLog"></appender-ref>
+            <appender-ref ref="fileErrorLog"></appender-ref>
+        </root>
+    </springProfile>
+</configuration>