From 3fe3f891daaa3b75b54ad8e96c08f18e8079f8ce Mon Sep 17 00:00:00 2001 From: beryl Date: Tue, 15 Jul 2025 13:12:39 +0800 Subject: [PATCH] init commit --- .gitignore | 59 +++--- Dockerfile | 82 ++++++++ Jenkinsfile | 118 ++++++++++++ README.md | 18 +- buildDocker.sh | 3 + pom.xml | 176 ++++++++++++++++++ .../bi/gateway/GatewayApplication.java | 18 ++ .../DynamicRoute/DynamicRouteConfig.java | 21 +++ .../DynamicRouteConfigConverter.java | 53 ++++++ .../config/DynamicRoute/GatewayConfig.java | 95 ++++++++++ .../config/DynamicRoute/RouteInfo.java | 108 +++++++++++ .../GatewaySwaggerResourcesProvider.java | 68 +++++++ .../ReqDecryptAndRespEncryptConfig.java | 51 +++++ .../bi/gateway/ctl/GatewayController.java | 31 +++ .../bi/gateway/filter/ReqDecryptFilter.java | 136 ++++++++++++++ .../bi/gateway/filter/RespEncryptFilter.java | 116 ++++++++++++ .../bi/gateway/util/RandomStringUtils.java | 18 ++ .../bi/gateway/util/SpringContextUtils.java | 31 +++ .../gateway/util/encrypt/AESEncryptUtil.java | 73 ++++++++ .../gateway/util/encrypt/RSAEncryptUtil.java | 144 ++++++++++++++ src/main/resources/bootstrap-dev.yml | 32 ++++ src/main/resources/bootstrap-home.yml | 42 +++++ src/main/resources/bootstrap-local.yml | 32 ++++ src/main/resources/bootstrap-qa.yml | 27 +++ src/main/resources/bootstrap-sentinel.yml | 49 +++++ src/main/resources/bootstrap-uat.yml | 27 +++ src/main/resources/bootstrap-ybg.yml | 43 +++++ src/main/resources/bootstrap.yml | 34 ++++ src/main/resources/logback-logstash.xml | 78 ++++++++ 29 files changed, 1760 insertions(+), 23 deletions(-) create mode 100644 Dockerfile create mode 100644 Jenkinsfile create mode 100644 buildDocker.sh create mode 100644 pom.xml create mode 100644 src/main/java/cn/com/do1/component/bi/gateway/GatewayApplication.java create mode 100644 src/main/java/cn/com/do1/component/bi/gateway/config/DynamicRoute/DynamicRouteConfig.java create mode 100644 src/main/java/cn/com/do1/component/bi/gateway/config/DynamicRoute/DynamicRouteConfigConverter.java create mode 100644 src/main/java/cn/com/do1/component/bi/gateway/config/DynamicRoute/GatewayConfig.java create mode 100644 src/main/java/cn/com/do1/component/bi/gateway/config/DynamicRoute/RouteInfo.java create mode 100644 src/main/java/cn/com/do1/component/bi/gateway/config/GatewaySwaggerResourcesProvider.java create mode 100644 src/main/java/cn/com/do1/component/bi/gateway/config/ReqDecryptAndRespEncryptConfig.java create mode 100644 src/main/java/cn/com/do1/component/bi/gateway/ctl/GatewayController.java create mode 100644 src/main/java/cn/com/do1/component/bi/gateway/filter/ReqDecryptFilter.java create mode 100644 src/main/java/cn/com/do1/component/bi/gateway/filter/RespEncryptFilter.java create mode 100644 src/main/java/cn/com/do1/component/bi/gateway/util/RandomStringUtils.java create mode 100644 src/main/java/cn/com/do1/component/bi/gateway/util/SpringContextUtils.java create mode 100644 src/main/java/cn/com/do1/component/bi/gateway/util/encrypt/AESEncryptUtil.java create mode 100644 src/main/java/cn/com/do1/component/bi/gateway/util/encrypt/RSAEncryptUtil.java create mode 100644 src/main/resources/bootstrap-dev.yml create mode 100644 src/main/resources/bootstrap-home.yml create mode 100644 src/main/resources/bootstrap-local.yml create mode 100644 src/main/resources/bootstrap-qa.yml create mode 100644 src/main/resources/bootstrap-sentinel.yml create mode 100644 src/main/resources/bootstrap-uat.yml create mode 100644 src/main/resources/bootstrap-ybg.yml create mode 100644 src/main/resources/bootstrap.yml create mode 100644 src/main/resources/logback-logstash.xml diff --git a/.gitignore b/.gitignore index 9154f4c..d8c63e9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,26 +1,41 @@ -# ---> Java -# Compiled class file -*.class +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ -# Log file -*.log +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache -# BlueJ files -*.ctxt +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* -replay_pid* +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ +### VS Code ### +.vscode/ +## IDEA maven +/.flattened-pom.xml +/**/flattened-pom.xml +admin-application/.flattened-pom.xml +admin-application/logs/ +admin-client/.flattened-pom.xml +admin-core/.flattened-pom.xml +admin-model/.flattened-pom.xml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a02210a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,82 @@ +FROM openjdk:8 + +ARG JAR_FILE="./target/*.jar" +ARG APP_NAME="bi-gateway" +ARG SERVER_PORT=80 +ARG PROFILES="dev" +ARG SPRING_CLOUD_SENTINEL_ENABLED="false" +ARG SPRING_CLOUD_SENTINEL_EAGER="false" + +# license配置 +ARG LICENSE_PATH="/opt/license/verifyinfo" +ARG LICENSE_REDIS_HOST="10.0.5.17:6679" +ARG LICENSE_REDIS_PWD="Do1admin@hr123" +ARG LICENSE_IP="10.0.5.17" +ARG NACOS_USERNAME="nacos" +ARG NACOS_PASSWORD="nacos" + +# 内存配置 +ENV JAVA_OPTS "-Xmx2048m -Xss256k" +#启动环境配置 +ENV PROFILES $PROFILES +#工作路径 +ENV WORK_PATH "/home" +#日志路径 +ENV LOG_FILE "logs/app.log" +#服务端口 +ENV SERVER_PORT $SERVER_PORT +# NACOS 配置 +ENV NACOS_SERVER $NACOS_SERVER +ENV NACOS_NAMESPACE $NACOS_NAMESPACE +ENV NACOS_USERNAME $NACOS_USERNAME +ENV NACOS_PASSWORD $NACOS_PASSWORD + +ENV LICENSE_PATH $LICENSE_PATH + +ENV LICENSE_REDIS_HOST $LICENSE_REDIS_HOST + +ENV LICENSE_REDIS_PWD $LICENSE_REDIS_PWD + +ENV LICENSE_IP $LICENSE_IP +ENV LOGGIN_FILE $LOGGIN_FILE + +# Sentinel配置 +ENV SPRING_CLOUD_SENTINEL_ENABLED $SPRING_CLOUD_SENTINEL_ENABLED +ENV SPRING_CLOUD_SENTINEL_EAGER $SPRING_CLOUD_SENTINEL_EAGER +ENV SPRING_CLOUD_SENTINEL_TRANSPORT_PORT $SPRING_CLOUD_SENTINEL_TRANSPORT_PORT +ENV SPRING_CLOUD_SENTINEL_TRANSPORT_DASHBOARD $SPRING_CLOUD_SENTINEL_TRANSPORT_DASHBOARD + +#设置时区 +RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime + +VOLUME /tmp + +EXPOSE ${SERVER_PORT} + +#WITH_SKYWALKING# ADD skywalking-agent.tar.gz /lib/ +COPY ${JAR_FILE} ${WORK_PATH}/app.jar +RUN sh -c 'touch ${WORK_PATH}/app.jar' + +#ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom --server.port=$SERVER_PORT -Dapollo.meta=$APOLLO_META -Dapp.id=$APOLLO_ID -Dspring.profiles.active=$PROFILES -jar $WORK_PATH/app.jar " ] +ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom \ +-Dserver.port=$SERVER_PORT \ +-Dspring.cloud.nacos.config.server-addr=$NACOS_SERVER \ +-Dspring.cloud.nacos.config.namespace=$NACOS_NAMESPACE \ +-Dspring.cloud.nacos.config.username=$NACOS_USERNAME \ +-Dspring.cloud.nacos.config.password=$NACOS_PASSWORD \ +-Dspring.cloud.nacos.discovery.server-addr=$NACOS_SERVER \ +-Dspring.cloud.nacos.discovery.username=$NACOS_USERNAME \ +-Dspring.cloud.nacos.discovery.password=$NACOS_PASSWORD \ +-Dspring.cloud.nacos.discovery.namespace=$NACOS_NAMESPACE \ +-Dspring.profiles.active=$PROFILES \ +-Ddo1.license=$LICENSE_PATH \ +-Dredis.host=$LICENSE_REDIS_HOST \ +-Dredis.pwd=$LICENSE_REDIS_PWD \ +-Dlicense.ip=$LICENSE_IP \ +-Dlogging.file=$LOGGIN_FILE \ +-Dspring.cloud.sentinel.enabled=$SPRING_CLOUD_SENTINEL_ENABLED \ +-Dspring.cloud.sentinel.eager=$SPRING_CLOUD_SENTINEL_EAGER \ +-Dspring.cloud.sentinel.transport.port=$SPRING_CLOUD_SENTINEL_TRANSPORT_PORT \ +-Dspring.cloud.sentinel.transport.dashboard=$SPRING_CLOUD_SENTINEL_TRANSPORT_DASHBOARD \ +-Duser.timezone=GMT+08 -jar $WORK_PATH/app.jar" ] + diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..6bc44b1 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,118 @@ +pipeline { + agent { + node { + label labelName + } + + } + stages { + stage('初始化') { + steps { + script { + + echo """ ==== 构建信息 ==== + 应 用 名:$serverName + 分 支:$gitBranch + gitkey:$gitkey + harborkey: $harborkey + 镜像版本:$DOCKER_TAG """ + echo """ $params""" + } + } + } + + stage('编译') { + steps { + script { + container(labelName) { + dir(serverName) { + checkout([ + $class: 'GitSCM', + branches: [[name: "*/$gitBranch"]], + doGenerateSubmoduleConfigurations: false, + extensions: [[$class: 'CloneOption', depth: 1, noTags: false, reference: '', shallow: true]], + submoduleCfg: [], + userRemoteConfigs: [[url: gitPath, credentialsId: gitkey]], + ]) + /** 打印基础信息 **/ + sh " echo 打印基础信息 " + sh " pwd " + sh " ls " + sh " mvn clean install -e -Dmaven.test.skip=true " + + } + } + } + } + } + + stage('镜像打包') { + steps { + script { + container(labelName) { + dir(serverName) { + withCredentials([usernamePassword(passwordVariable: 'DOCKER_PASSWORD', usernameVariable: 'DOCKER_USERNAME', credentialsId: "harborkey",)]) { + sh 'echo "$DOCKER_PASSWORD" | docker login $DOCKER_REGISTRY -u "$DOCKER_USERNAME" --password-stdin' + sh """ + cd admin-application + docker build -t $DOCKER_IMAGE --build-arg JAR_FILE=$filepath --build-arg APP_NAME=$serverName . + docker push $DOCKER_IMAGE + """ + } + } + } + } + } + } + + stage('发布服务') { + steps { + container(labelName) { + script{ + //查询服务是否已经创建,如果已经创建再次发布只更新镜像 + def result = "" + def global_config = true + def license = true + def harborkey = true + //服务是否已经部署 + catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE'){ + result = sh(returnStdout: true, script: "kubectl get deployment $serverName -n $namespace ").trim() + } + if(result==""){ + catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE'){ + sh "helm delete $serverName -n $namespace " + } + sh "helm repo update " + sh "helm install --set image.repository=$DOCKER_REGISTRY,image.tag=$DOCKER_TAG,nameOverride=$serverName $serverName do1-chart/base-server --version=$CHART_VERSION -n $namespace " + }else{ + sh "kubectl set image deploy $serverName *=$DOCKER_IMAGE -n $namespace" + } + } + } + } + } + } + + environment { + dockerFile = "Dockerfile" + filepath = " ./target/**.jar " + labelName = "maven" + DOCKER_REGISTRY="$DOCKER_REGISTRY/$namespace/$serverName".replaceAll("//","/") + DOCKER_IMAGE="$DOCKER_REGISTRY:$DOCKER_TAG".replaceAll("//","/") + CHART_VERSION = "1.1.0" + + } + + + // 说明: 参数初始化脚本,默认情况下是注释的,如果需要新增参数可以通过可视化界面添加也可以通过此脚本添加,一般用来初始化和调整顺序,注意,使用此代码片段添加参数,参数会在第一次构建生效,且会覆相同的参数值,以代码片段中的参数配置重新添加,意思就是如果初始化后,下面代码 parameters 没有 注释,那么每次运行后之前添加或修改的参数都会被下面的参数和默认值替换 + parameters { + string(name: 'gitBranch', defaultValue: 'dev', description: '选择需要发布的分支') + string(name: 'gitPath', defaultValue: 'https://git.qiweioa.cn/bi-system/bi-admin.git', description: 'git 仓库地址') + string(name: 'serverName', defaultValue: 'bi-admin-dev', description: 'KubeSphere服务名') + string(name: 'namespace', defaultValue: 'bi-dev', description: '需要发布的k8s命名空间,每个项目都应该有一个命名空间,且在集群中唯一') + string(name: 'gitkey', defaultValue: 'denghuizhi', description: 'git仓库访问凭证配置在流水线访问凭证中,gitkey为全局的配置在Jenkins中,不需要手动创建,模版中的值由主流程传递过来') + string(name: 'harborkey', defaultValue: 'harborkey', description: '镜像仓库访问凭证,harborkey 为默认值,镜像仓库地址发生变更时需要修改此参数,自定义凭证需要在流水线凭证中创建') + string(name: 'DOCKER_REGISTRY', defaultValue: 'harbor.uat.do1.com.cn:6001/do1cloud/', description: '镜像仓库,默认值 harbor.uat.do1.com.cn/do1cloud/ 为公司全局镜像仓库,如有需要可以自行更改,如果使用了其他镜像仓库地址,需要修改dockerkey的值') + string(name: 'DOCKER_TAG', defaultValue: 'dev', description: '镜像版本') + } +} \ No newline at end of file diff --git a/README.md b/README.md index cc4e1e6..8401177 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,18 @@ -# bi-gateway +## 默认访问路径 +- http://网关IP:端口/{nacos服务名}/{RequestMapping的路径},如下图 +```shell +http://127.0.0.1/saas-dubbo-all-application/acc/user?id=2&test=aba +``` +## 打印access log +```jvm +-Dreactor.netty.http.server.accessLogEnabled=true +``` +## 参考资料 +- [基础入门](https://spring.io/guides/gs/gateway/) 官方gateway使用示例 +- [gateway官方文档](https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#the-discoveryclient-route-definition-locator) +12.4节 The DiscoveryClient Route Definition Locator +- [核心重要接口](https://blog.csdn.net/u010647035/article/details/84480626) RouteLocator、RouteDefinitionRouteLocator、DiscoveryClient、DiscoveryClientRouteDefinitionLocator +、重要配置类DiscoveryLocatorProperties、GatewayDiscoveryClientAutoConfiguration以及不同厂家的DiscoveryClient实现配置、RouteRefreshListener +- [路由转发过程](https://zhuanlan.zhihu.com/p/449990867) 通过handler利用RouteLocator的getRoutes实现路由转发 +- [gateway性能优化](https://blog.csdn.net/weixin_42161936/article/details/123395773) 面向ISV的架构 \ No newline at end of file diff --git a/buildDocker.sh b/buildDocker.sh new file mode 100644 index 0000000..2e22cad --- /dev/null +++ b/buildDocker.sh @@ -0,0 +1,3 @@ +docker build -t harbor.uat.do1.com.cn:6001/do1cloud/bi-release/bi-gateway-release:0.5.0 . +docker login -u admin -p Harbor12345 harbor.uat.do1.com.cn:6001 +docker push harbor.uat.do1.com.cn:6001/do1cloud/bi-release/bi-gateway-release:0.5.0 \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..392ec2a --- /dev/null +++ b/pom.xml @@ -0,0 +1,176 @@ + + + 4.0.0 + + + cn.com.do1.do1cloud.bi + bi-denpendencies + 1.0-SNAPSHOT + + + cn.com.do1.do1cloud.bi + bi-gateway + 1.0-SNAPSHOT + + + + 8 + 8 + 2.1.0-SNAPSHOT + 2.1.1.RELEASE + 2.2.1-SNAPSHOT + + + + + org.springframework.cloud + spring-cloud-starter-gateway + 3.0.8 + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework + spring-webmvc + + + org.apache.tomcat.embed + tomcat-embed-core + + + + + + + io.projectreactor.netty + reactor-netty-http + 1.0.29 + + + io.projectreactor.netty + reactor-netty-core + 1.0.29 + + + io.projectreactor + reactor-core + 3.4.27 + + + + org.springframework.cloud + spring-cloud-starter-loadbalancer + 4.0.2 + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + io.springfox + springfox-boot-starter + 3.0.0 + + + org.springframework.cloud + spring-cloud-starter-bootstrap + + + org.springframework.boot + spring-boot-starter-actuator + + + cn.com.do1.do1cloud.bi + bi-model + ${parent.version} + + + org.codehaus.janino + janino + 3.0.16 + + + commons-compiler + org.codehaus.janino + + + + + org.codehaus.janino + commons-compiler + 3.0.16 + + + net.logstash.logback + logstash-logback-encoder + + + com.alibaba.cloud + spring-cloud-starter-alibaba-sentinel + + + com.alibaba.csp + sentinel-datasource-nacos + + + org.springframework.boot + spring-boot-starter-webflux + + + + + + + + cn.com.do1.do1cloud.bi + bi-denpendencies + ${project.version} + pom + import + + + + + + ${project.artifactId} + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot-maven-plugin.version} + + true + cn.com.do1.component.bi.gateway.GatewayApplication + + dqdp-license-${dqdp.license.version}.jar + + + + + cn.com.do1 + dqdp-license + ${dqdp.license.version} + + + + + + repackage + + + + + + + \ No newline at end of file diff --git a/src/main/java/cn/com/do1/component/bi/gateway/GatewayApplication.java b/src/main/java/cn/com/do1/component/bi/gateway/GatewayApplication.java new file mode 100644 index 0000000..9ebd174 --- /dev/null +++ b/src/main/java/cn/com/do1/component/bi/gateway/GatewayApplication.java @@ -0,0 +1,18 @@ +package cn.com.do1.component.bi.gateway; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.cloud.gateway.discovery.GatewayDiscoveryClientAutoConfiguration; +import org.springframework.web.bind.annotation.RestController; + +@SpringBootApplication(exclude = GatewayDiscoveryClientAutoConfiguration.class) +@RestController +@EnableDiscoveryClient +public class GatewayApplication { + + public static void main(String[] args) { + SpringApplication.run(GatewayApplication.class, args); + } + +} diff --git a/src/main/java/cn/com/do1/component/bi/gateway/config/DynamicRoute/DynamicRouteConfig.java b/src/main/java/cn/com/do1/component/bi/gateway/config/DynamicRoute/DynamicRouteConfig.java new file mode 100644 index 0000000..08f39d2 --- /dev/null +++ b/src/main/java/cn/com/do1/component/bi/gateway/config/DynamicRoute/DynamicRouteConfig.java @@ -0,0 +1,21 @@ +package cn.com.do1.component.bi.gateway.config.DynamicRoute; + +import lombok.Data; +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +@Configuration +@RefreshScope +@Data +@Getter +@Setter +@ConfigurationProperties(prefix = "dynamic-routes") +public class DynamicRouteConfig { + private List routeInfos; + +} diff --git a/src/main/java/cn/com/do1/component/bi/gateway/config/DynamicRoute/DynamicRouteConfigConverter.java b/src/main/java/cn/com/do1/component/bi/gateway/config/DynamicRoute/DynamicRouteConfigConverter.java new file mode 100644 index 0000000..42a50a2 --- /dev/null +++ b/src/main/java/cn/com/do1/component/bi/gateway/config/DynamicRoute/DynamicRouteConfigConverter.java @@ -0,0 +1,53 @@ +package cn.com.do1.component.bi.gateway.config.DynamicRoute; + + +import cn.hutool.core.util.ObjectUtil; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 动态路由转换器 + */ +public class DynamicRouteConfigConverter { + + + private final static Map> instanceToTenantIds=new HashMap<>(); + + private final static Map> tenantIdToInstances=new HashMap<>(); + + public static Map> getTenantIdToInstances(){ + return tenantIdToInstances; + } + public static Map> getInstanceToTenantIds(){ + return instanceToTenantIds; + } + + + public static Map> convertToInstanceToTenantIds(List routeInfos){ + for (RouteInfo routeInfo:routeInfos){ + DynamicRouteConfigConverter.instanceToTenantIds.putAll(routeInfo.getInstanceToTenantIds()); + } + return instanceToTenantIds; + } + + + public static Map> convertToTenantIdToInstances(List routeInfos){ + routeInfos.stream().forEach(routeInfo -> { + if (ObjectUtil.isNotEmpty(routeInfo.getInstanceToTenantIds())){ + routeInfo.getInstanceToTenantIds().entrySet().stream().forEach(entry -> entry.getValue().stream().forEach(tenantId -> { + String instanceTenantIdKey = routeInfo.getId() + tenantId; + List instances = tenantIdToInstances.getOrDefault(instanceTenantIdKey, new ArrayList()); + instances.add( entry.getKey()); + tenantIdToInstances.put(instanceTenantIdKey,instances); + })); + } + + }); + return tenantIdToInstances; + + } + +} diff --git a/src/main/java/cn/com/do1/component/bi/gateway/config/DynamicRoute/GatewayConfig.java b/src/main/java/cn/com/do1/component/bi/gateway/config/DynamicRoute/GatewayConfig.java new file mode 100644 index 0000000..a1d59a8 --- /dev/null +++ b/src/main/java/cn/com/do1/component/bi/gateway/config/DynamicRoute/GatewayConfig.java @@ -0,0 +1,95 @@ +package cn.com.do1.component.bi.gateway.config.DynamicRoute; + +import cn.com.do1.component.bi.gateway.filter.ReqDecryptFilter; +import cn.com.do1.component.bi.gateway.filter.RespEncryptFilter; +import cn.hutool.core.util.ObjectUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.route.Route; +import org.springframework.cloud.gateway.route.RouteLocator; +import org.springframework.cloud.gateway.route.builder.GatewayFilterSpec; +import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; +import org.springframework.cloud.gateway.route.builder.UriSpec; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpCookie; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.function.Function; + +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; + +@Configuration +public class GatewayConfig { + + @Autowired + private DynamicRouteConfig dynamicRouteConfig; + + @Bean + public RouteLocator headerRoutes(RouteLocatorBuilder builder){ + RouteLocatorBuilder.Builder routes = builder.routes(); + List routeInfos = dynamicRouteConfig.getRouteInfos(); + if (ObjectUtil.isNotEmpty(routeInfos)){ + DynamicRouteConfigConverter.convertToTenantIdToInstances(routeInfos); + for (RouteInfo routeInfo:routeInfos){ + routes.route(routeInfo.getId(), r ->{ + return r.path(routeInfo.getPredicatesPaths().get(0)) + .filters(new Function() { + @Override + public UriSpec apply(GatewayFilterSpec gatewayFilterSpec) { + Map rewritePathMap = routeInfo.getRewritePathMap(); + if (ObjectUtil.isNotEmpty(rewritePathMap)) { + gatewayFilterSpec.rewritePath(rewritePathMap.get("regexp"),rewritePathMap.get("replacement")); + } + return gatewayFilterSpec.filter(new HeaderToPathGatewayFilter(routeInfo.getId())) + .filter(new ReqDecryptFilter(-25)) + .filter(new RespEncryptFilter(-23)); + } + }).uri(routeInfo.getDefaultUri()); + }); + + } + } + return routes.build(); + } + + + /** + * 租户路由过滤器 + */ + public class HeaderToPathGatewayFilter implements GatewayFilter{ + private String routeInfoId; + + public HeaderToPathGatewayFilter(String routeInfoId) { + this.routeInfoId = routeInfoId; + } + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + List tenantIdCookies = exchange.getRequest().getCookies().get("tenantId"); + if (ObjectUtil.isNotEmpty(tenantIdCookies)){ + String tenantId = tenantIdCookies.get(0).getValue(); + if (ObjectUtil.isEmpty(tenantId)){ + Map> tenantIdToInstances = DynamicRouteConfigConverter.getTenantIdToInstances(); + String instanceTenantIdKey =routeInfoId + tenantId; + if (tenantIdToInstances.containsKey(instanceTenantIdKey)) { + List targetUris = tenantIdToInstances.get(instanceTenantIdKey); + int asInt = new Random().ints(0, targetUris.size()).findFirst().getAsInt(); + Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); + Route newRoute = Route.async().asyncPredicate(route.getPredicate()).filters(route.getFilters()).id(route.getId()) + .order(route.getOrder()).uri(targetUris.get(asInt)).build(); + exchange.getAttributes().put(GATEWAY_ROUTE_ATTR,newRoute); + } + } + } + return chain.filter(exchange); + } + } + + +} diff --git a/src/main/java/cn/com/do1/component/bi/gateway/config/DynamicRoute/RouteInfo.java b/src/main/java/cn/com/do1/component/bi/gateway/config/DynamicRoute/RouteInfo.java new file mode 100644 index 0000000..bcc5480 --- /dev/null +++ b/src/main/java/cn/com/do1/component/bi/gateway/config/DynamicRoute/RouteInfo.java @@ -0,0 +1,108 @@ +package cn.com.do1.component.bi.gateway.config.DynamicRoute; + +import com.alibaba.fastjson.JSON; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 路由信息配置 + */ +public class RouteInfo { + /** + * 路由规则id + */ + private String id; + + /** + * 服务实例对应租户id + */ + private Map> instanceToTenantIds; + + /** + * 默认路由规则 + */ + private String defaultUri; + + /** + * 断言请求路径 + */ + private List predicatesPaths; + + /** + * 过滤器重写路径匹配 + */ + private Map rewritePathMap; + private Map filters; + + public Map getRewritePathMap() { + return rewritePathMap; + } + + public void setRewritePathMap(Map rewritePathMap) { + this.rewritePathMap = rewritePathMap; + } + + public List getPredicatesPaths() { + return predicatesPaths; + } + + public void setPredicatesPaths(List predicatesPaths) { + this.predicatesPaths = predicatesPaths; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Map> getInstanceToTenantIds() { + return instanceToTenantIds; + } + + public void setInstanceToTenantIds(Map> instanceToTenantIds) { + this.instanceToTenantIds = instanceToTenantIds; + } + + public String getDefaultUri() { + return defaultUri; + } + + public void setDefaultUri(String defaultUri) { + this.defaultUri = defaultUri; + } + + public static void main(String[] args) { + RouteInfo routeInfo = new RouteInfo(); + routeInfo.setId("bi-system"); + List strings = new ArrayList<>(); + strings.add("1"); + strings.add("3"); + strings.add("4"); + List strings1 = new ArrayList<>(); + strings1.add("2"); + List predicatesPaths = new ArrayList<>(); + predicatesPaths.add("/system/**"); + Map> stringListHashMap = new HashMap<>(); + stringListHashMap.put("lb://bi-system-1",strings); + stringListHashMap.put("lb://bi-system-2",strings1); + routeInfo.setInstanceToTenantIds(stringListHashMap); + routeInfo.setDefaultUri("lb://bi-system"); + routeInfo.setPredicatesPaths(predicatesPaths); + Map stringStringHashMap = new HashMap<>(); + + stringStringHashMap.put("'/' + serviceId.replace('bi-', '') + '/(?.*)'","'/${remaining}'"); + routeInfo.setRewritePathMap(stringStringHashMap); + List routeInfos = new ArrayList<>(); + routeInfos.add(routeInfo); + String s = JSON.toJSONString(routeInfos); + + List routeInfos1 = JSON.parseArray(s, RouteInfo.class); + System.out.println(s); + } +} diff --git a/src/main/java/cn/com/do1/component/bi/gateway/config/GatewaySwaggerResourcesProvider.java b/src/main/java/cn/com/do1/component/bi/gateway/config/GatewaySwaggerResourcesProvider.java new file mode 100644 index 0000000..7b5bf81 --- /dev/null +++ b/src/main/java/cn/com/do1/component/bi/gateway/config/GatewaySwaggerResourcesProvider.java @@ -0,0 +1,68 @@ +package cn.com.do1.component.bi.gateway.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cloud.gateway.config.GatewayProperties; +import org.springframework.cloud.gateway.route.RouteLocator; +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.HashSet; +import java.util.List; +import java.util.Set; + +/** + * 网关的swagger的配置类 + */ +@Component +@Primary +public class GatewaySwaggerResourcesProvider implements SwaggerResourcesProvider { + /** + * swagger3默认的url后缀 + */ + private static final String SWAGGER2URL = "/v3/api-docs"; + /** + * 网关路由 + */ + @Autowired + private RouteLocator routeLocator; + + @Autowired + private GatewayProperties gatewayProperties; + /** + * 网关应用名称 + */ + @Value("${spring.application.name}") + private String self; + + @Override + public List get() { + List resources = new ArrayList<>(); + List routeHosts = new ArrayList<>(); + // 获取所有可用的host:serviceId + routeLocator.getRoutes().filter(route -> route.getUri().getHost() != null) + .filter(route -> !self.equals(route.getUri().getHost())) + .subscribe(route -> routeHosts.add(route.getUri().getHost())); + // 记录已经添加过的server + Set dealed = new HashSet<>(); + routeHosts.forEach(instance -> { + // 拼接url + String url = "/" + instance.toLowerCase().split("-")[1] + SWAGGER2URL; + if (!dealed.contains(url)) { + dealed.add(url); + SwaggerResource swaggerResource = new SwaggerResource(); + swaggerResource.setUrl(url); + swaggerResource.setName(instance); + resources.add(swaggerResource); + } + }); + return resources; + } + + + + +} \ No newline at end of file diff --git a/src/main/java/cn/com/do1/component/bi/gateway/config/ReqDecryptAndRespEncryptConfig.java b/src/main/java/cn/com/do1/component/bi/gateway/config/ReqDecryptAndRespEncryptConfig.java new file mode 100644 index 0000000..b02c0c7 --- /dev/null +++ b/src/main/java/cn/com/do1/component/bi/gateway/config/ReqDecryptAndRespEncryptConfig.java @@ -0,0 +1,51 @@ +package cn.com.do1.component.bi.gateway.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.context.annotation.Configuration; + +import java.util.ArrayList; +import java.util.List; + +@Configuration +@RefreshScope +@Data +@ConfigurationProperties(prefix = "encrypt-decrypt") +public class ReqDecryptAndRespEncryptConfig { + /** + * 开启req的body解密,true开启 + */ + private Boolean reqDecrypt = false; + /** + * 是否开启Response加密,true开启 + */ + private Boolean respEncrypt = false; + /** + * 加解密白名单,白名单内url不处理 + */ + private List whiteUrl = new ArrayList<>(); + /** + * rsa加密公钥 + */ + private String rsaPublicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC9Z3S1r2wPogXFY2UH2vzBgzUEdvXDq6j0DHnJ5YU5pF" + + "Ipgnz+G1Uf+5a5VzDcANrCT/GVqZAKPwbQM776eDv/xy1i4sS7wsI9m6pKQ3HSAJl1knT36qfKthMd6VdOQLAZuf9pH3ccbpO3GiJNWAfee/nJ" + + "egPK0Xle4Fl114vaiQIDAQAB"; + /** + * rsa加密私钥 + */ + private String rsaPrivateKey = "MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAL1ndLWvbA+iBcVj" + + "ZQfa/MGDNQR29cOrqPQMecnlhTmkUimCfP4bVR/7lrlXMNwA2sJP8ZWpkAo/BtAz" + + "vvp4O//HLWLixLvCwj2bqkpDcdIAmXWSdPfqp8q2Ex3pV05AsBm5/2kfdxxuk7ca" + + "Ik1YB957+cl6A8rReV7gWXXXi9qJAgMBAAECgYBJpwp2gHHoHlxaJs2p4Vl6stgS" + + "FWR6o60+wf82KL/G64RbyfdrJRvUJRS2nBZO5zIqb8YFKfvuUBYJLqYsZkcGA58H" + + "rxIt18bwdtbF3NEgPNLfnMhisD4KcMeBWySe+OlITiFY2IVMtCHAGT/lhSphkTru" + + "UoRc4VfmFRLBy3g/hQJBAN0HeuFQS2zXeAItc6E7YoInTQb01Fm4Ax1pmViQQCke" + + "Z6+Qe15jvktlppYJ0DM96lSPpDcBDJeMqM9vu5anBo8CQQDbXwo1VEkE+cROU+Cc" + + "0YqvxDFJonp9iDza2ZzO1ilo0lLZWKYdG41gFDdSACwCNFq39X769c+eCV2b5iH6" + + "J9lnAkEAvrrarZ1lSMnydCaWljYxflC9plgU+krQ3UunmQX5Z8ImBRjvbHcz2cog" + + "424abG1sTYYaVaChJhGqBj7LqGf/PwJBAIlo18Ed4XsvZEpX+drg2klM0D66epWF" + + "L/E53CInPdr9241vHOYgqwaiwyAnIWnkF2shaH+UV487eJo9pczHB0MCQQCwqgF6" + + "rs6IyoFb/acqVm0mH5o6ROPthW5CobjoO7R3YcKQ1Mr5iHZcYKOPTnibLOlclEsH" + + "CrPUk5sdCSkjd9NP"; +} diff --git a/src/main/java/cn/com/do1/component/bi/gateway/ctl/GatewayController.java b/src/main/java/cn/com/do1/component/bi/gateway/ctl/GatewayController.java new file mode 100644 index 0000000..a569542 --- /dev/null +++ b/src/main/java/cn/com/do1/component/bi/gateway/ctl/GatewayController.java @@ -0,0 +1,31 @@ +package cn.com.do1.component.bi.gateway.ctl; + +import cn.com.do1.component.bi.gateway.config.ReqDecryptAndRespEncryptConfig; +import com.alibaba.fastjson.JSONObject; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/env") +public class GatewayController { + @Autowired + private ReqDecryptAndRespEncryptConfig reqDecryptAndRespEncryptConfig; + + @GetMapping("secret") + public ResponseEntity secret() { + JSONObject json = new JSONObject(); + json.put("code",0); + JSONObject sonJson = new JSONObject(); + if(reqDecryptAndRespEncryptConfig.getReqDecrypt() || reqDecryptAndRespEncryptConfig.getRespEncrypt()){ + sonJson.put("reqDecrypt",reqDecryptAndRespEncryptConfig.getReqDecrypt()); + sonJson.put("respEncrypt",reqDecryptAndRespEncryptConfig.getRespEncrypt()); + sonJson.put("publicKey", reqDecryptAndRespEncryptConfig.getRsaPublicKey()); + sonJson.put("privateKey", reqDecryptAndRespEncryptConfig.getRsaPrivateKey()); + } + json.put("data",sonJson); + return ResponseEntity.ok(json.toString()); + } +} \ No newline at end of file diff --git a/src/main/java/cn/com/do1/component/bi/gateway/filter/ReqDecryptFilter.java b/src/main/java/cn/com/do1/component/bi/gateway/filter/ReqDecryptFilter.java new file mode 100644 index 0000000..60b77c4 --- /dev/null +++ b/src/main/java/cn/com/do1/component/bi/gateway/filter/ReqDecryptFilter.java @@ -0,0 +1,136 @@ +package cn.com.do1.component.bi.gateway.filter; + +import cn.com.do1.component.bi.gateway.config.ReqDecryptAndRespEncryptConfig; +import cn.com.do1.component.bi.gateway.util.SpringContextUtils; +import cn.com.do1.component.bi.gateway.util.encrypt.AESEncryptUtil; +import cn.com.do1.component.bi.gateway.util.encrypt.RSAEncryptUtil; +import com.alibaba.fastjson.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage; +import org.springframework.cloud.gateway.support.BodyInserterContext; +import org.springframework.context.annotation.DependsOn; +import org.springframework.core.Ordered; +import org.springframework.core.io.buffer.DataBuffer; +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.web.reactive.function.BodyInserter; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.server.HandlerStrategies; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +@DependsOn("reqDecryptAndRespEncryptConfig") +public class ReqDecryptFilter implements GatewayFilter, Ordered { + + private static final Logger logger = LoggerFactory.getLogger(ReqDecryptFilter.class); + + private int order; + + private static ReqDecryptAndRespEncryptConfig reqDecryptAndRespEncryptConfig = null; + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + if(reqDecryptAndRespEncryptConfig == null){ + reqDecryptAndRespEncryptConfig = SpringContextUtils.getBean("reqDecryptAndRespEncryptConfig"); + } + ServerHttpRequest request = exchange.getRequest(); + if(!reqDecryptAndRespEncryptConfig.getReqDecrypt()){ + //关闭解密 + return chain.filter(exchange); + } + if(reqDecryptAndRespEncryptConfig.getWhiteUrl().size()>0){ + for(String url : reqDecryptAndRespEncryptConfig.getWhiteUrl()){ + if(request.getURI().getPath().startsWith(url)){ + //白名单url直接跳过 + return chain.filter(exchange); + } + } + } + HttpMethod requestMethod = request.getMethod(); + if (!HttpMethod.POST.equals(requestMethod) && !HttpMethod.PUT.equals(requestMethod)) { + return chain.filter(exchange); + } + // 获取请求的Content-Type + String contentType = request.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE); + if (!MediaType.APPLICATION_JSON_VALUE.equals(contentType)) { + return chain.filter(exchange); + } + return readBody(exchange, chain); + } + ServerHttpRequestDecorator decorate(ServerWebExchange exchange, HttpHeaders headers, CachedBodyOutputMessage outputMessage) { + return new ServerHttpRequestDecorator(exchange.getRequest()) { + public HttpHeaders getHeaders() { + long contentLength = headers.getContentLength(); + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.putAll(super.getHeaders()); + if (contentLength > 0L) { + httpHeaders.setContentLength(contentLength); + } else { + httpHeaders.set("Transfer-Encoding", "chunked"); + } + return httpHeaders; + } + public Flux getBody() { + return outputMessage.getBody(); + } + }; + } + + + private Mono returnMononew(GatewayFilterChain chain,ServerWebExchange exchange){ + return chain.filter(exchange).then(Mono.fromRunnable(()->{ + })); + } + private Mono readBody(ServerWebExchange exchange, GatewayFilterChain chain) { + //重新构造request,参考ModifyRequestBodyGatewayFilterFactory + ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders()); + //重点 + Mono modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> { + String newBody = decryptAES(body); + return Mono.just(newBody); + }); + BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class); + HttpHeaders headers = new HttpHeaders(); + headers.putAll(exchange.getRequest().getHeaders()); + //猜测这个就是之前报400错误的元凶,之前修改了body但是没有重新写content length + headers.remove("Content-Length"); + CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers); + return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> { + ServerHttpRequest decorator = this.decorate(exchange, headers, outputMessage); + return returnMononew(chain, exchange.mutate().request(decorator).build()); + })); + } + private String decryptAES(String encryptedText) { + // 解密逻辑 + JSONObject param = JSONObject.parseObject(encryptedText); + if(param.containsKey("encrypted") && param.containsKey("requestData")){ + try { + String clientKey = RSAEncryptUtil.decryptByPrivateKey(param.getString("encrypted"),reqDecryptAndRespEncryptConfig.getRsaPrivateKey()); + return AESEncryptUtil.decrypt(param.getString("requestData"), clientKey); + } catch (Exception e) { + logger.error("解密接口出现问题",e); + } + }else{ + logger.debug("需要解密内容为空"); + } + return encryptedText; + } + + @Override + public int getOrder() { + return this.order; + } + + + public ReqDecryptFilter(int order){ + this.order = order; + } +} \ No newline at end of file diff --git a/src/main/java/cn/com/do1/component/bi/gateway/filter/RespEncryptFilter.java b/src/main/java/cn/com/do1/component/bi/gateway/filter/RespEncryptFilter.java new file mode 100644 index 0000000..4edf598 --- /dev/null +++ b/src/main/java/cn/com/do1/component/bi/gateway/filter/RespEncryptFilter.java @@ -0,0 +1,116 @@ +package cn.com.do1.component.bi.gateway.filter; + +import cn.com.do1.component.bi.gateway.config.ReqDecryptAndRespEncryptConfig; +import cn.com.do1.component.bi.gateway.util.RandomStringUtils; +import cn.com.do1.component.bi.gateway.util.SpringContextUtils; +import cn.com.do1.component.bi.gateway.util.encrypt.AESEncryptUtil; +import cn.com.do1.component.bi.gateway.util.encrypt.RSAEncryptUtil; +import com.alibaba.fastjson.JSONObject; +import org.reactivestreams.Publisher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.core.Ordered; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DataBufferFactory; +import org.springframework.core.io.buffer.DataBufferUtils; +import org.springframework.core.io.buffer.DefaultDataBufferFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.http.server.reactive.ServerHttpResponseDecorator; +import org.springframework.util.StringUtils; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.nio.charset.StandardCharsets; + +public class RespEncryptFilter implements GatewayFilter, Ordered { + private static final Logger logger = LoggerFactory.getLogger(RespEncryptFilter.class); + + private int order; + private static ReqDecryptAndRespEncryptConfig reqDecryptAndRespEncryptConfig = null; + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + ServerHttpRequest request = exchange.getRequest(); + if(reqDecryptAndRespEncryptConfig == null){ + reqDecryptAndRespEncryptConfig = SpringContextUtils.getBean("reqDecryptAndRespEncryptConfig"); + } + if(!reqDecryptAndRespEncryptConfig.getRespEncrypt()){ + //关闭解密 + return chain.filter(exchange); + } + if(reqDecryptAndRespEncryptConfig.getWhiteUrl().size()>0){ + for(String url : reqDecryptAndRespEncryptConfig.getWhiteUrl()){ + if(request.getURI().getPath().startsWith(url)){ + //白名单url直接跳过 + return chain.filter(exchange); + } + } + } + ServerHttpResponse originalResponse = exchange.getResponse(); + originalResponse.getHeaders().setContentType(MediaType.APPLICATION_JSON); + DataBufferFactory bufferFactory = originalResponse.bufferFactory(); + ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) { + @Override + public Mono writeWith(Publisher body) { + if (getStatusCode().equals(HttpStatus.OK) && body instanceof Flux) { + Flux fluxBody = Flux.from(body); + return super.writeWith(fluxBody.buffer().map(dataBuffer -> { + DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory(); + DataBuffer join = dataBufferFactory.join(dataBuffer); + byte[] content = new byte[join.readableByteCount()]; + join.read(content); + //释放掉内存 + DataBufferUtils.release(join); + // 正常返回的数据 + String rootData = new String(content, StandardCharsets.UTF_8); + logger.debug("--------"+request.getURI().getPath()+";"+rootData); + JSONObject result = JSONObject.parseObject(rootData); + if(!result.containsKey("code")){ + return bufferFactory.wrap(rootData.getBytes()); + } + JSONObject json = new JSONObject(); + json.put("code", result.getInteger("code")); + json.put("message", result.getString("message")); + json.put("traceId", result.getString("traceId")); + try { + String data = result.getString("data"); + if(result.containsKey("code") && 0 == result.getInteger("code") && StringUtils.hasLength(data)){ + String aesKey = RandomStringUtils.generateRandomString(16); + String key = RSAEncryptUtil.encryptByPublicKeyToBase64(aesKey.getBytes(),reqDecryptAndRespEncryptConfig.getRsaPublicKey()); + String encryptData = AESEncryptUtil.encrypt(data,aesKey); + JSONObject sonJson = new JSONObject(); + sonJson.put("encrypted", key); + sonJson.put("responseData", encryptData); + json.put("data",sonJson); + }else{ + json.put("data", null); + } + } catch (Exception e) { + logger.error("加密response失败",e); + } + // 加密后的数据返回给客户端 + byte[] uppedContent = json.toJSONString().getBytes(StandardCharsets.UTF_8); + originalResponse.getHeaders().setContentLength(uppedContent.length); + return bufferFactory.wrap(uppedContent); + })); + } + return super.writeWith(body); + } + }; + return chain.filter(exchange.mutate().response(decoratedResponse).build()); + } + @Override + public int getOrder() { + return this.order; + } + + public RespEncryptFilter(int order){ + this.order = order; + } +} \ No newline at end of file diff --git a/src/main/java/cn/com/do1/component/bi/gateway/util/RandomStringUtils.java b/src/main/java/cn/com/do1/component/bi/gateway/util/RandomStringUtils.java new file mode 100644 index 0000000..5e9f8eb --- /dev/null +++ b/src/main/java/cn/com/do1/component/bi/gateway/util/RandomStringUtils.java @@ -0,0 +1,18 @@ +package cn.com.do1.component.bi.gateway.util; + +import java.util.Random; + +public class RandomStringUtils { + + private static final String CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + + public static String generateRandomString(int length) { + StringBuilder sb = new StringBuilder(length); + Random random = new Random(); + for (int i = 0; i < length; i++) { + int index = random.nextInt(CHARACTERS.length()); + sb.append(CHARACTERS.charAt(index)); + } + return sb.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/cn/com/do1/component/bi/gateway/util/SpringContextUtils.java b/src/main/java/cn/com/do1/component/bi/gateway/util/SpringContextUtils.java new file mode 100644 index 0000000..433e27a --- /dev/null +++ b/src/main/java/cn/com/do1/component/bi/gateway/util/SpringContextUtils.java @@ -0,0 +1,31 @@ +package cn.com.do1.component.bi.gateway.util; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +@Component +public class SpringContextUtils implements ApplicationContextAware { + private static ApplicationContext applicationContext; + + public SpringContextUtils() { + } + + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + SpringContextUtils.applicationContext = applicationContext; + } + + public static ApplicationContext getContext() { + return applicationContext; + } + + public static T getBean(String name) { + return (T) applicationContext.getBean(name); + } + + public static T getBean(Class clazz) { + return applicationContext.getBean(clazz); + } + +} \ No newline at end of file diff --git a/src/main/java/cn/com/do1/component/bi/gateway/util/encrypt/AESEncryptUtil.java b/src/main/java/cn/com/do1/component/bi/gateway/util/encrypt/AESEncryptUtil.java new file mode 100644 index 0000000..18c2e74 --- /dev/null +++ b/src/main/java/cn/com/do1/component/bi/gateway/util/encrypt/AESEncryptUtil.java @@ -0,0 +1,73 @@ +package cn.com.do1.component.bi.gateway.util.encrypt; + +import lombok.extern.slf4j.Slf4j; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import java.io.IOException; +import java.util.Base64; + +@Slf4j +public class AESEncryptUtil { + + public AESEncryptUtil() { + } + + /** + * 加密 + * @param input + * @param key + * @return + */ + public static String encrypt(String input, String key) { + + String result = null; + try { + byte[] crypted = null; + SecretKeySpec skey = new SecretKeySpec(key.getBytes(), "AES"); + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, skey); + crypted = cipher.doFinal(input.getBytes("UTF-8")); + result = encryptBASE64(crypted); + } catch (Exception e) { + System.out.println(e.toString()); + } + return result; + } + public static void main(String[] args) throws Exception { + String key = "niaze7pp0zho1mgp"; + String str = encrypt("{\"type\":2,\"pageSize\":50,\"currPage\":1}",key); + System.out.println(str); + System.out.println(decrypt(str,key)); + } + /** + * 解密 + * @param input + * @param key + * @return + */ + public static String decrypt(String input, String key) { + String result = null; + try { + byte[] output = null; + SecretKeySpec skey = new SecretKeySpec(key.getBytes(), "AES"); + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, skey); + output = cipher.doFinal(decryptBASE64(input)); + result = new String(output, "UTF-8"); + } catch (Exception e) { + log.info(e.toString()); + } + return result; + } + + public static byte[] decryptBASE64(String key) throws IOException { + return Base64.getDecoder().decode(key); + } + + public static String encryptBASE64(byte[] key) { + return Base64.getEncoder().encodeToString(key); + } + + +} diff --git a/src/main/java/cn/com/do1/component/bi/gateway/util/encrypt/RSAEncryptUtil.java b/src/main/java/cn/com/do1/component/bi/gateway/util/encrypt/RSAEncryptUtil.java new file mode 100644 index 0000000..8d6a2aa --- /dev/null +++ b/src/main/java/cn/com/do1/component/bi/gateway/util/encrypt/RSAEncryptUtil.java @@ -0,0 +1,144 @@ +package cn.com.do1.component.bi.gateway.util.encrypt; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import java.io.UnsupportedEncodingException; +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +public class RSAEncryptUtil { + private static final Logger logger = LoggerFactory.getLogger(RSAEncryptUtil.class); + public RSAEncryptUtil() { + } + + public static String encryptBASE64(byte[] key) { + return Base64.getEncoder().encodeToString(key); + } + + public static byte[] decryptBASE64(String key) { + return Base64.getDecoder().decode(key); + } + + public static Map initKey() throws Exception { + Map keyMap = new HashMap(2); + KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); + keyPairGen.initialize(1024); + KeyPair keyPair = keyPairGen.generateKeyPair(); + RSAPublicKey publicKey = (RSAPublicKey)keyPair.getPublic(); + RSAPrivateKey privateKey = (RSAPrivateKey)keyPair.getPrivate(); + keyMap.put("RSAPublicKey", publicKey); + keyMap.put("RSAPrivateKey", privateKey); + return keyMap; + } + + public static String getPrivateKey(Map keyMap) { + Key key = (Key)keyMap.get("RSAPrivateKey"); + return encryptBASE64(key.getEncoded()); + } + + public static String getPublicKey(Map keyMap) { + Key key = (Key)keyMap.get("RSAPublicKey"); + return encryptBASE64(key.getEncoded()); + } + + public static byte[] decryptByPublicKey(byte[] data, String key) throws Exception { + byte[] keyBytes = decryptBASE64(key); + X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + Key publicKey = keyFactory.generatePublic(x509KeySpec); + Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); + cipher.init(2, publicKey); + return cipher.doFinal(data); + } + + public static String decryptByPrivateKey(String data, String key) throws Exception { + byte[] keyBytes = decryptBASE64(key); + PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec); + Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); + cipher.init(2, privateKey); + //当长度过长的时候,需要分割后解密 128个字节 + return new String(getMaxResultDecrypt(data, cipher)); + } + + private static byte[] getMaxResultDecrypt(String data, Cipher cipher) throws IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException { + byte[] inputArray = decryptBASE64(data); + int inputLength = inputArray.length; + // 最大解密字节数,超出最大字节数需要分组加密 + int MAX_ENCRYPT_BLOCK = 128; + // 标识 + int offSet = 0; + byte[] resultBytes = {}; + byte[] cache = {}; + while (inputLength - offSet > 0) { + if (inputLength - offSet > MAX_ENCRYPT_BLOCK) { + cache = cipher.doFinal(inputArray, offSet, MAX_ENCRYPT_BLOCK); + offSet += MAX_ENCRYPT_BLOCK; + } else { + cache = cipher.doFinal(inputArray, offSet, inputLength - offSet); + offSet = inputLength; + } + resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length); + System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length); + } + return resultBytes; + } + + public static byte[] encryptByPublicKey(byte[] data, String key) throws Exception { + byte[] keyBytes = decryptBASE64(key); + X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + Key publicKey = keyFactory.generatePublic(x509KeySpec); + Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); + cipher.init(1, publicKey); + return cipher.doFinal(data); + } + + public static byte[] encryptByPrivateKey(byte[] data, String key) throws Exception { + byte[] keyBytes = decryptBASE64(key); + PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec); + Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); + cipher.init(1, privateKey); + return cipher.doFinal(data); + } + + + + public static void main(String[] args) throws Exception { + Map keyMap = initKey(); + String publicKey = getPublicKey(keyMap); + String privateKey = getPrivateKey(keyMap); + System.out.println("公钥: \n\r" + publicKey); + String str = encryptByPublicKeyToBase64("crkXipxtMn5I4KXwwLMkeA==".getBytes(),publicKey); + System.out.println("公钥加密后: \n\r" + str); + System.out.println("私钥: \n\r" + privateKey); + //System.out.println("私钥解密后: \n\r" + decryptByPrivateKey(str.getBytes(),privateKey)); + } + + public static String encryptByPublicKeyToBase64(byte[] data, String key) throws Exception { + byte[] keyBytes = decryptBASE64(key); + X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + Key publicKey = keyFactory.generatePublic(x509KeySpec); + Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); + cipher.init(1, publicKey); + return Base64.getEncoder().encodeToString(cipher.doFinal(data)); + } +} diff --git a/src/main/resources/bootstrap-dev.yml b/src/main/resources/bootstrap-dev.yml new file mode 100644 index 0000000..a327b02 --- /dev/null +++ b/src/main/resources/bootstrap-dev.yml @@ -0,0 +1,32 @@ +server: + port: 80 +spring: + application: + name: bi-gateway + cloud: + nacos: + discovery: + server-addr: 192.168.83.10:32610 + namespace: bi-system + config: + server-addr: 192.168.83.10:32610 + file-extension: yml + namespace: bi-system + group: DEFAULT_GROUP + # 配置组,优先级从上至下递增,最下方的优先级最高 + shared-configs: + - data-id: global.yml + - data-id: bi-gateway.yml + + gateway: + discovery: + locator: + enabled: true + lower-case-service-id: true #小写的服务名称 + predicates[0]: + name: Path + args[pattern]: "'/'+ serviceId.replace('bi-', '') +'/**'" + filters[0]: + name: CustomRewritePath + args[regexp]: "'/' + serviceId.replace('bi-', '') + '/(?.*)'" + args[replacement]: "'/${remaining}'" diff --git a/src/main/resources/bootstrap-home.yml b/src/main/resources/bootstrap-home.yml new file mode 100644 index 0000000..e3c0871 --- /dev/null +++ b/src/main/resources/bootstrap-home.yml @@ -0,0 +1,42 @@ +server: + port: 80 +spring: + application: + name: saas-cpn-gateway + cloud: + nacos: + discovery: + server-addr: 127.0.0.1:8848 + config: + server-addr: 127.0.0.1:8848 + file-extension: properties + namespace: public + group: DEFAULT_GROUP + extension-configs[0]: + data-id: redis.properties + group: DEFAULT_GROUP + refresh: true + extension-configs[1]: + data-id: global.properties + group: DEFAULT_GROUP + refresh: true + gateway: + discovery: + locator: + enabled: true + lower-case-service-id: true #小写的服务名称 + predicates[0]: + name: Path + args[pattern]: "'/'+serviceId+'/**'" + filters[0]: + name: CustomRewritePath + args[regexp]: "'/' + serviceId + '/?(?.*)'" + args[replacement]: "'/${remaining}'" + routes: + - id: default_to_all + uri: lb://saas-dubbo-all-application + order: 10 + predicates: + - Path=/** + filters: + - RewritePath=/saas-dubbo-[-\w]+/, / \ No newline at end of file diff --git a/src/main/resources/bootstrap-local.yml b/src/main/resources/bootstrap-local.yml new file mode 100644 index 0000000..bbab0df --- /dev/null +++ b/src/main/resources/bootstrap-local.yml @@ -0,0 +1,32 @@ +server: + port: 80 +spring: + application: + name: bi-gateway +# profiles: +# active: local #启动环境 dev:开发环境 local:本地环境 prod:prod环境 + cloud: + nacos: + discovery: + server-addr: 192.168.83.10:32610 + namespace: bi-system-local + config: + server-addr: 192.168.83.10:32610 + file-extension: yml + namespace: bi-system-local + group: DEFAULT_GROUP + # 配置组,优先级从上至下递增,最下方的优先级最高 + shared-configs: + - data-id: bi-gateway.yml + gateway: + discovery: + locator: + enabled: true + lower-case-service-id: true #小写的服务名称 + predicates[0]: + name: Path + args[pattern]: "'/'+ serviceId.replace('bi-', '') +'/**'" + filters[0]: + name: CustomRewritePath + args[regexp]: "'/' + serviceId.replace('bi-', '') + '/(?.*)'" + args[replacement]: "'/${remaining}'" diff --git a/src/main/resources/bootstrap-qa.yml b/src/main/resources/bootstrap-qa.yml new file mode 100644 index 0000000..86db8a5 --- /dev/null +++ b/src/main/resources/bootstrap-qa.yml @@ -0,0 +1,27 @@ +server: + port: 80 +spring: + application: + name: bi-gateway + cloud: + nacos: + discovery: + server-addr: 192.168.83.10:32610 + namespace: bi-system + config: + server-addr: 192.168.83.10:32610 + file-extension: yml + namespace: bi-system + group: DEFAULT_GROUP + gateway: + discovery: + locator: + enabled: true + lower-case-service-id: true #小写的服务名称 + predicates[0]: + name: Path + args[pattern]: "'/'+ serviceId.replace('bi-', '') +'/**'" + filters[0]: + name: CustomRewritePath + args[regexp]: "'/' + serviceId.replace('bi-', '') + '/(?.*)'" + args[replacement]: "'/${remaining}'" diff --git a/src/main/resources/bootstrap-sentinel.yml b/src/main/resources/bootstrap-sentinel.yml new file mode 100644 index 0000000..7cadf86 --- /dev/null +++ b/src/main/resources/bootstrap-sentinel.yml @@ -0,0 +1,49 @@ +spring: + cloud: + sentinel: + enabled: false # sentinel开关 + eager: false + transport: + port: 8089 + dashboard: http://127.0.0.1:8858 # Sentinel控制台地址 + datasource: + # 流控规则 + flow: + nacos: + server-addr: ${spring.cloud.nacos.discovery.server-addr} + dataId: ${spring.application.name}-flow-rules + namespace: sentinel + file-extension: json + rule-type: flow + # 降级规则 + degrade: + nacos: + server-addr: ${spring.cloud.nacos.discovery.server-addr} + dataId: ${spring.application.name}-degrade-rules + namespace: sentinel + file-extension: json + rule-type: degrade + # 热点规则 + param-flow: + nacos: + server-addr: ${spring.cloud.nacos.discovery.server-addr} + dataId: ${spring.application.name}-param-flow-rules + namespace: sentinel + file-extension: json + rule-type: param-flow + # 系统规则 + system: + nacos: + server-addr: ${spring.cloud.nacos.discovery.server-addr} + dataId: ${spring.application.name}-system-rules + namespace: sentinel + file-extension: json + rule-type: system + # 授权规则 + authority: + nacos: + server-addr: ${spring.cloud.nacos.discovery.server-addr} + dataId: ${spring.application.name}-authority-rules + namespace: sentinel + file-extension: json + rule-type: authority \ No newline at end of file diff --git a/src/main/resources/bootstrap-uat.yml b/src/main/resources/bootstrap-uat.yml new file mode 100644 index 0000000..86db8a5 --- /dev/null +++ b/src/main/resources/bootstrap-uat.yml @@ -0,0 +1,27 @@ +server: + port: 80 +spring: + application: + name: bi-gateway + cloud: + nacos: + discovery: + server-addr: 192.168.83.10:32610 + namespace: bi-system + config: + server-addr: 192.168.83.10:32610 + file-extension: yml + namespace: bi-system + group: DEFAULT_GROUP + gateway: + discovery: + locator: + enabled: true + lower-case-service-id: true #小写的服务名称 + predicates[0]: + name: Path + args[pattern]: "'/'+ serviceId.replace('bi-', '') +'/**'" + filters[0]: + name: CustomRewritePath + args[regexp]: "'/' + serviceId.replace('bi-', '') + '/(?.*)'" + args[replacement]: "'/${remaining}'" diff --git a/src/main/resources/bootstrap-ybg.yml b/src/main/resources/bootstrap-ybg.yml new file mode 100644 index 0000000..57becb0 --- /dev/null +++ b/src/main/resources/bootstrap-ybg.yml @@ -0,0 +1,43 @@ +server: + port: 80 +spring: + application: + name: saas-cpn-gateway + cloud: + nacos: + discovery: + server-addr: 192.168.83.219:8848 + namespace: ybg + config: + server-addr: 192.168.83.219:8848 + file-extension: properties + namespace: ybg + group: DEFAULT_GROUP + extension-configs[0]: + data-id: redis.properties + group: DEFAULT_GROUP + refresh: true + extension-configs[1]: + data-id: global.properties + group: DEFAULT_GROUP + refresh: true + gateway: + discovery: + locator: + enabled: true + lower-case-service-id: true #小写的服务名称 + predicates[0]: + name: Path + args[pattern]: "'/'+serviceId+'/**'" + filters[0]: + name: CustomRewritePath + args[regexp]: "'/' + serviceId + '/?(?.*)'" + args[replacement]: "'/${remaining}'" + routes: + - id: default_to_all + uri: lb://wxqyh + order: 10 + predicates: + - Path=/** + filters: + - RewritePath=/saas-dubbo-[-\w]+/, / \ No newline at end of file diff --git a/src/main/resources/bootstrap.yml b/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..d563864 --- /dev/null +++ b/src/main/resources/bootstrap.yml @@ -0,0 +1,34 @@ +server: + port: 80 +spring: + application: + name: bi-gateway + profiles: + active: local #启动环境 dev:开发环境 local:本地环境 prod:prod环境 + include: sentinel + cloud: + nacos: + discovery: + server-addr: 192.168.83.10:32610 + namespace: bi-system + config: + server-addr: 192.168.83.10:32610 + file-extension: yml + namespace: bi-system + group: DEFAULT_GROUP + # 配置组,优先级从上至下递增,最下方的优先级最高 + shared-configs: + - data-id: global.yml + - data-id: bi-gateway.yml + gateway: + discovery: + locator: + enabled: true + lower-case-service-id: true #小写的服务名称 + predicates[0]: + name: Path + args[pattern]: "'/'+ serviceId.replace('bi-', '') +'/**'" + filters[0]: + name: CustomRewritePath + args[regexp]: "'/' + serviceId.replace('bi-', '') + '/(?.*)'" + args[replacement]: "'/${remaining}'" diff --git a/src/main/resources/logback-logstash.xml b/src/main/resources/logback-logstash.xml new file mode 100644 index 0000000..ca82b22 --- /dev/null +++ b/src/main/resources/logback-logstash.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + ${LOG_PATTERN} + UTF-8 + + + + + + + ${LOG_HOME}/${application}.log.%d{yyyy-MM-dd}.%i + + + 1024MB + + + 30 + + + + ${LOG_PATTERN} + UTF-8 + + + + + + + ${logStashHost}:${logStashPort} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file