Selaa lähdekoodia

feat: 更新git仓库

yangliu 4 kuukautta sitten
commit
cfe19f32ce
24 muutettua tiedostoa jossa 1104 lisäystä ja 0 poistoa
  1. 38 0
      .gitignore
  2. 29 0
      Dockerfile
  3. 96 0
      Jenkinsfile
  4. 148 0
      pom.xml
  5. 16 0
      src/main/java/cn/hfln/framework/gateway/GatewayApplication.java
  6. 32 0
      src/main/java/cn/hfln/framework/gateway/authorization/AuthorizationManager.java
  7. 21 0
      src/main/java/cn/hfln/framework/gateway/componet/RestAuthenticationEntryPoint.java
  8. 22 0
      src/main/java/cn/hfln/framework/gateway/componet/RestfulAccessDeniedHandler.java
  9. 32 0
      src/main/java/cn/hfln/framework/gateway/componet/ResultHandler.java
  10. 30 0
      src/main/java/cn/hfln/framework/gateway/config/GlobalCorsConfig.java
  11. 20 0
      src/main/java/cn/hfln/framework/gateway/config/IgnoreUrlsConfig.java
  12. 66 0
      src/main/java/cn/hfln/framework/gateway/config/ResourceServerConfig.java
  13. 56 0
      src/main/java/cn/hfln/framework/gateway/config/SwaggerResourceConfig.java
  14. 12 0
      src/main/java/cn/hfln/framework/gateway/constant/AuthConstant.java
  15. 72 0
      src/main/java/cn/hfln/framework/gateway/filter/AuthGlobalFilter.java
  16. 46 0
      src/main/java/cn/hfln/framework/gateway/filter/IgnoreUrlsRemoveJwtFilter.java
  17. 57 0
      src/main/java/cn/hfln/framework/gateway/handler/SwaggerHandler.java
  18. 36 0
      src/main/resources/bootstrap-dev.yml
  19. 38 0
      src/main/resources/bootstrap-local.yml
  20. 37 0
      src/main/resources/bootstrap-prod.yml
  21. 38 0
      src/main/resources/bootstrap-test.yml
  22. 63 0
      src/main/resources/bootstrap.yml
  23. 1 0
      src/main/resources/jwt_pub.key
  24. 98 0
      src/main/resources/logback-spring.xml

+ 38 - 0
.gitignore

@@ -0,0 +1,38 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+*.iws
+*.iml
+*.ipr
+*.class
+*.log
+*.jar
+mvnw
+mvnw.cmd
+.mvn
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+/.idea/

+ 29 - 0
Dockerfile

@@ -0,0 +1,29 @@
+# 第二阶段:使用精简版 JRE 镜像运行
+FROM openjdk:8-jre-alpine
+
+WORKDIR /app
+
+# 拷贝 jar 包路径(构建好的 jar)
+COPY target/gateway-service-boot.jar app.jar
+
+# 设置时区
+ENV TZ=Asia/Shanghai
+
+# 声明构建参数,用于动态指定环境
+ARG env=dev
+
+# 设置运行时环境变量(从构建参数传入)
+ENV SPRING_PROFILES_ACTIVE=${env}
+
+EXPOSE 8090
+
+# 启动应用
+ENTRYPOINT ["java", \
+    "-Xms512m", \
+    "-Xmx512m", \
+    "-XX:+UseContainerSupport", \
+    "-XX:+UseG1GC", \
+    "-Dfile.encoding=UTF-8", \
+    "-Djava.security.egd=file:/dev/./urandom", \
+    "-jar", "app.jar", \
+    "--spring.profiles.active=${SPRING_PROFILES_ACTIVE}"]

+ 96 - 0
Jenkinsfile

@@ -0,0 +1,96 @@
+pipeline {
+    agent any
+
+    parameters {
+        choice(name: 'env', choices: ['dev', 'test', 'prod'], description: '部署环境(dev/test/prod)')
+        string(name: 'NAMESPACE', defaultValue: 'hfln-dev', description: 'Kubernetes 命名空间')
+    }
+
+    environment {
+        PROJECT_NAME       = 'gateway-service'
+        MODULE_NAME        = 'gateway-service-server'
+        MAVEN_HOME         = '/usr/local/apache-maven-3.9.9/bin'
+        HARBOR_HOST        = '8.130.28.21:81'
+        KUBECONFIG_PATH    = '/root/.kube/config'
+        SPRING_PROFILES_ACTIVE = "${params.env}"
+    }
+
+    stages {
+        stage('🧬 拉取 Git 代码') {
+            steps {
+                script {
+                    echo ">>> 正在拉取 Git 代码..."
+                    checkout scm
+                    echo ">>> 拉取完成 ✅"
+                }
+            }
+        }
+
+        stage('📦 Maven 构建项目') {
+            steps {
+                script {
+                    echo ">>> 开始构建模块 ${MODULE_NAME}"
+                    sh "${MAVEN_HOME}/mvn clean package -DskipTests"
+                    echo ">>> 构建完成 ✅"
+                }
+            }
+        }
+
+        stage('🐳 构建并推送 Docker 镜像') {
+            steps {
+                script {
+                    def imageTag = "${HARBOR_HOST}/${params.env}/${PROJECT_NAME}:${BUILD_NUMBER}"
+                    echo ">>> 构建 Docker 镜像: ${imageTag}"
+                    sh """
+                        docker login -u admin -p Hfln@1024 ${HARBOR_HOST}
+                        docker build --build-arg env=${params.env} -t ${imageTag} .
+                        docker push ${imageTag}
+                        docker rmi ${imageTag}
+                    """
+                    echo ">>> 镜像推送完成 ✅"
+                }
+            }
+        }
+
+        stage('🚀 部署到 Kubernetes') {
+            steps {
+                script {
+                    def imageTag = "${HARBOR_HOST}/${params.env}/${PROJECT_NAME}:${BUILD_NUMBER}"
+                    echo ">>> 部署到 Kubernetes,命名空间:${params.NAMESPACE}"
+                    sh """
+                        export KUBECONFIG=${KUBECONFIG_PATH}
+                        kubectl set image deployment/${PROJECT_NAME} ${PROJECT_NAME}=${imageTag} -n ${params.NAMESPACE}
+                    """
+                    echo ">>> 部署完成 ✅"
+                }
+            }
+        }
+    }
+
+    post {
+        success {
+            script {
+                echo "✅ 构建 & 部署成功 🎉"
+
+                // 清理本地旧镜像(仅保留最新3个)
+                sh """
+                    docker images --format '{{.Repository}}:{{.Tag}} {{.CreatedAt}}' | \
+                    grep '${HARBOR_HOST}/${params.env}/${PROJECT_NAME}:' | \
+                    sort -rk2 | \
+                    tail -n +4 | \
+                    awk '{print \$1}' | \
+                    xargs -r docker rmi || true
+                """
+            }
+        }
+
+        failure {
+            echo "❌ 构建或部署失败,请检查日志。"
+        }
+
+        always {
+            echo "🧼 清理 Jenkins 工作目录..."
+            cleanWs()
+        }
+    }
+}

+ 148 - 0
pom.xml

@@ -0,0 +1,148 @@
+<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>cn.hfln.framework</groupId>
+        <artifactId>hfln-framework-parent</artifactId>
+        <version>1.0.0-SNAPSHOT</version>
+        <relativePath/>
+    </parent>
+
+    <groupId>cn.hfln.framework</groupId>
+    <artifactId>hfln-interior-gateway</artifactId>
+    <version>1.0.0-SNAPSHOT</version>
+    <name>hfln-interior-gateway</name>
+    <description>hfln-interior-gateway</description>
+
+    <properties>
+        <java.version>1.8</java.version> <!-- Update Java version -->
+        <spring-boot.version>2.3.12.RELEASE</spring-boot.version> <!-- Update Spring Boot version -->
+        <spring-cloud.version>Hoxton.SR12</spring-cloud.version> <!-- Update Spring Cloud version -->
+        <nimbus-jose-jwt.version>9.1</nimbus-jose-jwt.version> <!-- Update Nimbus JWT version -->
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.hfln.framework</groupId>
+            <artifactId>hfln-framework-dto</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>cn.hfln.framework</groupId>
+            <artifactId>hfln-framework-common</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework</groupId>
+                    <artifactId>spring-webmvc</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.fastjson2</groupId>
+            <artifactId>fastjson2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+        </dependency>
+
+        <!-- 注册中心配置中心相关 -->
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-gateway</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-config</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-oauth2-resource-server</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-oauth2-client</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-oauth2-jose</artifactId>
+        </dependency>
+
+        <!-- 接口文档 -->
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>knife4j-micro-spring-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>knife4j-spring-boot-starter</artifactId>
+        </dependency>
+
+        <!-- JWT(Json Web Token)登录支持 -->
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+            <version>0.9.0</version>
+        </dependency>
+
+        <!-- Nimbus JWT支持 -->
+        <dependency>
+            <groupId>com.nimbusds</groupId>
+            <artifactId>nimbus-jose-jwt</artifactId>
+            <version>${nimbus-jose-jwt.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.codehaus.janino</groupId>
+            <artifactId>janino</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+
+
+
+    </dependencies>
+
+    <build>
+        <finalName>gateway-service</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>
+                <configuration>
+                    <classifier>boot</classifier>
+                    <mainClass>cn.hfln.framework.gateway.GatewayApplication</mainClass>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 16 - 0
src/main/java/cn/hfln/framework/gateway/GatewayApplication.java

@@ -0,0 +1,16 @@
+package cn.hfln.framework.gateway;
+
+import cn.hfln.framework.common.redis.RedisConfig;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+
+@SpringBootApplication(scanBasePackages = {"cn.hfln.framework.gateway"},scanBasePackageClasses = {RedisConfig.class})
+@EnableDiscoveryClient
+public class GatewayApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(GatewayApplication.class, args);
+    }
+
+}

+ 32 - 0
src/main/java/cn/hfln/framework/gateway/authorization/AuthorizationManager.java

@@ -0,0 +1,32 @@
+package cn.hfln.framework.gateway.authorization;
+
+import cn.hfln.framework.gateway.config.IgnoreUrlsConfig;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.authorization.AuthorizationDecision;
+import org.springframework.security.authorization.ReactiveAuthorizationManager;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.server.authorization.AuthorizationContext;
+import org.springframework.stereotype.Component;
+import reactor.core.publisher.Mono;
+
+/**
+ * @USER: YangLiu
+ */
+@Component
+@Slf4j
+public class AuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {
+
+    private final IgnoreUrlsConfig ignoreUrlsConfig;
+
+    public AuthorizationManager(IgnoreUrlsConfig ignoreUrlsConfig) {
+        this.ignoreUrlsConfig = ignoreUrlsConfig;
+    }
+
+
+    @Override
+    public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {
+
+        return Mono.just(new AuthorizationDecision(true));
+    }
+
+}

+ 21 - 0
src/main/java/cn/hfln/framework/gateway/componet/RestAuthenticationEntryPoint.java

@@ -0,0 +1,21 @@
+package cn.hfln.framework.gateway.componet;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+/**
+ * @USER: YangLiu
+ * 自定义返回结果:没有登录或token过期时
+ */
+@Component
+@Slf4j
+public class RestAuthenticationEntryPoint implements ServerAuthenticationEntryPoint {
+    @Override
+    public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException e) {
+        return new ResultHandler().handle(exchange,e);
+    }
+}

+ 22 - 0
src/main/java/cn/hfln/framework/gateway/componet/RestfulAccessDeniedHandler.java

@@ -0,0 +1,22 @@
+package cn.hfln.framework.gateway.componet;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+
+/**
+ * @USER: YangLiu
+ * 自定义返回结果:没有权限访问时
+ */
+@Component
+@Slf4j
+public class RestfulAccessDeniedHandler implements ServerAccessDeniedHandler {
+    @Override
+    public Mono<Void> handle(ServerWebExchange exchange, AccessDeniedException e) {
+        return new ResultHandler().handle(exchange,e);
+    }
+}

+ 32 - 0
src/main/java/cn/hfln/framework/gateway/componet/ResultHandler.java

@@ -0,0 +1,32 @@
+package cn.hfln.framework.gateway.componet;
+
+import cn.hutool.json.JSONUtil;
+import cn.hfln.framework.dto.ApiResult;
+import com.alibaba.fastjson2.JSON;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+/**
+ * @USER: YangLiu
+ */
+@Slf4j
+public class ResultHandler {
+    public Mono<Void> handle(ServerWebExchange exchange, Exception denied) {
+        ServerHttpResponse response = exchange.getResponse();
+        response.setStatusCode(HttpStatus.OK);
+        response.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
+        response.getHeaders().set("Access-Control-Allow-Origin", "*");
+        response.getHeaders().set("Cache-Control", "no-cache");
+        String body = JSONUtil.toJsonStr(denied.getMessage());
+        log.info("认证错误!!! 错误信息:{}", body);
+        ApiResult<?> result = ApiResult.unauthorized(null);
+        DataBuffer buffer = response.bufferFactory().wrap(JSON.toJSONString(result).getBytes());
+        return response.writeWith(Mono.just(buffer));
+    }
+}

+ 30 - 0
src/main/java/cn/hfln/framework/gateway/config/GlobalCorsConfig.java

@@ -0,0 +1,30 @@
+package cn.hfln.framework.gateway.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.reactive.CorsWebFilter;
+import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
+import org.springframework.web.util.pattern.PathPatternParser;
+
+/**
+ * @USER: YangLiu
+ */
+
+@Configuration
+public class GlobalCorsConfig {
+
+    @Bean
+    public CorsWebFilter corsFilter() {
+        CorsConfiguration config = new CorsConfiguration();
+        config.addAllowedMethod("*");
+        config.addAllowedOrigin("*");
+        config.addAllowedHeader("*");
+        config.setAllowCredentials(true);
+        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
+        source.registerCorsConfiguration("/**", config);
+
+        return new CorsWebFilter(source);
+    }
+
+}

+ 20 - 0
src/main/java/cn/hfln/framework/gateway/config/IgnoreUrlsConfig.java

@@ -0,0 +1,20 @@
+package cn.hfln.framework.gateway.config;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * @USER: YangLiu
+ * 网关白名单配置
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Component
+@ConfigurationProperties(prefix="secure.ignore")
+public class IgnoreUrlsConfig {
+    private List<String> urls;
+}

+ 66 - 0
src/main/java/cn/hfln/framework/gateway/config/ResourceServerConfig.java

@@ -0,0 +1,66 @@
+package cn.hfln.framework.gateway.config;
+
+import cn.hutool.core.util.ArrayUtil;
+
+import cn.hfln.framework.gateway.authorization.AuthorizationManager;
+import cn.hfln.framework.gateway.componet.RestAuthenticationEntryPoint;
+import cn.hfln.framework.gateway.componet.RestfulAccessDeniedHandler;
+import cn.hfln.framework.gateway.constant.AuthConstant;
+import cn.hfln.framework.gateway.filter.IgnoreUrlsRemoveJwtFilter;
+import lombok.AllArgsConstructor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
+import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
+import org.springframework.security.config.web.server.ServerHttpSecurity;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
+import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
+import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;
+import org.springframework.security.web.server.SecurityWebFilterChain;
+import reactor.core.publisher.Mono;
+
+/**
+ * @USER: YangLiu
+ */
+@AllArgsConstructor
+@Configuration
+@EnableWebFluxSecurity
+public class ResourceServerConfig {
+    private final AuthorizationManager authorizationManager;
+    private final IgnoreUrlsConfig ignoreUrlsConfig;
+    private final RestfulAccessDeniedHandler restfulAccessDeniedHandler;
+    private final RestAuthenticationEntryPoint restAuthenticationEntryPoint;
+    private final IgnoreUrlsRemoveJwtFilter ignoreUrlsRemoveJwtFilter;
+
+    @Bean
+    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+        http.oauth2ResourceServer().jwt()
+                .jwtAuthenticationConverter(jwtAuthenticationConverter());
+        //自定义处理JWT请求头过期或签名错误的结果
+        http.oauth2ResourceServer().authenticationEntryPoint(restAuthenticationEntryPoint);
+        //对白名单路径,直接移除JWT请求头
+        http.addFilterBefore(ignoreUrlsRemoveJwtFilter,SecurityWebFiltersOrder.AUTHENTICATION);
+        http.authorizeExchange()
+                .pathMatchers(ArrayUtil.toArray(ignoreUrlsConfig.getUrls(),String.class)).permitAll()//白名单配置
+                .anyExchange().access(authorizationManager)//鉴权管理器配置
+                .and().exceptionHandling()
+                .accessDeniedHandler(restfulAccessDeniedHandler)//处理未授权
+                .authenticationEntryPoint(restAuthenticationEntryPoint)//处理未认证
+                .and().csrf().disable();
+        return http.build();
+    }
+
+    @Bean
+    public Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter() {
+        JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
+        jwtGrantedAuthoritiesConverter.setAuthorityPrefix(AuthConstant.AUTHORITY_PREFIX);
+        jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(AuthConstant.AUTHORITY_CLAIM_NAME);
+        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
+        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
+        return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
+    }
+
+}

+ 56 - 0
src/main/java/cn/hfln/framework/gateway/config/SwaggerResourceConfig.java

@@ -0,0 +1,56 @@
+package cn.hfln.framework.gateway.config;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cloud.gateway.config.GatewayProperties;
+import org.springframework.cloud.gateway.route.RouteLocator;
+import org.springframework.cloud.gateway.support.NameUtils;
+import org.springframework.context.annotation.Primary;
+import org.springframework.stereotype.Component;
+import springfox.documentation.swagger.web.SwaggerResource;
+import springfox.documentation.swagger.web.SwaggerResourcesProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @USER: YangLiu
+ * Swagger资源配置
+ */
+@Slf4j
+@Component
+@Primary
+@AllArgsConstructor
+public class SwaggerResourceConfig implements SwaggerResourcesProvider {
+
+    private final RouteLocator routeLocator;
+    private final GatewayProperties gatewayProperties;
+
+    @Override
+    public List<SwaggerResource> get() {
+        List<SwaggerResource> resources = new ArrayList<>();
+        List<String> routes = new ArrayList<>();
+        //获取所有路由的ID
+        routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
+        //过滤出配置文件中定义的路由->过滤出Path Route Predicate->根据路径拼接成api-docs路径->生成SwaggerResource
+        gatewayProperties.getRoutes().stream().filter(
+                routeDefinition -> routes.contains(routeDefinition.getId())).forEach(route -> {
+            route.getPredicates().stream()
+                    .filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
+                    .forEach(predicateDefinition -> resources.add(swaggerResource(route.getId(),
+                            predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
+                                    .replace("**", "v2/api-docs"))));
+        });
+
+        return resources;
+    }
+
+    private SwaggerResource swaggerResource(String name, String location) {
+        log.info("name:{},location:{}", name, location);
+        SwaggerResource swaggerResource = new SwaggerResource();
+        swaggerResource.setName(name);
+        swaggerResource.setLocation(location);
+        swaggerResource.setSwaggerVersion("2.0");
+        return swaggerResource;
+    }
+}

+ 12 - 0
src/main/java/cn/hfln/framework/gateway/constant/AuthConstant.java

@@ -0,0 +1,12 @@
+package cn.hfln.framework.gateway.constant;
+
+/**
+ * @USER: YangLiu
+ */
+public interface AuthConstant {
+    String AUTHORITY_PREFIX = "ROLE_";
+    String AUTHORITY_CLAIM_NAME = "authorities";
+    String JWT_TOKEN_HEADER = "Authorization";
+    String JWT_TOKEN_PREFIX = "Bearer ";
+    String USER_TOKEN_HEADER = "user";
+}

+ 72 - 0
src/main/java/cn/hfln/framework/gateway/filter/AuthGlobalFilter.java

@@ -0,0 +1,72 @@
+package cn.hfln.framework.gateway.filter;
+
+import cn.hfln.framework.common.constant.SecurityConstants;
+import cn.hfln.framework.common.redis.service.RedisService;
+import cn.hfln.framework.gateway.constant.AuthConstant;
+import cn.hutool.core.util.StrUtil;
+import com.nimbusds.jose.JWSObject;
+import com.nimbusds.jose.Payload;
+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.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+
+/**
+ * 拦截JWT设置token信息
+ * @author YangLiu
+ */
+@Component
+@Slf4j
+public class AuthGlobalFilter implements GlobalFilter, Ordered {
+
+    @Autowired
+    private RedisService redisService;
+
+    @Override
+    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+        ServerHttpRequest request = exchange.getRequest();
+        String token = request.getHeaders().getFirst(AuthConstant.JWT_TOKEN_HEADER);
+
+        if (StrUtil.isBlank(token)) {
+            return chain.filter(exchange);
+        }
+
+        try {
+            // 解析JWT,提取用户信息
+            String realToken = token.replace(AuthConstant.JWT_TOKEN_PREFIX, "");
+            JWSObject jwsObject = JWSObject.parse(realToken);
+            Payload payload = jwsObject.getPayload();
+            Map<String, Object> payloadMap = payload.toJSONObject();
+
+            String userKey = String.valueOf(payloadMap.get(SecurityConstants.USER_KEY));
+
+            // 重新构建带用户信息的请求头
+            request = request.mutate()
+                    .header(SecurityConstants.USER_KEY, userKey)
+                    .header(SecurityConstants.DETAILS_USER_ID, String.valueOf(payloadMap.get(SecurityConstants.DETAILS_USER_ID)))
+                    .header(SecurityConstants.DETAILS_USERNAME, String.valueOf(payloadMap.get(SecurityConstants.DETAILS_USERNAME)))
+                    .header(SecurityConstants.REAL_NAME, URLEncoder.encode(String.valueOf(payloadMap.get(SecurityConstants.REAL_NAME)), StandardCharsets.UTF_8.name()))
+                    .build();
+
+            exchange = exchange.mutate().request(request).build();
+        } catch (Exception e) {
+            log.error("AuthGlobalFilter.filter() JWT Parse Exception: {}", e.getMessage(), e);
+        }
+
+        return chain.filter(exchange);
+    }
+
+    @Override
+    public int getOrder() {
+        return 0;
+    }
+}

+ 46 - 0
src/main/java/cn/hfln/framework/gateway/filter/IgnoreUrlsRemoveJwtFilter.java

@@ -0,0 +1,46 @@
+package cn.hfln.framework.gateway.filter;
+
+import cn.hfln.framework.gateway.config.IgnoreUrlsConfig;
+import cn.hfln.framework.gateway.constant.AuthConstant;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.stereotype.Component;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.util.PathMatcher;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.WebFilter;
+import org.springframework.web.server.WebFilterChain;
+import reactor.core.publisher.Mono;
+
+import java.net.URI;
+import java.util.List;
+
+/**
+ * @USER: YangLiu
+ * 拦截去除白名单中JWT信息
+ */
+@Component
+public class IgnoreUrlsRemoveJwtFilter implements WebFilter {
+    private final IgnoreUrlsConfig ignoreUrlsConfig;
+
+    public IgnoreUrlsRemoveJwtFilter(IgnoreUrlsConfig ignoreUrlsConfig) {
+        this.ignoreUrlsConfig = ignoreUrlsConfig;
+    }
+
+    @Override
+    @SuppressWarnings("all")
+    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
+        ServerHttpRequest request = exchange.getRequest();
+        URI uri = request.getURI();
+        PathMatcher pathMatcher = new AntPathMatcher();
+        //白名单路径移除JWT请求头
+        List<String> ignoreUrls = ignoreUrlsConfig.getUrls();
+        for (String ignoreUrl : ignoreUrls) {
+            if (pathMatcher.match(ignoreUrl, uri.getPath())) {
+                request = exchange.getRequest().mutate().header(AuthConstant.JWT_TOKEN_HEADER, "").build();
+                exchange = exchange.mutate().request(request).build();
+                return chain.filter(exchange);
+            }
+        }
+        return chain.filter(exchange);
+    }
+}

+ 57 - 0
src/main/java/cn/hfln/framework/gateway/handler/SwaggerHandler.java

@@ -0,0 +1,57 @@
+package cn.hfln.framework.gateway.handler;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+import reactor.core.publisher.Mono;
+import springfox.documentation.swagger.web.*;
+
+import java.util.Optional;
+
+/**
+ * @USER: YangLiu
+ * @DESC: 通过Http获取业务服务的资源配置
+ */
+@RestController
+@SuppressWarnings("all")
+public class SwaggerHandler {
+
+
+    @Autowired(required = false)
+    private SecurityConfiguration securityConfiguration;
+    @Autowired(required = false)
+    private UiConfiguration uiConfiguration;
+    @Autowired(required = false)
+    private SwaggerResourcesProvider swaggerResources;
+
+
+    /**
+     * Swagger安全配置,支持oauth和apiKey设置
+     */
+    @GetMapping("/swagger-resources/configuration/security")
+    public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
+        return Mono.just(new ResponseEntity<>(
+                Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()),
+                HttpStatus.OK));
+    }
+
+    /**
+     * Swagger UI配置
+     */
+    @GetMapping("/swagger-resources/configuration/ui")
+    public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
+        return Mono.just(new ResponseEntity<>(
+                Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()),
+                HttpStatus.OK));
+    }
+
+    /**
+     * Swagger资源配置,获取各个服务的api-docs信息
+     */
+    @GetMapping("/swagger-resources")
+    public Mono<ResponseEntity<?>> swaggerResources() {
+        return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
+    }
+}

+ 36 - 0
src/main/resources/bootstrap-dev.yml

@@ -0,0 +1,36 @@
+spring:
+  cloud:
+    nacos:
+      discovery:
+        server-addr: 8.130.28.21:8848
+        group: DEFAULT_GROUP
+        namespace: dev
+      config:
+        server-addr: 8.130.28.21:8848
+        group: DEFAULT_GROUP
+        namespace: dev
+        file-extension: yaml
+  redis:
+    # 地址
+    host: 8.130.28.21
+    # 端口
+    port: 6379
+    # 数据库索引
+    database: 5
+    # 连接超时时间
+    timeout: 10s
+    lettuce:
+      pool:
+        # 连接池中的最小空闲连接
+        min-idle: 0
+
+        # 连接池中的最大空闲连接
+        max-idle: 8
+        # 连接池的最大数据库连接数
+        max-active: 8
+        # #连接池最大阻塞等待时间(使用负值表示没有限制)
+        max-wait: -1ms
+security:
+  captcha:
+    enabled: false
+    type: char

+ 38 - 0
src/main/resources/bootstrap-local.yml

@@ -0,0 +1,38 @@
+spring:
+  cloud:
+    nacos:
+      discovery:
+        server-addr: 8.130.28.21:8848
+        group: DEFAULT_GROUP
+        namespace: local
+      config:
+        server-addr: 8.130.28.21:8848
+        group: DEFAULT_GROUP
+        namespace: local
+        file-extension: yaml
+  redis:
+    # 地址
+    host: 8.130.28.21
+    # 端口
+    port: 6379
+    # 数据库索引
+    database: 5
+    # 密码
+    password: Hfln@1024
+    # 连接超时时间
+    timeout: 10s
+    lettuce:
+      pool:
+        # 连接池中的最小空闲连接
+        min-idle: 0
+
+        # 连接池中的最大空闲连接
+        max-idle: 8
+        # 连接池的最大数据库连接数
+        max-active: 8
+        # #连接池最大阻塞等待时间(使用负值表示没有限制)
+        max-wait: -1ms
+security:
+  captcha:
+    enabled: false
+    type: char

+ 37 - 0
src/main/resources/bootstrap-prod.yml

@@ -0,0 +1,37 @@
+spring:
+  cloud:
+    nacos:
+      discovery:
+        server-addr: node01:8848
+        group: DEFAULT_GROUP
+        namespace: public
+      config:
+        server-addr: ${spring.cloud.nacos.discovery.server-addr}
+        group: ${spring.cloud.nacos.discovery.group}
+        namespace: ${spring.cloud.nacos.discovery.namespace}
+        file-extension: yaml
+#  redis:
+#    # 地址
+#    host: node04
+#    # 端口
+#    port: 6379
+#    # 数据库索引
+#    database: 5
+#    # 密码
+#    password: Wideth@123
+#    # 连接超时时间
+#    timeout: 10s
+#    lettuce:
+#      pool:
+#        # 连接池中的最小空闲连接
+#        min-idle: 0
+#        # 连接池中的最大空闲连接
+#        max-idle: 8
+#        # 连接池的最大数据库连接数
+#        max-active: 8
+#        # #连接池最大阻塞等待时间(使用负值表示没有限制)
+#        max-wait: -1ms
+security:
+  captcha:
+    enabled: false
+    type: char

+ 38 - 0
src/main/resources/bootstrap-test.yml

@@ -0,0 +1,38 @@
+spring:
+  cloud:
+    nacos:
+      discovery:
+        server-addr: 8.130.28.21:8848
+        group: DEFAULT_GROUP
+        namespace: test
+      config:
+        server-addr: 8.130.28.21:8848
+        group: DEFAULT_GROUP
+        namespace: test
+        file-extension: yaml
+  redis:
+    # 地址
+    host: 8.130.28.21
+    # 端口
+    port: 6379
+    # 数据库索引
+    database: 5
+    # 密码
+    password: Hfln@1024
+    # 连接超时时间
+    timeout: 10s
+    lettuce:
+      pool:
+        # 连接池中的最小空闲连接
+        min-idle: 0
+
+        # 连接池中的最大空闲连接
+        max-idle: 8
+        # 连接池的最大数据库连接数
+        max-active: 8
+        # #连接池最大阻塞等待时间(使用负值表示没有限制)
+        max-wait: -1ms
+security:
+  captcha:
+    enabled: false
+    type: char

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

@@ -0,0 +1,63 @@
+server:
+  port: 8090
+spring:
+  profiles:
+    active: dev
+  application:
+    name: hfln-interior-gateway
+  cloud:
+    gateway:
+      discovery:
+        locator:
+          enabled: true
+          lower-case-service-id: true #????service-id
+      routes: #??????
+        - id: portal-service-server
+          uri: lb://portal-service-server
+          predicates:
+            - Path=/portal-service-server/**
+          filters:
+            - StripPrefix=1
+
+        - id: test
+          uri: lb://test
+          predicates:
+            - Path=/test/**
+          filters:
+            - StripPrefix=1
+
+
+  # RSA密钥地址
+  security:
+    oauth2:
+      resourceserver:
+        jwt:
+          public-key-location: classpath:jwt_pub.key
+secure:
+  ignore:
+    urls: # 配置白名单路径
+      - "/doc.html"
+      - "/code"
+      - "/swagger-resources/**"
+      - "/swagger/**"
+      - "/*/v2/api-docs"
+      - "/*/*.js"
+      - "/*/*/*.js"
+      - "/*/*.css"
+      - "/*/*/*.css"
+      - "/*/*.png"
+      - "/*/*/*.png"
+      - "/*.ico"
+      - "/*/*.ico"
+      - "/*/*/*.ico"
+      - "/webjars/springfox-swagger-ui/**"
+      - "/actuator/**"
+logging:
+  level:
+    root: info
+management:
+  metrics:
+    tags:
+      application: ${spring.application.name}
+
+

+ 1 - 0
src/main/resources/jwt_pub.key

@@ -0,0 +1 @@
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCV8tpvKfqlzUIvkyD0Zi/8nQHFZx2xxpfal17iHNTYtIZil2YgkANYrRHEnF1wKFbKRZH1zHQsLLkEojt3nz0jyLmZjv94pVmcoXGIsbOkYHp1WaO6HegRdhdqfDZiTlCs4lPqQgGHh4yBdBgb8ZH8DI5Xp/koetVi0LpwdPxJrQIDAQAB

+ 98 - 0
src/main/resources/logback-spring.xml

@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration scan="false" scanPeriod="60 seconds">
+
+    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
+    <springProperty scope="context" name="appName" source="spring.application.name" defaultValue="wideth-service"  />
+    <springProperty scope="context" name="printConsole" source="log.console.print" defaultValue="true"  />
+
+    <property name="LOG_PATH" value="/data/logs" />
+
+    <!-- 控制台设置 -->
+    <contextName>logback</contextName>
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${PID:- }){magenta} %clr(${appName}){faint} %clr([%15.15t]){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(%-40.40logger{39}){cyan} %clr([line: %4line]){magenta} %clr(:){faint} %m%n</pattern>
+        </encoder>
+    </appender>
+
+    <!-- ALL -->
+    <appender name="allAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${LOG_PATH}/${appName}/all.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <FileNamePattern>${LOG_PATH}/${appName}/${appName}-info-%d{yyyy-MM-dd}.%i.log</FileNamePattern>
+            <maxFileSize>100MB</maxFileSize>
+            <maxHistory>30</maxHistory>
+            <totalSizeCap>3000MB</totalSizeCap>
+        </rollingPolicy>
+        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS}|-|${appName}|-|%thread|-|%level|-|%logger|-|%msg%n</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>DEBUG</level>
+            <onMatch>DENY</onMatch>
+            <onMismatch>ACCEPT</onMismatch>
+        </filter>
+    </appender>
+
+
+    <!-- ERROR -->
+    <appender name="errorAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${LOG_PATH}/${appName}/error.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <FileNamePattern>${LOG_PATH}/${appName}/${appName}-error-%d{yyyy-MM-dd}.%i.log</FileNamePattern>
+            <maxFileSize>100MB</maxFileSize>
+            <maxHistory>30</maxHistory>
+            <totalSizeCap>3000MB</totalSizeCap>
+        </rollingPolicy>
+        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS}|-|${appName}|-|%thread|-|%level|-|%logger|-|%msg%n</pattern>
+        </encoder>
+
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>ERROR</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <appender name="allAsyncLog" class="ch.qos.logback.classic.AsyncAppender">
+        <param name="discardingThreshold" value="0"/>
+        <param name="queueSize" value="4096"/>
+        <appender-ref ref="allAppender" />
+    </appender>
+
+    <appender name="errorAsyncLog" class="ch.qos.logback.classic.AsyncAppender">
+        <param name="discardingThreshold" value="0"/>
+        <param name="queueSize" value="2048"/>
+        <appender-ref ref="errorAppender" />
+    </appender>
+
+    <logger name="org" level="info"/>
+    <logger name="com.netflix" level="ERROR"/>
+    <logger name="com.sun" level="ERROR"/>
+    <logger name="io.lettuce" level="ERROR"/>
+    <logger name="com.alibaba" level="ERROR"/>
+    <logger name="springfox" level="ERROR"/>
+    <logger name="sun.rmi" level="ERROR"/>
+    <logger name="sun.net" level="ERROR"/>
+    <logger name="javax" level="ERROR"/>
+    <logger name="com.hfln" level="DEBUG"/>
+    <!--<logger name="com.xxl" level="ERROR"/>-->
+    <logger name="org.mongodb.driver" level="ERROR"/>
+
+
+    <root level="INFO">
+        <!--<root level="DEBUG">-->
+        <if condition='property("printConsole").equals("true")'>
+            <then>
+                <appender-ref ref="STDOUT"/>
+            </then>
+        </if>
+        <appender-ref ref="allAsyncLog"/>
+        <!--<appender-ref ref="debugAsyncLog"/>
+        <appender-ref ref="warnAsyncLog"/>-->
+        <appender-ref ref="errorAsyncLog"/>
+    </root>
+</configuration>
+
+