diff --git a/README.md b/README.md index fc0279ad..5c5afaf2 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,8 @@ SOP封装了开放平台大部分功能包括:签名验证、统一异常处 - 监控日志 - 支持nacos - 网关动态修改参数 +- 预发布/灰度环境切换 +- 支持eureka注册中心 ## 界面预览 @@ -69,7 +71,7 @@ SOP封装了开放平台大部分功能包括:签名验证、统一异常处 - doc:开发文档 - sop-common:公共模块,封装常用功能,包含签名校验、错误处理、限流等功能 -- sop-gateway:网关,统一访问入口,Spring Cloud Zuul实现,可切换成Spring Cloud Gateway +- sop-gateway:网关,统一访问入口,含`Spring Cloud Zuul`和`Spring Cloud Gateway`实现 - sop-example:微服务示例,含springboot,springmvc示例 - sop-website:开放平台对应网站,提供文档API、沙箱测试等内容 - sop-auth:应用授权服务 @@ -79,11 +81,9 @@ SOP封装了开放平台大部分功能包括:签名验证、统一异常处 ## 分支说明 -- master:发版分支 -- spring-cloud-gateway:Spring Cloud Gateway作为网关(不推荐) +- master:发版分支(当前为3.0版本,2.x版本见`2.x`分支) - develop:日常开发分支 - eureka:使用eureka注册中心 -- 1.x:老的1.x版本代码 ## 相关文档 diff --git a/changelog.md b/changelog.md index 30cdc776..e81dc56c 100644 --- a/changelog.md +++ b/changelog.md @@ -1,12 +1,13 @@ # changelog -## 2.5.12 - -- 修复限流BUG - -## 2.5.11 - -- 优化灰度/预发布请求 +## 3.0.0 + +- 重构spring cloud gateway网关 +- 重构`预发布/灰度发布环境选择` +- zuul和gateway网关二合一,可随意切换 +- 精简配置文件 +- 优化文档中心页面 +- 优化接口限流 ## 2.5.10 diff --git a/doc/docs/_sidebar.md b/doc/docs/_sidebar.md index d72faede..c1076802 100644 --- a/doc/docs/_sidebar.md +++ b/doc/docs/_sidebar.md @@ -1,35 +1,38 @@ -* [首页](/?t=1574408534264) +* [首页](/?t=1579512230184) * 开发文档 - * [快速体验](files/10010_快速体验.md?t=1574408534265) - * [项目接入到SOP](files/10011_项目接入到SOP.md?t=1574408534281) - * [新增接口](files/10020_新增接口.md?t=1574408534281) - * [开发流程](files/10021_开发流程.md?t=1574408534281) - * [业务参数校验](files/10030_业务参数校验.md?t=1574408534281) - * [错误处理](files/10040_错误处理.md?t=1574408534281) - * [编写文档](files/10041_编写文档.md?t=1574408534282) - * [接口交互详解](files/10050_接口交互详解.md?t=1574408534282) - * [easyopen支持](files/10070_easyopen支持.md?t=1574408534282) - * [使用签名校验工具](files/10080_使用签名校验工具.md?t=1574408534282) - * [ISV管理](files/10085_ISV管理.md?t=1574408534282) - * [自定义返回结果](files/10087_自定义返回结果.md?t=1574408534282) - * [自定义过滤器](files/10088_自定义过滤器.md?t=1574408534282) - * [自定义校验token](files/10089_自定义校验token.md?t=1574408534282) - * [路由授权](files/10090_路由授权.md?t=1574408534282) - * [接口限流](files/10092_接口限流.md?t=1574408534282) - * [监控日志](files/10093_监控日志.md?t=1574408534282) - * [SDK开发](files/10095_SDK开发.md?t=1574408534283) - * [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1574408534283) - * [应用授权](files/10097_应用授权.md?t=1574408534283) - * [提供restful接口](files/10100_提供restful接口.md?t=1574408534283) - * [文件上传](files/10104_文件上传.md?t=1574408534283) - * [配置Sleuth链路追踪](files/10109_配置Sleuth链路追踪.md?t=1574408534283) - * [预发布灰度发布](files/10110_预发布灰度发布.md?t=1574408534283) - * [动态修改请求参数](files/10111_动态修改请求参数.md?t=1574408534283) - * [使用eureka](files/10112_使用eureka.md?t=1574408534283) - * [扩展其它注册中心](files/10113_扩展其它注册中心.md?t=1574408534283) + * [快速体验](files/10010_快速体验.md?t=1579512230186) + * [项目接入到SOP](files/10011_项目接入到SOP.md?t=1579512230207) + * [新增接口](files/10020_新增接口.md?t=1579512230208) + * [开发流程](files/10021_开发流程.md?t=1579512230208) + * [业务参数校验](files/10030_业务参数校验.md?t=1579512230208) + * [错误处理](files/10040_错误处理.md?t=1579512230208) + * [编写文档](files/10041_编写文档.md?t=1579512230208) + * [接口交互详解](files/10050_接口交互详解.md?t=1579512230208) + * [easyopen支持](files/10070_easyopen支持.md?t=1579512230208) + * [使用签名校验工具](files/10080_使用签名校验工具.md?t=1579512230209) + * [ISV管理](files/10085_ISV管理.md?t=1579512230209) + * [自定义返回结果](files/10087_自定义返回结果.md?t=1579512230209) + * [自定义过滤器](files/10088_自定义过滤器.md?t=1579512230209) + * [自定义校验token](files/10089_自定义校验token.md?t=1579512230209) + * [路由授权](files/10090_路由授权.md?t=1579512230209) + * [接口限流](files/10092_接口限流.md?t=1579512230209) + * [监控日志](files/10093_监控日志.md?t=1579512230209) + * [SDK开发](files/10095_SDK开发.md?t=1579512230210) + * [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1579512230210) + * [应用授权](files/10097_应用授权.md?t=1579512230210) + * [提供restful接口](files/10100_提供restful接口.md?t=1579512230210) + * [文件上传](files/10104_文件上传.md?t=1579512230210) + * [配置Sleuth链路追踪](files/10109_配置Sleuth链路追踪.md?t=1579512230210) + * [预发布灰度发布](files/10110_预发布灰度发布.md?t=1579512230211) + * [动态修改请求参数](files/10111_动态修改请求参数.md?t=1579512230211) + * [使用eureka](files/10112_使用eureka.md?t=1579512230211) + * [扩展其它注册中心](files/10113_扩展其它注册中心.md?t=1579512230211) * 原理分析 - * [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1574408534283) - * [原理分析之如何存储路由](files/90011_原理分析之如何存储路由.md?t=1574408534283) - * [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1574408534283) - * [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1574408534284) - * [常见问题](files/90100_常见问题.md?t=1574408534284) + * [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1579512230211) + * [原理分析之如何存储路由](files/90011_原理分析之如何存储路由.md?t=1579512230211) + * [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1579512230211) + * [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1579512230211) + * [原理分析之预发布灰度发布](files/90014_原理分析之预发布灰度发布.md?t=1579512230211) + * [2.x升3.x注意事项](files/90099_2.x升3.x注意事项.md?t=1579512230212) + * [常见问题](files/90100_常见问题.md?t=1579512230212) + * [网关性能测试](files/90999_网关性能测试.md?t=1579512230212) diff --git a/doc/docs/files/10010_快速体验.md b/doc/docs/files/10010_快速体验.md index 5f374f39..8029b969 100644 --- a/doc/docs/files/10010_快速体验.md +++ b/doc/docs/files/10010_快速体验.md @@ -3,7 +3,7 @@ > 运行环境:JDK8,Maven3,[Nacos](https://nacos.io/zh-cn/docs/what-is-nacos.html),Mysql - 安装并启动Nacos,[安装教程](https://nacos.io/zh-cn/docs/quick-start.html) -- 执行Mysql脚本`sop.sql` +- 执行Mysql脚本`sop.sql`(Mysql版本5.6+),5.6以下运行`sop-mysql5.6以下版本.sql` - IDE安装lombok插件,然后打开项目(IDEA下可以打开根pom.xml,然后open as project) - 启动网关:打开sop-gateway下的`application-dev.properties` 1. 修改数据库`username/password` @@ -22,4 +22,28 @@ 登录账号:admin/123456 +## 启动文档中心 +文档中心代码在sop-website工程中 + +- 确保注册中心、网关、微服务正常启动 +- 修改sop-website下的application-dev.properties相关配置 +- 运行WebsiteServerApplication.java +- 访问http://localhost:8083 + +## 基本配置 + +在`sop-gateway`下的application-dev.properties配置,各项配置说明如下 + +```properties +# 忽略验证,设置true,则所有接口不会进行签名校验,默认false +sop.api-config.ignore-validate=false +# 是否对结果进行合并,默认true +sop.api-config.merge-result=true +# 显示返回sign,默认true +sop.api-config.show-return-sign=true +# 是否开启限流功能,默认true +sop.api-config.open-limit=true +# 请求超时时间,默认5分钟,即允许在5分钟内重复请求,默认300 +sop.api-config.timeout-seconds=300 +``` \ No newline at end of file diff --git a/doc/docs/files/10096_使用SpringCloudGateway.md b/doc/docs/files/10096_使用SpringCloudGateway.md index dc74bbc6..68d59c37 100644 --- a/doc/docs/files/10096_使用SpringCloudGateway.md +++ b/doc/docs/files/10096_使用SpringCloudGateway.md @@ -1,92 +1,16 @@ # 使用SpringCloudGateway -SOP默认网关是使用Spring Cloud Zuul,您也可以切换成Spring Cloud Gateway,完整代码见`spring-cloud-gateway`分支。 - -如果您的系统并发量不大,建议使用zuul,因为zuul的功能更全面,有新功能会优先实现在zuul上。 - -- SOP中 Spring Cloud Zuul 和 Spring Cloud Gateway功能对比 - -| 功能 | Spring Cloud Zuul | Spring Cloud Gateway | -| ----- | ---- | ----------------------- | -| 签名验证|√ | √ | -| 统一异常处理|√ | √ | -| 统一返回内容|√ | √ | -| session管理|√ | √ | -| 秘钥管理|√ | √ | -| 微服务端自动验证(JSR-303)|√ | √ | -| 文件上传|√ | √ | -| 文件下载|√ | √ | -| 接口限流|√ | √ | -| 文档整合|√ | √ | -| 应用授权|√ | √ | -| 监控日志|√ | √ | -| 支持nacos|√ | √ | -| 网关动态修改参数|√ | √ | - -使用Spring Cloud Gateway步骤如下: - -- 打开sop-gateway/pom.xml,注释spring cloud zuul依赖,打开Spring Cloud Gateway依赖 +修改`sop-gateway/pom.xml`配置,artifactId部分改成`sop-bridge-gateway`即可 ```xml - - - - - - - org.springframework.cloud - spring-cloud-starter-gateway - - org.springframework.boot - spring-boot-starter-webflux + com.gitee.sop + + + + sop-bridge-gateway + 对应版本 - - org.springframework.boot - spring-boot-starter-actuator - - - -``` - -- 打开启动类`SopGatewayApplication.java`, 注释zuul相关注解 - -```java -package com.gitee.sop.gateway; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -//import org.springframework.cloud.netflix.zuul.EnableZuulProxy; - -// 开启网关功能 -//@EnableZuulProxy -@SpringBootApplication -public class SopGatewayApplication { - - public static void main(String[] args) { - SpringApplication.run(SopGatewayApplication.class, args); - } - -} -``` - -- 禁用ZuulConfig类,注释掉@Configuration注解即可 - -```java -//@Configuration -public class ZuulConfig extends AlipayZuulConfiguration {...} -``` - -- 启用GatewayConfig类,打开@Configuration注释 - -```java -@Configuration -public class GatewayConfig extends AlipayGatewayConfiguration {...} ``` 修改完毕,重启sop-gateway diff --git a/doc/docs/files/10110_预发布灰度发布.md b/doc/docs/files/10110_预发布灰度发布.md index 74370b5c..6b469196 100644 --- a/doc/docs/files/10110_预发布灰度发布.md +++ b/doc/docs/files/10110_预发布灰度发布.md @@ -4,43 +4,60 @@ ## 使用预发布 -假设网关工程在阿里云负载均衡有两台服务器,域名分别为: +SOP中预发布的思路如下: + +假设网关工程sop-gateway在阿里云负载均衡有两台服务器,域名分别为: |域名|说明| |:---- |:---- | |open1.domain.com |网关服务器1 | |openpre.domain.com | 网关服务器2,作为预发布请求入口| -线上网关入口为`http://open.domain.com/api`,请求网关`http://open.domain.com/api`会负载均衡到这两台服务器 +SLB对外域名为:`open.domain.com`,即开放平台入口为:`http://open.domain.com` + +访问`open.domain.com`会负载均衡到`open1.domain.com`和`openpre.domain.com`这两台实例 + +如果单独从`openpre.domain.com`访问,则会访问到预发布微服务。 SOP开启预发布步骤如下: 修改网关工程配置文件,指定预发布域名 ```properties -# 预发布网关域名 +# 预发布网关域名,多个用英文逗号(,)隔开 pre.domain=openpre.domain.com ``` + 重启网关 + 微服务启动参数添加:`--spring.cloud.nacos.discovery.metadata.env=pre`(eureka下是:`--eureka.instance.metadata-map.env=pre`)。 -建议线上配两套启动脚本,其中预发布启动脚本添加启动参数`--eureka.instance.metadata-map.env=pre` +建议线上配两套启动脚本,其中预发布启动脚本添加启动参数`--spring.cloud.nacos.discovery.metadata.env=pre` + +登录SOP-Admin,在服务列表中点击预发布。 -登录SOP-Admin,在服务列表中点击预发布,然后预发布请求地址变成:`http://openpre.domain.com/api`。 -从`openpre.domain.com`请求进来的用户都会进预发布服务器,其它情况都走非预发布服务器。 +从`openpre.domain.com`请求进来的用户都会进预发布服务器,从SLB域名进来请求路由到非预发服务器 ## 使用灰度发布 灰度发布可允许指定的用户访问灰度服务器,其它用户还是走正常流程。 -微服务启动参数添加:`--spring.cloud.nacos.discovery.metadata.env=gray`(eureka下是:`--eureka.instance.metadata-map.env=gray`)。 +登录SOP-Admin,前往`服务列表`。 -登录SOP-Admin,前往服务列表。 - -- 先设置灰度参数,指定灰度用户和灰度接口 +- 先设置灰度参数,指定灰度appId和灰度接口 - 服务器实例开启灰度 + 参考类: -- PreEnvGrayFilter.java -- EnvironmentServerChooser.java \ No newline at end of file +- LoadBalanceServerChooser.java 预发布/灰度发布服务实例选择 + +### 自定义判断灰度用户 + +默认根据`appId`和`IP`来判断灰度用户,如果要通过其它维度来判断是否是灰度用户,可实现GrayUserBuilder接口, +然后在springboot main方法中调用如下方法 + +```java +ApiConfig.getInstance().addGrayUserBuilder(new XXGrayUserBuilder()); +``` +参考:com.gitee.sop.gatewaycommon.loadbalancer.builder.AppIdGrayUserBuilder.java \ No newline at end of file diff --git a/doc/docs/files/90014_原理分析之预发布灰度发布.md b/doc/docs/files/90014_原理分析之预发布灰度发布.md new file mode 100644 index 00000000..04bc5350 --- /dev/null +++ b/doc/docs/files/90014_原理分析之预发布灰度发布.md @@ -0,0 +1,92 @@ +# 原理分析之预发布灰度发布 + +SOP网关采用`自定义负载均衡策略`来实现对预发布/灰度发布服务器实例的选择。 + +spring cloud gateway默认的负载均衡实现类在:`org.springframework.cloud.gateway.filter.LoadBalancerClientFilter.java`中 + +这个类主要做了几件事情: + +1. 解析出请求路径中的scheme +2. 如果scheme不是以`lb`协议开头直接跳过 +3. 如果scheme以`lb`协议开头,则说明需要进行负载均衡,选出一台微服务实例 +4. 将`lb`协议解析成`http://ip:port`,继续向下请求 + +其中第4步是由`org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient`来完成的, +我们只要分别继承`LoadBalancerClientFilter`和`RibbonLoadBalancerClient`,然后重写其中的方法就能完成自定义负载均衡。 + +SOP中的重写类是:`SopLoadBalancerClientFilter`和`SopLoadBalancerClient`,核心代码委托给了`LoadBalanceServerChooser`处理,核心代码如下: + +```java + /** + * 选择服务器 + * + * @param serviceId serviceId,仅gateway网关有作用 + * @param exchange 请求上下文 + * @param loadBalancer loadBalancer + * @param superChooser 父类默认的选择 + * @param serverChooserFunction 执行选择操作 + * @return 返回服务器实例,没有选到则返回null + */ + public R choose( + String serviceId + , T exchange + , ILoadBalancer loadBalancer + , Supplier superChooser + , Function, R> serverChooserFunction) { + // 获取所有服务实例 + List servers = loadBalancer.getReachableServers(); + + // 存放预发服务器 + List preServers = new ArrayList<>(4); + // 存放灰度发布服务器 + List grayServers = new ArrayList<>(4); + // 存放非预发服务器 + List notPreServers = new ArrayList<>(4); + + for (Server server : servers) { + // 获取实例metadata + Map metadata = getMetadata(serviceId, server); + // 是否开启了预发模式 + if (this.isPreServer(metadata)) { + preServers.add(server); + } else if (this.isGrayServer(metadata)) { + grayServers.add(server); + } else { + notPreServers.add(server); + } + } + notPreServers.addAll(grayServers); + // 如果没有开启预发布服务和灰度发布,直接用默认的方式 + if (preServers.isEmpty() && grayServers.isEmpty()) { + return superChooser.get(); + } + // 如果是从预发布域名访问过来,则认为是预发布请求,选出预发服务器 + if (this.isRequestFromPreDomain(exchange)) { + return serverChooserFunction.apply(preServers); + } + // 如果是灰度请求,则认为是灰度用户,选出灰度服务器 + if (this.isRequestGrayServer(exchange)) { + return serverChooserFunction.apply(grayServers); + } + + // 到这里说明不能访问预发/灰度服务器,则需要路由到非预发服务器 + // 注意:这里允许走灰度服务器,如果不允许走,注释notPreServers.addAll(grayServers);这行 + return serverChooserFunction.apply(notPreServers); + } +``` + +其业务逻辑如下: + +1. 选出`serviceId`对应的所有服务器实例 +2. 将服务器实例进行分类,分别放进`预发布List`,`灰度List`,`非预发布List`中 +3. 如果`预发布List`,`灰度List`都为空,表示没有开启任何预发/灰度服务,直接使用父类的负载均衡策略 +4. 如果是从预发布域名访问过来,则认为是预发布请求,选出预发服务器 +5. 如果是灰度请求,则认为是灰度用户,选出灰度服务器 +6. 最后剩下的是正常用户,正常用户不能走预发环境 + +## 参考类 + +- com.gitee.sop.gatewaycommon.gateway.filter.SopLoadBalancerClientFilter +- com.gitee.sop.gatewaycommon.gateway.loadbalancer.SopLoadBalancerClient +- com.gitee.sop.gatewaycommon.gateway.loadbalancer.GatewayLoadBalanceServerChooser + diff --git a/doc/docs/files/90099_2.x升3.x注意事项.md b/doc/docs/files/90099_2.x升3.x注意事项.md new file mode 100644 index 00000000..1cf1d194 --- /dev/null +++ b/doc/docs/files/90099_2.x升3.x注意事项.md @@ -0,0 +1,23 @@ +# 2.x升3.x注意事项 + +升级到3.x后`本地访问接口`方式会有不同。 + +```java +@ApiMapping(value = "alipay.story.get") +public StoryResult getStory(StoryParam param) { + StoryResult story = new StoryResult(); + story.setId(1L); + story.setName("海底小纵队(alipay.story.get1.0), port:" + environment.getProperty("server.port") + ", param:" + param); + return story; +} +``` + +- 2.x版本访问方式: + +`http://localhost:2222/alipay.story.get/?name=Jim&version=1.0` + +- 3.x版本访问方式: + +`http://localhost:2222/alipay.story.get/1.0/?name=Jim` + +3.x版本中把版本号融合在了url中,如果这个功能没有用到,可以放心升级。 \ No newline at end of file diff --git a/doc/docs/files/90999_网关性能测试.md b/doc/docs/files/90999_网关性能测试.md new file mode 100644 index 00000000..38c47d0e --- /dev/null +++ b/doc/docs/files/90999_网关性能测试.md @@ -0,0 +1,112 @@ +# 网关性能测试 + +**测试环境** + +> 注意:记得关闭限流功能 + +- 测试工具:[wrk](https://github.com/wg/wrk),[安装教程](https://www.cnblogs.com/quanxiaoha/p/10661650.html) +- 服务器:CentOS7(虚拟机,宿主机:macbookpro),内存:2G,CPU:1,核数:2核 +- 运行环境:Java8、Mysql-5.7、Nacos-1.1.3 +- 运行参数:`-verbose:gc -XX:+PrintGCDetails -XX:+PrintHeapAtGC -Xloggc:gc-zuul.log \ + -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m -Xms1024m -Xmx1024m -Xmn512m -Xss256k -XX:SurvivorRatio=8\ + -XX:+UseConcMarkSweepGC` + +- zuul配置: + +```properties +# 不校验时间,这样一个链接可以一直进行测试 +sop.api-config.timeout-seconds=0 +sop.restful.enable=true + +logging.level.com.gitee=info + +# zuul调优 +zuul.host.max-per-route-connections=5000 +zuul.host.max-total-connections=5000 +zuul.semaphore.max-semaphores=5000 + +ribbon.ReadTimeout=5000 +hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=13000 + +logging.file=sop-gateway.log +``` + +以上配置仅针对zuul,Spring Cloud Gateway没有做优化配置 + +CentOS允许最大连接数 +``` +$ ulimit -n +65535 +``` + +## 调用开放接口 + +- wrk命令: + +``` +wrk -t8 -c200 -d30s "http://10.1.31.227:8081/?charset=utf-8&biz_content=%7B%22name%22%3A%22%E8%91%AB%E8%8A%A6%E5%A8%83%22%2C%22id%22%3A%221%22%7D&method=alipay.story.get&format=json&sign=RjK%2FThnzAJQOw%2BfoVLS18PgWZAR%2B25SI2XdONFhS%2BmS8vsv2jNT3rygFoh%2ByX1AJbMgIEfcBzkQyqrs29jjd5dcwHVkcf7vxXshyfcEgl0fbMF6Ihycnz7rqSqkW3lzAWx4NuWUfkPnTX8Ffuf%2BhYRaI0NCpNv%2FV300HvsvmUjS6ZzS4YHaT1peSq0agfUhwRPd97aYMnUwRZDzxNfc5wuXA7OQ1o%2FPYIuIb%2FajVfwNP5ysitc%2FKtYEqt9rNAuzkcFmsw71d2RRnrPLsDN%2BuBXnIEh482f%2FbMj2Rj4%2FMq%2B0PEtlTRbg3rYnxyfowymfX%2BNmI4gNRUt70D4a%2FL3Qiug%3D%3D&app_id=2019032617262200001&sign_type=RSA2&version=1.0×tamp=2020-01-19+13%3A34%3A12" +``` + +- Spring Cloud Gateway测试结果 + +``` + 8 threads and 200 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 139.74ms 69.39ms 617.14ms 69.82% + Req/Sec 182.12 55.74 343.00 66.24% + 43391 requests in 30.09s, 11.96MB read +Requests/sec: 1441.96 +Transfer/sec: 406.96KB +``` + +- Spring Cloud Zuul测试结果 + +``` + 8 threads and 200 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 230.14ms 331.27ms 2.00s 86.98% + Req/Sec 141.69 51.04 323.00 66.99% + 33945 requests in 30.09s, 9.88MB read + Socket errors: connect 0, read 0, write 0, timeout 385 +Requests/sec: 1128.05 +Transfer/sec: 336.15KB +``` + +## 调用restful请求 + +- wrk命令: + +``` +wrk -t8 -c200 -d30s "http://10.1.31.227:8081/rest/story-service/food/getFoodById?id=2" +``` + +线程数为 8,模拟 200 个并发请求,持续 30 秒 + +- Spring Cloud Gateway测试结果 + +``` + 8 threads and 200 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 120.14ms 58.30ms 513.85ms 67.47% + Req/Sec 210.47 54.26 770.00 69.37% + 50301 requests in 30.10s, 7.53MB read +Requests/sec: 1670.97 +Transfer/sec: 256.21KB +``` + + +- Spring Cloud Zuul测试结果 + +``` + 8 threads and 200 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 185.86ms 285.65ms 1.99s 88.55% + Req/Sec 167.75 55.60 460.00 68.05% + 40070 requests in 30.09s, 6.65MB read + Socket errors: connect 0, read 0, write 0, timeout 466 +Requests/sec: 1331.81 +Transfer/sec: 226.50KB +``` + + +综上所述,Spring Cloud Gateway在没有优化的情况下,压测表现比zuul好,但zuul的数据表现也不差,但是出现超时现象,总的来说还是Spring Cloud Gateway具有优势。 \ No newline at end of file diff --git a/sop-2.2.0.sql b/sop-2.2.0.sql deleted file mode 100644 index 8a01be97..00000000 --- a/sop-2.2.0.sql +++ /dev/null @@ -1,23 +0,0 @@ -use sop; - -DROP TABLE IF EXISTS `config_service_route`; - -CREATE TABLE `config_service_route` ( - `id` varchar(128) NOT NULL DEFAULT '' COMMENT '路由id', - `service_id` varchar(128) NOT NULL DEFAULT '', - `name` varchar(128) NOT NULL DEFAULT '' COMMENT '接口名', - `version` varchar(64) NOT NULL DEFAULT '' COMMENT '版本号', - `predicates` varchar(256) DEFAULT NULL COMMENT '路由断言(SpringCloudGateway专用)', - `filters` varchar(256) DEFAULT NULL COMMENT '路由过滤器(SpringCloudGateway专用)', - `uri` varchar(128) NOT NULL DEFAULT '' COMMENT '路由规则转发的目标uri', - `path` varchar(128) NOT NULL DEFAULT '' COMMENT 'uri后面跟的path', - `order` int(11) NOT NULL DEFAULT '0' COMMENT '路由执行的顺序', - `ignore_validate` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否忽略验证,业务参数验证除外', - `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态,0:待审核,1:启用,2:禁用', - `merge_result` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否合并结果', - `permission` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否需要授权才能访问', - `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP, - `gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`), - KEY `idx_serviceid` (`service_id`) USING BTREE -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='路由配置'; diff --git a/sop-2.4.0.sql b/sop-2.4.0.sql deleted file mode 100644 index 6f5243f7..00000000 --- a/sop-2.4.0.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE `sop`.`config_limit` ADD COLUMN `duration_seconds` INT NOT NULL DEFAULT 1 COMMENT '限流持续时间,默认1秒,即每durationSeconds秒允许多少请求(当limit_type=1时有效)' AFTER `exec_count_per_second`; \ No newline at end of file diff --git a/sop-2.5.0.sql b/sop-2.5.0.sql deleted file mode 100644 index 5743cdad..00000000 --- a/sop-2.5.0.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE `sop`.`config_service_route` ADD COLUMN `need_token` TINYINT NOT NULL DEFAULT 0 COMMENT '是否需要token' AFTER `permission`; diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/LogApi.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/LogApi.java index 9b16cf4e..72ef5e55 100644 --- a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/LogApi.java +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/LogApi.java @@ -41,13 +41,15 @@ public class LogApi { public static final String LOG_MONITOR_INSTANCE = "log.monitor.instance"; public static final String CODE_SUCCESS = "10000"; private static final String CODE_KEY = "code"; + public static final String SOP_LIST_ERRORS_PATH = "/sop/listErrors"; + public static final String SOP_CLEAR_ERRORS_PATH = "/sop/clearErrors"; @Autowired ConfigCommonMapper configCommonMapper; RestTemplate restTemplate = new RestTemplate(); - @Value("${zuul.secret}") + @Value("${sop.secret}") private String secret; @Api(name = "monitor.log.list") @@ -66,7 +68,7 @@ public class LogApi { logMonitorInstanceVOParent.setMonitorName(configCommon.getContent()); ret.add(logMonitorInstanceVOParent); try { - String logData = this.requestLogServer(ipPort, "listErrors"); + String logData = this.requestLogServer(ipPort, SOP_LIST_ERRORS_PATH); JSONObject jsonObject = JSON.parseObject(logData); if (CODE_SUCCESS.equals(jsonObject.getString("code"))) { int errorTotal = 0; @@ -97,7 +99,7 @@ public class LogApi { } try { String ipPort = configCommon.getConfigKey(); - this.requestLogServer(ipPort, "clearErrors"); + this.requestLogServer(ipPort, SOP_CLEAR_ERRORS_PATH); } catch (Exception e) { throw new BizException("清除失败"); } @@ -134,7 +136,7 @@ public class LogApi { private void checkInstance(String ipPort) { try { - String json = this.requestLogServer(ipPort, "listErrors"); + String json = this.requestLogServer(ipPort, SOP_LIST_ERRORS_PATH); JSONObject jsonObject = JSON.parseObject(json); if (!CODE_SUCCESS.equals(jsonObject.getString(CODE_KEY))) { log.error("请求结果:{}", json); diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/config/SopAdminEnvironmentPostProcessor.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/config/SopAdminEnvironmentPostProcessor.java new file mode 100644 index 00000000..a8905697 --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/config/SopAdminEnvironmentPostProcessor.java @@ -0,0 +1,40 @@ +package com.gitee.sop.adminserver.config; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.env.EnvironmentPostProcessor; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.PropertiesPropertySource; +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; + +import java.io.IOException; +import java.util.Properties; + +/** + * 自定义环境处理,在运行SpringApplication之前加载任意配置文件到Environment环境中 + * https://www.jianshu.com/p/be6c818fe6ff + */ +public class SopAdminEnvironmentPostProcessor implements EnvironmentPostProcessor { + + private final Properties properties = new Properties(); + + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + Resource resource = new ClassPathResource("META-INF/sop-admin.properties"); + // 加载成PropertySource对象,并添加到Environment环境中 + environment.getPropertySources().addLast(loadProfiles(resource)); + } + + private PropertySource loadProfiles(Resource resource) { + if (resource == null || !resource.exists()) { + throw new IllegalArgumentException("资源" + resource + "不存在"); + } + try { + properties.load(resource.getInputStream()); + return new PropertiesPropertySource(resource.getFilename(), properties); + } catch (IOException ex) { + throw new IllegalStateException("加载配置文件失败" + resource, ex); + } + } +} diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/ConfigPushService.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/ConfigPushService.java index d5f2d023..c2b0e943 100644 --- a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/ConfigPushService.java +++ b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/ConfigPushService.java @@ -29,7 +29,7 @@ import java.util.stream.Stream; @Service public class ConfigPushService { - private static final String GATEWAY_PUSH_URL = "http://%s/configChannelMsg"; + private static final String GATEWAY_PUSH_URL = "http://%s/sop/configChannelMsg"; private static final String API_GATEWAY_SERVICE_ID = "sop-gateway"; private static HttpTool httpTool = new HttpTool(); @@ -40,7 +40,7 @@ public class ConfigPushService { @Value("${gateway.host:}") private String gatewayHost; - @Value("${zuul.secret}") + @Value("${sop.secret}") private String secret; public void publishConfig(String dataId, String groupId, ChannelMsg channelMsg) { @@ -59,6 +59,11 @@ public class ConfigPushService { private void pushByHost(Collection hosts, GatewayPushDTO gatewayPushDTO) { for (String host : hosts) { String url = String.format(GATEWAY_PUSH_URL, host); + log.info("推送配置, dataId={}, groupId={}, operation={}, url={}", + gatewayPushDTO.getDataId() + , gatewayPushDTO.getGroupId() + , gatewayPushDTO.getChannelMsg().getOperation() + , url); try { String requestBody = JSON.toJSONString(gatewayPushDTO); Map header = new HashMap<>(8); diff --git a/sop-admin/sop-admin-server/src/main/resources/META-INF/sop-admin.properties b/sop-admin/sop-admin-server/src/main/resources/META-INF/sop-admin.properties new file mode 100644 index 00000000..a98965a0 --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/resources/META-INF/sop-admin.properties @@ -0,0 +1,32 @@ +# 这里的配置不用改,如果要改在application-xx.properties中改 +# 在application-xx.properties中配置会覆盖这里的值 +# 参考:com.gitee.sop.adminserver.config.SopAdminEnvironmentPostProcessor + +spring.application.name=sop-admin + +# session过期时间,分钟 +admin.access-token.timeout-minutes=30 +# 签名方式,rsa:支付宝开放平台签名方式,md5:淘宝开放平台签名方式 +sop.sign-type=rsa + +# nacos配置 +nacos.config.server-addr=${nacos.url} +nacos.discovery.server-addr=${nacos.url} + +# 数据库配置 +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.url=jdbc:mysql://${mysql.host}/sop?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai +spring.datasource.username=${mysql.username} +spring.datasource.password=${mysql.password} + +# 固定不用改 +mybatis.mapper-locations=classpath:mybatis/mapper/*.xml +easyopen.show-doc=false +easyopen.ignore-validate=true + +# 不用改 +mybatis.fill.com.gitee.fastmybatis.core.support.DateFillInsert=gmt_create +mybatis.fill.com.gitee.fastmybatis.core.support.DateFillUpdate=gmt_modified + +# 不用改,如果要改,请全局替换修改 +sop.secret=MZZOUSTua6LzApIWXCwEgbBmxSzpzC diff --git a/sop-admin/sop-admin-server/src/main/resources/META-INF/spring.factories b/sop-admin/sop-admin-server/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..201aa743 --- /dev/null +++ b/sop-admin/sop-admin-server/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +# 自定义自动配置类,如果有多个类,使用逗号(,)分隔,\正斜杠标示换行还可以读取到指定的类 +org.springframework.boot.env.EnvironmentPostProcessor=com.gitee.sop.adminserver.config.SopAdminEnvironmentPostProcessor diff --git a/sop-admin/sop-admin-server/src/main/resources/application-dev.properties b/sop-admin/sop-admin-server/src/main/resources/application-dev.properties index d3995efc..917d0f92 100644 --- a/sop-admin/sop-admin-server/src/main/resources/application-dev.properties +++ b/sop-admin/sop-admin-server/src/main/resources/application-dev.properties @@ -1,7 +1,5 @@ server.port=8082 -spring.application.name=sop-admin -# ------- 需要改的配置 ------- # mysql数据库账号 mysql.host=localhost:3306 mysql.username=root @@ -9,33 +7,5 @@ mysql.password=root # nacos注册中心地址 nacos.url=127.0.0.1:8848 -# ------- 需要改的配置end ------- - -# session过期时间,分钟 -admin.access-token.timeout-minutes=30 -# 签名方式,rsa:支付宝开放平台签名方式,md5:淘宝开放平台签名方式 -sop.sign-type=rsa - -# nacos配置 -nacos.config.server-addr=${nacos.url} -nacos.discovery.server-addr=${nacos.url} - -# 数据库配置 -spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver -spring.datasource.url=jdbc:mysql://${mysql.host}/sop?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai -spring.datasource.username=${mysql.username} -spring.datasource.password=${mysql.password} - -# 固定不用改 -mybatis.mapper-locations=classpath:mybatis/mapper/*.xml -easyopen.show-doc=false -easyopen.ignore-validate=true logging.level.com.gitee=debug - -# 不用改 -mybatis.fill.com.gitee.fastmybatis.core.support.DateFillInsert=gmt_create -mybatis.fill.com.gitee.fastmybatis.core.support.DateFillUpdate=gmt_modified - -# 不用改,如果要改,请全局替换修改 -zuul.secret=MZZOUSTua6LzApIWXCwEgbBmxSzpzC diff --git a/sop-admin/sop-admin-server/src/main/resources/public/index.html b/sop-admin/sop-admin-server/src/main/resources/public/index.html index 4c586b35..138c8d0a 100644 --- a/sop-admin/sop-admin-server/src/main/resources/public/index.html +++ b/sop-admin/sop-admin-server/src/main/resources/public/index.html @@ -1 +1 @@ -SOP Admin
\ No newline at end of file +SOP Admin
\ No newline at end of file diff --git a/sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-9f479afe.0d0ed757.js b/sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-9f479afe.b0599ada.js similarity index 56% rename from sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-9f479afe.0d0ed757.js rename to sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-9f479afe.b0599ada.js index 8095a6ce..baeadadc 100644 --- a/sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-9f479afe.0d0ed757.js +++ b/sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-9f479afe.b0599ada.js @@ -1 +1 @@ -(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-9f479afe"],{"02f4":function(e,t,n){var r=n("4588"),o=n("be13");e.exports=function(e){return function(t,n){var i,a,s=String(o(t)),l=r(n),c=s.length;return l<0||l>=c?e?"":void 0:(i=s.charCodeAt(l),i<55296||i>56319||l+1===c||(a=s.charCodeAt(l+1))<56320||a>57343?e?s.charAt(l):i:e?s.slice(l,l+2):a-56320+(i-55296<<10)+65536)}}},"0390":function(e,t,n){"use strict";var r=n("02f4")(!0);e.exports=function(e,t,n){return t+(n?r(e,t).length:1)}},"0bfb":function(e,t,n){"use strict";var r=n("cb7c");e.exports=function(){var e=r(this),t="";return e.global&&(t+="g"),e.ignoreCase&&(t+="i"),e.multiline&&(t+="m"),e.unicode&&(t+="u"),e.sticky&&(t+="y"),t}},"214f":function(e,t,n){"use strict";n("b0c5");var r=n("2aba"),o=n("32e9"),i=n("79e5"),a=n("be13"),s=n("2b4c"),l=n("520a"),c=s("species"),u=!i(function(){var e=/./;return e.exec=function(){var e=[];return e.groups={a:"7"},e},"7"!=="".replace(e,"$")}),f=function(){var e=/(?:)/,t=e.exec;e.exec=function(){return t.apply(this,arguments)};var n="ab".split(e);return 2===n.length&&"a"===n[0]&&"b"===n[1]}();e.exports=function(e,t,n){var d=s(e),v=!i(function(){var t={};return t[d]=function(){return 7},7!=""[e](t)}),p=v?!i(function(){var t=!1,n=/a/;return n.exec=function(){return t=!0,null},"split"===e&&(n.constructor={},n.constructor[c]=function(){return n}),n[d](""),!t}):void 0;if(!v||!p||"replace"===e&&!u||"split"===e&&!f){var g=/./[d],y=n(a,d,""[e],function(e,t,n,r,o){return t.exec===l?v&&!o?{done:!0,value:g.call(t,n,r)}:{done:!0,value:e.call(n,t,r)}:{done:!1}}),h=y[0],m=y[1];r(String.prototype,e,h),o(RegExp.prototype,d,2==t?function(e,t){return m.call(e,this,t)}:function(e){return m.call(e,this)})}}},"28a5":function(e,t,n){"use strict";var r=n("aae3"),o=n("cb7c"),i=n("ebd6"),a=n("0390"),s=n("9def"),l=n("5f1b"),c=n("520a"),u=n("79e5"),f=Math.min,d=[].push,v="split",p="length",g="lastIndex",y=4294967295,h=!u(function(){RegExp(y,"y")});n("214f")("split",2,function(e,t,n,u){var m;return m="c"=="abbc"[v](/(b)*/)[1]||4!="test"[v](/(?:)/,-1)[p]||2!="ab"[v](/(?:ab)*/)[p]||4!="."[v](/(.?)(.?)/)[p]||"."[v](/()()/)[p]>1||""[v](/.?/)[p]?function(e,t){var o=String(this);if(void 0===e&&0===t)return[];if(!r(e))return n.call(o,e,t);var i,a,s,l=[],u=(e.ignoreCase?"i":"")+(e.multiline?"m":"")+(e.unicode?"u":"")+(e.sticky?"y":""),f=0,v=void 0===t?y:t>>>0,h=new RegExp(e.source,u+"g");while(i=c.call(h,o)){if(a=h[g],a>f&&(l.push(o.slice(f,i.index)),i[p]>1&&i.index=v))break;h[g]===i.index&&h[g]++}return f===o[p]?!s&&h.test("")||l.push(""):l.push(o.slice(f)),l[p]>v?l.slice(0,v):l}:"0"[v](void 0,0)[p]?function(e,t){return void 0===e&&0===t?[]:n.call(this,e,t)}:n,[function(n,r){var o=e(this),i=void 0==n?void 0:n[t];return void 0!==i?i.call(n,o,r):m.call(String(o),n,r)},function(e,t){var r=u(m,e,this,t,m!==n);if(r.done)return r.value;var c=o(e),d=String(this),v=i(c,RegExp),p=c.unicode,g=(c.ignoreCase?"i":"")+(c.multiline?"m":"")+(c.unicode?"u":"")+(h?"y":"g"),b=new v(h?c:"^(?:"+c.source+")",g),_=void 0===t?y:t>>>0;if(0===_)return[];if(0===d.length)return null===l(b,d)?[d]:[];var w=0,x=0,I=[];while(x1&&i.call(a[0],n,function(){for(u=1;u0&&"pre"===t.row.metadata.env?n("el-tag",{attrs:{type:"warning"}},[e._v("预发布")]):e._e(),e._v(" "),t.row.parentId>0&&"gray"===t.row.metadata.env?n("el-tag",{attrs:{type:"info"}},[e._v("灰度")]):e._e(),e._v(" "),t.row.parentId>0&&!t.row.metadata.env?n("el-tag",{attrs:{type:"success"}},[e._v("线上")]):e._e()],1):e._e()]}}])}),e._v(" "),n("el-table-column",{attrs:{prop:"metadata",label:"metadata",width:"250"},scopedSlots:e._u([{key:"default",fn:function(t){return[t.row.parentId>0?n("span",[e._v(e._s(JSON.stringify(t.row.metadata)))]):e._e()]}}])}),e._v(" "),n("el-table-column",{attrs:{prop:"status",label:"服务状态",width:"100"},scopedSlots:e._u([{key:"default",fn:function(t){return[t.row.parentId>0&&"UP"===t.row.status?n("el-tag",{attrs:{type:"success"}},[e._v("正常")]):e._e(),e._v(" "),t.row.parentId>0&&"STARTING"===t.row.status?n("el-tag",{attrs:{type:"info"}},[e._v("正在启动")]):e._e(),e._v(" "),t.row.parentId>0&&"UNKNOWN"===t.row.status?n("el-tag",[e._v("未知")]):e._e(),e._v(" "),t.row.parentId>0&&("OUT_OF_SERVICE"===t.row.status||"DOWN"===t.row.status)?n("el-tag",{attrs:{type:"danger"}},[e._v("已禁用")]):e._e()]}}])}),e._v(" "),n("el-table-column",{attrs:{prop:"updateTime",label:"最后更新时间",width:"160"}}),e._v(" "),n("el-table-column",{attrs:{label:"操作",width:"250"},scopedSlots:e._u([{key:"default",fn:function(t){return[0===t.row.parentId?n("div",[n("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(n){return e.onGrayConfigUpdate(t.row)}}},[e._v("设置灰度参数")])],1):e._e(),e._v(" "),t.row.parentId>0?n("div",["UP"===t.row.status?n("span",["pre"===t.row.metadata.env?n("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(n){return e.onEnvPreClose(t.row)}}},[e._v("结束预发布")]):e._e(),e._v(" "),"gray"===t.row.metadata.env?n("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(n){return e.onEnvGrayClose(t.row)}}},[e._v("结束灰度")]):e._e(),e._v(" "),t.row.metadata.env?e._e():n("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(n){return e.onEnvPreOpen(t.row)}}},[e._v("开启预发布")]),e._v(" "),t.row.metadata.env?e._e():n("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(n){return e.onEnvGrayOpen(t.row)}}},[e._v("开启灰度")])],1):e._e(),e._v(" "),n("span",{staticStyle:{"margin-left":"10px"}},["UP"===t.row.status?n("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(n){return e.onDisable(t.row)}}},[e._v("禁用")]):e._e(),e._v(" "),"OUT_OF_SERVICE"===t.row.status?n("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(n){return e.onEnable(t.row)}}},[e._v("启用")]):e._e()],1)]):e._e()]}}])})],1),e._v(" "),n("el-dialog",{attrs:{title:"灰度设置",visible:e.grayDialogVisible,"close-on-click-modal":!1},on:{"update:visible":function(t){e.grayDialogVisible=t},close:function(t){return e.resetForm("grayForm")}}},[n("el-form",{ref:"grayForm",attrs:{model:e.grayForm,rules:e.grayFormRules,size:"mini"}},[n("el-form-item",{attrs:{label:"serviceId"}},[e._v("\n "+e._s(e.grayForm.serviceId)+"\n ")]),e._v(" "),n("el-tabs",{attrs:{type:"card"},model:{value:e.tabsActiveName,callback:function(t){e.tabsActiveName=t},expression:"tabsActiveName"}},[n("el-tab-pane",{attrs:{label:"灰度用户",name:"first"}},[n("el-alert",{staticStyle:{"margin-bottom":"20px"},attrs:{title:"可以是appId,或userId,多个用英文逗号隔开",type:"info",closable:!1}}),e._v(" "),n("el-form-item",{attrs:{prop:"userKeyContent"}},[n("el-input",{attrs:{placeholder:"可以是appId,或userId,多个用英文逗号隔开",type:"textarea",rows:6},model:{value:e.grayForm.userKeyContent,callback:function(t){e.$set(e.grayForm,"userKeyContent",t)},expression:"grayForm.userKeyContent"}})],1)],1),e._v(" "),n("el-tab-pane",{attrs:{label:"接口配置",name:"second"}},[n("el-form-item",[n("el-button",{attrs:{type:"text"},on:{click:e.addNameVersion}},[e._v("新增灰度接口")])],1),e._v(" "),n("table",{attrs:{cellpadding:"0",cellspacing:"0"}},e._l(e.grayForm.grayRouteConfigList,function(t,r){return n("tr",{key:t.key},[n("td",[n("el-form-item",{key:t.key,attrs:{prop:"grayRouteConfigList."+r+".oldRouteId",rules:{required:!0,message:"不能为空",trigger:["blur","change"]}}},[e._v("\n 老接口:\n "),n("el-select",{staticStyle:{"margin-right":"10px"},on:{change:function(n){return e.onChangeOldRoute(t)}},model:{value:t.oldRouteId,callback:function(n){e.$set(t,"oldRouteId",n)},expression:"grayRouteConfig.oldRouteId"}},e._l(e.routeList,function(e){return n("el-option",{key:e.id,attrs:{label:e.name+"("+e.version+")",value:e.id}})}),1)],1)],1),e._v(" "),n("td",[n("el-form-item",{key:t.key+1,attrs:{prop:"grayRouteConfigList."+r+".newVersion",rules:{required:!0,message:"不能为空",trigger:["blur","change"]}}},[e._v("\n 灰度接口:\n "),n("el-select",{attrs:{"no-data-text":"无数据"},model:{value:t.newVersion,callback:function(n){e.$set(t,"newVersion",n)},expression:"grayRouteConfig.newVersion"}},e._l(e.getGraySelectData(t.oldRouteId),function(e){return n("el-option",{key:e.id,attrs:{label:e.name+"("+e.version+")",value:e.version}})}),1)],1)],1),e._v(" "),n("td",{staticStyle:{"vertical-align":"baseline"}},[n("el-button",{directives:[{name:"show",rawName:"v-show",value:e.grayForm.grayRouteConfigList.length>1,expression:"grayForm.grayRouteConfigList.length > 1"}],attrs:{type:"text"},on:{click:function(n){return n.preventDefault(),e.removeNameVersion(t)}}},[e._v("删除")])],1)])}),0)],1)],1)],1),e._v(" "),n("span",{staticClass:"dialog-footer",attrs:{slot:"footer"},slot:"footer"},[n("el-button",{on:{click:function(t){e.grayDialogVisible=!1}}},[e._v("取 消")]),e._v(" "),n("el-button",{attrs:{type:"primary"},on:{click:e.onGrayConfigSave}},[e._v("确 定")])],1)],1)],1)},o=[],i=(n("28a5"),n("ac6a"),n("7f7f"),{data:function(){var e=/^\S+(,\S+)*$/,t=function(t,n,r){""===n?r(new Error("不能为空")):(e.test(n)||r(new Error("格式不正确")),r())};return{searchFormData:{serviceId:""},grayDialogVisible:!1,grayForm:{serviceId:"",userKeyContent:"",onlyUpdateGrayUserkey:!1,grayRouteConfigList:[]},tabsActiveName:"first",routeList:[],selectNameVersion:[],grayFormRules:{userKeyContent:[{required:!0,message:"不能为空",trigger:"blur"},{validator:t,trigger:"blur"}]},tableData:[]}},created:function(){this.loadTable()},methods:{loadTable:function(){this.post("service.instance.list",this.searchFormData,function(e){this.tableData=this.buildTreeData(e.data)})},loadRouteList:function(e){this.post("route.list/1.2",{serviceId:e.toLowerCase()},function(e){this.routeList=e.data})},getGraySelectData:function(e){return this.routeList.filter(function(t){return e!==t.id&&e.indexOf(t.name)>-1})},buildTreeData:function(e){return e.forEach(function(t){var n=t.parentId;0===n||e.forEach(function(e){if(e.id===n){var r=e.children;r||(r=[]),r.push(t),e.children=r}})}),e=e.filter(function(e){return 0===e.parentId}),e},onSearchTable:function(){this.loadTable()},onDisable:function(e){this.confirm("确定要禁用 ".concat(e.serviceId,"(").concat(e.ipPort,") 吗?"),function(t){this.post("service.instance.offline",e,function(){this.tip("禁用成功"),t(),this.loadTableDelay()})})},onEnable:function(e){this.confirm("确定要启用 ".concat(e.serviceId,"(").concat(e.ipPort,") 吗?"),function(t){this.post("service.instance.online",e,function(){this.tip("启用成功"),t(),this.loadTableDelay()})})},doEnvOnline:function(e,t){this.post("service.instance.env.online",e,function(){t&&t.call(this)})},onEnvPreOpen:function(e){this.confirm("确定要开启 ".concat(e.serviceId,"(").concat(e.ipPort,") 预发布吗?"),function(t){this.post("service.instance.env.pre.open",e,function(){this.tip("预发布成功"),t(),this.loadTableDelay()})})},onEnvPreClose:function(e){this.confirm("确定要结束 ".concat(e.serviceId,"(").concat(e.ipPort,") 预发布吗?"),function(t){this.doEnvOnline(e,function(){this.tip("操作成功"),t(),this.loadTableDelay()})})},onEnvGrayOpen:function(e){this.confirm("确定要开启 ".concat(e.serviceId,"(").concat(e.ipPort,") 灰度吗?"),function(t){this.post("service.instance.env.gray.open",e,function(){this.tip("开启成功"),t(),this.loadTableDelay()})})},onEnvGrayClose:function(e){this.confirm("确定要结束 ".concat(e.serviceId,"(").concat(e.ipPort,") 灰度吗?"),function(t){this.doEnvOnline(e,function(){this.tip("操作成功"),t(),this.loadTableDelay()})})},onGrayConfigUpdate:function(e){var t=e.serviceId;this.loadRouteList(t),this.post("service.gray.config.get",{serviceId:t},function(e){var n=this;this.grayDialogVisible=!0,this.$nextTick(function(){var r=e.data;Object.assign(n.grayForm,{serviceId:t,userKeyContent:r.userKeyContent||"",grayRouteConfigList:n.createGrayRouteConfigList(r.nameVersionContent)})})})},onGrayConfigSave:function(){var e=this;this.$refs.grayForm.validate(function(t){if(t){for(var n=[],r=e.grayForm.grayRouteConfigList,o=0;o=c?e?"":void 0:(i=s.charCodeAt(l),i<55296||i>56319||l+1===c||(a=s.charCodeAt(l+1))<56320||a>57343?e?s.charAt(l):i:e?s.slice(l,l+2):a-56320+(i-55296<<10)+65536)}}},"0390":function(e,t,n){"use strict";var r=n("02f4")(!0);e.exports=function(e,t,n){return t+(n?r(e,t).length:1)}},"0bfb":function(e,t,n){"use strict";var r=n("cb7c");e.exports=function(){var e=r(this),t="";return e.global&&(t+="g"),e.ignoreCase&&(t+="i"),e.multiline&&(t+="m"),e.unicode&&(t+="u"),e.sticky&&(t+="y"),t}},"214f":function(e,t,n){"use strict";n("b0c5");var r=n("2aba"),o=n("32e9"),i=n("79e5"),a=n("be13"),s=n("2b4c"),l=n("520a"),c=s("species"),u=!i(function(){var e=/./;return e.exec=function(){var e=[];return e.groups={a:"7"},e},"7"!=="".replace(e,"$")}),f=function(){var e=/(?:)/,t=e.exec;e.exec=function(){return t.apply(this,arguments)};var n="ab".split(e);return 2===n.length&&"a"===n[0]&&"b"===n[1]}();e.exports=function(e,t,n){var d=s(e),v=!i(function(){var t={};return t[d]=function(){return 7},7!=""[e](t)}),p=v?!i(function(){var t=!1,n=/a/;return n.exec=function(){return t=!0,null},"split"===e&&(n.constructor={},n.constructor[c]=function(){return n}),n[d](""),!t}):void 0;if(!v||!p||"replace"===e&&!u||"split"===e&&!f){var g=/./[d],y=n(a,d,""[e],function(e,t,n,r,o){return t.exec===l?v&&!o?{done:!0,value:g.call(t,n,r)}:{done:!0,value:e.call(n,t,r)}:{done:!1}}),h=y[0],m=y[1];r(String.prototype,e,h),o(RegExp.prototype,d,2==t?function(e,t){return m.call(e,this,t)}:function(e){return m.call(e,this)})}}},"28a5":function(e,t,n){"use strict";var r=n("aae3"),o=n("cb7c"),i=n("ebd6"),a=n("0390"),s=n("9def"),l=n("5f1b"),c=n("520a"),u=n("79e5"),f=Math.min,d=[].push,v="split",p="length",g="lastIndex",y=4294967295,h=!u(function(){RegExp(y,"y")});n("214f")("split",2,function(e,t,n,u){var m;return m="c"=="abbc"[v](/(b)*/)[1]||4!="test"[v](/(?:)/,-1)[p]||2!="ab"[v](/(?:ab)*/)[p]||4!="."[v](/(.?)(.?)/)[p]||"."[v](/()()/)[p]>1||""[v](/.?/)[p]?function(e,t){var o=String(this);if(void 0===e&&0===t)return[];if(!r(e))return n.call(o,e,t);var i,a,s,l=[],u=(e.ignoreCase?"i":"")+(e.multiline?"m":"")+(e.unicode?"u":"")+(e.sticky?"y":""),f=0,v=void 0===t?y:t>>>0,h=new RegExp(e.source,u+"g");while(i=c.call(h,o)){if(a=h[g],a>f&&(l.push(o.slice(f,i.index)),i[p]>1&&i.index=v))break;h[g]===i.index&&h[g]++}return f===o[p]?!s&&h.test("")||l.push(""):l.push(o.slice(f)),l[p]>v?l.slice(0,v):l}:"0"[v](void 0,0)[p]?function(e,t){return void 0===e&&0===t?[]:n.call(this,e,t)}:n,[function(n,r){var o=e(this),i=void 0==n?void 0:n[t];return void 0!==i?i.call(n,o,r):m.call(String(o),n,r)},function(e,t){var r=u(m,e,this,t,m!==n);if(r.done)return r.value;var c=o(e),d=String(this),v=i(c,RegExp),p=c.unicode,g=(c.ignoreCase?"i":"")+(c.multiline?"m":"")+(c.unicode?"u":"")+(h?"y":"g"),b=new v(h?c:"^(?:"+c.source+")",g),_=void 0===t?y:t>>>0;if(0===_)return[];if(0===d.length)return null===l(b,d)?[d]:[];var w=0,x=0,I=[];while(x1&&i.call(a[0],n,function(){for(u=1;u0&&"pre"===t.row.metadata.env?n("el-tag",{attrs:{type:"warning"}},[e._v("预发布")]):e._e(),e._v(" "),t.row.parentId>0&&"gray"===t.row.metadata.env?n("el-tag",{attrs:{type:"info"}},[e._v("灰度")]):e._e(),e._v(" "),t.row.parentId>0&&!t.row.metadata.env?n("el-tag",{attrs:{type:"success"}},[e._v("线上")]):e._e()],1):e._e()]}}])}),e._v(" "),n("el-table-column",{attrs:{prop:"metadata",label:"metadata",width:"250"},scopedSlots:e._u([{key:"default",fn:function(t){return[t.row.parentId>0?n("span",[e._v(e._s(JSON.stringify(t.row.metadata)))]):e._e()]}}])}),e._v(" "),n("el-table-column",{attrs:{prop:"status",label:"服务状态",width:"100"},scopedSlots:e._u([{key:"default",fn:function(t){return[t.row.parentId>0&&"UP"===t.row.status?n("el-tag",{attrs:{type:"success"}},[e._v("正常")]):e._e(),e._v(" "),t.row.parentId>0&&"STARTING"===t.row.status?n("el-tag",{attrs:{type:"info"}},[e._v("正在启动")]):e._e(),e._v(" "),t.row.parentId>0&&"UNKNOWN"===t.row.status?n("el-tag",[e._v("未知")]):e._e(),e._v(" "),t.row.parentId>0&&("OUT_OF_SERVICE"===t.row.status||"DOWN"===t.row.status)?n("el-tag",{attrs:{type:"danger"}},[e._v("已禁用")]):e._e()]}}])}),e._v(" "),n("el-table-column",{attrs:{prop:"updateTime",label:"最后更新时间",width:"160"}}),e._v(" "),n("el-table-column",{attrs:{label:"操作",width:"250"},scopedSlots:e._u([{key:"default",fn:function(t){return[0===t.row.parentId?n("div",[n("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(n){return e.onGrayConfigUpdate(t.row)}}},[e._v("设置灰度参数")])],1):e._e(),e._v(" "),t.row.parentId>0?n("div",["UP"===t.row.status?n("span",["pre"===t.row.metadata.env?n("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(n){return e.onEnvPreClose(t.row)}}},[e._v("结束预发布")]):e._e(),e._v(" "),"gray"===t.row.metadata.env?n("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(n){return e.onEnvGrayClose(t.row)}}},[e._v("结束灰度")]):e._e(),e._v(" "),t.row.metadata.env?e._e():n("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(n){return e.onEnvPreOpen(t.row)}}},[e._v("开启预发布")]),e._v(" "),t.row.metadata.env?e._e():n("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(n){return e.onEnvGrayOpen(t.row)}}},[e._v("开启灰度")])],1):e._e(),e._v(" "),n("span",{staticStyle:{"margin-left":"10px"}},["UP"===t.row.status?n("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(n){return e.onDisable(t.row)}}},[e._v("禁用")]):e._e(),e._v(" "),"OUT_OF_SERVICE"===t.row.status?n("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(n){return e.onEnable(t.row)}}},[e._v("启用")]):e._e()],1)]):e._e()]}}])})],1),e._v(" "),n("el-dialog",{attrs:{title:"灰度设置",visible:e.grayDialogVisible,"close-on-click-modal":!1},on:{"update:visible":function(t){e.grayDialogVisible=t},close:function(t){return e.resetForm("grayForm")}}},[n("el-form",{ref:"grayForm",attrs:{model:e.grayForm,rules:e.grayFormRules,size:"mini"}},[n("el-form-item",{attrs:{label:"serviceId"}},[e._v("\n "+e._s(e.grayForm.serviceId)+"\n ")]),e._v(" "),n("el-tabs",{attrs:{type:"card"},model:{value:e.tabsActiveName,callback:function(t){e.tabsActiveName=t},expression:"tabsActiveName"}},[n("el-tab-pane",{attrs:{label:"灰度用户",name:"first"}},[n("el-alert",{staticStyle:{"margin-bottom":"20px"},attrs:{title:"可以是appId或IP地址,多个用英文逗号隔开",type:"info",closable:!1}}),e._v(" "),n("el-form-item",{attrs:{prop:"userKeyContent"}},[n("el-input",{attrs:{placeholder:"可以是appId或IP地址,多个用英文逗号隔开",type:"textarea",rows:6},model:{value:e.grayForm.userKeyContent,callback:function(t){e.$set(e.grayForm,"userKeyContent",t)},expression:"grayForm.userKeyContent"}})],1)],1),e._v(" "),n("el-tab-pane",{attrs:{label:"接口配置",name:"second"}},[n("el-form-item",[n("el-button",{attrs:{type:"text"},on:{click:e.addNameVersion}},[e._v("新增灰度接口")])],1),e._v(" "),n("table",{attrs:{cellpadding:"0",cellspacing:"0"}},e._l(e.grayForm.grayRouteConfigList,function(t,r){return n("tr",{key:t.key},[n("td",[n("el-form-item",{key:t.key,attrs:{prop:"grayRouteConfigList."+r+".oldRouteId",rules:{required:!0,message:"不能为空",trigger:["blur","change"]}}},[e._v("\n 老接口:\n "),n("el-select",{staticStyle:{"margin-right":"10px"},on:{change:function(n){return e.onChangeOldRoute(t)}},model:{value:t.oldRouteId,callback:function(n){e.$set(t,"oldRouteId",n)},expression:"grayRouteConfig.oldRouteId"}},e._l(e.routeList,function(e){return n("el-option",{key:e.id,attrs:{label:e.name+"("+e.version+")",value:e.id}})}),1)],1)],1),e._v(" "),n("td",[n("el-form-item",{key:t.key+1,attrs:{prop:"grayRouteConfigList."+r+".newVersion",rules:{required:!0,message:"不能为空",trigger:["blur","change"]}}},[e._v("\n 灰度接口:\n "),n("el-select",{attrs:{"no-data-text":"无数据"},model:{value:t.newVersion,callback:function(n){e.$set(t,"newVersion",n)},expression:"grayRouteConfig.newVersion"}},e._l(e.getGraySelectData(t.oldRouteId),function(e){return n("el-option",{key:e.id,attrs:{label:e.name+"("+e.version+")",value:e.version}})}),1)],1)],1),e._v(" "),n("td",{staticStyle:{"vertical-align":"baseline"}},[n("el-button",{directives:[{name:"show",rawName:"v-show",value:e.grayForm.grayRouteConfigList.length>1,expression:"grayForm.grayRouteConfigList.length > 1"}],attrs:{type:"text"},on:{click:function(n){return n.preventDefault(),e.removeNameVersion(t)}}},[e._v("删除")])],1)])}),0)],1)],1)],1),e._v(" "),n("span",{staticClass:"dialog-footer",attrs:{slot:"footer"},slot:"footer"},[n("el-button",{on:{click:function(t){e.grayDialogVisible=!1}}},[e._v("取 消")]),e._v(" "),n("el-button",{attrs:{type:"primary"},on:{click:e.onGrayConfigSave}},[e._v("确 定")])],1)],1)],1)},o=[],i=(n("28a5"),n("ac6a"),n("7f7f"),{data:function(){var e=/^\S+(,\S+)*$/,t=function(t,n,r){""===n?r(new Error("不能为空")):(e.test(n)||r(new Error("格式不正确")),r())};return{searchFormData:{serviceId:""},grayDialogVisible:!1,grayForm:{serviceId:"",userKeyContent:"",onlyUpdateGrayUserkey:!1,grayRouteConfigList:[]},tabsActiveName:"first",routeList:[],selectNameVersion:[],grayFormRules:{userKeyContent:[{required:!0,message:"不能为空",trigger:"blur"},{validator:t,trigger:"blur"}]},tableData:[]}},created:function(){this.loadTable()},methods:{loadTable:function(){this.post("service.instance.list",this.searchFormData,function(e){this.tableData=this.buildTreeData(e.data)})},loadRouteList:function(e){this.post("route.list/1.2",{serviceId:e.toLowerCase()},function(e){this.routeList=e.data})},getGraySelectData:function(e){return this.routeList.filter(function(t){return e!==t.id&&e.indexOf(t.name)>-1})},buildTreeData:function(e){return e.forEach(function(t){var n=t.parentId;0===n||e.forEach(function(e){if(e.id===n){var r=e.children;r||(r=[]),r.push(t),e.children=r}})}),e=e.filter(function(e){return 0===e.parentId}),e},onSearchTable:function(){this.loadTable()},onDisable:function(e){this.confirm("确定要禁用 ".concat(e.serviceId,"(").concat(e.ipPort,") 吗?"),function(t){this.post("service.instance.offline",e,function(){this.tip("禁用成功"),t(),this.loadTableDelay()})})},onEnable:function(e){this.confirm("确定要启用 ".concat(e.serviceId,"(").concat(e.ipPort,") 吗?"),function(t){this.post("service.instance.online",e,function(){this.tip("启用成功"),t(),this.loadTableDelay()})})},doEnvOnline:function(e,t){this.post("service.instance.env.online",e,function(){t&&t.call(this)})},onEnvPreOpen:function(e){this.confirm("确定要开启 ".concat(e.serviceId,"(").concat(e.ipPort,") 预发布吗?"),function(t){this.post("service.instance.env.pre.open",e,function(){this.tip("预发布成功"),t(),this.loadTableDelay()})})},onEnvPreClose:function(e){this.confirm("确定要结束 ".concat(e.serviceId,"(").concat(e.ipPort,") 预发布吗?"),function(t){this.doEnvOnline(e,function(){this.tip("操作成功"),t(),this.loadTableDelay()})})},onEnvGrayOpen:function(e){this.confirm("确定要开启 ".concat(e.serviceId,"(").concat(e.ipPort,") 灰度吗?"),function(t){this.post("service.instance.env.gray.open",e,function(){this.tip("开启成功"),t(),this.loadTableDelay()})})},onEnvGrayClose:function(e){this.confirm("确定要结束 ".concat(e.serviceId,"(").concat(e.ipPort,") 灰度吗?"),function(t){this.doEnvOnline(e,function(){this.tip("操作成功"),t(),this.loadTableDelay()})})},onGrayConfigUpdate:function(e){var t=e.serviceId;this.loadRouteList(t),this.post("service.gray.config.get",{serviceId:t},function(e){var n=this;this.grayDialogVisible=!0,this.$nextTick(function(){var r=e.data;Object.assign(n.grayForm,{serviceId:t,userKeyContent:r.userKeyContent||"",grayRouteConfigList:n.createGrayRouteConfigList(r.nameVersionContent)})})})},onGrayConfigSave:function(){var e=this;this.$refs.grayForm.validate(function(t){if(t){for(var n=[],r=e.grayForm.grayRouteConfigList,o=0;o diff --git a/sop-auth/pom.xml b/sop-auth/pom.xml index d7204352..113cfff8 100644 --- a/sop-auth/pom.xml +++ b/sop-auth/pom.xml @@ -26,7 +26,7 @@ com.gitee.sop sop-service-common - 2.5.12-SNAPSHOT + 3.0.0-SNAPSHOT diff --git a/sop-common/pom.xml b/sop-common/pom.xml index a9f7750e..3a464160 100644 --- a/sop-common/pom.xml +++ b/sop-common/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.gitee.sop sop-common - 2.5.12-SNAPSHOT + 3.0.0-SNAPSHOT pom @@ -22,7 +22,7 @@ 4.11 - 1.2.60 + 1.2.62 2.5 1.3.3 3.2.2 @@ -36,7 +36,9 @@ sop-gateway-common sop-service-common - + sop-bridge-zuul + sop-bridge-gateway + @@ -117,6 +119,12 @@ true + + org.springframework.boot + spring-boot-configuration-processor + true + + org.projectlombok @@ -132,6 +140,14 @@ + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + diff --git a/sop-common/readme.md b/sop-common/readme.md index aa8b9864..d5109565 100644 --- a/sop-common/readme.md +++ b/sop-common/readme.md @@ -1,5 +1,7 @@ # sop-common +- sop-bridge-gateway:网关桥接器,供sop-gateway依赖,依赖后使用spring cloud gateway网关 +- sop-bridge-zuul:网关桥接器,供sop-gateway依赖,依赖后使用spring cloud zuul网关 - sop-gateway-common:提供给网关使用 - sop-service-common:提供给微服务端使用,需要打成jar diff --git a/sop-common/sop-bridge-gateway/pom.xml b/sop-common/sop-bridge-gateway/pom.xml new file mode 100644 index 00000000..aa776b53 --- /dev/null +++ b/sop-common/sop-bridge-gateway/pom.xml @@ -0,0 +1,36 @@ + + + + sop-common + com.gitee.sop + 3.0.0-SNAPSHOT + ../pom.xml + + 4.0.0 + 3.0.0-SNAPSHOT + + sop-bridge-gateway + + + + com.gitee.sop + sop-gateway-common + 3.0.0-SNAPSHOT + + + + org.springframework.cloud + spring-cloud-starter-gateway + + + org.springframework.boot + spring-boot-starter-webflux + + + org.springframework.boot + spring-boot-starter-actuator + + + \ No newline at end of file diff --git a/sop-common/sop-bridge-gateway/src/main/java/com/gitee/sop/bridge/SopGatewayAutoConfiguration.java b/sop-common/sop-bridge-gateway/src/main/java/com/gitee/sop/bridge/SopGatewayAutoConfiguration.java new file mode 100644 index 00000000..34c61cd8 --- /dev/null +++ b/sop-common/sop-bridge-gateway/src/main/java/com/gitee/sop/bridge/SopGatewayAutoConfiguration.java @@ -0,0 +1,18 @@ +package com.gitee.sop.bridge; + +import com.gitee.sop.gatewaycommon.config.BaseGatewayAutoConfiguration; +import com.gitee.sop.gatewaycommon.gateway.configuration.AlipayGatewayConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * https://blog.csdn.net/seashouwang/article/details/80299571 + * @author tanghc + */ +@Configuration +@Import(AlipayGatewayConfiguration.class) +@AutoConfigureBefore(RibbonAutoConfiguration.class) +public class SopGatewayAutoConfiguration extends BaseGatewayAutoConfiguration { +} diff --git a/sop-common/sop-bridge-gateway/src/main/resources/sop-bridge.properties b/sop-common/sop-bridge-gateway/src/main/resources/sop-bridge.properties new file mode 100644 index 00000000..115692b7 --- /dev/null +++ b/sop-common/sop-bridge-gateway/src/main/resources/sop-bridge.properties @@ -0,0 +1,39 @@ +# 固定不变,不能改 +spring.application.name=sop-gateway +# 不用改,如果要改,请全局替换修改 +sop.secret=MZZOUSTua6LzApIWXCwEgbBmxSzpzC + +# 网关入口 +sop.gateway-index-path=/ + +# nacos cloud配置 +spring.cloud.nacos.discovery.server-addr=${nacos.url} + +# 数据库配置 +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.url=jdbc:mysql://${mysql.host}/sop?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai +spring.datasource.username=${mysql.username} +spring.datasource.password=${mysql.password} + +# https://blog.csdn.net/qq_36872046/article/details/81058045 +# 路由转发超时时间,毫秒,默认值1000,详见:RibbonClientConfiguration.DEFAULT_READ_TIMEOUT。 +# 如果微服务端 处理时间过长,会导致ribbon read超时,解决办法将这个值调大一点 +ribbon.ReadTimeout=2000 +# 设置为true(默认false),则所有请求都重试,默认只支持get请求重试 +# 请谨慎设置,因为post请求大多都是写入请求,如果要支持重试,确保服务的幂等性 +ribbon.OkToRetryOnAllOperations=false + +spring.cloud.gateway.discovery.locator.lower-case-service-id=true +spring.cloud.gateway.discovery.locator.enabled=true + +# 不用改 +mybatis.fill.com.gitee.fastmybatis.core.support.DateFillInsert=gmt_create +mybatis.fill.com.gitee.fastmybatis.core.support.DateFillUpdate=gmt_modified + +# 文件上传配置 +spring.servlet.multipart.enabled=true +# 这里设置大一点没关系,真实大小由upload.max-file-size控制 +spring.servlet.multipart.max-file-size=50MB + +# 允许上传文件大小,不能超过这个值,单位:B,KB,MB +upload.max-file-size=2MB \ No newline at end of file diff --git a/sop-common/sop-bridge-zuul/pom.xml b/sop-common/sop-bridge-zuul/pom.xml new file mode 100644 index 00000000..723ce3d0 --- /dev/null +++ b/sop-common/sop-bridge-zuul/pom.xml @@ -0,0 +1,32 @@ + + + + sop-common + com.gitee.sop + 3.0.0-SNAPSHOT + ../pom.xml + + 4.0.0 + 3.0.0-SNAPSHOT + + sop-bridge-zuul + + + + com.gitee.sop + sop-gateway-common + 3.0.0-SNAPSHOT + + + + org.springframework.cloud + spring-cloud-starter-netflix-zuul + + + org.springframework.retry + spring-retry + + + \ No newline at end of file diff --git a/sop-common/sop-bridge-zuul/src/main/java/com/gitee/sop/bridge/SopGatewayAutoConfiguration.java b/sop-common/sop-bridge-zuul/src/main/java/com/gitee/sop/bridge/SopGatewayAutoConfiguration.java new file mode 100644 index 00000000..2e17861c --- /dev/null +++ b/sop-common/sop-bridge-zuul/src/main/java/com/gitee/sop/bridge/SopGatewayAutoConfiguration.java @@ -0,0 +1,24 @@ +package com.gitee.sop.bridge; + +import com.gitee.sop.gatewaycommon.config.BaseGatewayAutoConfiguration; +import com.gitee.sop.gatewaycommon.zuul.configuration.AlipayZuulConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration; +import org.springframework.cloud.netflix.zuul.EnableZuulProxy; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * https://blog.csdn.net/seashouwang/article/details/80299571 + * @author tanghc + */ +@Configuration +@EnableZuulProxy +@Import(AlipayZuulConfiguration.class) +// 在ErrorMvcAutoConfiguration之前加载 +// 如果不加会出现basicErrorController和zuulErrorController冲突 +// zuulErrorController是SOP中的,提前加载后basicErrorController就不会加载 +@AutoConfigureBefore({ErrorMvcAutoConfiguration.class}) +public class SopGatewayAutoConfiguration extends BaseGatewayAutoConfiguration { +} + diff --git a/sop-common/sop-bridge-zuul/src/main/resources/sop-bridge.properties b/sop-common/sop-bridge-zuul/src/main/resources/sop-bridge.properties new file mode 100644 index 00000000..4f95afd1 --- /dev/null +++ b/sop-common/sop-bridge-zuul/src/main/resources/sop-bridge.properties @@ -0,0 +1,44 @@ +# 固定不变,不能改 +spring.application.name=sop-gateway +# 入口地址,不用改,默认是/zuul +zuul.servlet-path=/api +# 禁用默认的过滤器,不能删,不用改 +zuul.FormBodyWrapperFilter.pre.disable=true +zuul.Servlet30WrapperFilter.pre.disable=true +# 不用改,如果要改,请全局替换修改 +sop.secret=MZZOUSTua6LzApIWXCwEgbBmxSzpzC + +# zuul优化配置 +zuul.host.max-per-route-connections=1000 +zuul.host.max-total-connections=1000 +zuul.semaphore.max-semaphores=1000 + +# nacos cloud配置 +spring.cloud.nacos.discovery.server-addr=${nacos.url} + +# 数据库配置 +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.url=jdbc:mysql://${mysql.host}/sop?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai +spring.datasource.username=${mysql.username} +spring.datasource.password=${mysql.password} + +# https://blog.csdn.net/qq_36872046/article/details/81058045 +# 路由转发超时时间,毫秒,默认值1000,详见:RibbonClientConfiguration.DEFAULT_READ_TIMEOUT。 +# 如果微服务端 处理时间过长,会导致ribbon read超时,解决办法将这个值调大一点 +ribbon.ReadTimeout=5000 +# 设置为true(默认false),则所有请求都重试,默认只支持get请求重试 +# 请谨慎设置,因为post请求大多都是写入请求,如果要支持重试,确保服务的幂等性 +ribbon.OkToRetryOnAllOperations=false +hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000 + +# 不用改 +mybatis.fill.com.gitee.fastmybatis.core.support.DateFillInsert=gmt_create +mybatis.fill.com.gitee.fastmybatis.core.support.DateFillUpdate=gmt_modified + +# 文件上传配置 +spring.servlet.multipart.enabled=true +# 这里设置大一点没关系,真实大小由upload.max-file-size控制 +spring.servlet.multipart.max-file-size=50MB + +# 允许上传文件大小,不能超过这个值,单位:B,KB,MB +upload.max-file-size=2MB diff --git a/sop-common/sop-gateway-common/pom.xml b/sop-common/sop-gateway-common/pom.xml index 29390522..040b57d8 100644 --- a/sop-common/sop-gateway-common/pom.xml +++ b/sop-common/sop-gateway-common/pom.xml @@ -5,11 +5,11 @@ com.gitee.sop sop-common - 2.5.12-SNAPSHOT + 3.0.0-SNAPSHOT ../pom.xml sop-gateway-common - 2.5.12-SNAPSHOT + 3.0.0-SNAPSHOT jar sop-gateway-common diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiConfig.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiConfig.java index 34ee0bb1..acda4d79 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiConfig.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiConfig.java @@ -1,10 +1,11 @@ package com.gitee.sop.gatewaycommon.bean; -import com.gitee.sop.gatewaycommon.gateway.param.GatewayParamBuilder; -import com.gitee.sop.gatewaycommon.gateway.result.GatewayResult; import com.gitee.sop.gatewaycommon.gateway.result.GatewayResultExecutor; import com.gitee.sop.gatewaycommon.limit.DefaultLimitManager; import com.gitee.sop.gatewaycommon.limit.LimitManager; +import com.gitee.sop.gatewaycommon.loadbalancer.builder.AppIdGrayUserBuilder; +import com.gitee.sop.gatewaycommon.loadbalancer.builder.GrayUserBuilder; +import com.gitee.sop.gatewaycommon.loadbalancer.builder.IpGrayUserBuilder; import com.gitee.sop.gatewaycommon.manager.DefaultEnvGrayManager; import com.gitee.sop.gatewaycommon.manager.DefaultIPBlacklistManager; import com.gitee.sop.gatewaycommon.manager.DefaultIsvRoutePermissionManager; @@ -17,12 +18,12 @@ import com.gitee.sop.gatewaycommon.manager.IsvRoutePermissionManager; import com.gitee.sop.gatewaycommon.manager.LimitConfigManager; import com.gitee.sop.gatewaycommon.manager.RouteConfigManager; import com.gitee.sop.gatewaycommon.manager.ServiceErrorManager; -import com.gitee.sop.gatewaycommon.param.ParamBuilder; import com.gitee.sop.gatewaycommon.param.ParameterFormatter; import com.gitee.sop.gatewaycommon.result.DataNameBuilder; import com.gitee.sop.gatewaycommon.result.DefaultDataNameBuilder; import com.gitee.sop.gatewaycommon.result.ResultAppender; -import com.gitee.sop.gatewaycommon.result.ResultExecutor; +import com.gitee.sop.gatewaycommon.result.ResultExecutorForGateway; +import com.gitee.sop.gatewaycommon.result.ResultExecutorForZuul; import com.gitee.sop.gatewaycommon.secret.CacheIsvManager; import com.gitee.sop.gatewaycommon.secret.IsvManager; import com.gitee.sop.gatewaycommon.session.ApiSessionManager; @@ -34,15 +35,14 @@ import com.gitee.sop.gatewaycommon.validate.Encrypter; import com.gitee.sop.gatewaycommon.validate.Signer; import com.gitee.sop.gatewaycommon.validate.TokenValidator; import com.gitee.sop.gatewaycommon.validate.Validator; -import com.gitee.sop.gatewaycommon.zuul.configuration.ZuulErrorController; +import com.gitee.sop.gatewaycommon.zuul.controller.ZuulErrorController; import com.gitee.sop.gatewaycommon.zuul.param.ZuulParamBuilder; import com.gitee.sop.gatewaycommon.zuul.result.ZuulResultExecutor; -import com.netflix.zuul.context.RequestContext; import lombok.Data; import org.apache.commons.lang3.StringUtils; -import org.springframework.web.server.ServerWebExchange; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import java.util.Map; @@ -55,17 +55,20 @@ public class ApiConfig { private static ApiConfig instance = new ApiConfig(); private ApiConfig() { + grayUserBuilders = new ArrayList<>(4); + grayUserBuilders.add(new AppIdGrayUserBuilder()); + grayUserBuilders.add(new IpGrayUserBuilder()); } /** * gateway合并结果处理 */ - private ResultExecutor gatewayResultExecutor = new GatewayResultExecutor(); + private ResultExecutorForGateway gatewayResultExecutor = new GatewayResultExecutor(); /** * zuul合并结果处理 */ - private ResultExecutor zuulResultExecutor = new ZuulResultExecutor(); + private ResultExecutorForZuul zuulResultExecutor = new ZuulResultExecutor(); /** * isv管理 @@ -82,15 +85,10 @@ public class ApiConfig { */ private Signer signer = new ApiSigner(); - /** - * 参数解析,gateway - */ - private ParamBuilder gatewayParamBuilder = new GatewayParamBuilder(); - /** * 参数解析,zuul */ - private ParamBuilder zuulParamBuilder = new ZuulParamBuilder(); + private ZuulParamBuilder zuulParamBuilder = new ZuulParamBuilder(); /** * 验证 @@ -200,6 +198,13 @@ public class ApiConfig { private boolean useGateway; + private List grayUserBuilders; + + public void addGrayUserBuilder(GrayUserBuilder grayUserBuilder) { + grayUserBuilders.add(grayUserBuilder); + grayUserBuilders.sort(Comparator.comparing(GrayUserBuilder::order)); + } + public void addAppSecret(Map appSecretPair) { for (Map.Entry entry : appSecretPair.entrySet()) { this.isvManager.update(new IsvDefinition(entry.getKey(), entry.getValue())); diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiParamAware.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiParamAware.java new file mode 100644 index 00000000..51b03d06 --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiParamAware.java @@ -0,0 +1,10 @@ +package com.gitee.sop.gatewaycommon.bean; + +import com.gitee.sop.gatewaycommon.param.ApiParam; + +/** + * @author tanghc + */ +public interface ApiParamAware { + ApiParam getApiParam(T t); +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/BaseErrorLogController.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/BaseErrorLogController.java new file mode 100644 index 00000000..3505efc0 --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/BaseErrorLogController.java @@ -0,0 +1,64 @@ +package com.gitee.sop.gatewaycommon.bean; + +import com.gitee.sop.gatewaycommon.manager.ServiceErrorManager; +import com.gitee.sop.gatewaycommon.param.ApiParam; +import com.gitee.sop.gatewaycommon.result.ApiResult; +import com.gitee.sop.gatewaycommon.result.JsonResult; +import com.gitee.sop.gatewaycommon.validate.taobao.TaobaoSigner; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; + +import java.util.Collection; + +/** + * @author tanghc + */ +public abstract class BaseErrorLogController { + + TaobaoSigner signer = new TaobaoSigner(); + + @Value("${sop.secret}") + private String secret; + + protected abstract ApiParam getApiParam(T t); + + @GetMapping("/sop/listErrors") + public ApiResult listErrors(T request) { + try { + this.check(request); + ServiceErrorManager serviceErrorManager = ApiConfig.getInstance().getServiceErrorManager(); + Collection allErrors = serviceErrorManager.listAllErrors(); + JsonResult apiResult = new JsonResult(); + apiResult.setData(allErrors); + return apiResult; + } catch (Exception e) { + ApiResult apiResult = new ApiResult(); + apiResult.setCode("505050"); + apiResult.setMsg(e.getMessage()); + return apiResult; + } + } + + @GetMapping("/sop/clearErrors") + public ApiResult clearErrors(T request) { + try { + this.check(request); + ServiceErrorManager serviceErrorManager = ApiConfig.getInstance().getServiceErrorManager(); + serviceErrorManager.clear(); + return new ApiResult(); + } catch (Exception e) { + ApiResult apiResult = new ApiResult(); + apiResult.setCode("505050"); + apiResult.setMsg(e.getMessage()); + return apiResult; + } + } + + protected void check(T request) { + ApiParam apiParam = getApiParam(request); + boolean right = signer.checkSign(apiParam, secret); + if (!right) { + throw new RuntimeException("签名校验失败"); + } + } +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/RouteDefinition.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/RouteDefinition.java index eb6fba9f..c8d239ef 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/RouteDefinition.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/RouteDefinition.java @@ -75,4 +75,9 @@ public class RouteDefinition { * 是否需要token */ private int needToken; + + /** + * 是否是兼容模式,即使用了@ApiAbility注解 + */ + private int compatibleMode; } \ No newline at end of file diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/SopConstants.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/SopConstants.java index 61fbe357..ab9cad69 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/SopConstants.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/SopConstants.java @@ -54,4 +54,8 @@ public class SopConstants { public static final String UNKNOWN_METHOD = "_sop_unknown_method_"; public static final String UNKNOWN_VERSION = "_sop_unknown_version_"; + public static final String METADATA_ENV_KEY = "env"; + public static final String METADATA_ENV_PRE_VALUE = "pre"; + public static final String METADATA_ENV_GRAY_VALUE = "gray"; + } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/config/ApiConfigProperties.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/config/ApiConfigProperties.java new file mode 100644 index 00000000..c9d5526d --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/config/ApiConfigProperties.java @@ -0,0 +1,48 @@ +package com.gitee.sop.gatewaycommon.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author tanghc + */ +@Data +@ConfigurationProperties(prefix = "sop.api-config") +public class ApiConfigProperties { + + private List i18nModules = new ArrayList<>(); + + /** + * 忽略验证,设置true,则所有接口不会进行签名校验 + */ + private boolean ignoreValidate; + + /** + * 是否对结果进行合并。
+ * 默认情况下是否合并结果由微服务端决定,一旦指定该值,则由该值决定,不管微服务端如何设置。 + */ + private Boolean mergeResult; + + /** + * 请求超时时间,默认5分钟,即允许在5分钟内重复请求 + */ + private int timeoutSeconds = 300; + + /** + * 是否开启限流功能 + */ + private boolean openLimit = true; + + /** + * 显示返回sign + */ + private boolean showReturnSign = true; + + /** + * 保存错误信息容器的容量 + */ + private int storeErrorCapacity = 20; +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/config/BaseGatewayAutoConfiguration.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/config/BaseGatewayAutoConfiguration.java new file mode 100644 index 00000000..e11c49eb --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/config/BaseGatewayAutoConfiguration.java @@ -0,0 +1,29 @@ +package com.gitee.sop.gatewaycommon.config; + +import com.alibaba.fastjson.JSON; +import com.gitee.sop.gatewaycommon.bean.ApiConfig; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; + +import javax.annotation.PostConstruct; + +/** + * @author tanghc + */ +@Slf4j +@EnableConfigurationProperties(ApiConfigProperties.class) +public class BaseGatewayAutoConfiguration { + + @Autowired + private ApiConfigProperties apiConfigProperties; + + @PostConstruct + public void after() { + log.info("网关基本配置:{}", JSON.toJSONString(apiConfigProperties)); + ApiConfig apiConfig = ApiConfig.getInstance(); + BeanUtils.copyProperties(apiConfigProperties, apiConfig); + } + +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/config/SopGatewayEnvironmentPostProcessor.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/config/SopGatewayEnvironmentPostProcessor.java new file mode 100644 index 00000000..9ea1cbc1 --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/config/SopGatewayEnvironmentPostProcessor.java @@ -0,0 +1,39 @@ +package com.gitee.sop.gatewaycommon.config; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.env.EnvironmentPostProcessor; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.PropertiesPropertySource; +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; + +import java.io.IOException; +import java.util.Properties; + +/** + * 自定义环境处理,在运行SpringApplication之前加载任意配置文件到Environment环境中 + */ +public class SopGatewayEnvironmentPostProcessor implements EnvironmentPostProcessor { + + private final Properties properties = new Properties(); + + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + Resource resource = new ClassPathResource("sop-bridge.properties"); + // 加载成PropertySource对象,并添加到Environment环境中 + environment.getPropertySources().addLast(loadProfiles(resource)); + } + + private PropertySource loadProfiles(Resource resource) { + if (resource == null || !resource.exists()) { + throw new IllegalArgumentException("资源" + resource + "不存在"); + } + try { + properties.load(resource.getInputStream()); + return new PropertiesPropertySource(resource.getFilename(), properties); + } catch (IOException ex) { + throw new IllegalStateException("加载配置文件失败" + resource, ex); + } + } +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/easyopen/EasyopenResultExecutor.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/easyopen/EasyopenResultExecutor.java index d0ab701c..ba322249 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/easyopen/EasyopenResultExecutor.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/easyopen/EasyopenResultExecutor.java @@ -4,7 +4,7 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.gitee.sop.gatewaycommon.message.Error; import com.gitee.sop.gatewaycommon.result.ApiResult; -import com.gitee.sop.gatewaycommon.result.ResultExecutor; +import com.gitee.sop.gatewaycommon.result.ResultExecutorForZuul; import com.gitee.sop.gatewaycommon.zuul.result.ZuulResultExecutor; import com.netflix.zuul.context.RequestContext; @@ -13,7 +13,7 @@ import java.util.Optional; /** * @author tanghc */ -public class EasyopenResultExecutor implements ResultExecutor { +public class EasyopenResultExecutor implements ResultExecutorForZuul { boolean onlyReturnData; diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/ServerWebExchangeUtil.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/ServerWebExchangeUtil.java index 688db17e..6a7a1bba 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/ServerWebExchangeUtil.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/ServerWebExchangeUtil.java @@ -1,119 +1,185 @@ package com.gitee.sop.gatewaycommon.gateway; import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; import com.gitee.sop.gatewaycommon.bean.SopConstants; import com.gitee.sop.gatewaycommon.gateway.common.FileUploadHttpServletRequest; import com.gitee.sop.gatewaycommon.gateway.common.RequestContentDataExtractor; import com.gitee.sop.gatewaycommon.gateway.common.SopServerHttpRequestDecorator; import com.gitee.sop.gatewaycommon.param.ApiParam; import com.gitee.sop.gatewaycommon.param.FormHttpOutputMessage; +import com.gitee.sop.gatewaycommon.route.ForwardInfo; +import com.gitee.sop.gatewaycommon.param.ParamNames; import com.gitee.sop.gatewaycommon.util.RequestUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; +import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.util.MultiValueMap; import org.springframework.web.multipart.MultipartHttpServletRequest; import org.springframework.web.multipart.commons.CommonsMultipartResolver; +import org.springframework.web.reactive.function.server.HandlerStrategies; +import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilterChain; +import reactor.core.publisher.Mono; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Consumer; -import static com.gitee.sop.gatewaycommon.bean.SopConstants.CACHE_REQUEST_BODY_FOR_MAP; -import static com.gitee.sop.gatewaycommon.bean.SopConstants.CACHE_REQUEST_BODY_OBJECT_KEY; - /** * @author tanghc */ @Slf4j public class ServerWebExchangeUtil { + private static final String THROWABLE_KEY = "sop.throwable"; + private static final String UNKNOWN_PATH = "/sop/unknown"; + private static final String REST_PATH = "/rest"; + private static FormHttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter(); + private static final List> messageReaders; + + static { + messageReaders = HandlerStrategies.withDefaults().messageReaders(); + } + /** - * 获取请求参数 + * 重定向 * - * @param exchange ServerWebExchange - * @return 返回请求参数 + * @param exchange exchange + * @param forwardInfo forwardInfo + * @return 返回新的ServerWebExchange,配合chain.filter(newExchange);使用 */ - public static ApiParam getApiParam(ServerWebExchange exchange) { - return exchange.getAttribute(SopConstants.CACHE_API_PARAM); + public static ServerWebExchange getForwardExchange(ServerWebExchange exchange, ForwardInfo forwardInfo) { + ServerHttpRequest.Builder builder = exchange.getRequest() + .mutate(); + ServerHttpRequest newRequest = builder + .header(ParamNames.HEADER_VERSION_NAME, forwardInfo.getVersion()) + .path(forwardInfo.getPath()).build(); + return exchange.mutate().request(newRequest).build(); } + /** - * 设置请求参数 + * 构建一个接受请求体的request * - * @param exchange ServerWebExchange - * @param apiParam 请求参数 + * @param exchange exchange + * @return 返回ServerRequest */ - public static void setApiParam(ServerWebExchange exchange, ApiParam apiParam) { - exchange.getAttributes().put(SopConstants.CACHE_API_PARAM, apiParam); + public static ServerRequest createReadBodyRequest(ServerWebExchange exchange) { + return ServerRequest.create(exchange, messageReaders); + } + + public static ServerWebExchange getRestfulExchange(ServerWebExchange exchange, String path) { + int index = path.indexOf(REST_PATH); + // 取"/rest"的后面部分 + String newPath = path.substring(index + REST_PATH.length()); + ApiParam apiParam = new ApiParam(); + apiParam.setName(newPath); + apiParam.setVersion(""); + setApiParam(exchange, apiParam); + return getForwardExchange(exchange, newPath); } /** - * 获取Spring Cloud Gateway请求的原始参数。前提是要使用ReadBodyRoutePredicateFactory + * 重定向 * - * @param exchange ServerWebExchange - * @return 没有参数返回null - * @see com.gitee.sop.gatewaycommon.gateway.route.ReadBodyRoutePredicateFactory + * @param exchange exchange + * @param forwardPath 重定向path + * @return 返回新的ServerWebExchange,配合chain.filter(newExchange);使用 */ - public static ApiParam getRequestParams(ServerWebExchange exchange) { - ApiParam apiParamExist = exchange.getAttribute(CACHE_REQUEST_BODY_FOR_MAP); - if (apiParamExist != null) { - return apiParamExist; + public static ServerWebExchange getForwardExchange(ServerWebExchange exchange, String forwardPath) { + ServerHttpRequest newRequest = exchange.getRequest() + .mutate() + .path(forwardPath).build(); + return exchange.mutate().request(newRequest).build(); + } + + public static Mono forwardUnknown(ServerWebExchange exchange, WebFilterChain chain) { + // 非法访问 + ServerWebExchange newExchange = ServerWebExchangeUtil.getForwardExchange(exchange, UNKNOWN_PATH); + return chain.filter(newExchange); + } + + public static ApiParam getApiParam(ServerWebExchange exchange, String body) { + MediaType contentType = exchange.getRequest().getHeaders().getContentType(); + if (contentType == null) { + contentType = MediaType.APPLICATION_FORM_URLENCODED; } ApiParam apiParam = new ApiParam(); + String ip = Optional.ofNullable(exchange.getRequest().getRemoteAddress()) + .map(address -> address.getAddress().getHostAddress()) + .orElse(""); + apiParam.setIp(ip); Map params = null; - if (exchange.getRequest().getMethod() == HttpMethod.GET) { - MultiValueMap queryParams = exchange.getRequest().getQueryParams(); - params = buildParams(queryParams); + String contentTypeStr = contentType.toString().toLowerCase(); + // 如果是json方式提交 + if (StringUtils.containsAny(contentTypeStr, "json", "text")) { + JSONObject jsonObject = JSON.parseObject(body); + apiParam.putAll(jsonObject); + } else if (StringUtils.containsIgnoreCase(contentTypeStr, "multipart")) { + // 如果是文件上传请求 + HttpServletRequest fileUploadRequest = getFileUploadRequest(exchange, body); + setFileUploadRequest(exchange, fileUploadRequest); + RequestUtil.UploadInfo uploadInfo = RequestUtil.getUploadInfo(fileUploadRequest); + params = uploadInfo.getUploadParams(); + apiParam.setUploadContext(uploadInfo.getUploadContext()); } else { - String cachedBody = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY); - if (cachedBody != null) { - MediaType contentType = exchange.getRequest().getHeaders().getContentType(); - String contentTypeStr = contentType == null ? "" : contentType.toString().toLowerCase(); - // 如果是json方式提交 - if (StringUtils.containsAny(contentTypeStr, "json", "text")) { - params = JSON.parseObject(cachedBody); - } else if (StringUtils.containsIgnoreCase(contentTypeStr, "multipart")) { - // 如果是文件上传请求 - HttpServletRequest fileUploadRequest = getFileUploadRequest(exchange, cachedBody); - setFileUploadRequest(exchange, fileUploadRequest); - RequestUtil.UploadInfo uploadInfo = RequestUtil.getUploadInfo(fileUploadRequest); - params = uploadInfo.getUploadParams(); - apiParam.setUploadContext(uploadInfo.getUploadContext()); - } else { - params = RequestUtil.parseQueryToMap(cachedBody); - } - } + // APPLICATION_FORM_URLENCODED请求 + params = RequestUtil.parseQueryToMap(body); } if (params != null) { apiParam.putAll(params); - exchange.getAttributes().put(CACHE_REQUEST_BODY_FOR_MAP, apiParam); } + setApiParam(exchange, apiParam); return apiParam; } + public static ApiParam getApiParam(ServerWebExchange exchange, Map params) { + ApiParam apiParam = new ApiParam(); + apiParam.putAll(params); + setApiParam(exchange, apiParam); + return apiParam; + } - public static Map buildParams(MultiValueMap queryParams) { - if (queryParams == null || queryParams.size() == 0) { - return null; - } - Map params = new HashMap<>(queryParams.size()); - for (Map.Entry> entry : queryParams.entrySet()) { - params.put(entry.getKey(), entry.getValue().get(0)); - } - return params; + public static void setThrowable(ServerWebExchange exchange, Throwable throwable) { + exchange.getAttributes().put(THROWABLE_KEY, throwable); + } + + public static Throwable getThrowable(ServerWebExchange exchange) { + return (Throwable)exchange.getAttribute(THROWABLE_KEY); + } + + /** + * 获取请求参数 + * + * @param exchange ServerWebExchange + * @return 返回请求参数 + */ + public static ApiParam getApiParam(ServerWebExchange exchange) { + return exchange.getAttribute(SopConstants.CACHE_API_PARAM); + } + + /** + * 设置请求参数 + * + * @param exchange ServerWebExchange + * @param apiParam 请求参数 + */ + public static void setApiParam(ServerWebExchange exchange, ApiParam apiParam) { + exchange.getAttributes().put(SopConstants.CACHE_API_PARAM, apiParam); } /** diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/configuration/BaseGatewayConfiguration.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/configuration/BaseGatewayConfiguration.java index 7df72e6f..f65151a4 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/configuration/BaseGatewayConfiguration.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/configuration/BaseGatewayConfiguration.java @@ -1,47 +1,35 @@ package com.gitee.sop.gatewaycommon.gateway.configuration; import com.gitee.sop.gatewaycommon.bean.ApiConfig; -import com.gitee.sop.gatewaycommon.gateway.filter.EnvGrayFilter; +import com.gitee.sop.gatewaycommon.gateway.controller.ConfigChannelController; +import com.gitee.sop.gatewaycommon.gateway.controller.ErrorLogController; +import com.gitee.sop.gatewaycommon.gateway.controller.GatewayController; import com.gitee.sop.gatewaycommon.gateway.filter.GatewayModifyResponseGatewayFilter; +import com.gitee.sop.gatewaycommon.gateway.filter.IndexFilter; import com.gitee.sop.gatewaycommon.gateway.filter.LimitFilter; -import com.gitee.sop.gatewaycommon.gateway.filter.LoadBalancerClientExtFilter; import com.gitee.sop.gatewaycommon.gateway.filter.ParameterFormatterFilter; -import com.gitee.sop.gatewaycommon.gateway.filter.ValidateFilter; +import com.gitee.sop.gatewaycommon.gateway.filter.SopLoadBalancerClientFilter; import com.gitee.sop.gatewaycommon.gateway.handler.GatewayExceptionHandler; +import com.gitee.sop.gatewaycommon.gateway.loadbalancer.SopLoadBalancerClient; +import com.gitee.sop.gatewaycommon.gateway.route.GatewayForwardChooser; import com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteCache; import com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteRepository; -import com.gitee.sop.gatewaycommon.gateway.route.NameVersionRoutePredicateFactory; -import com.gitee.sop.gatewaycommon.gateway.route.ReadBodyRoutePredicateFactory; import com.gitee.sop.gatewaycommon.manager.AbstractConfiguration; import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext; -import com.gitee.sop.gatewaycommon.param.ParamBuilder; -import com.gitee.sop.gatewaycommon.param.ParamNames; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; +import org.springframework.cloud.gateway.config.LoadBalancerProperties; +import org.springframework.cloud.gateway.filter.LoadBalancerClientFilter; +import org.springframework.cloud.netflix.ribbon.SpringClientFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.core.io.buffer.DefaultDataBufferFactory; import org.springframework.http.codec.ServerCodecConfigurer; -import org.springframework.util.MultiValueMap; -import org.springframework.web.reactive.function.BodyInserters; -import org.springframework.web.reactive.function.server.RequestPredicate; -import org.springframework.web.reactive.function.server.RequestPredicates; -import org.springframework.web.reactive.function.server.RouterFunction; -import org.springframework.web.reactive.function.server.RouterFunctions; -import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.result.view.ViewResolver; -import org.springframework.web.server.ServerWebExchange; -import reactor.core.publisher.Mono; -import java.net.URI; import java.util.Collections; import java.util.List; @@ -56,8 +44,25 @@ public class BaseGatewayConfiguration extends AbstractConfiguration { ApiConfig.getInstance().setUseGateway(true); } - @Value("${sop.restful.path:/rest}") - private String restPath; + @Bean + public IndexFilter indexFilter() { + return new IndexFilter(); + } + + @Bean + public GatewayController gatewayErrorController() { + return new GatewayController(); + } + + @Bean + public ConfigChannelController configChannelController() { + return new ConfigChannelController(); + } + + @Bean + public ErrorLogController errorLogController() { + return new ErrorLogController(); + } /** * 自定义异常处理[@@]注册Bean时依赖的Bean,会从容器中直接获取,所以直接注入即可 @@ -68,7 +73,7 @@ public class BaseGatewayConfiguration extends AbstractConfiguration { @Primary @Bean @Order(Ordered.HIGHEST_PRECEDENCE) - public ErrorWebExceptionHandler errorWebExceptionHandler(ObjectProvider> viewResolversProvider, + public ErrorWebExceptionHandler sopErrorWebExceptionHandler(ObjectProvider> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) { GatewayExceptionHandler jsonExceptionHandler = new GatewayExceptionHandler(); @@ -78,12 +83,6 @@ public class BaseGatewayConfiguration extends AbstractConfiguration { return jsonExceptionHandler; } - @Bean - @ConditionalOnMissingBean - ParamBuilder paramBuilder() { - return ApiConfig.getInstance().getGatewayParamBuilder(); - } - /** * 处理返回结果 */ @@ -92,24 +91,6 @@ public class BaseGatewayConfiguration extends AbstractConfiguration { return new GatewayModifyResponseGatewayFilter(); } - /** - * 读取post请求参数 - */ - @Bean - ReadBodyRoutePredicateFactory readBodyRoutePredicateFactory() { - return new ReadBodyRoutePredicateFactory(); - } - - @Bean - NameVersionRoutePredicateFactory paramRoutePredicateFactory() { - return new NameVersionRoutePredicateFactory(); - } - - @Bean - ValidateFilter validateFilter() { - return new ValidateFilter(); - } - @Bean ParameterFormatterFilter parameterFormatterFilter() { return new ParameterFormatterFilter(); @@ -120,11 +101,6 @@ public class BaseGatewayConfiguration extends AbstractConfiguration { return new LimitFilter(); } - @Bean - LoadBalancerClientExtFilter loadBalancerClientExtFilter() { - return new LoadBalancerClientExtFilter(); - } - @Bean GatewayRouteCache gatewayRouteCache(GatewayRouteRepository gatewayRouteRepository) { return new GatewayRouteCache(gatewayRouteRepository); @@ -137,47 +113,30 @@ public class BaseGatewayConfiguration extends AbstractConfiguration { return gatewayRouteRepository; } + @Bean + GatewayForwardChooser gatewayForwardChooser() { + return new GatewayForwardChooser(); + } + /** + * 扩展默认的负载均衡选择,默认使用的是RibbonLoadBalancerClient + * @param clientFactory + * @return + */ @Bean - EnvGrayFilter envGrayFilter() { - return new EnvGrayFilter(); + LoadBalancerClient loadBalancerClient(SpringClientFactory clientFactory) { + return new SopLoadBalancerClient(clientFactory); } /** - * 307 Temporary Redirect(临时重定向): - *

- * 在这种情况下,请求应该与另一个URI重复,但后续的请求应仍使用原始的URI。 - * 与302相反,当重新发出原始请求时,不允许更改请求方法。 例如,应该使用另一个POST请求来重复POST请求 - *

- * 308 Permanent Redirect (永久重定向): - *

- * 请求和所有将来的请求应该使用另一个URI重复。 - * 307和308重复302和301的行为,但不允许HTTP方法更改。 例如,将表单提交给永久重定向的资源可能会顺利进行。 - *

- * https://www.cnblogs.com/wuguanglin/p/redirect.html - * + * 扩展默认的负载均衡过滤器,默认是LoadBalancerClientFilter + * @param sopLoadBalancerClient SopLoadBalancerClient + * @param loadBalancerProperties loadBalancerProperties * @return */ @Bean - @ConditionalOnProperty(value = "sop.restful.enable", havingValue = "true") - RouterFunction routerFunction() { - RequestPredicate requestPredicate = RequestPredicates.all() - .and(RequestPredicates.path(restPath + "/**")); - return RouterFunctions.route(requestPredicate, (serverRequest) -> { - String path = serverRequest.path(); - int index = path.indexOf(restPath); - // 取/rest的后面部分 - String servletPath = path.substring(index + restPath.length()); - String query = serverRequest.uri().getQuery(); - String appendQuery = ParamNames.API_NAME + "=" + servletPath + "&" + ParamNames.VERSION_NAME + "="; - if (StringUtils.isBlank(query)) { - query = appendQuery; - } else { - query += '&' + appendQuery; - } - return ServerResponse - .temporaryRedirect(URI.create("/?" + query)) - .build(); - }); + LoadBalancerClientFilter loadBalancerClientFilter(LoadBalancerClient sopLoadBalancerClient, LoadBalancerProperties loadBalancerProperties) { + return new SopLoadBalancerClientFilter(sopLoadBalancerClient, loadBalancerProperties); } + } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/ConfigChannelController.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/ConfigChannelController.java new file mode 100644 index 00000000..1ccc64e1 --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/ConfigChannelController.java @@ -0,0 +1,75 @@ +package com.gitee.sop.gatewaycommon.gateway.controller; + +import com.alibaba.fastjson.JSON; +import com.gitee.sop.gatewaycommon.bean.GatewayPushDTO; +import com.gitee.sop.gatewaycommon.bean.NacosConfigs; +import com.gitee.sop.gatewaycommon.bean.SpringContext; +import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil; +import com.gitee.sop.gatewaycommon.manager.ChannelMsgProcessor; +import com.gitee.sop.gatewaycommon.manager.EnvGrayManager; +import com.gitee.sop.gatewaycommon.manager.IPBlacklistManager; +import com.gitee.sop.gatewaycommon.manager.IsvRoutePermissionManager; +import com.gitee.sop.gatewaycommon.manager.LimitConfigManager; +import com.gitee.sop.gatewaycommon.manager.RouteConfigManager; +import com.gitee.sop.gatewaycommon.secret.IsvManager; +import com.gitee.sop.gatewaycommon.util.RequestUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author tanghc + */ +@Slf4j +@RestController +public class ConfigChannelController { + + private static Map> processorMap = new HashMap<>(16); + + static { + processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_GRAY, EnvGrayManager.class); + processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_IP_BLACKLIST, IPBlacklistManager.class); + processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_ISV, IsvManager.class); + processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_ROUTE_PERMISSION, IsvRoutePermissionManager.class); + processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_LIMIT_CONFIG, LimitConfigManager.class); + processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_ROUTE_CONFIG, RouteConfigManager.class); + } + + @Value("${sop.secret}") + private String secret; + + @PostMapping("/sop/configChannelMsg") + public Mono configChannel(ServerWebExchange exchange) { + ServerRequest serverRequest = ServerWebExchangeUtil.createReadBodyRequest(exchange); + // 读取请求体中的内容 + return serverRequest.bodyToMono(String.class) + .flatMap(requestJson -> { + String sign = exchange.getRequest().getHeaders().getFirst("sign"); + try { + // 签名验证 + RequestUtil.checkResponseBody(requestJson, sign, secret); + } catch (Exception e) { + log.error("configChannelMsg错误", e); + return Mono.just(e.getMessage()); + } + GatewayPushDTO gatewayPushDTO = JSON.parseObject(requestJson, GatewayPushDTO.class); + ChannelMsgProcessor channelMsgProcessor = getChannelMsgProcessor(gatewayPushDTO); + channelMsgProcessor.process(gatewayPushDTO.getChannelMsg()); + return Mono.just("ok"); + }); + } + + private ChannelMsgProcessor getChannelMsgProcessor(GatewayPushDTO gatewayPushDTO) { + String key = gatewayPushDTO.getGroupId() + gatewayPushDTO.getDataId(); + Class aClass = processorMap.get(key); + return SpringContext.getBean(aClass); + } + +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/ErrorLogController.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/ErrorLogController.java new file mode 100644 index 00000000..978e533a --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/ErrorLogController.java @@ -0,0 +1,21 @@ +package com.gitee.sop.gatewaycommon.gateway.controller; + +import com.gitee.sop.gatewaycommon.bean.BaseErrorLogController; +import com.gitee.sop.gatewaycommon.param.ApiParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ServerWebExchange; + +import java.util.Map; + +/** + * @author tanghc + */ +@RestController +public class ErrorLogController extends BaseErrorLogController { + + @Override + protected ApiParam getApiParam(ServerWebExchange request) { + Map params = request.getRequest().getQueryParams().toSingleValueMap(); + return ApiParam.build(params); + } +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/GatewayController.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/GatewayController.java new file mode 100644 index 00000000..8836ad13 --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/GatewayController.java @@ -0,0 +1,42 @@ +package com.gitee.sop.gatewaycommon.gateway.controller; + +import com.gitee.sop.gatewaycommon.bean.ApiContext; +import com.gitee.sop.gatewaycommon.exception.ApiException; +import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil; +import com.gitee.sop.gatewaycommon.message.ErrorEnum; +import com.gitee.sop.gatewaycommon.result.ResultExecutor; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +/** + * @author tanghc + */ +@RestController +public class GatewayController { + + /** + * 处理签名错误返回 + * + * @param exchange exchange + * @return 返回最终结果 + */ + @RequestMapping("/sop/validateError") + public Mono validateError(ServerWebExchange exchange) { + Throwable throwable = ServerWebExchangeUtil.getThrowable(exchange); + // 合并微服务传递过来的结果,变成最终结果 + ResultExecutor resultExecutor = ApiContext.getApiConfig().getGatewayResultExecutor(); + String gatewayResult = resultExecutor.buildErrorResult(exchange, throwable); + return Mono.just(gatewayResult); + } + + @RequestMapping("/sop/unknown") + public Mono unknown(ServerWebExchange exchange) { + ApiException exception = ErrorEnum.ISV_INVALID_METHOD.getErrorMeta().getException(); + ResultExecutor resultExecutor = ApiContext.getApiConfig().getGatewayResultExecutor(); + String gatewayResult = resultExecutor.buildErrorResult(exchange, exception); + return Mono.just(gatewayResult); + } + +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/EnvGrayFilter.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/EnvGrayFilter.java index 38c9aebe..84dbbed6 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/EnvGrayFilter.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/EnvGrayFilter.java @@ -15,7 +15,10 @@ import reactor.core.publisher.Mono; /** * @author tanghc + * @deprecated + * @see com.gitee.sop.gatewaycommon.gateway.route.GatewayForwardChooser */ +@Deprecated public class EnvGrayFilter implements GlobalFilter, Ordered { @Autowired @@ -27,7 +30,7 @@ public class EnvGrayFilter implements GlobalFilter, Ordered { String nameVersion = apiParam.fetchNameVersion(); TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(nameVersion); if (targetRoute == null) { - return null; + return chain.filter(exchange); } String serviceId = targetRoute.getServiceRouteInfo().fetchServiceIdLowerCase(); // 如果服务在灰度阶段,返回一个灰度版本号 diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/IndexFilter.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/IndexFilter.java new file mode 100644 index 00000000..9af4a5e3 --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/IndexFilter.java @@ -0,0 +1,156 @@ +package com.gitee.sop.gatewaycommon.gateway.filter; + +import com.gitee.sop.gatewaycommon.exception.ApiException; +import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil; +import com.gitee.sop.gatewaycommon.gateway.route.GatewayForwardChooser; +import com.gitee.sop.gatewaycommon.manager.EnvironmentKeys; +import com.gitee.sop.gatewaycommon.param.ApiParam; +import com.gitee.sop.gatewaycommon.route.ForwardInfo; +import com.gitee.sop.gatewaycommon.validate.Validator; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cloud.gateway.support.BodyInserterContext; +import org.springframework.cloud.gateway.support.CachedBodyOutputMessage; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpRequestDecorator; +import org.springframework.web.reactive.function.BodyInserter; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilter; +import org.springframework.web.server.WebFilterChain; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.net.URI; +import java.util.Objects; + +/** + * 入口 + * + * @author tanghc + */ +@Slf4j +@Order(Ordered.HIGHEST_PRECEDENCE) +public class IndexFilter implements WebFilter { + + private static final String REST_PATH_PREFIX = "/rest"; + private static final String SOP_PATH_PREFIX = "/sop"; + + @Value("${sop.gateway-index-path:/}") + private String indexPath; + + @Autowired + private Validator validator; + + @Autowired + private GatewayForwardChooser gatewayForwardChooser; + + @Override + public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + ServerHttpRequest request = exchange.getRequest(); + String path = request.getURI().getPath(); + // SOP路径,直接放行 + if (path.startsWith(SOP_PATH_PREFIX)) { + return chain.filter(exchange); + } + // 如果是restful请求,直接转发 + if (path.startsWith(REST_PATH_PREFIX)) { + String sopRestfulEnableValue = EnvironmentKeys.SOP_RESTFUL_ENABLE.getValue(); + if (!Objects.equals("true", sopRestfulEnableValue)) { + log.error("尝试调用restful请求,但sop.restful.enable未开启"); + return ServerWebExchangeUtil.forwardUnknown(exchange, chain); + } + ServerWebExchange newExchange = ServerWebExchangeUtil.getRestfulExchange(exchange, path); + return chain.filter(newExchange); + } + if (Objects.equals(path, indexPath)) { + if (request.getMethod() == HttpMethod.POST) { + ServerRequest serverRequest = ServerWebExchangeUtil.createReadBodyRequest(exchange); + // 读取请求体中的内容 + Mono modifiedBody = serverRequest.bodyToMono(String.class) + .flatMap(body -> { + // 构建ApiParam + ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange, body); + // 签名验证 + doValidate(exchange, apiParam); + return Mono.just(body); + }); + BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, (Class) String.class); + HttpHeaders headers = new HttpHeaders(); + headers.putAll(exchange.getRequest().getHeaders()); + + // the new content type will be computed by bodyInserter + // and then set in the request decorator + headers.remove(HttpHeaders.CONTENT_LENGTH); + + CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage( + exchange, headers); + return bodyInserter.insert(outputMessage, new BodyInserterContext()) + .then(Mono.defer(() -> { + ForwardInfo forwardInfo = gatewayForwardChooser.getForwardInfo(exchange); + ServerHttpRequest decorator = decorate(exchange, headers, outputMessage); + ServerWebExchange newExchange = exchange.mutate().request(decorator).build(); + ServerWebExchange forwardExchange = ServerWebExchangeUtil.getForwardExchange(newExchange, forwardInfo); + return chain.filter(forwardExchange); + })); + + } else { + URI uri = exchange.getRequest().getURI(); + // 原始参数 + String originalQuery = uri.getRawQuery(); + // 构建ApiParam + ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange, originalQuery); + // 签名验证 + doValidate(exchange, apiParam); + + ForwardInfo forwardInfo = gatewayForwardChooser.getForwardInfo(exchange); + ServerWebExchange forwardExchange = ServerWebExchangeUtil.getForwardExchange(exchange, forwardInfo); + return chain.filter(forwardExchange); + } + } else { + return ServerWebExchangeUtil.forwardUnknown(exchange, chain); + } + } + + private void doValidate(ServerWebExchange exchange, ApiParam apiParam) { + try { + validator.validate(apiParam); + } catch (ApiException e) { + log.error("验证失败,ip:{}, params:{}, errorMsg:{}", apiParam.fetchIp(), apiParam.toJSONString(), e.getMessage()); + ServerWebExchangeUtil.setThrowable(exchange, e); + } + } + + private ServerHttpRequestDecorator decorate( + ServerWebExchange exchange + , HttpHeaders headers + , CachedBodyOutputMessage outputMessage + ) { + return new ServerHttpRequestDecorator(exchange.getRequest()) { + @Override + public HttpHeaders getHeaders() { + long contentLength = headers.getContentLength(); + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.putAll(super.getHeaders()); + if (contentLength > 0) { + httpHeaders.setContentLength(contentLength); + } else { + httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked"); + } + return httpHeaders; + } + + @Override + public Flux getBody() { + return outputMessage.getBody(); + } + }; + } +} \ No newline at end of file diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/LoadBalancerClientExtFilter.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/LoadBalancerClientExtFilter.java deleted file mode 100644 index 8cd9ad21..00000000 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/LoadBalancerClientExtFilter.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.gitee.sop.gatewaycommon.gateway.filter; - -import com.gitee.sop.gatewaycommon.bean.SopConstants; -import com.gitee.sop.gatewaycommon.util.RouteUtil; -import lombok.extern.slf4j.Slf4j; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.GlobalFilter; -import org.springframework.cloud.gateway.route.Route; -import org.springframework.core.Ordered; -import org.springframework.util.StringUtils; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.util.UriComponentsBuilder; -import reactor.core.publisher.Mono; - -import java.net.URI; - -import static org.springframework.cloud.gateway.filter.LoadBalancerClientFilter.LOAD_BALANCER_CLIENT_FILTER_ORDER; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; - -/** - * 在LoadBalancerClientFilter后面处理,从Route中找到具体的path,然后插入到uri的path中 - * - * @author tanghc - */ -@Slf4j -public class LoadBalancerClientExtFilter implements GlobalFilter, Ordered { - - @Override - public int getOrder() { - return LOAD_BALANCER_CLIENT_FILTER_ORDER + 1; - } - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); - String path = this.findPath(exchange, route); - if (StringUtils.hasLength(path)) { - URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR); - UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUri(url); - uriComponentsBuilder.path(path); - URI requestUrl = uriComponentsBuilder.build(true).toUri(); - exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl); - } - return chain.filter(exchange); - } - - protected String findPath(ServerWebExchange exchange, Route route) { - String path = exchange.getAttribute(SopConstants.REDIRECT_PATH_KEY); - if (path != null) { - return path; - } - URI routeUri = route.getUri(); - String uriStr = routeUri.toString(); - return RouteUtil.findPath(uriStr); - } - -} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/SopLoadBalancerClientFilter.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/SopLoadBalancerClientFilter.java new file mode 100644 index 00000000..b8e74c55 --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/SopLoadBalancerClientFilter.java @@ -0,0 +1,32 @@ +package com.gitee.sop.gatewaycommon.gateway.filter; + +import com.gitee.sop.gatewaycommon.gateway.loadbalancer.SopLoadBalancerClient; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; +import org.springframework.cloud.gateway.config.LoadBalancerProperties; +import org.springframework.cloud.gateway.filter.LoadBalancerClientFilter; +import org.springframework.web.server.ServerWebExchange; + +import java.net.URI; + +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; + +/** + * 扩展负载均衡过滤器 + * @author tanghc + */ +public class SopLoadBalancerClientFilter extends LoadBalancerClientFilter { + public SopLoadBalancerClientFilter(LoadBalancerClient loadBalancer, LoadBalancerProperties properties) { + super(loadBalancer, properties); + } + + @Override + protected ServiceInstance choose(ServerWebExchange exchange) { + if (loadBalancer instanceof SopLoadBalancerClient) { + SopLoadBalancerClient sopLoadBalancerClient = (SopLoadBalancerClient)loadBalancer; + return sopLoadBalancerClient.choose(((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost(), exchange); + } else { + return super.choose(exchange); + } + } +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/ValidateFilter.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/ValidateFilter.java deleted file mode 100644 index 72c6d582..00000000 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/ValidateFilter.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.gitee.sop.gatewaycommon.gateway.filter; - -import com.gitee.sop.gatewaycommon.exception.ApiException; -import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil; -import com.gitee.sop.gatewaycommon.param.ApiParam; -import com.gitee.sop.gatewaycommon.param.ParamBuilder; -import com.gitee.sop.gatewaycommon.validate.Validator; -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.web.server.ServerWebExchange; -import reactor.core.publisher.Mono; - -/** - * @author tanghc - */ -@Slf4j -public class ValidateFilter implements GlobalFilter, Ordered { - - @Autowired - private ParamBuilder paramBuilder; - - @Autowired - private Validator validator; - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - // 解析参数 - ApiParam param = paramBuilder.build(exchange); - ServerWebExchangeUtil.setApiParam(exchange, param); - // 验证操作,这里有负责验证签名参数 - try { - validator.validate(param); - } catch (ApiException e) { - log.error("验证失败,params:{}", param.toJSONString(), e); - throw e; - } - return chain.filter(exchange); - } - - @Override - public int getOrder() { - // 最优先执行 - return Orders.VALIDATE_FILTER_ORDER; - } -} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/handler/GatewayExceptionHandler.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/handler/GatewayExceptionHandler.java index 7d19b212..0f623358 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/handler/GatewayExceptionHandler.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/handler/GatewayExceptionHandler.java @@ -2,15 +2,15 @@ package com.gitee.sop.gatewaycommon.gateway.handler; import com.gitee.sop.gatewaycommon.bean.ApiContext; import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil; -import com.gitee.sop.gatewaycommon.gateway.result.GatewayResult; import com.gitee.sop.gatewaycommon.param.ApiParam; import com.gitee.sop.gatewaycommon.result.ResultExecutor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.codec.HttpMessageWriter; -import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.util.Assert; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.server.RequestPredicates; @@ -33,10 +33,26 @@ public class GatewayExceptionHandler implements ErrorWebExceptionHandler { private static final Logger log = LoggerFactory.getLogger(GatewayExceptionHandler.class); + + /** + * MessageReader + */ + private List> messageReaders = Collections.emptyList(); + + /** + * MessageWriter + */ + private List> messageWriters = Collections.emptyList(); + + /** + * ViewResolvers + */ + private List viewResolvers = Collections.emptyList(); + @Override public Mono handle(ServerWebExchange exchange, Throwable ex) { - ResultExecutor resultExecutor = ApiContext.getApiConfig().getGatewayResultExecutor(); - GatewayResult errorResult = resultExecutor.buildErrorResult(exchange, ex); + ResultExecutor resultExecutor = ApiContext.getApiConfig().getGatewayResultExecutor(); + String errorResult = resultExecutor.buildErrorResult(exchange, ex); ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange); // 错误记录 log.error("gateway网关报错,params:{}, errorMsg:{}", apiParam, ex.getMessage(), ex); @@ -52,21 +68,6 @@ public class GatewayExceptionHandler implements ErrorWebExceptionHandler { } - /** - * MessageReader - */ - private List> messageReaders = Collections.emptyList(); - - /** - * MessageWriter - */ - private List> messageWriters = Collections.emptyList(); - - /** - * ViewResolvers - */ - private List viewResolvers = Collections.emptyList(); - /** * 参考AbstractErrorWebExceptionHandler * @@ -103,11 +104,11 @@ public class GatewayExceptionHandler implements ErrorWebExceptionHandler { * @param result 返回结果 * @return 返回mono */ - protected Mono renderErrorResponse(GatewayResult result) { + protected Mono renderErrorResponse(String result) { return ServerResponse - .status(result.getHttpStatus()) - .contentType(result.getContentType()) - .body(BodyInserters.fromObject(result.getBody())); + .status(HttpStatus.OK) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .body(BodyInserters.fromObject(result)); } /** diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/loadbalancer/GatewayLoadBalanceServerChooser.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/loadbalancer/GatewayLoadBalanceServerChooser.java new file mode 100644 index 00000000..8a6f8886 --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/loadbalancer/GatewayLoadBalanceServerChooser.java @@ -0,0 +1,29 @@ +package com.gitee.sop.gatewaycommon.gateway.loadbalancer; + +import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil; +import com.gitee.sop.gatewaycommon.loadbalancer.LoadBalanceServerChooser; +import com.gitee.sop.gatewaycommon.param.ApiParam; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.netflix.ribbon.SpringClientFactory; +import org.springframework.web.server.ServerWebExchange; + +/** + * @author tanghc + */ +public class GatewayLoadBalanceServerChooser extends LoadBalanceServerChooser { + + public GatewayLoadBalanceServerChooser(SpringClientFactory clientFactory) { + this.setClientFactory(clientFactory); + } + + @Override + public String getHost(ServerWebExchange exchange) { + return exchange.getRequest().getURI().getHost(); + } + + @Override + public ApiParam getApiParam(ServerWebExchange exchange) { + return ServerWebExchangeUtil.getApiParam(exchange); + } + +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/loadbalancer/NacosServerIntrospector.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/loadbalancer/NacosServerIntrospector.java new file mode 100644 index 00000000..565fa79a --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/loadbalancer/NacosServerIntrospector.java @@ -0,0 +1,23 @@ +package com.gitee.sop.gatewaycommon.gateway.loadbalancer; + +import com.netflix.loadbalancer.Server; +import org.springframework.cloud.alibaba.nacos.ribbon.NacosServer; +import org.springframework.cloud.netflix.ribbon.DefaultServerIntrospector; + +import java.util.Map; + +/** + * @author tanghc + */ +public class NacosServerIntrospector extends DefaultServerIntrospector { + + @Override + public Map getMetadata(Server server) { + if (server instanceof NacosServer) { + NacosServer discoveryServer = (NacosServer)server; + return discoveryServer.getInstance().getMetadata(); + } else { + return super.getMetadata(server); + } + } +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/loadbalancer/SopLoadBalancerClient.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/loadbalancer/SopLoadBalancerClient.java new file mode 100644 index 00000000..9f38866b --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/loadbalancer/SopLoadBalancerClient.java @@ -0,0 +1,107 @@ +package com.gitee.sop.gatewaycommon.gateway.loadbalancer; + +import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil; +import com.gitee.sop.gatewaycommon.loadbalancer.ServerChooserContext; +import com.gitee.sop.gatewaycommon.param.ApiParam; +import com.netflix.client.config.IClientConfig; +import com.netflix.loadbalancer.Server; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.netflix.ribbon.DefaultServerIntrospector; +import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient; +import org.springframework.cloud.netflix.ribbon.RibbonUtils; +import org.springframework.cloud.netflix.ribbon.ServerIntrospector; +import org.springframework.cloud.netflix.ribbon.SpringClientFactory; +import org.springframework.web.server.ServerWebExchange; + +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +/** + * 重写负载均衡处理。 + * 默认使用的是RibbonLoadBalancerClient类,详见org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration#loadBalancerClient() + * + * @author tanghc + */ +public class SopLoadBalancerClient extends RibbonLoadBalancerClient implements ServerChooserContext { + + private final SpringClientFactory clientFactory; + private GatewayLoadBalanceServerChooser loadBalanceServerChooser; + + public SopLoadBalancerClient(SpringClientFactory clientFactory) { + super(clientFactory); + this.clientFactory = clientFactory; + this.loadBalanceServerChooser = new GatewayLoadBalanceServerChooser(clientFactory); + } + + /** + * New: Select a server using a 'key'. + */ + @Override + public ServiceInstance choose(String serviceId, Object hint) { + return loadBalanceServerChooser.choose( + serviceId + , (ServerWebExchange) hint + , this.getLoadBalancer(serviceId) + , () -> super.choose(serviceId, hint) + , (servers) -> getRibbonServer(serviceId, servers) + ); + } + + @Override + public ApiParam getApiParam(ServerWebExchange exchange) { + return ServerWebExchangeUtil.getApiParam(exchange); + } + + @Override + public String getHost(ServerWebExchange exchange) { + return exchange.getRequest().getURI().getHost(); + } + + private RibbonServer getRibbonServer(String serviceId, List servers) { + Server server = this.chooseRandomServer(servers); + if (server == null) { + return null; + } + return new RibbonServer( + serviceId + , server + , isSecure(server, serviceId) + , serverIntrospector(serviceId).getMetadata(server) + ); + } + + /** + * 随机选取一台实例 + * + * @param servers 服务列表 + * @return 返回实例,没有返回null + */ + private Server chooseRandomServer(List servers) { + if (servers.isEmpty()) { + return null; + } + int serverCount = servers.size(); + // 随机选取一台实例 + int index = chooseRandomInt(serverCount); + return servers.get(index); + } + + private int chooseRandomInt(int serverCount) { + return ThreadLocalRandom.current().nextInt(serverCount); + } + + private ServerIntrospector serverIntrospector(String serviceId) { + ServerIntrospector serverIntrospector = this.clientFactory.getInstance(serviceId, + ServerIntrospector.class); + if (serverIntrospector == null) { + serverIntrospector = new DefaultServerIntrospector(); + } + return serverIntrospector; + } + + private boolean isSecure(Server server, String serviceId) { + IClientConfig config = this.clientFactory.getClientConfig(serviceId); + ServerIntrospector serverIntrospector = serverIntrospector(serviceId); + return RibbonUtils.isSecure(config, serverIntrospector, server); + } +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/param/GatewayParamBuilder.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/param/GatewayParamBuilder.java deleted file mode 100644 index a1054f8d..00000000 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/param/GatewayParamBuilder.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.gitee.sop.gatewaycommon.gateway.param; - -import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil; -import com.gitee.sop.gatewaycommon.param.ApiParam; -import com.gitee.sop.gatewaycommon.param.BaseParamBuilder; -import org.springframework.web.server.ServerWebExchange; - -/** - * @author tanghc - */ -public class GatewayParamBuilder extends BaseParamBuilder { - - @Override - public ApiParam buildRequestParams(ServerWebExchange exchange) { - return ServerWebExchangeUtil.getRequestParams(exchange); - } - - @Override - public String getIP(ServerWebExchange ctx) { - return ctx.getRequest().getRemoteAddress().getAddress().getHostAddress(); - } - - @Override - public void setVersionInHeader(ServerWebExchange ctx, String headerName, String version) { - ctx.getRequest().getHeaders().add(headerName, version); - } -} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/result/GatewayResultExecutor.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/result/GatewayResultExecutor.java index 55f57a92..3c69cdda 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/result/GatewayResultExecutor.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/result/GatewayResultExecutor.java @@ -4,12 +4,14 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.gitee.sop.gatewaycommon.bean.SopConstants; import com.gitee.sop.gatewaycommon.exception.ApiException; +import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil; import com.gitee.sop.gatewaycommon.message.Error; import com.gitee.sop.gatewaycommon.message.ErrorEnum; +import com.gitee.sop.gatewaycommon.param.ApiParam; import com.gitee.sop.gatewaycommon.result.BaseExecutorAdapter; +import com.gitee.sop.gatewaycommon.result.ResultExecutorForGateway; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.util.CollectionUtils; import org.springframework.web.server.ServerWebExchange; @@ -21,7 +23,8 @@ import java.util.Map; * @author tanghc */ @Slf4j -public class GatewayResultExecutor extends BaseExecutorAdapter { +public class GatewayResultExecutor extends BaseExecutorAdapter + implements ResultExecutorForGateway { @Override public int getResponseStatus(ServerWebExchange exchange) { @@ -46,12 +49,12 @@ public class GatewayResultExecutor extends BaseExecutorAdapter getApiParam(ServerWebExchange exchange) { - return exchange.getAttribute(SopConstants.CACHE_REQUEST_BODY_FOR_MAP); + public ApiParam getApiParam(ServerWebExchange exchange) { + return ServerWebExchangeUtil.getApiParam(exchange); } @Override - public GatewayResult buildErrorResult(ServerWebExchange exchange, Throwable ex) { + public String buildErrorResult(ServerWebExchange exchange, Throwable ex) { Error error; if (ex instanceof ApiException) { ApiException apiException = (ApiException) ex; @@ -59,11 +62,8 @@ public class GatewayResultExecutor extends BaseExecutorAdapter { + + private static final String VALIDATE_ERROR_PATH = "/sop/validateError"; + + @Override + public ApiParam getApiParam(ServerWebExchange exchange) { + return ServerWebExchangeUtil.getApiParam(exchange); + } + + @Override + public ForwardInfo getForwardInfo(ServerWebExchange exchange) { + // 如果有异常,直接跳转到异常处理 + if (ServerWebExchangeUtil.getThrowable(exchange) != null) { + return new ForwardInfo(VALIDATE_ERROR_PATH, ""); + } + return super.getForwardInfo(exchange); + } +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/GatewayRouteCache.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/GatewayRouteCache.java index dfb8e464..3e47b41c 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/GatewayRouteCache.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/GatewayRouteCache.java @@ -6,11 +6,8 @@ import com.gitee.sop.gatewaycommon.manager.BaseRouteCache; import com.gitee.sop.gatewaycommon.manager.RouteRepository; import org.springframework.beans.BeanUtils; import org.springframework.cloud.gateway.filter.FilterDefinition; -import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition; import java.net.URI; -import java.util.Collections; -import java.util.LinkedList; import java.util.List; import java.util.stream.Collectors; @@ -32,7 +29,7 @@ public class GatewayRouteCache extends BaseRouteCache { path = path.replace('{', '?'); path = path.replace('}', '?'); } - targetRoute.setUri(URI.create(routeDefinition.getUri() + "#" + path)); + targetRoute.setUri(URI.create(routeDefinition.getUri() + "/" + path)); targetRoute.setOrder(routeDefinition.getOrder()); // 添加过滤器 List filterDefinitionList = routeDefinition.getFilters() @@ -43,40 +40,7 @@ public class GatewayRouteCache extends BaseRouteCache { return filterDefinition; }) .collect(Collectors.toList()); - - - LinkedList predicateDefinitionList = routeDefinition.getPredicates() - .stream() - .map(predicate -> { - PredicateDefinition predicateDefinition = new PredicateDefinition(); - BeanUtils.copyProperties(predicate, predicateDefinition); - return predicateDefinition; - }) - .collect(Collectors.toCollection(LinkedList::new)); - // 下面两个自定义的断言添加到顶部,注意:ReadBody需要放在最前面 - // 对应断言: - // NameVersion -> com.gitee.sop.gatewaycommon.gateway.route.NameVersionRoutePredicateFactory - // ReadBody -> com.gitee.sop.gatewaycommon.gateway.route.ReadBodyRoutePredicateFactory - predicateDefinitionList.addFirst(new PredicateDefinition("NameVersion=" + routeDefinition.getId())); - predicateDefinitionList.addFirst(new PredicateDefinition("ReadBody=")); - - targetRoute.setFilters(filterDefinitionList); - targetRoute.setPredicates(predicateDefinitionList); return new GatewayTargetRoute(serviceRouteInfo, routeDefinition, targetRoute); } - @Override - protected List getExtRouteDefinitionList(ServiceRouteInfo serviceRouteInfo) { - // 在第一个位置放一个没用的路由,SpringCloudGateway会从第二个路由开始找,原因不详 - RouteDefinition routeDefinition = new RouteDefinition(); - String name = "_first_route_"; - String version = String.valueOf(System.currentTimeMillis()); - routeDefinition.setId(name + version); - routeDefinition.setName(name); - routeDefinition.setVersion(version); - routeDefinition.setUri("lb://" + serviceRouteInfo.getServiceId()); - routeDefinition.setOrder(Integer.MIN_VALUE); - - return Collections.singletonList(routeDefinition); - } } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/GatewayRouteRepository.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/GatewayRouteRepository.java index 2a54578c..fa8c0f54 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/GatewayRouteRepository.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/GatewayRouteRepository.java @@ -1,15 +1,19 @@ package com.gitee.sop.gatewaycommon.gateway.route; +import com.gitee.sop.gatewaycommon.bean.AbstractTargetRoute; import com.gitee.sop.gatewaycommon.bean.RouteDefinition; -import com.gitee.sop.gatewaycommon.bean.TargetRoute; import com.gitee.sop.gatewaycommon.manager.RouteRepository; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.springframework.cloud.gateway.route.RouteDefinitionRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.gateway.event.RefreshRoutesEvent; +import org.springframework.cloud.gateway.route.Route; +import org.springframework.cloud.gateway.route.RouteLocator; +import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; +import org.springframework.context.ApplicationContext; import org.springframework.util.AntPathMatcher; import org.springframework.util.PathMatcher; import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; import java.util.Collection; import java.util.LinkedHashMap; @@ -25,28 +29,37 @@ import static java.util.Collections.synchronizedMap; * @author tanghc */ @Slf4j -public class GatewayRouteRepository implements RouteDefinitionRepository, RouteRepository { +public class GatewayRouteRepository implements RouteRepository, RouteLocator { private PathMatcher pathMatcher = new AntPathMatcher(); private final Map routes = synchronizedMap(new LinkedHashMap<>()); - @Override - public Flux getRouteDefinitions() { - List list = routes.values().parallelStream() - .map(TargetRoute::getTargetRouteDefinition) - .collect(Collectors.toList()); - return Flux.fromIterable(list); - } + @Autowired + private RouteLocatorBuilder routeLocatorBuilder; + + @Autowired + private ApplicationContext applicationContext; + + private RouteLocator routeLocator; @Override - public Mono save(Mono route) { - return null; + public Flux getRoutes() { + return routeLocator.getRoutes(); } - @Override - public Mono delete(Mono routeId) { - return null; + public void refresh() { + RouteLocatorBuilder.Builder builder = routeLocatorBuilder.routes(); + List routeDefinitionList = this.routes.values() + .stream() + .map(AbstractTargetRoute::getRouteDefinition) + .collect(Collectors.toList()); + routeDefinitionList.forEach(routeDefinition -> builder.route(routeDefinition.getId(), + r -> r.path(routeDefinition.getPath()) + .uri(routeDefinition.getUri()))); + this.routeLocator = builder.build(); + // 触发 + applicationContext.publishEvent(new RefreshRoutesEvent(new Object())); } /** diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/NameVersionRoutePredicateFactory.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/NameVersionRoutePredicateFactory.java deleted file mode 100644 index a3a61203..00000000 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/NameVersionRoutePredicateFactory.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.gitee.sop.gatewaycommon.gateway.route; - -import com.gitee.sop.gatewaycommon.bean.SopConstants; -import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil; -import com.gitee.sop.gatewaycommon.param.ParamNames; -import lombok.extern.slf4j.Slf4j; -import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory; -import org.springframework.util.AntPathMatcher; -import org.springframework.util.CollectionUtils; -import org.springframework.util.PathMatcher; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.server.ServerWebExchange; - -import javax.validation.constraints.NotEmpty; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.function.Predicate; - -/** - * 此断言决定执行哪个路由 - * - * 使用地方: - * @see com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteCache - * - * @author tanghc - */ -@Slf4j -public class NameVersionRoutePredicateFactory extends AbstractRoutePredicateFactory { - - private static final String PARAM_KEY = "param"; - private static final String REGEXP_KEY = "regexp"; - - private PathMatcher pathMatcher = new AntPathMatcher(); - - public NameVersionRoutePredicateFactory() { - super(Config.class); - } - - @Override - public List shortcutFieldOrder() { - return Arrays.asList(PARAM_KEY, REGEXP_KEY); - } - - /** - * config.param为nameVersion,即路由id - * - * @param config - * @return 返回断言 - */ - @Override - public Predicate apply(Config config) { - - return exchange -> { - Map params = ServerWebExchangeUtil.getRequestParams(exchange); - if (CollectionUtils.isEmpty(params)) { - return false; - } - String nameVersion = config.param; - Object name = params.get(ParamNames.API_NAME); - Object version = params.get(ParamNames.VERSION_NAME); - if (name == null || version == null) { - return false; - } - String key = name.toString() + version.toString(); - // 如果是通配符 - if (nameVersion.contains("{")) { - boolean match = pathMatcher.match(nameVersion, key); - if (match) { - Map attributes = exchange.getAttributes(); - attributes.put(SopConstants.REDIRECT_PATH_KEY, key); - } - return match; - } else { - return key.equals(nameVersion); - } - }; - } - - @Validated - public static class Config { - @NotEmpty - private String param; - - private String regexp; - - public String getParam() { - return param; - } - - public Config setParam(String param) { - this.param = param; - return this; - } - - public String getRegexp() { - return regexp; - } - - public Config setRegexp(String regexp) { - this.regexp = regexp; - return this; - } - } -} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/ReadBodyRoutePredicateFactory.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/ReadBodyRoutePredicateFactory.java deleted file mode 100644 index d8f48b1c..00000000 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/ReadBodyRoutePredicateFactory.java +++ /dev/null @@ -1,202 +0,0 @@ -package com.gitee.sop.gatewaycommon.gateway.route; - -import org.apache.commons.lang.StringUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.cloud.gateway.handler.AsyncPredicate; -import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.ReadBodyPredicateFactory; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.core.io.buffer.DataBufferUtils; -import org.springframework.http.HttpMethod; -import org.springframework.http.codec.HttpMessageReader; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpRequestDecorator; -import org.springframework.util.MultiValueMap; -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; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.function.Predicate; - -import static org.springframework.cloud.gateway.filter.AdaptCachedBodyGlobalFilter.CACHED_REQUEST_BODY_KEY; - -/** - * 获取请求参数插件,兼容get,post,使用方式: - * @Bean - * ReadBodyRoutePredicateFactory readBodyRoutePredicateFactory() { - * return new ReadBodyRoutePredicateFactory(); - * } - * - * @see org.springframework.cloud.gateway.handler.predicate.ReadBodyPredicateFactory - * 详见:https://blog.51cto.com/thinklili/2329184 - * - * 使用地方: - * @see com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteCache - * - * @author tanghc - */ -public class ReadBodyRoutePredicateFactory extends AbstractRoutePredicateFactory { - - protected static final Log LOGGER = LogFactory.getLog(ReadBodyPredicateFactory.class); - - private static final String TEST_ATTRIBUTE = "read_body_predicate_test_attribute"; - private static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject"; - private static final List> HTTP_MESSAGE_READERS = HandlerStrategies.withDefaults().messageReaders(); - - - public ReadBodyRoutePredicateFactory() { - super(ReadBodyRoutePredicateFactory.Config.class); - } - - @Override - public AsyncPredicate applyAsync(Config config) { - return exchange -> { - HttpMethod method = exchange.getRequest().getMethod(); - if (method == HttpMethod.POST) { - return this.applyForPost(exchange, config); - } else { - return this.applyForGet(exchange, config); - } - }; - } - - /** - * 获取post表单参数 - * @param exchange - * @param config - * @return - */ - protected Mono applyForPost(ServerWebExchange exchange, Config config) { - Class inClass = config.getInClass(); - - Object cachedBody = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY); - // We can only read the body from the request once, once that happens if we try to read the body again an - // exception will be thrown. The below if/else caches the body object as a request attribute in the ServerWebExchange - // so if this filter is run more than once (due to more than one route using it) we do not try to read the - // request body multiple times - if (cachedBody != null) { - try { - boolean test = config.getPredicate().test(cachedBody); - exchange.getAttributes().put(TEST_ATTRIBUTE, test); - return Mono.just(test); - } catch (ClassCastException e) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Predicate test failed because class in predicate does not canVisit the cached body object", - e); - } - } - return Mono.just(false); - } else { - //Join all the DataBuffers so we have a single DataBuffer for the body - return DataBufferUtils.join(exchange.getRequest().getBody()) - .flatMap(dataBuffer -> { - //Update the retain counts so we can read the body twice, once to parse into an object - //that we can test the predicate against and a second time when the HTTP client sends - //the request downstream - //Note: if we end up reading the body twice we will run into a problem, but as of right - //now there is no good use case for doing this - DataBufferUtils.retain(dataBuffer); - //Make a slice for each read so each read has its own read/write indexes - Flux cachedFlux = Flux.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount()))); - - ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) { - @Override - public Flux getBody() { - return cachedFlux; - } - }; - return ServerRequest.create(exchange.mutate().request(mutatedRequest).build(), HTTP_MESSAGE_READERS) - .bodyToMono(inClass) - .doOnNext(objectValue -> { - exchange.getAttributes().put(CACHE_REQUEST_BODY_OBJECT_KEY, objectValue); - exchange.getAttributes().put(CACHED_REQUEST_BODY_KEY, cachedFlux); - }) - .map(objectValue -> config.getPredicate().test(objectValue)); - }); - - } - } - - /** - * 获取GET请求参数 - * @param exchange - * @param config - * @return - */ - protected Mono applyForGet(ServerWebExchange exchange, Config config) { - // 处理get请求 - MultiValueMap queryParams = exchange.getRequest().getQueryParams(); - String objectValue = null; - if (queryParams != null && queryParams.size() > 0) { - List params = new ArrayList<>(queryParams.size()); - for (Map.Entry> entry : queryParams.entrySet()) { - params.add(entry.getKey() + "=" + entry.getValue().get(0)); - } - objectValue = StringUtils.join(params.toArray()); - exchange.getAttributes().put(CACHE_REQUEST_BODY_OBJECT_KEY, objectValue); - } - return Mono.just(config.getPredicate().test(objectValue)); - } - - @Override - public Config newConfig() { - Config config = super.newConfig(); - config.setInClass(String.class); - config.setPredicate(Objects::nonNull); - return config; - } - - @Override - @SuppressWarnings("unchecked") - public Predicate apply(Config config) { - throw new UnsupportedOperationException( - "ReadBodyPredicateFactory is only async."); - } - - public static class Config { - private Class inClass; - private Predicate predicate; - private Map hints; - - public Class getInClass() { - return inClass; - } - - public Config setInClass(Class inClass) { - this.inClass = inClass; - return this; - } - - public Predicate getPredicate() { - return predicate; - } - - public Config setPredicate(Class inClass, Predicate predicate) { - setInClass(inClass); - this.predicate = predicate; - return this; - } - - public Config setPredicate(Predicate predicate) { - this.predicate = predicate; - return this; - } - - public Map getHints() { - return hints; - } - - public Config setHints(Map hints) { - this.hints = hints; - return this; - } - } - -} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/EnvironmentServerChooser.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/EnvironmentServerChooser.java deleted file mode 100644 index a3e7ddea..00000000 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/EnvironmentServerChooser.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.gitee.sop.gatewaycommon.loadbalancer; - -import com.gitee.sop.gatewaycommon.bean.SpringContext; -import com.gitee.sop.gatewaycommon.zuul.loadbalancer.BaseServerChooser; -import com.netflix.loadbalancer.Server; -import org.springframework.cloud.alibaba.nacos.ribbon.NacosServer; -import org.springframework.core.env.Environment; - -import javax.servlet.http.HttpServletRequest; -import java.util.Map; - -/** - * 预发布、灰度环境选择,参考自:https://segmentfault.com/a/1190000017412946 - * - * @author tanghc - */ -public class EnvironmentServerChooser extends BaseServerChooser { - - private static final String MEDATA_KEY_ENV = "env"; - private static final String ENV_PRE_VALUE = "pre"; - private static final String ENV_GRAY_VALUE = "gray"; - - /** - * 预发布机器域名 - */ - private static final String PRE_DOMAIN = "localhost"; - - @Override - protected boolean isPreServer(Server server) { - String env = getEnvValue(server); - return ENV_PRE_VALUE.equals(env); - } - - @Override - protected boolean isGrayServer(Server server) { - String env = getEnvValue(server); - return ENV_GRAY_VALUE.equals(env); - } - - private String getEnvValue(Server server) { - // eureka存储的metadata - Map metadata = getMetada(server); - return metadata.get(MEDATA_KEY_ENV); - } - - protected Map getMetada(Server server) { - return ((NacosServer) server).getMetadata(); - } - - - /** - * 通过判断hostname来确定是否是预发布请求,可修改此方法实现自己想要的 - * - * @param request request - * @return 返回true:可以进入到预发环境 - */ - @Override - protected boolean canVisitPre(Server server, HttpServletRequest request) { - String serverName = request.getServerName(); - String domain = SpringContext.getBean(Environment.class).getProperty("pre.domain", PRE_DOMAIN); - return domain.equals(serverName); - } - -} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/EurekaEnvironmentServerChooser.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/EurekaEnvironmentServerChooser.java deleted file mode 100644 index f67ba85b..00000000 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/EurekaEnvironmentServerChooser.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.gitee.sop.gatewaycommon.loadbalancer; - -import com.netflix.loadbalancer.Server; -import com.netflix.niws.loadbalancer.DiscoveryEnabledServer; - -import java.util.Map; - -/** - * @author tanghc - */ -public class EurekaEnvironmentServerChooser extends EnvironmentServerChooser { - @Override - protected Map getMetada(Server server) { - return ((DiscoveryEnabledServer) server).getInstanceInfo().getMetadata(); - } -} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/LoadBalanceConfig.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/LoadBalanceConfig.java new file mode 100644 index 00000000..77f6c009 --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/LoadBalanceConfig.java @@ -0,0 +1,8 @@ +package com.gitee.sop.gatewaycommon.loadbalancer; + +/** + * @author tanghc + */ +public class LoadBalanceConfig { + +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/LoadBalanceServerChooser.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/LoadBalanceServerChooser.java new file mode 100644 index 00000000..11dba443 --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/LoadBalanceServerChooser.java @@ -0,0 +1,128 @@ +package com.gitee.sop.gatewaycommon.loadbalancer; + +import com.gitee.sop.gatewaycommon.bean.SopConstants; +import com.gitee.sop.gatewaycommon.bean.SpringContext; +import com.netflix.loadbalancer.ILoadBalancer; +import com.netflix.loadbalancer.Server; +import org.springframework.cloud.netflix.ribbon.DefaultServerIntrospector; +import org.springframework.cloud.netflix.ribbon.ServerIntrospector; +import org.springframework.cloud.netflix.ribbon.SpringClientFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * 预发布、灰度发布服务器选择 + * + * @author tanghc + */ +public abstract class LoadBalanceServerChooser implements ServerChooserContext { + + private SpringClientFactory clientFactory; + + /** + * 选择服务器 + * + * @param serviceId serviceId,仅gateway网关有作用 + * @param exchange 请求上下文 + * @param loadBalancer loadBalancer + * @param superChooser 父类默认的选择 + * @param serverChooserFunction 执行选择操作 + * @return 返回服务器实例,没有选到则返回null + */ + public R choose( + String serviceId + , T exchange + , ILoadBalancer loadBalancer + , Supplier superChooser + , Function, R> serverChooserFunction) { + // 获取所有服务实例 + List servers = loadBalancer.getReachableServers(); + + // 存放预发服务器 + List preServers = new ArrayList<>(4); + // 存放灰度发布服务器 + List grayServers = new ArrayList<>(4); + // 存放非预发服务器 + List notPreServers = new ArrayList<>(4); + + for (Server server : servers) { + // 获取实例metadata + Map metadata = getMetadata(serviceId, server); + // 是否开启了预发模式 + if (this.isPreServer(metadata)) { + preServers.add(server); + } else if (this.isGrayServer(metadata)) { + grayServers.add(server); + } else { + notPreServers.add(server); + } + } + notPreServers.addAll(grayServers); + // 如果没有开启预发布服务和灰度发布,直接用默认的方式 + if (preServers.isEmpty() && grayServers.isEmpty()) { + return superChooser.get(); + } + // 如果是从预发布域名访问过来,则认为是预发布请求,选出预发服务器 + if (this.isRequestFromPreDomain(exchange)) { + return serverChooserFunction.apply(preServers); + } + // 如果是灰度请求,则认为是灰度用户,选出灰度服务器 + if (this.isRequestGrayServer(exchange)) { + return serverChooserFunction.apply(grayServers); + } + + // 到这里说明不能访问预发/灰度服务器,则需要路由到非预发服务器 + // 注意:这里允许走灰度服务器,如果不允许走,注释notPreServers.addAll(grayServers);这行 + return serverChooserFunction.apply(notPreServers); + } + + protected Map getMetadata(String serviceId, Server server) { + return serverIntrospector(serviceId).getMetadata(server); + } + + protected SpringClientFactory getSpringClientFactory() { + if (clientFactory == null) { + clientFactory = SpringContext.getBean(SpringClientFactory.class); + } + return clientFactory; + } + + public void setClientFactory(SpringClientFactory clientFactory) { + this.clientFactory = clientFactory; + } + + private ServerIntrospector serverIntrospector(String serviceId) { + ServerIntrospector serverIntrospector = getSpringClientFactory().getInstance(serviceId, + ServerIntrospector.class); + if (serverIntrospector == null) { + serverIntrospector = new DefaultServerIntrospector(); + } + return serverIntrospector; + } + + /** + * 是否是预发布服务器 + * + * @param metadata metadata + * @return true:是 + */ + private boolean isPreServer(Map metadata) { + return Objects.equals(metadata.get(SopConstants.METADATA_ENV_KEY), SopConstants.METADATA_ENV_PRE_VALUE); + } + + /** + * 是否是灰度发布服务器 + * + * @param metadata metadata + * @return true:是 + */ + private boolean isGrayServer(Map metadata) { + return Objects.equals(metadata.get(SopConstants.METADATA_ENV_KEY), SopConstants.METADATA_ENV_GRAY_VALUE); + } + +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/ServerChooserContext.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/ServerChooserContext.java new file mode 100644 index 00000000..8b38406b --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/ServerChooserContext.java @@ -0,0 +1,35 @@ +package com.gitee.sop.gatewaycommon.loadbalancer; + +import com.gitee.sop.gatewaycommon.bean.ApiParamAware; +import com.gitee.sop.gatewaycommon.manager.EnvironmentKeys; +import com.gitee.sop.gatewaycommon.param.ApiParam; +import org.apache.commons.lang.ArrayUtils; +import org.springframework.util.StringUtils; + +/** + * @author tanghc + */ +public interface ServerChooserContext extends ApiParamAware { + + /** + * 通过判断hostname来确定是否是预发布请求 + * + * @param t t + * @return 返回true:可以进入到预发环境 + */ + default boolean isRequestFromPreDomain(T t) { + String domain = EnvironmentKeys.PRE_DOMAIN.getValue(); + if (StringUtils.isEmpty(domain)) { + return false; + } + String[] domains = domain.split("\\,"); + return ArrayUtils.contains(domains, getHost(t)); + } + + default boolean isRequestGrayServer(T t) { + ApiParam apiParam = getApiParam(t); + return apiParam.isGrayRequest(); + } + + String getHost(T t); +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/builder/AppIdGrayUserBuilder.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/builder/AppIdGrayUserBuilder.java new file mode 100644 index 00000000..1d4a6027 --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/builder/AppIdGrayUserBuilder.java @@ -0,0 +1,19 @@ +package com.gitee.sop.gatewaycommon.loadbalancer.builder; + +import com.gitee.sop.gatewaycommon.param.ApiParam; + +/** + * @author tanghc + */ +public class AppIdGrayUserBuilder implements GrayUserBuilder { + + @Override + public String buildGrayUserKey(ApiParam apiParam) { + return apiParam.fetchAppKey(); + } + + @Override + public int order() { + return 0; + } +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/builder/GrayUserBuilder.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/builder/GrayUserBuilder.java new file mode 100644 index 00000000..7e23eecb --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/builder/GrayUserBuilder.java @@ -0,0 +1,24 @@ +package com.gitee.sop.gatewaycommon.loadbalancer.builder; + +import com.gitee.sop.gatewaycommon.param.ApiParam; + +/** + * @author tanghc + */ +public interface GrayUserBuilder { + + /** + * 获取灰度用户key + * + * @param apiParam apiParam + * @return 返回用户key + */ + String buildGrayUserKey(ApiParam apiParam); + + /** + * 优先级,数字小优先 + * + * @return 返回数字 + */ + int order(); +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/builder/IpGrayUserBuilder.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/builder/IpGrayUserBuilder.java new file mode 100644 index 00000000..c629b183 --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/loadbalancer/builder/IpGrayUserBuilder.java @@ -0,0 +1,19 @@ +package com.gitee.sop.gatewaycommon.loadbalancer.builder; + +import com.gitee.sop.gatewaycommon.param.ApiParam; + +/** + * @author tanghc + */ +public class IpGrayUserBuilder implements GrayUserBuilder { + + @Override + public String buildGrayUserKey(ApiParam apiParam) { + return apiParam.fetchIp(); + } + + @Override + public int order() { + return 1; + } +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/AbstractConfiguration.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/AbstractConfiguration.java index c8b8845a..f502e6ab 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/AbstractConfiguration.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/AbstractConfiguration.java @@ -4,25 +4,31 @@ import com.gitee.sop.gatewaycommon.bean.ApiConfig; import com.gitee.sop.gatewaycommon.bean.ApiContext; import com.gitee.sop.gatewaycommon.bean.BeanInitializer; import com.gitee.sop.gatewaycommon.bean.SpringContext; +import com.gitee.sop.gatewaycommon.gateway.loadbalancer.NacosServerIntrospector; import com.gitee.sop.gatewaycommon.limit.LimitManager; import com.gitee.sop.gatewaycommon.loadbalancer.SopPropertiesFactory; import com.gitee.sop.gatewaycommon.message.ErrorFactory; import com.gitee.sop.gatewaycommon.param.ParameterFormatter; -import com.gitee.sop.gatewaycommon.route.ServiceRouteListener; import com.gitee.sop.gatewaycommon.route.EurekaRegistryListener; import com.gitee.sop.gatewaycommon.route.NacosRegistryListener; import com.gitee.sop.gatewaycommon.route.RegistryListener; import com.gitee.sop.gatewaycommon.route.ServiceListener; +import com.gitee.sop.gatewaycommon.route.ServiceRouteListener; import com.gitee.sop.gatewaycommon.secret.IsvManager; import com.gitee.sop.gatewaycommon.session.SessionManager; import com.gitee.sop.gatewaycommon.validate.SignConfig; import com.gitee.sop.gatewaycommon.validate.Validator; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.client.discovery.event.HeartbeatEvent; import org.springframework.cloud.netflix.ribbon.PropertiesFactory; +import org.springframework.cloud.netflix.ribbon.ServerIntrospector; +import org.springframework.cloud.netflix.ribbon.eureka.EurekaServerIntrospector; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationEvent; @@ -34,11 +40,20 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; import javax.annotation.PostConstruct; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; /** * @author tanghc */ -public class AbstractConfiguration implements ApplicationContextAware { +@Slf4j +public class AbstractConfiguration implements ApplicationContextAware, ApplicationRunner { + + private Lock lock = new ReentrantLock(); + private Condition condition = lock.newCondition(); + + private volatile boolean isStartupCompleted; @Autowired protected Environment environment; @@ -60,6 +75,17 @@ public class AbstractConfiguration implements ApplicationContextAware { */ @EventListener(classes = HeartbeatEvent.class) public void listenNacosEvent(ApplicationEvent heartbeatEvent) { + // 没有启动完毕先等待 + if (!isStartupCompleted) { + lock.lock(); + try { + condition.await(); + } catch (InterruptedException e) { + log.error("condition.await() error", e); + } finally { + lock.unlock(); + } + } registryListener.onEvent(heartbeatEvent); } @@ -159,6 +185,26 @@ public class AbstractConfiguration implements ApplicationContextAware { return createCorsFilter(); } + /** + * 负责获取nacos实例的metadata + * @return + */ + @Bean + @ConditionalOnProperty("spring.cloud.nacos.discovery.server-addr") + ServerIntrospector nacosServerIntrospector() { + return new NacosServerIntrospector(); + } + + /** + * 负责获取eureka实例的metadata + * @return + */ + @Bean + @ConditionalOnProperty("eureka.client.serviceUrl.defaultZone") + ServerIntrospector eurekaServerIntrospector() { + return new EurekaServerIntrospector(); + } + protected CorsFilter createCorsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); this.registerCorsConfiguration(source); @@ -177,10 +223,22 @@ public class AbstractConfiguration implements ApplicationContextAware { return corsConfiguration; } + @Override + public void run(ApplicationArguments args) throws Exception { + this.isStartupCompleted = true; + lock.lock(); + condition.signalAll(); + lock.unlock(); + after(); + } + @PostConstruct - public final void after() { + private void post() { EnvironmentContext.setEnvironment(environment); SpringContext.setApplicationContext(applicationContext); + } + + public final void after() { if (RouteRepositoryContext.getRouteRepository() == null) { throw new IllegalArgumentException("RouteRepositoryContext.setRouteRepository()方法未使用"); } @@ -196,7 +254,6 @@ public class AbstractConfiguration implements ApplicationContextAware { initMessage(); initBeanInitializer(); doAfter(); - } protected void initBeanInitializer() { diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/BaseRouteCache.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/BaseRouteCache.java index e2dbddd1..0c0ace23 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/BaseRouteCache.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/BaseRouteCache.java @@ -5,12 +5,9 @@ import com.gitee.sop.gatewaycommon.bean.RouteDefinition; import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo; import com.gitee.sop.gatewaycommon.bean.TargetRoute; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.collections.CollectionUtils; import org.springframework.util.DigestUtils; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -55,21 +52,15 @@ public abstract class BaseRouteCache implements RouteLoad } serviceIdMd5Map.put(serviceId, newMd5); - List extRouteDefinitionList = this.getExtRouteDefinitionList(serviceRouteInfo); List routeDefinitionList = serviceRouteInfo.getRouteDefinitionList(); - int size = extRouteDefinitionList.size() + routeDefinitionList.size(); - List allRoutes = new ArrayList<>(size); - if (CollectionUtils.isNotEmpty(extRouteDefinitionList)) { - allRoutes.addAll(extRouteDefinitionList); - } - allRoutes.addAll(routeDefinitionList); - for (RouteDefinition routeDefinition : allRoutes) { + for (RouteDefinition routeDefinition : routeDefinitionList) { T targetRoute = this.buildTargetRoute(serviceRouteInfo, routeDefinition); routeRepository.add(targetRoute); if (log.isDebugEnabled()) { log.debug("新增路由:{}", JSON.toJSONString(routeDefinition)); } } + this.routeRepository.refresh(); callback.accept(null); } catch (Exception e) { log.error("加载路由信息失败,serviceRouteInfo:{}", serviceRouteInfo, e); @@ -91,14 +82,6 @@ public abstract class BaseRouteCache implements RouteLoad return DigestUtils.md5DigestAsHex(md5Source.getBytes(StandardCharsets.UTF_8)); } - /** - * 返回附加的路由选项 - * @return 返回附加的路由选项 - */ - protected List getExtRouteDefinitionList(ServiceRouteInfo serviceRouteInfo) { - return Collections.emptyList(); - } - @Override public void remove(String serviceId) { serviceIdMd5Map.remove(serviceId); diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/DefaultEnvGrayManager.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/DefaultEnvGrayManager.java index 9e6df3be..abf3ae4f 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/DefaultEnvGrayManager.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/DefaultEnvGrayManager.java @@ -49,7 +49,7 @@ public class DefaultEnvGrayManager implements EnvGrayManager { } private ServiceGrayConfig getGrayConfig(String serviceId) { - if (serviceId == null) { + if (serviceId == null || !instanceIdServiceIdMap.containsValue(serviceId)) { return null; } return serviceGrayConfigMap.get(serviceId); diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/EnvironmentKeys.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/EnvironmentKeys.java index f05fa2eb..3fa1b20f 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/EnvironmentKeys.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/EnvironmentKeys.java @@ -1,6 +1,6 @@ package com.gitee.sop.gatewaycommon.manager; -import com.gitee.sop.gatewaycommon.loadbalancer.EnvironmentServerChooser; +import com.gitee.sop.gatewaycommon.zuul.loadbalancer.EnvironmentServerChooser; public enum EnvironmentKeys { SPRING_PROFILES_ACTIVE("spring.profiles.active", "default"), @@ -9,7 +9,7 @@ public enum EnvironmentKeys { */ SPRING_APPLICATION_NAME("spring.application.name"), /** - * 指定负载均衡规则类,默认使用com.gitee.sop.gateway.loadbalancer.PreEnvironmentServerChooser + * 指定负载均衡规则类 */ ZUUL_CUSTOM_RULE_CLASSNAME("zuul.custom-rule-classname", EnvironmentServerChooser.class.getName()), @@ -35,8 +35,11 @@ public enum EnvironmentKeys { /** * 排除其它微服务,正则形式,多个用英文逗号隔开 */ - SOP_SERVICE_EXCLUDE_REGEX("sop.service.exclude-regex") - ; + SOP_SERVICE_EXCLUDE_REGEX("sop.service.exclude-regex"), + /** + * 预发布域名 + */ + PRE_DOMAIN("pre.domain"); private String key; private String defaultValue; @@ -57,4 +60,8 @@ public enum EnvironmentKeys { public String getValue() { return EnvironmentContext.getValue(key, defaultValue); } + + public String getValue(String defaultValue) { + return EnvironmentContext.getValue(key, defaultValue); + } } \ No newline at end of file diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/RouteRepository.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/RouteRepository.java index 8976090f..b6435e07 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/RouteRepository.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/RouteRepository.java @@ -45,4 +45,9 @@ public interface RouteRepository { * @param serviceId 服务id */ void deleteAll(String serviceId); + + /** + * 刷新 + */ + default void refresh() {} } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/RouteRepositoryContext.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/RouteRepositoryContext.java index f103f55e..ec4eb090 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/RouteRepositoryContext.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/RouteRepositoryContext.java @@ -25,12 +25,14 @@ public class RouteRepositoryContext { * 检查路由是否存在,不存在报错 * @param routeId 路由id * @param errorEnum 报错信息 + * @return 返回TargetRoute */ - public static void checkExist(String routeId, ErrorEnum errorEnum) { + public static TargetRoute checkExist(String routeId, ErrorEnum errorEnum) { TargetRoute targetRoute = routeRepository.get(routeId); if (targetRoute == null) { throw errorEnum.getErrorMeta().getException(); } + return targetRoute; } } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiParam.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiParam.java index ea5e43bd..1e3559b1 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiParam.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiParam.java @@ -14,6 +14,8 @@ import java.util.Map; */ public class ApiParam extends JSONObject implements Param { + + public ApiParam() { } @@ -29,6 +31,8 @@ public class ApiParam extends JSONObject implements Param { private String ip; + private boolean isGrayRequest; + private transient UploadContext uploadContext; public void fitNameVersion() { @@ -246,4 +250,12 @@ public class ApiParam extends JSONObject implements Param { public String fetchIp() { return ip; } + + public boolean isGrayRequest() { + return isGrayRequest; + } + + public void setGrayRequest(boolean grayRequest) { + isGrayRequest = grayRequest; + } } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/BaseExecutorAdapter.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/BaseExecutorAdapter.java index c986bd2f..7361b758 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/BaseExecutorAdapter.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/BaseExecutorAdapter.java @@ -5,14 +5,15 @@ import com.alibaba.fastjson.JSONObject; import com.gitee.sop.gatewaycommon.bean.ApiConfig; import com.gitee.sop.gatewaycommon.bean.ApiContext; import com.gitee.sop.gatewaycommon.bean.ErrorDefinition; -import com.gitee.sop.gatewaycommon.bean.RouteDefinition; import com.gitee.sop.gatewaycommon.bean.Isv; +import com.gitee.sop.gatewaycommon.bean.RouteDefinition; import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo; import com.gitee.sop.gatewaycommon.bean.SopConstants; import com.gitee.sop.gatewaycommon.bean.TargetRoute; import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext; import com.gitee.sop.gatewaycommon.message.ErrorEnum; import com.gitee.sop.gatewaycommon.message.ErrorMeta; +import com.gitee.sop.gatewaycommon.param.ApiParam; import com.gitee.sop.gatewaycommon.param.ParamNames; import com.gitee.sop.gatewaycommon.secret.IsvManager; import com.gitee.sop.gatewaycommon.validate.alipay.AlipayConstants; @@ -69,7 +70,7 @@ public abstract class BaseExecutorAdapter implements ResultExecutor * @param t request * @return 返回api参数 */ - public abstract Map getApiParam(T t); + public abstract ApiParam getApiParam(T t); @Override public String mergeResult(T request, String serviceResult) { @@ -91,12 +92,12 @@ public abstract class BaseExecutorAdapter implements ResultExecutor responseData = JSON.parseObject(serviceResult); responseData.put(GATEWAY_CODE_NAME, ISP_BIZ_ERROR.getCode()); responseData.put(GATEWAY_MSG_NAME, ISP_BIZ_ERROR.getError().getMsg()); - } else if(responseStatus == HttpStatus.NOT_FOUND.value()) { + } else if (responseStatus == HttpStatus.NOT_FOUND.value()) { responseData = JSON.parseObject(serviceResult); responseData.put(GATEWAY_CODE_NAME, ISV_MISSING_METHOD_META.getCode()); responseData.put(GATEWAY_MSG_NAME, ISV_MISSING_METHOD_META.getError().getCode()); } else { - Map params = this.getApiParam(request); + ApiParam params = this.getApiParam(request); log.error("微服务端报错,params:{}, 微服务返回结果:{}", params, serviceResult); this.storeError(request, ErrorType.UNKNOWN); // 微服务端有可能返回500错误 @@ -140,8 +141,11 @@ public abstract class BaseExecutorAdapter implements ResultExecutor if (defaultSetting != null) { return defaultSetting; } - ApiInfo apiInfo = this.getApiInfo(request); - RouteDefinition baseRouteDefinition = apiInfo.gatewayRouteDefinition; + ApiParam params = this.getApiParam(request); + TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(params.fetchNameVersion()); + RouteDefinition baseRouteDefinition = Optional.ofNullable(targetRoute) + .map(TargetRoute::getRouteDefinition) + .orElse(null); return Optional.ofNullable(baseRouteDefinition) .map(routeDefinition -> { int mergeResult = baseRouteDefinition.getMergeResult(); @@ -151,11 +155,8 @@ public abstract class BaseExecutorAdapter implements ResultExecutor } protected ApiInfo getApiInfo(T request) { - Map params = this.getApiParam(request); - String name = this.getParamValue(params, ParamNames.API_NAME, SopConstants.UNKNOWN_METHOD); - String version = this.getParamValue(params, ParamNames.VERSION_NAME, SopConstants.UNKNOWN_VERSION); - - TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(name + version); + ApiParam params = this.getApiParam(request); + TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(params.fetchNameVersion()); String serviceId = Optional.ofNullable(targetRoute) .flatMap(route -> Optional.ofNullable(route.getServiceRouteInfo())) @@ -167,8 +168,8 @@ public abstract class BaseExecutorAdapter implements ResultExecutor .orElse(null); ApiInfo apiInfo = new ApiInfo(); - apiInfo.name = name; - apiInfo.version = version; + apiInfo.name = params.fetchName(); + apiInfo.version = params.fetchVersion(); apiInfo.serviceId = serviceId; apiInfo.gatewayRouteDefinition = baseRouteDefinition; return apiInfo; @@ -190,8 +191,8 @@ public abstract class BaseExecutorAdapter implements ResultExecutor public String merge(T exchange, JSONObject responseData) { JSONObject finalData = new JSONObject(true); - Map params = this.getApiParam(exchange); - String name = this.getParamValue(params, ParamNames.API_NAME, ERROR_METHOD); + ApiParam params = this.getApiParam(exchange); + String name = params.fetchName(); ApiConfig apiConfig = ApiConfig.getInstance(); // 点换成下划线 DataNameBuilder dataNameBuilder = apiConfig.getDataNameBuilder(); diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/ResultExecutorForGateway.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/ResultExecutorForGateway.java new file mode 100644 index 00000000..050309b1 --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/ResultExecutorForGateway.java @@ -0,0 +1,9 @@ +package com.gitee.sop.gatewaycommon.result; + +import org.springframework.web.server.ServerWebExchange; + +/** + * @author tanghc + */ +public interface ResultExecutorForGateway extends ResultExecutor { +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/ResultExecutorForZuul.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/ResultExecutorForZuul.java new file mode 100644 index 00000000..12cbc89b --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/ResultExecutorForZuul.java @@ -0,0 +1,9 @@ +package com.gitee.sop.gatewaycommon.result; + +import com.netflix.zuul.context.RequestContext; + +/** + * @author tanghc + */ +public interface ResultExecutorForZuul extends ResultExecutor { +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/BaseForwardChooser.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/BaseForwardChooser.java new file mode 100644 index 00000000..0b7715d5 --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/BaseForwardChooser.java @@ -0,0 +1,66 @@ +package com.gitee.sop.gatewaycommon.route; + +import com.gitee.sop.gatewaycommon.bean.ApiConfig; +import com.gitee.sop.gatewaycommon.bean.RouteDefinition; +import com.gitee.sop.gatewaycommon.bean.ApiParamAware; +import com.gitee.sop.gatewaycommon.bean.TargetRoute; +import com.gitee.sop.gatewaycommon.loadbalancer.builder.GrayUserBuilder; +import com.gitee.sop.gatewaycommon.manager.EnvGrayManager; +import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext; +import com.gitee.sop.gatewaycommon.param.ApiParam; +import org.apache.commons.lang3.BooleanUtils; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.annotation.PostConstruct; +import java.util.Comparator; +import java.util.List; + +/** + * @author tanghc + */ +public abstract class BaseForwardChooser implements ForwardChooser, ApiParamAware { + + @Autowired + private EnvGrayManager envGrayManager; + + @Override + public ForwardInfo getForwardInfo(T t) { + ApiParam apiParam = getApiParam(t); + String nameVersion = apiParam.fetchNameVersion(); + TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(nameVersion); + RouteDefinition routeDefinitionOrig = targetRoute.getRouteDefinition(); + String path = routeDefinitionOrig.getPath(); + String version = apiParam.fetchVersion(); + String serviceId = targetRoute.getServiceRouteInfo().fetchServiceIdLowerCase(); + // 如果服务在灰度阶段,返回一个灰度版本号 + String grayVersion = envGrayManager.getVersion(serviceId, nameVersion); + // 如果是灰度环境 + if (grayVersion != null && isGrayUser(serviceId, apiParam)) { + String newNameVersion = apiParam.fetchName() + grayVersion; + TargetRoute targetRouteDest = RouteRepositoryContext.getRouteRepository().get(newNameVersion); + if (targetRouteDest != null) { + apiParam.setGrayRequest(true); + if (BooleanUtils.toBoolean(routeDefinitionOrig.getCompatibleMode())) { + version = grayVersion; + } else { + // 获取灰度接口 + RouteDefinition routeDefinition = targetRouteDest.getRouteDefinition(); + path = routeDefinition.getPath(); + } + } + } + return new ForwardInfo(path, version); + } + + protected boolean isGrayUser(String serviceId, ApiParam apiParam) { + List grayUserBuilders = ApiConfig.getInstance().getGrayUserBuilders(); + for (GrayUserBuilder grayUserBuilder : grayUserBuilders) { + String userKey = grayUserBuilder.buildGrayUserKey(apiParam); + if (envGrayManager.containsKey(serviceId, userKey)) { + return true; + } + } + return false; + } + +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/BaseRegistryListener.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/BaseRegistryListener.java index 4167e93b..39ef7da9 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/BaseRegistryListener.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/BaseRegistryListener.java @@ -26,6 +26,7 @@ public abstract class BaseRegistryListener implements RegistryListener { static { EXCLUDE_SERVICE_ID_LIST.add("sop-gateway"); + EXCLUDE_SERVICE_ID_LIST.add("sop-website"); EXCLUDE_SERVICE_ID_LIST.add("website-server"); } @@ -37,7 +38,7 @@ public abstract class BaseRegistryListener implements RegistryListener { * * @param serviceId serviceId */ - public void removeRoutes(String serviceId) { + public synchronized void removeRoutes(String serviceId) { serviceListener.onRemoveService(serviceId.toLowerCase()); } @@ -46,7 +47,7 @@ public abstract class BaseRegistryListener implements RegistryListener { * * @param instance 服务实例 */ - public void pullRoutes(InstanceDefinition instance) { + public synchronized void pullRoutes(InstanceDefinition instance) { // serviceId统一小写 instance.setServiceId(instance.getServiceId().toLowerCase()); serviceListener.onAddInstance(instance); diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/EurekaRegistryListener.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/EurekaRegistryListener.java index 6eb37113..55f71a3a 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/EurekaRegistryListener.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/EurekaRegistryListener.java @@ -1,8 +1,6 @@ package com.gitee.sop.gatewaycommon.route; import com.gitee.sop.gatewaycommon.bean.InstanceDefinition; -import com.gitee.sop.gatewaycommon.loadbalancer.EurekaEnvironmentServerChooser; -import com.gitee.sop.gatewaycommon.manager.EnvironmentKeys; import com.netflix.appinfo.InstanceInfo; import com.netflix.discovery.shared.Application; import com.netflix.discovery.shared.Applications; @@ -25,10 +23,6 @@ import java.util.stream.Collectors; */ public class EurekaRegistryListener extends BaseRegistryListener { - static { - System.setProperty(EnvironmentKeys.ZUUL_CUSTOM_RULE_CLASSNAME.getKey(), EurekaEnvironmentServerChooser.class.getName()); - } - private Set cacheServices = new HashSet<>(); @Override diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/ForwardChooser.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/ForwardChooser.java new file mode 100644 index 00000000..346ac33d --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/ForwardChooser.java @@ -0,0 +1,16 @@ +package com.gitee.sop.gatewaycommon.route; + +/** + * 转发选择 + * @author tanghc + */ +public interface ForwardChooser { + + /** + * 返回转发信息 + * @param t 上下文 + * @return 返回转发信息 + */ + ForwardInfo getForwardInfo(T t); + +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/ForwardInfo.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/ForwardInfo.java new file mode 100644 index 00000000..00c9333d --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/ForwardInfo.java @@ -0,0 +1,19 @@ +package com.gitee.sop.gatewaycommon.route; + +import lombok.Data; + +/** + * @author tanghc + */ +@Data +public class ForwardInfo { + private String path; + private String version; + private String domain; + + public ForwardInfo(String path, String version) { + this.path = path; + this.version = version; + } + +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/ApiValidator.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/ApiValidator.java index 8bdf8f24..bfca1c63 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/ApiValidator.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/ApiValidator.java @@ -68,9 +68,10 @@ public class ApiValidator implements Validator { @Override public void validate(ApiParam param) { checkIP(param); - checkEnable(param); + TargetRoute targetRoute = checkEnable(param); ApiConfig apiConfig = ApiContext.getApiConfig(); - if (apiConfig.isIgnoreValidate() || param.fetchIgnoreValidate()) { + if (apiConfig.isIgnoreValidate() + || BooleanUtils.toBoolean(targetRoute.getRouteDefinition().getIgnoreValidate())) { if (log.isDebugEnabled()) { log.debug("忽略所有验证(ignoreValidate=true), name:{}, version:{}", param.fetchName(), param.fetchVersion()); } @@ -103,7 +104,7 @@ public class ApiValidator implements Validator { * * @param param 接口参数 */ - protected void checkEnable(ApiParam param) { + protected TargetRoute checkEnable(ApiParam param) { String name = param.fetchName(); if (name == null) { throw ErrorEnum.ISV_MISSING_METHOD.getErrorMeta().getException(); @@ -114,12 +115,13 @@ public class ApiValidator implements Validator { } String routeId = param.fetchNameVersion(); // 检查路由是否存在 - RouteRepositoryContext.checkExist(routeId, ErrorEnum.ISV_INVALID_METHOD); + TargetRoute targetRoute = RouteRepositoryContext.checkExist(routeId, ErrorEnum.ISV_INVALID_METHOD); // 检查路由是否启用 RouteConfig routeConfig = routeConfigManager.get(routeId); if (!routeConfig.enable()) { throw ErrorEnum.ISP_API_DISABLED.getErrorMeta().getException(); } + return targetRoute; } /** diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/ValidateService.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/ValidateService.java index 3ad55621..c5c69032 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/ValidateService.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/ValidateService.java @@ -37,7 +37,13 @@ public class ValidateService { RequestContext currentContext = RequestContext.getCurrentContext(); currentContext.setRequest(RequestUtil.wrapRequest(request)); currentContext.setResponse(response); - doValidate(currentContext, callback); + // 解析参数 + ApiParam param = ZuulContext.getApiParam(); + if (param == null) { + param = paramBuilder.build(currentContext); + ZuulContext.setApiParam(param); + } + doValidate(currentContext, param, callback); } /** @@ -45,10 +51,7 @@ public class ValidateService { * * @param currentContext currentContext */ - private void doValidate(RequestContext currentContext, ValidateCallback callback) { - // 解析参数 - ApiParam param = paramBuilder.build(currentContext); - ZuulContext.setApiParam(param); + private void doValidate(RequestContext currentContext, ApiParam param, ValidateCallback callback) { Exception error = null; // 验证操作,这里有负责验证签名参数 try { diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/configuration/BaseZuulConfiguration.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/configuration/BaseZuulConfiguration.java index 3b6dbc9f..258f5f70 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/configuration/BaseZuulConfiguration.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/configuration/BaseZuulConfiguration.java @@ -8,17 +8,18 @@ import com.gitee.sop.gatewaycommon.param.ParamBuilder; import com.gitee.sop.gatewaycommon.zuul.ValidateService; import com.gitee.sop.gatewaycommon.zuul.controller.ConfigChannelController; import com.gitee.sop.gatewaycommon.zuul.controller.ErrorLogController; +import com.gitee.sop.gatewaycommon.zuul.controller.ZuulErrorController; import com.gitee.sop.gatewaycommon.zuul.controller.ZuulIndexController; import com.gitee.sop.gatewaycommon.zuul.filter.ErrorFilter; import com.gitee.sop.gatewaycommon.zuul.filter.FormBodyWrapperFilterExt; import com.gitee.sop.gatewaycommon.zuul.filter.PostResultFilter; -import com.gitee.sop.gatewaycommon.zuul.filter.PreEnvGrayFilter; import com.gitee.sop.gatewaycommon.zuul.filter.PreHttpServletRequestWrapperFilter; import com.gitee.sop.gatewaycommon.zuul.filter.PreLimitFilter; import com.gitee.sop.gatewaycommon.zuul.filter.PreParameterFormatterFilter; import com.gitee.sop.gatewaycommon.zuul.filter.PreValidateFilter; import com.gitee.sop.gatewaycommon.zuul.filter.Servlet30WrapperFilterExt; import com.gitee.sop.gatewaycommon.zuul.route.SopRouteLocator; +import com.gitee.sop.gatewaycommon.zuul.route.ZuulForwardChooser; import com.gitee.sop.gatewaycommon.zuul.route.ZuulRouteCache; import com.gitee.sop.gatewaycommon.zuul.route.ZuulRouteRepository; import com.netflix.zuul.context.RequestContext; @@ -26,7 +27,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper; -import org.springframework.cloud.netflix.zuul.filters.RouteLocator; import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; import org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter; import org.springframework.context.annotation.Bean; @@ -89,22 +89,28 @@ public class BaseZuulConfiguration extends AbstractConfiguration { return new Servlet30WrapperFilterExt(); } + @Bean + SopRouteLocator sopRouteLocator() { + return new SopRouteLocator(); + } + /** * 选取路由 - * @param zuulRouteRepository + * @param sopRouteLocator * @param proxyRequestHelper * @return */ @Bean - PreDecorationFilter preDecorationFilter(ZuulRouteRepository zuulRouteRepository, ProxyRequestHelper proxyRequestHelper) { + PreDecorationFilter preDecorationFilter(SopRouteLocator sopRouteLocator, ProxyRequestHelper proxyRequestHelper) { // 自定义路由 - RouteLocator routeLocator = new SopRouteLocator(zuulRouteRepository); - return new PreDecorationFilter(routeLocator, + return new PreDecorationFilter(sopRouteLocator, this.server.getServlet().getContextPath(), this.zuulProperties, proxyRequestHelper); } + + /** * 路由管理 * @param zuulRouteRepository 路由仓库 @@ -140,14 +146,6 @@ public class BaseZuulConfiguration extends AbstractConfiguration { return new PreLimitFilter(); } - /** - * 决定版本号 - */ - @Bean - PreEnvGrayFilter preEnvGrayFilter() { - return new PreEnvGrayFilter(); - } - /** * 错误处理扩展 */ @@ -169,9 +167,13 @@ public class BaseZuulConfiguration extends AbstractConfiguration { * 统一错误处理 */ @Bean - @ConditionalOnMissingBean - ZuulErrorController baseZuulController() { + ZuulErrorController zuulErrorController() { return ApiContext.getApiConfig().getZuulErrorController(); } + @Bean + ZuulForwardChooser zuulForwardChooser() { + return new ZuulForwardChooser(); + } + } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/controller/ConfigChannelController.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/controller/ConfigChannelController.java index 3ff5371a..859af8a1 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/controller/ConfigChannelController.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/controller/ConfigChannelController.java @@ -40,7 +40,7 @@ public class ConfigChannelController { processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_ROUTE_CONFIG, RouteConfigManager.class); } - @Value("${zuul.secret}") + @Value("${sop.secret}") private String secret; @PostMapping("/configChannelMsg") diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/controller/ErrorLogController.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/controller/ErrorLogController.java index 3c773afb..c4907461 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/controller/ErrorLogController.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/controller/ErrorLogController.java @@ -1,72 +1,23 @@ package com.gitee.sop.gatewaycommon.zuul.controller; -import com.gitee.sop.gatewaycommon.bean.ApiConfig; -import com.gitee.sop.gatewaycommon.bean.ErrorEntity; -import com.gitee.sop.gatewaycommon.manager.ServiceErrorManager; +import com.gitee.sop.gatewaycommon.bean.BaseErrorLogController; import com.gitee.sop.gatewaycommon.param.ApiParam; -import com.gitee.sop.gatewaycommon.result.ApiResult; -import com.gitee.sop.gatewaycommon.result.JsonResult; import com.gitee.sop.gatewaycommon.util.RequestUtil; -import com.gitee.sop.gatewaycommon.validate.taobao.TaobaoSigner; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; -import java.util.Collection; import java.util.Map; /** * @author tanghc */ -@Controller -public class ErrorLogController { +@RestController +public class ErrorLogController extends BaseErrorLogController { - TaobaoSigner signer = new TaobaoSigner(); - - @Value("${zuul.secret}") - private String secret; - - @GetMapping("listErrors") - public ApiResult listErrors(HttpServletRequest request) { - try { - this.check(request); - ServiceErrorManager serviceErrorManager = ApiConfig.getInstance().getServiceErrorManager(); - Collection allErrors = serviceErrorManager.listAllErrors(); - JsonResult apiResult = new JsonResult(); - apiResult.setData(allErrors); - return apiResult; - } catch (Exception e) { - ApiResult apiResult = new ApiResult(); - apiResult.setCode("505050"); - apiResult.setMsg(e.getMessage()); - return apiResult; - } - } - - @GetMapping("clearErrors") - public ApiResult clearErrors(HttpServletRequest request) { - try { - this.check(request); - ServiceErrorManager serviceErrorManager = ApiConfig.getInstance().getServiceErrorManager(); - serviceErrorManager.clear(); - return new ApiResult(); - } catch (Exception e) { - ApiResult apiResult = new ApiResult(); - apiResult.setCode("505050"); - apiResult.setMsg(e.getMessage()); - return apiResult; - } - } - - private void check(HttpServletRequest request) { + @Override + protected ApiParam getApiParam(HttpServletRequest request) { Map params = RequestUtil.convertRequestParamsToMap(request); - ApiParam apiParam = ApiParam.build(params); - boolean right = signer.checkSign(apiParam, secret); - if (!right) { - throw new RuntimeException("签名校验失败"); - } + return ApiParam.build(params); } - } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/controller/RestServlet.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/controller/RestServlet.java deleted file mode 100644 index 1a1a484e..00000000 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/controller/RestServlet.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.gitee.sop.gatewaycommon.zuul.controller; - -import com.gitee.sop.gatewaycommon.bean.SopConstants; -import org.springframework.beans.factory.annotation.Value; - -import javax.servlet.ServletException; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -/** - * 传统web开发入口 - * @author tanghc - */ -//@WebServlet(urlPatterns = "/rest/*") -public class RestServlet extends HttpServlet { - - private static final String EMPTY_VERSION = ""; - - @Value("${zuul.servlet-path:/zuul}") - private String path; - - @Value("${sop.restful.path:/rest}") - private String restPath; - - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - doPost(request, response); - } - - @Override - protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - String url = request.getRequestURL().toString(); - int index = url.indexOf(restPath); - // 取/rest的后面部分 - String path = url.substring(index + restPath.length()); - request.setAttribute(SopConstants.REDIRECT_METHOD_KEY, path); - request.setAttribute(SopConstants.REDIRECT_VERSION_KEY, EMPTY_VERSION); - request.setAttribute(SopConstants.SOP_NOT_MERGE, true); - request.getRequestDispatcher(this.path).forward(request, response); - } - -} \ No newline at end of file diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/configuration/ZuulErrorController.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/controller/ZuulErrorController.java similarity index 79% rename from sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/configuration/ZuulErrorController.java rename to sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/controller/ZuulErrorController.java index cb20a6ae..ce682db5 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/configuration/ZuulErrorController.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/controller/ZuulErrorController.java @@ -1,4 +1,4 @@ -package com.gitee.sop.gatewaycommon.zuul.configuration; +package com.gitee.sop.gatewaycommon.zuul.controller; import com.gitee.sop.gatewaycommon.bean.ApiContext; import com.gitee.sop.gatewaycommon.result.ResultExecutor; @@ -6,30 +6,26 @@ import com.gitee.sop.gatewaycommon.zuul.ZuulContext; import com.netflix.zuul.context.RequestContext; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.web.servlet.error.ErrorController; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** - * 处理网关自身异常 + * zuul的异常处理 * * @author tanghc */ -@Controller @Slf4j +@RestController public class ZuulErrorController implements ErrorController { - private static final String ERROR_PATH = "/error"; - /** * 错误最终会到这里来 */ - @RequestMapping(ERROR_PATH) - @ResponseBody + @RequestMapping("/error") public Object error(HttpServletRequest request, HttpServletResponse response) { RequestContext ctx = RequestContext.getCurrentContext(); if (ctx.getResponse() == null) { @@ -51,6 +47,6 @@ public class ZuulErrorController implements ErrorController { @Override public String getErrorPath() { - return ERROR_PATH; + return "/error"; } } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/controller/ZuulIndexController.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/controller/ZuulIndexController.java index ac1606e4..bc7ebd27 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/controller/ZuulIndexController.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/controller/ZuulIndexController.java @@ -6,7 +6,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.ServletException; @@ -75,15 +74,4 @@ public class ZuulIndexController { request.getRequestDispatcher(this.path).forward(request, response); } - @RequestMapping("/{method}/{version}/") - public void redirect( - @PathVariable("method") String method - , @PathVariable("version") String version - , HttpServletRequest request - , HttpServletResponse response - ) { - request.setAttribute(SopConstants.REDIRECT_METHOD_KEY, method); - request.setAttribute(SopConstants.REDIRECT_VERSION_KEY, version); - validateService.validate(request, response, callback); - } } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/PreEnvGrayFilter.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/PreEnvGrayFilter.java index bc5e00de..cd00823f 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/PreEnvGrayFilter.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/PreEnvGrayFilter.java @@ -14,7 +14,10 @@ import org.springframework.beans.factory.annotation.Autowired; * 灰度发布判断,改变版本号 * * @author tanghc + * @deprecated + * @see com.gitee.sop.gatewaycommon.zuul.route.ZuulForwardChooser */ +@Deprecated public class PreEnvGrayFilter extends BaseZuulFilter { @Autowired diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/loadbalancer/BaseServerChooser.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/loadbalancer/BaseServerChooser.java deleted file mode 100644 index e92a2fb2..00000000 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/loadbalancer/BaseServerChooser.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.gitee.sop.gatewaycommon.zuul.loadbalancer; - -import com.gitee.sop.gatewaycommon.bean.SpringContext; -import com.gitee.sop.gatewaycommon.manager.EnvGrayManager; -import com.google.common.base.Optional; -import com.netflix.loadbalancer.ILoadBalancer; -import com.netflix.loadbalancer.Server; -import com.netflix.loadbalancer.ZoneAvoidanceRule; -import com.netflix.zuul.context.RequestContext; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang.ArrayUtils; -import org.springframework.core.env.Environment; -import org.springframework.util.StringUtils; - -import javax.servlet.http.HttpServletRequest; -import java.util.ArrayList; -import java.util.List; - -/** - * 服务实例选择器 - * - * @author tanghc - */ -@Slf4j -public abstract class BaseServerChooser extends ZoneAvoidanceRule { - - - /** - * 是否是预发布服务器 - * @param server 服务器 - * @return true:是 - */ - protected abstract boolean isPreServer(Server server); - - /** - * 是否是灰度发布服务器 - * @param server 服务器 - * @return true:是 - */ - protected abstract boolean isGrayServer(Server server); - - /** - * 能否访问预发布 - * @param server 预发布服务器 - * @param request request - * @return true:能 - */ - protected abstract boolean canVisitPre(Server server, HttpServletRequest request); - - - @Override - public Server choose(Object key) { - ILoadBalancer lb = getLoadBalancer(); - // 获取服务实例列表 - List servers = lb.getReachableServers(); - RequestContext currentContext = RequestContext.getCurrentContext(); - // 存放预发服务器 - List preServers = new ArrayList<>(4); - // 存放灰度发布服务器 - List grayServers = new ArrayList<>(4); - // 存放非预发服务器 - List notPreServers = new ArrayList<>(4); - - for (Server server : servers) { - // 是否开启了预发模式 - if (this.isPreServer(server)) { - preServers.add(server); - } else if (this.isGrayServer(server)) { - grayServers.add(server); - } else { - notPreServers.add(server); - } - } - notPreServers.addAll(grayServers); - // 如果没有开启预发布服务和灰度发布,直接用默认的方式 - if (preServers.isEmpty() && grayServers.isEmpty()) { - return super.choose(key); - } - // 如果是从预发布域名访问过来,则认为是预发布请求 - if (this.isRequestFromPreDomain(currentContext.getRequest())) { - return doChoose(preServers, key); - } - // 如果是灰度请求 - if (this.isRequestGrayServer(currentContext)) { - return doChoose(grayServers, key); - } - - // 到这里说明不能访问预发/灰度服务器,则需要路由到非预发服务器 - return doChoose(notPreServers, key); - } - - /** - * 通过判断hostname来确定是否是预发布请求 - * - * @param t t - * @return 返回true:可以进入到预发环境 - */ - protected boolean isRequestFromPreDomain(HttpServletRequest t) { - String domain = SpringContext.getBean(Environment.class).getProperty("pre.domain"); - if (StringUtils.isEmpty(domain)) { - return false; - } - String[] domains = domain.split("\\,"); - return ArrayUtils.contains(domains, getHost(t)); - } - - protected boolean isRequestGrayServer(RequestContext t) { - return t.get(EnvGrayManager.ENV_GRAY) != null; - } - - protected String getHost(HttpServletRequest t) { - return t.getServerName(); - } - - protected Server doChoose(List servers, Object key) { - Optional server = getPredicate().chooseRoundRobinAfterFiltering(servers, key); - if (server.isPresent()) { - return server.get(); - } else { - return null; - } - } - -} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/loadbalancer/EnvironmentServerChooser.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/loadbalancer/EnvironmentServerChooser.java new file mode 100644 index 00000000..a9a6c477 --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/loadbalancer/EnvironmentServerChooser.java @@ -0,0 +1,55 @@ +package com.gitee.sop.gatewaycommon.zuul.loadbalancer; + +import com.gitee.sop.gatewaycommon.loadbalancer.ServerChooserContext; +import com.gitee.sop.gatewaycommon.param.ApiParam; +import com.gitee.sop.gatewaycommon.zuul.ZuulContext; +import com.google.common.base.Optional; +import com.netflix.loadbalancer.Server; +import com.netflix.loadbalancer.ZoneAvoidanceRule; +import com.netflix.zuul.context.RequestContext; +import lombok.extern.slf4j.Slf4j; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +/** + * 预发布、灰度环境选择,参考自:https://segmentfault.com/a/1190000017412946 + * + * @author tanghc + */ +@Slf4j +public class EnvironmentServerChooser extends ZoneAvoidanceRule implements ServerChooserContext { + + private ZuulLoadBalanceServerChooser loadBalanceServerChooser = new ZuulLoadBalanceServerChooser(); + + @Override + public String getHost(HttpServletRequest request) { + return request.getServerName(); + } + + @Override + public ApiParam getApiParam(HttpServletRequest request) { + return ZuulContext.getApiParam(); + } + + @Override + public Server choose(Object key) { + return loadBalanceServerChooser.choose( + String.valueOf(key) + , RequestContext.getCurrentContext().getRequest() + , getLoadBalancer() + , () -> super.choose(key) + , (servers) -> this.doChoose(servers, key) + ); + } + + protected Server doChoose(List servers, Object key) { + Optional server = getPredicate().chooseRoundRobinAfterFiltering(servers, key); + if (server.isPresent()) { + return server.get(); + } else { + return null; + } + } + +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/loadbalancer/ZuulLoadBalanceServerChooser.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/loadbalancer/ZuulLoadBalanceServerChooser.java new file mode 100644 index 00000000..b1046ee9 --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/loadbalancer/ZuulLoadBalanceServerChooser.java @@ -0,0 +1,24 @@ +package com.gitee.sop.gatewaycommon.zuul.loadbalancer; + +import com.gitee.sop.gatewaycommon.loadbalancer.LoadBalanceServerChooser; +import com.gitee.sop.gatewaycommon.param.ApiParam; +import com.gitee.sop.gatewaycommon.zuul.ZuulContext; +import com.netflix.loadbalancer.Server; + +import javax.servlet.http.HttpServletRequest; + +/** + * @author tanghc + */ +public class ZuulLoadBalanceServerChooser extends LoadBalanceServerChooser { + + @Override + public String getHost(HttpServletRequest request) { + return request.getServerName(); + } + + @Override + public ApiParam getApiParam(HttpServletRequest request) { + return ZuulContext.getApiParam(); + } +} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/result/ZuulResultExecutor.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/result/ZuulResultExecutor.java index 9cfd0cd7..ad4de22d 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/result/ZuulResultExecutor.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/result/ZuulResultExecutor.java @@ -6,7 +6,9 @@ import com.gitee.sop.gatewaycommon.bean.SopConstants; import com.gitee.sop.gatewaycommon.exception.ApiException; import com.gitee.sop.gatewaycommon.message.Error; import com.gitee.sop.gatewaycommon.message.ErrorEnum; +import com.gitee.sop.gatewaycommon.param.ApiParam; import com.gitee.sop.gatewaycommon.result.BaseExecutorAdapter; +import com.gitee.sop.gatewaycommon.result.ResultExecutorForZuul; import com.gitee.sop.gatewaycommon.zuul.ZuulContext; import com.netflix.util.Pair; import com.netflix.zuul.context.RequestContext; @@ -14,13 +16,12 @@ import com.netflix.zuul.exception.ZuulException; import lombok.extern.slf4j.Slf4j; import java.util.List; -import java.util.Map; /** * @author tanghc */ @Slf4j -public class ZuulResultExecutor extends BaseExecutorAdapter { +public class ZuulResultExecutor extends BaseExecutorAdapter implements ResultExecutorForZuul { @Override protected boolean isMergeResult(RequestContext request) { @@ -62,7 +63,7 @@ public class ZuulResultExecutor extends BaseExecutorAdapter getApiParam(RequestContext requestContext) { + public ApiParam getApiParam(RequestContext requestContext) { return ZuulContext.getApiParam(); } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/route/SopRouteLocator.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/route/SopRouteLocator.java index ce07b1ca..edb41374 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/route/SopRouteLocator.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/route/SopRouteLocator.java @@ -2,7 +2,11 @@ package com.gitee.sop.gatewaycommon.zuul.route; import com.gitee.sop.gatewaycommon.bean.AbstractTargetRoute; import com.gitee.sop.gatewaycommon.param.ApiParam; +import com.gitee.sop.gatewaycommon.route.ForwardInfo; +import com.gitee.sop.gatewaycommon.param.ParamNames; import com.gitee.sop.gatewaycommon.zuul.ZuulContext; +import com.netflix.zuul.context.RequestContext; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.netflix.zuul.filters.Route; import org.springframework.cloud.netflix.zuul.filters.RouteLocator; import org.springframework.core.Ordered; @@ -19,11 +23,11 @@ import java.util.stream.Collectors; */ public class SopRouteLocator implements RouteLocator, Ordered { + @Autowired private ZuulRouteRepository zuulRouteRepository; - public SopRouteLocator(ZuulRouteRepository zuulRouteRepository) { - this.zuulRouteRepository = zuulRouteRepository; - } + @Autowired + private ZuulForwardChooser zuulForwardChooser; @Override public Collection getIgnoredPaths() { @@ -52,7 +56,13 @@ public class SopRouteLocator implements RouteLocator, Ordered { if (zuulTargetRoute == null) { return null; } - return zuulTargetRoute.getTargetRouteDefinition(); + Route targetRouteDefinition = zuulTargetRoute.getTargetRouteDefinition(); + ForwardInfo forwardInfo = zuulForwardChooser.getForwardInfo(RequestContext.getCurrentContext()); + String forwardPath = forwardInfo.getPath(); + targetRouteDefinition.setPath(forwardPath); + String versionInHead = forwardInfo.getVersion(); + RequestContext.getCurrentContext().addZuulRequestHeader(ParamNames.HEADER_VERSION_NAME, versionInHead); + return targetRouteDefinition; } @Override diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/route/ZuulForwardChooser.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/route/ZuulForwardChooser.java new file mode 100644 index 00000000..625d129f --- /dev/null +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/route/ZuulForwardChooser.java @@ -0,0 +1,18 @@ +package com.gitee.sop.gatewaycommon.zuul.route; + +import com.gitee.sop.gatewaycommon.param.ApiParam; +import com.gitee.sop.gatewaycommon.route.BaseForwardChooser; +import com.gitee.sop.gatewaycommon.zuul.ZuulContext; +import com.netflix.zuul.context.RequestContext; + +/** + * @author tanghc + */ +public class ZuulForwardChooser extends BaseForwardChooser { + + @Override + public ApiParam getApiParam(RequestContext requestContext) { + return ZuulContext.getApiParam(); + } + +} diff --git a/sop-common/sop-service-common/pom.xml b/sop-common/sop-service-common/pom.xml index d471851b..e0558fc1 100644 --- a/sop-common/sop-service-common/pom.xml +++ b/sop-common/sop-service-common/pom.xml @@ -6,11 +6,11 @@ com.gitee.sop sop-common - 2.5.12-SNAPSHOT + 3.0.0-SNAPSHOT ../pom.xml sop-service-common - 2.5.12-SNAPSHOT + 3.0.0-SNAPSHOT jar sop-service-common diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/ServiceApiInfo.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/ServiceApiInfo.java index 85d1f9a3..d200c4b4 100644 --- a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/ServiceApiInfo.java +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/ServiceApiInfo.java @@ -34,6 +34,8 @@ public class ServiceApiInfo { private int permission; /** 是否需要token */ private int needToken; + /** 是否是兼容模式,即使用了@ApiAbility注解 */ + private int compatibleMode; /** 是否是原始Mapping */ private boolean originalMapping; diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/ApiMetaBuilder.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/ApiMetaBuilder.java index 8f3ecb17..55c2bf58 100644 --- a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/ApiMetaBuilder.java +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/ApiMetaBuilder.java @@ -95,6 +95,7 @@ public class ApiMetaBuilder { apiMeta.setMergeResult(BooleanUtils.toInteger(apiMappingInfo.isMergeResult())); apiMeta.setPermission(BooleanUtils.toInteger(apiMappingInfo.isPermission())); apiMeta.setNeedToken(BooleanUtils.toInteger(apiMappingInfo.isNeedToken())); + apiMeta.setCompatibleMode(BooleanUtils.toInteger(apiMappingInfo.isCompatibleMode())); return apiMeta; } else { if (!ServiceContext.getCurrentContext().getBoolean(ServiceContext.RESTFUL_KEY, false)) { diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/ServiceRouteInfoHandler.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/ServiceRouteInfoHandler.java deleted file mode 100644 index 882963ea..00000000 --- a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/ServiceRouteInfoHandler.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.gitee.sop.servercommon.manager; - -import org.springframework.web.bind.annotation.RequestMapping; - -/** - * @author tanghc - */ -public interface ServiceRouteInfoHandler { -} diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingHandlerMapping.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingHandlerMapping.java index 9f6467fd..1d706005 100644 --- a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingHandlerMapping.java +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingHandlerMapping.java @@ -5,6 +5,7 @@ import com.gitee.sop.servercommon.annotation.ApiMapping; import com.gitee.sop.servercommon.bean.ServiceConfig; import com.gitee.sop.servercommon.bean.ServiceContext; import com.gitee.sop.servercommon.util.OpenUtil; +import org.apache.commons.lang.StringUtils; import org.springframework.core.PriorityOrdered; import org.springframework.util.StringValueResolver; import org.springframework.web.servlet.mvc.condition.RequestCondition; @@ -18,7 +19,6 @@ import java.lang.reflect.Method; */ public class ApiMappingHandlerMapping extends RequestMappingHandlerMapping implements PriorityOrdered { - private static StringValueResolver stringValueResolver = new ApiMappingStringValueResolver(); private static StringValueResolver stringValueResolverMVC = new ApiMappingStringValueResolverMVC(); @Override @@ -29,7 +29,11 @@ public class ApiMappingHandlerMapping extends RequestMappingHandlerMapping imple ApiAbility apiAbility = OpenUtil.getAnnotationFromMethodOrClass(method, ApiAbility.class); StringValueResolver valueResolver = null; if (apiMapping != null) { - valueResolver = stringValueResolver; + String version = apiMapping.version(); + if (StringUtils.isBlank(version)) { + version = ServiceConfig.getInstance().getDefaultVersion(); + } + valueResolver = new ApiMappingStringValueResolverVersion(version); } if (isMvc || apiAbility != null) { valueResolver = stringValueResolverMVC; @@ -46,7 +50,8 @@ public class ApiMappingHandlerMapping extends RequestMappingHandlerMapping imple boolean ignoreValidate; boolean mergeResult; boolean permission; - boolean needToken = false; + boolean needToken; + boolean compatibleMode = false; ApiMapping apiMapping = method.getAnnotation(ApiMapping.class); if (apiMapping != null) { name = apiMapping.value()[0]; @@ -63,6 +68,7 @@ public class ApiMappingHandlerMapping extends RequestMappingHandlerMapping imple mergeResult = apiAbility.mergeResult(); permission = apiAbility.permission(); needToken = apiAbility.needToken(); + compatibleMode = true; } else { return super.getCustomMethodCondition(method); } @@ -87,6 +93,7 @@ public class ApiMappingHandlerMapping extends RequestMappingHandlerMapping imple apiMappingInfo.setMergeResult(mergeResult); apiMappingInfo.setPermission(permission); apiMappingInfo.setNeedToken(needToken); + apiMappingInfo.setCompatibleMode(compatibleMode); logger.info("注册接口,name:" + method + ", version:" + version); return new ApiMappingRequestCondition(apiMappingInfo); } diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingInfo.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingInfo.java index cc2dfb3d..765de153 100644 --- a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingInfo.java +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingInfo.java @@ -13,6 +13,8 @@ public class ApiMappingInfo { private boolean mergeResult; private boolean permission; private boolean needToken; + private boolean compatibleMode; + public ApiMappingInfo(String name, String version) { this.name = name; diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingRequestCondition.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingRequestCondition.java index c64ecadf..6ef894ea 100644 --- a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingRequestCondition.java +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingRequestCondition.java @@ -37,6 +37,11 @@ public class ApiMappingRequestCondition implements RequestCondition com.gitee.sop sop-service-common - 2.5.12-SNAPSHOT + 3.0.0-SNAPSHOT com.gitee.sop diff --git a/sop-example/sop-easyopen/pom.xml b/sop-example/sop-easyopen/pom.xml index 5c2099d7..8140f57a 100644 --- a/sop-example/sop-easyopen/pom.xml +++ b/sop-example/sop-easyopen/pom.xml @@ -29,7 +29,7 @@ com.gitee.sop sop-service-common - 2.5.12-SNAPSHOT + 3.0.0-SNAPSHOT diff --git a/sop-example/sop-story/sop-story-web/pom.xml b/sop-example/sop-story/sop-story-web/pom.xml index f39b5429..7b7c6ba6 100644 --- a/sop-example/sop-story/sop-story-web/pom.xml +++ b/sop-example/sop-story/sop-story-web/pom.xml @@ -28,7 +28,7 @@ com.gitee.sop sop-service-common - 2.5.12-SNAPSHOT + 3.0.0-SNAPSHOT com.gitee.sop diff --git a/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/AlipayController.java b/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/AlipayController.java index b0530de1..f7510610 100644 --- a/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/AlipayController.java +++ b/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/AlipayController.java @@ -17,6 +17,8 @@ import io.swagger.annotations.ApiOperation; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -39,6 +41,9 @@ import java.util.List; @Api(tags = "故事接口") public class AlipayController { + @Autowired + private Environment environment; + // http://localhost:2222/story_get // 原生的接口,可正常调用 @RequestMapping("story_get") @@ -49,7 +54,7 @@ public class AlipayController { return result; } - // http://localhost:2222/story.get/ + // http://localhost:2222/story.get/1.0/ // 接口名,使用默认版本号 @ApiMapping(value = "story.get") public StoryResult storyget() { @@ -62,7 +67,7 @@ public class AlipayController { return result; } - // http://localhost:2222/story.get/?version=1.1 + // http://localhost:2222/story.get/1.1/ // 接口名 + 版本号 @ApiMapping(value = "story.get", version = "1.1") public StoryResult getStory2() { @@ -72,7 +77,7 @@ public class AlipayController { return result; } - // http://localhost:2222/story.get/?name=111&version=2.0 + // http://localhost:2222/story.get/2.0/?name=111 // 接口名 + 版本号 // StoryParam对应biz_content内容 @ApiMapping(value = "story.get", version = "2.0") @@ -174,13 +179,13 @@ public class AlipayController { return story; } - // http://localhost:2222/alipay.story.get/ - @ApiOperation(value="获取故事信息2", notes = "获取故事信息2的详细信息") + // http://localhost:2222/alipay.story.get/1.0/?name=Jim + @ApiOperation(value="获取故事信息", notes = "获取故事信息的详细信息") @ApiMapping(value = "alipay.story.get") public StoryResult getStory(StoryParam param) { StoryResult story = new StoryResult(); story.setId(1L); - story.setName("海底小纵队(alipay.story.get1.0), param:" + param); + story.setName("海底小纵队(alipay.story.get1.0), port:" + environment.getProperty("server.port") + ", param:" + param); return story; } @@ -188,6 +193,7 @@ public class AlipayController { * @param param 对应biz_content中的内容,并自动JSR-303校验 * @return */ + @ApiOperation(value="获取故事信息", notes = "获取故事信息的详细信息") @ApiMapping(value = "alipay.story.get", version = "1.2") public StoryResult getStory11(StoryParam param) { StoryResult story2 = new StoryResult(); diff --git a/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/FileUploadDemoController.java b/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/FileUploadDemoController.java index ff57a354..e5854db1 100644 --- a/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/FileUploadDemoController.java +++ b/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/FileUploadDemoController.java @@ -34,7 +34,6 @@ public class FileUploadDemoController { @ApiOperation(value = "文件上传例1", notes = "上传文件demo") @ApiMapping(value = "demo.file.upload") public FileUploadResult file1(FileUploadParam param) { - System.out.println(param.getRemark()); // 获取上传的文件 MultipartFile file1 = param.getFile1(); MultipartFile file2 = param.getFile2(); @@ -43,6 +42,7 @@ public class FileUploadDemoController { FileUploadResult.FileMeta fileMeta1 = buildFileMeta(file1); FileUploadResult.FileMeta fileMeta2 = buildFileMeta(file2); + result.setRemark(param.getRemark()); result.getFiles().add(fileMeta1); result.getFiles().add(fileMeta2); return result; diff --git a/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/result/FileUploadResult.java b/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/result/FileUploadResult.java index 447292ad..11f20176 100644 --- a/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/result/FileUploadResult.java +++ b/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/result/FileUploadResult.java @@ -13,6 +13,7 @@ import java.util.List; public class FileUploadResult { private List files = new ArrayList(); + private String remark; @Data public static class FileMeta { diff --git a/sop-gateway/pom.xml b/sop-gateway/pom.xml index 2f5c34c6..2b18e01e 100644 --- a/sop-gateway/pom.xml +++ b/sop-gateway/pom.xml @@ -26,23 +26,17 @@ - + + com.gitee.sop - sop-gateway-common - 2.5.12-SNAPSHOT + sop-bridge-gateway + + 3.0.0-SNAPSHOT - - - org.springframework.cloud - spring-cloud-starter-netflix-zuul - - - org.springframework.retry - spring-retry - - - net.oschina.durcframework fastmybatis-spring-boot-starter @@ -70,17 +64,6 @@ - - - com.alibaba.boot - nacos-config-spring-boot-starter - ${nacos-spring-boot-starter.version} - - - org.springframework.boot diff --git a/sop-gateway/readme.md b/sop-gateway/readme.md new file mode 100644 index 00000000..c0790aa6 --- /dev/null +++ b/sop-gateway/readme.md @@ -0,0 +1,12 @@ +# sop-gateway + +网关入口,默认使用的是Spring Cloud Gateway,如果要使用Zuul,修改pom.xml + +```xml + + com.gitee.sop + + sop-bridge-zuul + version + +``` \ No newline at end of file diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/SopGatewayApplication.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/SopGatewayApplication.java index 8b3fe901..20b6880a 100644 --- a/sop-gateway/src/main/java/com/gitee/sop/gateway/SopGatewayApplication.java +++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/SopGatewayApplication.java @@ -2,13 +2,7 @@ package com.gitee.sop.gateway; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.web.servlet.ServletComponentScan; -import org.springframework.cloud.netflix.zuul.EnableZuulProxy; -// 开启网关功能 -@EnableZuulProxy -// 扫描自定义的servlet(类上标注@WebServle) -@ServletComponentScan @SpringBootApplication public class SopGatewayApplication { diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/config/GatewayConfig.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/config/GatewayConfig.java deleted file mode 100644 index 3345063d..00000000 --- a/sop-gateway/src/main/java/com/gitee/sop/gateway/config/GatewayConfig.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.gitee.sop.gateway.config; - -import com.gitee.sop.gatewaycommon.gateway.configuration.AlipayGatewayConfiguration; - - -/** - * 使用Spring Cloud Gateway - * - * 注意:下面两个只能使用一个。 - * - * 使用前,前往启动类SopGatewayApplication.java 注释掉@EnableZuulProxy - */ - -/** - * 开通支付宝开放平台能力 - * @author tanghc - */ - -//@Configuration -public class GatewayConfig extends AlipayGatewayConfiguration { - -} - -/** - * 开通淘宝开放平能力 - */ -//@Configuration -//public class GatewayConfig extends TaobaoGatewayConfiguration { -// -// { -// Map appSecretStore = new HashMap(); -// appSecretStore.put("taobao_test", "G9w0BAQEFAAOCAQ8AMIIBCgKCA"); -// ApiContext.getApiConfig().addAppSecret(appSecretStore); -// } -//} - diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/config/ZuulConfig.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/config/ZuulConfig.java deleted file mode 100644 index 4f0863a1..00000000 --- a/sop-gateway/src/main/java/com/gitee/sop/gateway/config/ZuulConfig.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.gitee.sop.gateway.config; - -/** - * 使用Spring Cloud Zuul,推荐使用 - * - * 注意:下面两个只能使用一个 - */ - -import com.gitee.sop.gatewaycommon.zuul.configuration.AlipayZuulConfiguration; -import org.springframework.context.annotation.Configuration; - -/** - * 开通支付宝开放平台能力 - * @author tanghc - */ -@Configuration -public class ZuulConfig extends AlipayZuulConfiguration { - -} - -/** - * 开通淘宝开放平台能力 - * @author tanghc - */ -//@Configuration -//public class ZuulConfig extends TaobaoZuulConfiguration { -// static { -// new ManagerInitializer(); -// } -//} - -/** - * 对接easyopen - */ -//@Configuration -//public class ZuulConfig extends EasyopenZuulConfiguration { -// static { -// Map appSecretMap = new HashMap<>(); -// appSecretMap.put("easyopen_test", "G9w0BAQEFAAOCAQ8AMIIBCgKCA"); -// ApiConfig.getInstance().addAppSecret(appSecretMap); -// } -// -//} diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbLimitConfigManager.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbLimitConfigManager.java index 3cb71232..20645d62 100644 --- a/sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbLimitConfigManager.java +++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbLimitConfigManager.java @@ -29,7 +29,7 @@ public class DbLimitConfigManager extends DefaultLimitConfigManager { public void load() { Query query = new Query(); configLimitMapper.list(query) - .forEach(configLimit -> putVal(configLimit)); + .forEach(this::putVal); } diff --git a/sop-gateway/src/main/resources/META-INF/spring.factories b/sop-gateway/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..c7499f27 --- /dev/null +++ b/sop-gateway/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +# 自定义自动配置类,如果有多个类,使用逗号(,)分隔,\正斜杠标示换行还可以读取到指定的类 +org.springframework.boot.env.EnvironmentPostProcessor=com.gitee.sop.gatewaycommon.config.SopGatewayEnvironmentPostProcessor +org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.gitee.sop.bridge.SopGatewayAutoConfiguration diff --git a/sop-gateway/src/main/resources/application-dev.properties b/sop-gateway/src/main/resources/application-dev.properties index 21b95d95..8b4920f6 100644 --- a/sop-gateway/src/main/resources/application-dev.properties +++ b/sop-gateway/src/main/resources/application-dev.properties @@ -1,9 +1,6 @@ server.port=8081 -# 固定不变 -spring.application.name=sop-gateway -# ------- 需要改的配置 ------- -# mysql数据库账号 +# mysql数据库配置 mysql.host=localhost:3306 mysql.username=root mysql.password=root @@ -11,55 +8,4 @@ mysql.password=root # nacos地址 nacos.url=127.0.0.1:8848 -# zipkin服务监控地址,没有开启不用改 -zipkin.url=http://127.0.0.1:9411/ - -# 预发布网关域名 -pre.domain=localhost -# ------- 需要改的配置end ------- - -# 入口地址,不用改,默认是/zuul -zuul.servlet-path=/api -# 禁用默认的过滤器,不能删,不用改 -zuul.FormBodyWrapperFilter.pre.disable=true -zuul.Servlet30WrapperFilter.pre.disable=true -# 不用改,如果要改,请全局替换修改 -zuul.secret=MZZOUSTua6LzApIWXCwEgbBmxSzpzC -# 不用改 -zuul.rest-default-version=1.0 - -# https://blog.csdn.net/qq_36872046/article/details/81058045 -# 路由转发超时时间,毫秒,默认值1000,详见:RibbonClientConfiguration.DEFAULT_READ_TIMEOUT。 -# 如果微服务端 处理时间过长,会导致ribbon read超时,解决办法将这个值调大一点 -ribbon.ReadTimeout=2000 -# 设置为true(默认false),则所有请求都重试,默认只支持get请求重试 -# 请谨慎设置,因为post请求大多都是写入请求,如果要支持重试,确保服务的幂等性 -ribbon.OkToRetryOnAllOperations=false - -# nacos cloud配置 -spring.cloud.nacos.discovery.server-addr=${nacos.url} -nacos.config.server-addr=${nacos.url} - -spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver -spring.datasource.url=jdbc:mysql://${mysql.host}/sop?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai -spring.datasource.username=${mysql.username} -spring.datasource.password=${mysql.password} - -# 不用改 -mybatis.fill.com.gitee.fastmybatis.core.support.DateFillInsert=gmt_create -mybatis.fill.com.gitee.fastmybatis.core.support.DateFillUpdate=gmt_modified - -# 文件上传配置 -spring.servlet.multipart.enabled=true -# 这里设置大一点没关系,真实大小由upload.max-file-size控制 -spring.servlet.multipart.max-file-size=50MB - -# 允许上传文件大小,不能超过这个值,单位:B,KB,MB -upload.max-file-size=2MB - -# zipkin服务跟踪 -spring.zipkin.base-url=${zipkin.url} -# 设置sleuth收集信息的比率,默认0.1,最大是1,数字越大越耗性能 -spring.sleuth.sampler.probability=1 - logging.level.com.gitee=debug \ No newline at end of file diff --git a/sop-mysql5.6以下版本.sql b/sop-mysql5.6以下版本.sql new file mode 100644 index 00000000..7d0cd7fa --- /dev/null +++ b/sop-mysql5.6以下版本.sql @@ -0,0 +1,396 @@ + +CREATE DATABASE IF NOT EXISTS `sop` DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci; +USE `sop`; + + + + +SET @PREVIOUS_FOREIGN_KEY_CHECKS = @@FOREIGN_KEY_CHECKS; +SET FOREIGN_KEY_CHECKS = 0; + + +DROP TABLE IF EXISTS `user_info`; +DROP TABLE IF EXISTS `perm_role_permission`; +DROP TABLE IF EXISTS `perm_role`; +DROP TABLE IF EXISTS `perm_isv_role`; +DROP TABLE IF EXISTS `isv_keys`; +DROP TABLE IF EXISTS `isv_info`; +DROP TABLE IF EXISTS `config_route_limit`; +DROP TABLE IF EXISTS `config_route_base`; +DROP TABLE IF EXISTS `config_limit`; +DROP TABLE IF EXISTS `config_ip_blacklist`; +DROP TABLE IF EXISTS `config_gray_instance`; +DROP TABLE IF EXISTS `config_gray`; +DROP TABLE IF EXISTS `config_common`; +DROP TABLE IF EXISTS `admin_user_info`; +DROP TABLE IF EXISTS `config_service_route`; + + +CREATE TABLE `admin_user_info` ( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `username` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '用户名', + `password` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '密码', + `status` TINYINT(3) UNSIGNED NOT NULL DEFAULT '1' COMMENT '状态,1:启用,2:禁用', + `gmt_create` DATETIME DEFAULT NULL, + `gmt_modified` DATETIME DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_username` (`username`) USING BTREE +) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='后台用户表'; + + +CREATE TABLE `config_common` ( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `config_group` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '配置分组', + `config_key` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '配置key', + `content` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '内容', + `remark` VARCHAR(128) DEFAULT NULL COMMENT '备注', + `gmt_create` DATETIME DEFAULT NULL, + `gmt_modified` DATETIME DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_groupkey` (`config_group`,`config_key`) USING BTREE +) ENGINE=INNODB DEFAULT CHARSET=utf8 COMMENT='通用配置表'; + + +CREATE TABLE `config_gray` ( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `service_id` VARCHAR(64) NOT NULL DEFAULT '', + `user_key_content` TEXT COMMENT '用户key,多个用引文逗号隔开', + `name_version_content` TEXT COMMENT '需要灰度的接口,goods.get1.0=1.2,多个用英文逗号隔开', + `gmt_create` DATETIME DEFAULT NULL, + `gmt_modified` DATETIME DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_serviceid` (`service_id`) USING BTREE +) ENGINE=INNODB DEFAULT CHARSET=utf8 COMMENT='服务灰度配置'; + + +CREATE TABLE `config_gray_instance` ( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `instance_id` VARCHAR(128) NOT NULL DEFAULT '' COMMENT 'instance_id', + `service_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'service_id', + `status` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '0:禁用,1:启用', + `gmt_create` DATETIME DEFAULT NULL, + `gmt_modified` DATETIME DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_instanceid` (`instance_id`) USING BTREE, + KEY `idx_serviceid` (`service_id`) USING BTREE +) ENGINE=INNODB DEFAULT CHARSET=utf8 COMMENT='开启灰度服务器实例'; + + +CREATE TABLE `config_ip_blacklist` ( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `ip` VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'ip', + `remark` VARCHAR(128) DEFAULT NULL COMMENT '备注', + `gmt_create` DATETIME DEFAULT NULL, + `gmt_modified` DATETIME DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_ip` (`ip`) USING BTREE +) ENGINE=INNODB DEFAULT CHARSET=utf8 COMMENT='IP黑名单'; + + +CREATE TABLE `config_limit` ( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `route_id` VARCHAR(128) DEFAULT NULL COMMENT '路由id', + `app_key` VARCHAR(128) DEFAULT NULL, + `limit_ip` VARCHAR(300) DEFAULT NULL COMMENT '限流ip,多个用英文逗号隔开', + `service_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '服务id', + `limit_type` TINYINT(4) NOT NULL DEFAULT '1' COMMENT '限流策略,1:漏桶策略,2:令牌桶策略', + `exec_count_per_second` INT(11) DEFAULT NULL COMMENT '每秒可处理请求数', + `duration_seconds` INT(11) NOT NULL DEFAULT '1' COMMENT '限流持续时间,默认1秒,即每durationSeconds秒允许多少请求(当limit_type=1时有效)', + `limit_code` VARCHAR(64) DEFAULT NULL COMMENT '返回的错误码', + `limit_msg` VARCHAR(100) DEFAULT NULL COMMENT '返回的错误信息', + `token_bucket_count` INT(11) DEFAULT NULL COMMENT '令牌桶容量', + `limit_status` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '限流开启状态,1:开启,0关闭', + `order_index` INT(11) NOT NULL DEFAULT '0' COMMENT '顺序,值小的优先执行', + `remark` VARCHAR(128) DEFAULT NULL COMMENT '备注', + `gmt_create` DATETIME DEFAULT NULL, + `gmt_modified` DATETIME DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=INNODB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8 COMMENT='限流配置'; + + +CREATE TABLE `config_route_base` ( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `route_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '路由id', + `status` TINYINT(4) NOT NULL DEFAULT '1' COMMENT '状态,1:启用,2:禁用', + `gmt_create` DATETIME DEFAULT NULL, + `gmt_modified` DATETIME DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_routeid` (`route_id`) USING BTREE +) ENGINE=INNODB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8 COMMENT='路由配置表'; + + +CREATE TABLE `config_route_limit` ( + `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `route_id` VARCHAR(64) NOT NULL COMMENT '路由id', + `service_id` VARCHAR(64) NOT NULL DEFAULT '', + `limit_type` TINYINT(4) NOT NULL DEFAULT '1' COMMENT '限流策略,1:漏桶策略,2:令牌桶策略', + `exec_count_per_second` INT(11) DEFAULT NULL COMMENT '每秒可处理请求数', + `limit_code` VARCHAR(64) DEFAULT NULL COMMENT '返回的错误码', + `limit_msg` VARCHAR(100) DEFAULT NULL COMMENT '返回的错误信息', + `token_bucket_count` INT(11) DEFAULT NULL COMMENT '令牌桶容量', + `limit_status` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '限流开启状态,1:开启,0关闭', + `gmt_create` DATETIME DEFAULT NULL, + `gmt_modified` DATETIME DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_routeid` (`route_id`) USING BTREE +) ENGINE=INNODB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COMMENT='路由限流配置'; + + +CREATE TABLE `isv_info` ( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `app_key` VARCHAR(100) NOT NULL COMMENT 'appKey', + `secret` VARCHAR(200) DEFAULT NULL COMMENT 'secret', + `pub_key` TEXT COMMENT '公钥', + `pri_key` TEXT COMMENT '私钥', + `status` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0' COMMENT '1启用,2禁用', + `sign_type` TINYINT(4) NOT NULL DEFAULT '1' COMMENT '1:RSA2,2:MD5', + `remark` VARCHAR(128) DEFAULT NULL COMMENT '备注', + `gmt_create` DATETIME DEFAULT NULL, + `gmt_modified` DATETIME DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_app_key` (`app_key`) +) ENGINE=INNODB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8 COMMENT='isv信息表'; + + +CREATE TABLE `isv_keys` ( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `app_key` VARCHAR(128) NOT NULL DEFAULT '', + `sign_type` TINYINT(4) NOT NULL DEFAULT '1' COMMENT '1:RSA2,2:MD5', + `secret` VARCHAR(200) NOT NULL DEFAULT '' COMMENT 'sign_type=2时使用', + `key_format` TINYINT(4) NOT NULL DEFAULT '1' COMMENT '秘钥格式,1:PKCS8(JAVA适用),2:PKCS1(非JAVA适用)', + `public_key_isv` TEXT NOT NULL COMMENT '开发者生成的公钥', + `private_key_isv` TEXT NOT NULL COMMENT '开发者生成的私钥(交给开发者)', + `public_key_platform` TEXT NOT NULL COMMENT '平台生成的公钥(交给开发者)', + `private_key_platform` TEXT NOT NULL COMMENT '平台生成的私钥', + `gmt_create` DATETIME DEFAULT NULL, + `gmt_modified` DATETIME DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_appkey` (`app_key`) USING BTREE +) ENGINE=INNODB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8 COMMENT='ISV秘钥'; + + +CREATE TABLE `perm_isv_role` ( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `isv_id` BIGINT(20) NOT NULL COMMENT 'isv_info表id', + `role_code` VARCHAR(64) NOT NULL COMMENT '角色code', + `gmt_create` DATETIME DEFAULT NULL, + `gmt_modified` DATETIME DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_user_role` (`isv_id`,`role_code`) +) ENGINE=INNODB AUTO_INCREMENT=59 DEFAULT CHARSET=utf8 COMMENT='isv角色'; + + +CREATE TABLE `perm_role` ( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `role_code` VARCHAR(64) NOT NULL COMMENT '角色代码', + `description` VARCHAR(64) NOT NULL COMMENT '角色描述', + `gmt_create` DATETIME DEFAULT NULL, + `gmt_modified` DATETIME DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_code` (`role_code`) +) ENGINE=INNODB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT='角色表'; + + +CREATE TABLE `perm_role_permission` ( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `role_code` VARCHAR(64) NOT NULL COMMENT '角色表code', + `route_id` VARCHAR(64) NOT NULL COMMENT 'api_id', + `gmt_create` DATETIME DEFAULT NULL, + `gmt_modified` DATETIME DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_role_perm` (`role_code`,`route_id`) USING BTREE +) ENGINE=INNODB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8 COMMENT='角色权限表'; + + +CREATE TABLE `user_info` ( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `username` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '用户名', + `password` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '密码', + `nickname` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '昵称', + `gmt_create` DATETIME DEFAULT NULL, + `gmt_modified` DATETIME DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx_unamepwd` (`username`,`password`) USING BTREE +) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='用户信息表'; + +CREATE TABLE `config_service_route` ( + `id` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '路由id', + `service_id` VARCHAR(128) NOT NULL DEFAULT '', + `name` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '接口名', + `version` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '版本号', + `predicates` VARCHAR(256) DEFAULT NULL COMMENT '路由断言(SpringCloudGateway专用)', + `filters` VARCHAR(256) DEFAULT NULL COMMENT '路由过滤器(SpringCloudGateway专用)', + `uri` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '路由规则转发的目标uri', + `path` VARCHAR(128) NOT NULL DEFAULT '' COMMENT 'uri后面跟的path', + `order` INT(11) NOT NULL DEFAULT '0' COMMENT '路由执行的顺序', + `ignore_validate` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '是否忽略验证,业务参数验证除外', + `status` TINYINT(4) NOT NULL DEFAULT '1' COMMENT '状态,0:待审核,1:启用,2:禁用', + `merge_result` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '是否合并结果', + `permission` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '是否需要授权才能访问', + `need_token` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '是否需要token', + `gmt_create` DATETIME DEFAULT NULL, + `gmt_modified` DATETIME DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx_serviceid` (`service_id`) USING BTREE +) ENGINE=INNODB DEFAULT CHARSET=utf8 COMMENT='路由配置'; + + +SET FOREIGN_KEY_CHECKS = @PREVIOUS_FOREIGN_KEY_CHECKS; + + +SET @PREVIOUS_FOREIGN_KEY_CHECKS = @@FOREIGN_KEY_CHECKS; +SET FOREIGN_KEY_CHECKS = 0; + + +LOCK TABLES `admin_user_info` WRITE; +ALTER TABLE `admin_user_info` DISABLE KEYS; +INSERT INTO `admin_user_info` (`id`, `username`, `password`, `status`, `gmt_create`, `gmt_modified`) VALUES + (1,'admin','14e1b600b1fd579f47433b88e8d85291',1,'2019-04-02 19:55:26','2019-04-02 19:55:26'); +ALTER TABLE `admin_user_info` ENABLE KEYS; +UNLOCK TABLES; + + +LOCK TABLES `config_common` WRITE; +ALTER TABLE `config_common` DISABLE KEYS; +ALTER TABLE `config_common` ENABLE KEYS; +UNLOCK TABLES; + + +LOCK TABLES `config_gray` WRITE; +ALTER TABLE `config_gray` DISABLE KEYS; +ALTER TABLE `config_gray` ENABLE KEYS; +UNLOCK TABLES; + + +LOCK TABLES `config_gray_instance` WRITE; +ALTER TABLE `config_gray_instance` DISABLE KEYS; +ALTER TABLE `config_gray_instance` ENABLE KEYS; +UNLOCK TABLES; + + +LOCK TABLES `config_ip_blacklist` WRITE; +ALTER TABLE `config_ip_blacklist` DISABLE KEYS; +ALTER TABLE `config_ip_blacklist` ENABLE KEYS; +UNLOCK TABLES; + + +LOCK TABLES `config_limit` WRITE; +ALTER TABLE `config_limit` DISABLE KEYS; +INSERT INTO `config_limit` (`id`, `route_id`, `app_key`, `limit_ip`, `service_id`, `limit_type`, `exec_count_per_second`, `limit_code`, `limit_msg`, `token_bucket_count`, `limit_status`, `order_index`, `remark`, `gmt_create`, `gmt_modified`) VALUES + (1,'alipay.story.get1.0','','192.168.1.1,172.2.2.3','story-service',2,5,'','',6,1,3,NULL,'2019-05-17 19:21:35','2019-05-21 09:12:15'), + (2,'alipay.story.get1.0','2019032617262200001','','story-service',2,5,'service-budy','服务器忙',10,1,0,'','2019-05-17 19:39:30','2019-05-21 15:36:52'), + (3,'alipay.story.find1.0','20190331562013861008375808','','story-service',1,3,'service-busy','服务器忙',5,1,1,NULL,'2019-05-17 20:20:32','2019-05-20 17:40:17'), + (4,'alipay.story.get1.2','','','story-service',1,5,'service-busy','服务器忙',3,1,1,'','2019-05-20 16:27:21','2019-05-21 15:53:10'), + (5,'','20190401562373796095328256','','story-service',1,5,'service-busy','服务器忙',5,1,0,'这个appKey调用很频繁,重点照顾','2019-05-21 15:48:08','2019-05-21 18:45:32'), + (6,'','','10.1.30.54','story-service',1,5,'service-busy','服务器忙',5,1,0,'这个ip在攻击我们','2019-05-21 15:55:33','2019-05-21 18:17:29'), + (7,'story.get1.1','','10.1.30.54','story-service',1,5,'service-busy','服务器忙',5,1,0,NULL,'2019-05-21 16:30:48','2019-05-21 16:30:48'), + (8,'','20190513577548661718777857','10.1.30.54','story-service',1,5,'service-busy','服务器忙',5,1,0,NULL,'2019-05-21 17:10:45','2019-05-21 17:10:52'); +ALTER TABLE `config_limit` ENABLE KEYS; +UNLOCK TABLES; + + +LOCK TABLES `config_route_base` WRITE; +ALTER TABLE `config_route_base` DISABLE KEYS; +INSERT INTO `config_route_base` (`id`, `route_id`, `status`, `gmt_create`, `gmt_modified`) VALUES + (1,'story.get1.1',1,'2019-04-09 19:15:58','2019-04-09 19:16:54'), + (2,'alipay.story.get1.0',1,'2019-04-09 19:19:57','2019-04-19 14:45:33'), + (3,'alipay.story.find1.0',1,'2019-04-11 09:29:55','2019-04-11 09:35:02'), + (4,'alipay.book.story.get1.0',1,'2019-04-16 10:23:44','2019-04-16 10:23:44'), + (5,'spirngmvc.goods.get1.0',2,'2019-04-16 10:24:08','2019-04-16 10:24:08'), + (6,'alipay.category.get1.0',1,'2019-05-06 16:50:39','2019-05-20 17:01:48'), + (7,'permission.story.get1.0',1,'2019-05-06 20:03:17','2019-05-06 20:03:21'), + (8,'goods.add1.0',1,'2019-05-13 17:23:00','2019-05-13 17:23:11'); +ALTER TABLE `config_route_base` ENABLE KEYS; +UNLOCK TABLES; + + +LOCK TABLES `config_route_limit` WRITE; +ALTER TABLE `config_route_limit` DISABLE KEYS; +INSERT INTO `config_route_limit` (`id`, `route_id`, `service_id`, `limit_type`, `exec_count_per_second`, `limit_code`, `limit_msg`, `token_bucket_count`, `limit_status`, `gmt_create`, `gmt_modified`) VALUES + (1,'alipay.story.find1.0','story-service',1,10,'isp.service-busy','服务器正忙',NULL,1,'2019-04-10 20:55:08','2019-04-10 21:04:35'), + (2,'alipay.story.get1.0','story-service',2,5,'isp.service-busy','服务器正忙',5,0,'2019-04-10 21:05:51','2019-04-11 18:18:03'), + (3,'alipay.story.get1.2','story-service',2,10,'isp.service-busy','服务器正忙',3,1,'2019-04-11 17:26:04','2019-04-11 18:19:33'), + (4,'alipay.category.get1.0','story-service',1,5,'error-code','服务器忙',5,1,'2019-05-07 12:22:04','2019-05-07 12:25:06'); +ALTER TABLE `config_route_limit` ENABLE KEYS; +UNLOCK TABLES; + + +LOCK TABLES `isv_info` WRITE; +ALTER TABLE `isv_info` DISABLE KEYS; +INSERT INTO `isv_info` (`id`, `app_key`, `secret`, `pub_key`, `pri_key`, `status`, `sign_type`, `remark`, `gmt_create`, `gmt_modified`) VALUES + (1,'2019032617262200001','','MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlyb9aUBaljQP/vjmBFe1mF8HsWSvyfC2NTlpT/V9E+sBxTr8TSkbzJCeeeOEm4LCaVXL0Qz63MZoT24v7AIXTuMdj4jyiM/WJ4tjrWAgnmohNOegfntTto16C3l234vXz4ryWZMR/7W+MXy5B92wPGQEJ0LKFwNEoLspDEWZ7RdE53VH7w6y6sIZUfK+YkXWSwehfKPKlx+lDw3zRJ3/yvMF+U+BAdW/MfECe1GuBnCFKnlMRh3UKczWyXWkL6ItOpYHHJi/jx85op5BWDje2pY9QowzfN94+0DB3T7UvZeweu3zlP6diwAJDzLaFQX8ULfWhY+wfKxIRgs9NoiSAQIDAQAB','MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXJv1pQFqWNA/++OYEV7WYXwexZK/J8LY1OWlP9X0T6wHFOvxNKRvMkJ5544SbgsJpVcvRDPrcxmhPbi/sAhdO4x2PiPKIz9Yni2OtYCCeaiE056B+e1O2jXoLeXbfi9fPivJZkxH/tb4xfLkH3bA8ZAQnQsoXA0SguykMRZntF0TndUfvDrLqwhlR8r5iRdZLB6F8o8qXH6UPDfNEnf/K8wX5T4EB1b8x8QJ7Ua4GcIUqeUxGHdQpzNbJdaQvoi06lgccmL+PHzminkFYON7alj1CjDN833j7QMHdPtS9l7B67fOU/p2LAAkPMtoVBfxQt9aFj7B8rEhGCz02iJIBAgMBAAECggEARqOuIpY0v6WtJBfmR3lGIOOokLrhfJrGTLF8CiZMQha+SRJ7/wOLPlsH9SbjPlopyViTXCuYwbzn2tdABigkBHYXxpDV6CJZjzmRZ+FY3S/0POlTFElGojYUJ3CooWiVfyUMhdg5vSuOq0oCny53woFrf32zPHYGiKdvU5Djku1onbDU0Lw8w+5tguuEZ76kZ/lUcccGy5978FFmYpzY/65RHCpvLiLqYyWTtaNT1aQ/9pw4jX9HO9NfdJ9gYFK8r/2f36ZE4hxluAfeOXQfRC/WhPmiw/ReUhxPznG/WgKaa/OaRtAx3inbQ+JuCND7uuKeRe4osP2jLPHPP6AUwQKBgQDUNu3BkLoKaimjGOjCTAwtp71g1oo+k5/uEInAo7lyEwpV0EuUMwLA/HCqUgR4K9pyYV+Oyb8d6f0+Hz0BMD92I2pqlXrD7xV2WzDvyXM3s63NvorRooKcyfd9i6ccMjAyTR2qfLkxv0hlbBbsPHz4BbU63xhTJp3Ghi0/ey/1HQKBgQC2VsgqC6ykfSidZUNLmQZe3J0p/Qf9VLkfrQ+xaHapOs6AzDU2H2osuysqXTLJHsGfrwVaTs00ER2z8ljTJPBUtNtOLrwNRlvgdnzyVAKHfOgDBGwJgiwpeE9voB1oAV/mXqSaUWNnuwlOIhvQEBwekqNyWvhLqC7nCAIhj3yvNQKBgQCqYbeec56LAhWP903Zwcj9VvG7sESqXUhIkUqoOkuIBTWFFIm54QLTA1tJxDQGb98heoCIWf5x/A3xNI98RsqNBX5JON6qNWjb7/dobitti3t99v/ptDp9u8JTMC7penoryLKK0Ty3bkan95Kn9SC42YxaSghzqkt+uvfVQgiNGQKBgGxU6P2aDAt6VNwWosHSe+d2WWXt8IZBhO9d6dn0f7ORvcjmCqNKTNGgrkewMZEuVcliueJquR47IROdY8qmwqcBAN7Vg2K7r7CPlTKAWTRYMJxCT1Hi5gwJb+CZF3+IeYqsJk2NF2s0w5WJTE70k1BSvQsfIzAIDz2yE1oPHvwVAoGAA6e+xQkVH4fMEph55RJIZ5goI4Y76BSvt2N5OKZKd4HtaV+eIhM3SDsVYRLIm9ZquJHMiZQGyUGnsvrKL6AAVNK7eQZCRDk9KQz+0GKOGqku0nOZjUbAu6A2/vtXAaAuFSFx1rUQVVjFulLexkXR3KcztL1Qu2k5pB6Si0K/uwQ=',1,1,NULL,'2019-03-27 10:10:34','2019-05-09 11:10:38'), + (3,'asdfasdf','','MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOJTAsGoAsNGJdzaWm835mtpcY2YxGr4NPjnhQmfUrPdT+Zgi6jmm+olYuygNeB8cOSALmTzgXlef+6SdwfRJVEMYVAO7hqF0Ood9zTOc+kolgnWJtqX54CoodfABt0SNS/bsr6hCAWu17RGnbgBaK+ZaJD3NVqXVXE8E30cYHiQIDAQAB','MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAM4lMCwagCw0Yl3Npabzfma2lxjZjEavg0+OeFCZ9Ss91P5mCLqOab6iVi7KA14Hxw5IAuZPOBeV5/7pJ3B9ElUQxhUA7uGoXQ6h33NM5z6SiWCdYm2pfngKih18AG3RI1L9uyvqEIBa7XtEaduAFor5lokPc1WpdVcTwTfRxgeJAgMBAAECgYAM3XFGL1k0aQiChiUCaEvJKTgAywLgHm/5dRC5JwKP8knqnn+I9P5QcV0jimPvaFjZ4VCdAvCjOC3EUNSvRn7wR2Lb1+BGZZePTdxtHWE2aqJ1W1SvgQTqMsLlPBRPnXo5XH/ng3WEH15ynd5NR035xAluaI0X/y+PsRxE6TlfIQJBAPSYUyXa2yaEqmvIN+ECKALCLLeDdi2YW3Kjahgz0X9V4Y4aTdrHh8y603zXC0Wy8HeOhwGoyciaS8SmjxCMn4UCQQDXweW8xsUreLH8hfVUtyiY/KgUz+R5foJDNXD7TLE9CDoPSHy09qBe99HyVCZg/gNJH4O+tNr6C4916dYaVk01AkBYZ2HOEc8ZmeOaty/zJHtfm9zbqykgi6upwISNINV8Z4bxfHJdO7bKeVANFBBf7a/aFmqXX/EmjxYJioW03o6dAkEAp7ViXJCtJpNU1pNSFZ2hgvmxtSu7zuyVWKSrw8rjYiuI5eRUe13RXsCHgzQB+Ra5exdyEsUGCaL+yosPD73RmQJBALGuM8EQUcBgrpgpeLZ39Ni1DYXYG9aj+u+ar/UL6kI1mCNFgwroO4EVIvXPVxikMxUgiE2tVaBML5nm8VDNJ7s=',2,1,NULL,'2019-03-27 11:01:11','2019-05-11 10:45:01'), + (5,'20190331562013861008375808','29864b93427447f5ac6c44df746f84ef','MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC4aMIx2q8rCVu5z6dgNQQSX2vwhvIcRb7FaqSk0ZK8AV9qQeE1TvfFVlAzOHlysE1yTRb0Mb6W2aw7IAS7Bkc3onYBQR4zNQYjYoDBzLukjF8o84hoVFRnh7sV8zszid2vb5H/YQr3M+5sYhlXY8KfILk3vhdbWpHM/umplcrxlwIDAQAB','MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALhowjHarysJW7nPp2A1BBJfa/CG8hxFvsVqpKTRkrwBX2pB4TVO98VWUDM4eXKwTXJNFvQxvpbZrDsgBLsGRzeidgFBHjM1BiNigMHMu6SMXyjziGhUVGeHuxXzOzOJ3a9vkf9hCvcz7mxiGVdjwp8guTe+F1takcz+6amVyvGXAgMBAAECgYBj40LFVGoryp7n0CYeg7kX5p4GJGKCk/jY4IIcUPTFZ4zydorxoDuvpag9hmqqh/r7XeyAC23sMi4LvLUzRRxPh+7PuwL6nLce7vytsMCZQTPpBgz7dUfbi2HAxsuMOLjH3sVGycutARJsz6bT+9PyBEuVtUqwBrDGpFvwT0z6yQJBAOANC8nysb+O4rn/fbJtHIhtQoV74yu00mLnfwv8/J1+WyAEc32WZ4KYINqCe8ft1UknhPQx9UV6JaPCnlpF6w0CQQDStJhd38uQ7dVUQZHGP24xS/K38AYiSheEr7uewhkJfC2cKqE/lBk3oEG4s7asjhwlFLWLWSBLVM/Ta9Yj0hYzAkBe82hxl1bY9bcEWFBu02rqLlOouk4V8bXPkIf5DqgIHsqDkR9Ys+r+H3ac4/uNSS/ApuzjiGCHpzJYalwtqb/pAkEAvAKlSm5dCC8QAaSYXJtQyfAI3hPwhTwzjBP6iAiNqqcBU62+QCr37Wiz/Alv4LzVZEj8TSDz7gP5hZ9dbo0RfQJBALJ7NhTaeMN4jxBJ6Xg4rNZPb4yhAXuFxCp+a+FyXTsbWnW/ar8KJ3LHox0GOao6wne4qN3h7eqLOrYnnvOSFl0=',1,2,NULL,'2019-03-31 20:34:12','2019-04-19 17:30:09'), + (6,'20190331562037310372184064','d6b2a6603236491f87eed958292be136','','',2,2,NULL,'2019-03-31 22:07:20','2019-05-07 18:14:53'), + (7,'20190401562373672858288128','','MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCOgSo6GfwM8a+rUT4m61nZa7q5cJpjL7luxQKuJvkMl/QydVrzRPinCh6zqgBJDa7YY2X6z5TyTkjYuDkYMrdyHcYvCDuJ0NpRPZMR+QDE70yp9wJ+kOqTa8bepJtxzuCYLxFz+wRsIFt0nIxC6ttCaM9OEw/Bflhws3xczw4uqQIDAQAB','MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAI6BKjoZ/Azxr6tRPibrWdlrurlwmmMvuW7FAq4m+QyX9DJ1WvNE+KcKHrOqAEkNrthjZfrPlPJOSNi4ORgyt3Idxi8IO4nQ2lE9kxH5AMTvTKn3An6Q6pNrxt6km3HO4JgvEXP7BGwgW3ScjELq20Joz04TD8F+WHCzfFzPDi6pAgMBAAECgYA/zuQ6ieILZbjUDBe5U46yxQMh/6KRoQ/14m81zauckPm+EkA8R3jTSru+lPN1wpO0vqUuLf6ylI0XxT2DcUokOgY07ZdE54pu5XAsyY0eanFwt6C1LrHYpORV3Mp9XuI3fXrxYqVlxLuj1N7MGinXUuW7aZCHaEuSnZ55OL9dkQJBANKOeFiYDISSuIFHSrndSgr+a8E44jS/2/7lE49p5l3WVOFTHh0IZQNTs/IKsKJkUnYDE4W/Ab7NTnWZpXYeM30CQQCtQrtxPEzLl4dtupPOBJJoApj2lq7Q6tIGx178K6wS9Rz3GEvkA7fz1Tpm+nmPZflWZ9mVmEaVuMTMpl3HN/edAkBylyzx8lYltIALg5QskT1hvFNChkW9tYjyMROzIkxIV4Q8WPLzlAT9iYlOOfkld/nU1hnC2VAG2k9P+z2sigU1AkBAl1AptsEqZSMn1RalBy9NdypvQ12IpQIHZOwUNnO/3YEe3P/t0TUSwbs0CMyomOuLOsvy6QHnbypu4Na1HjhBAkAWjtdhuvU15HAa5jMgiUVfQM3YFuz2k3QkRagtZZ33bqnYs4wNxEZqB5t+vEj+8r3fmSN0BpNR1VW71j53Ir0H',1,1,NULL,'2019-04-01 20:24:01','2019-04-01 20:27:33'), + (8,'20190401562373796095328256','','MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCbxn0En2cj29t8i+m1dT7seF9XQx3cEaoClXFTlmc78Y1ZuQFbyY99VSwv8JbtpGxPvBSVD9SPmXWNBRBbSHT+gxStcdXD9QeD3knGFy/1V+z3E2Rj/6LIlFmgTlGh1fmEgttPjsUW1Dj704/ydyIrw+CAyAMKQyrFg0wYmNW+UQIDAQAB','MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAJvGfQSfZyPb23yL6bV1Pux4X1dDHdwRqgKVcVOWZzvxjVm5AVvJj31VLC/wlu2kbE+8FJUP1I+ZdY0FEFtIdP6DFK1x1cP1B4PeScYXL/VX7PcTZGP/osiUWaBOUaHV+YSC20+OxRbUOPvTj/J3IivD4IDIAwpDKsWDTBiY1b5RAgMBAAECgYAE27/ycPZKjATgcYyseCeqQGbY1eMMhhCDXB3YuYwmtnXuInMEZdjv08Q5CovqhYJLSlZp/8BlaifcahgEgNIFQXmxAF0U0HsNC6W4Dk1gGgQaVmYaZv5ex7uIcFB1qFvlO60kWf82YeRnO5KsFBODOJ1XSNwqjL2GeLSHBSVyQQJBAOsvDmClBsETSdiNSFMz+D9WCnCh1Ip4AoCzA/yG+PRSwYjZDdceP2DXieiZXPlxTFZ7MIXxAafgeyeQA2hpkQUCQQCpkCUSbrZ+nd4BYdnxZOSf0//cUT0o6+3kROX7gsXV7zRAWWxojT6DkGVlduDLZM/hjWeHRjWUxKC/jgbzvundAkBckhUSrWJPNQxoFJRXS6l3JKLPWqOSLVKu3ce/6lCrurc66lSsS9eegrhhuZwDAzmNAMhEsGx6a72OAP2WZ5cRAkBd8cT4X2qw4BpePa6YdcPNYZHCqSfvgje9XwbkwGGH1A3pESJlEsxt7BShkKmfRu1+E/AmHJoXIJHHT5M+fKnpAkA+VfyAAviKeCwUSq+5oUa0B+ozEA3frp/40cKQP7k02aamocAQCDRaC1ZlWffeQqYMnYe1/Mjr/SdX/Ut3X0CC',1,1,NULL,'2019-04-01 20:24:25','2019-04-01 20:24:48'), + (9,'201904035630907729292csharp','','MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5+OvJxeSzf44NxQ/cl7Ii+BzPg2k6sRcvH4ffOtU5Dzq1/oEvg02nxIhmwOHBZmjbmuUu0aLsfglUTAwqfXftfAKZidshsgj9NNh0/kxk0avRZ1UoljWGz/FxVZA0ogbxxhohPZ9jWcD+eBQcIwF2DtHfAJqWWZrYFnCMeHD8mPzxo2kwXSvDzi0vf9I2tKiYvNG26a9FqeYtPOoi81sdS3+70HOMdxP8ejXtyfnKpKz7Dx506LCIRS5moWS3Q5eTLV3NGX/1CSJ8wpQA2DAQTjVhX5eVu7Yqz12t8W+sjWM/tHUR6cgwYYR10p7tSCeCPzkigjGxKm4cYXWtATQJQIDAQAB','MIIEowIBAAKCAQEA5+OvJxeSzf44NxQ/cl7Ii+BzPg2k6sRcvH4ffOtU5Dzq1/oEvg02nxIhmwOHBZmjbmuUu0aLsfglUTAwqfXftfAKZidshsgj9NNh0/kxk0avRZ1UoljWGz/FxVZA0ogbxxhohPZ9jWcD+eBQcIwF2DtHfAJqWWZrYFnCMeHD8mPzxo2kwXSvDzi0vf9I2tKiYvNG26a9FqeYtPOoi81sdS3+70HOMdxP8ejXtyfnKpKz7Dx506LCIRS5moWS3Q5eTLV3NGX/1CSJ8wpQA2DAQTjVhX5eVu7Yqz12t8W+sjWM/tHUR6cgwYYR10p7tSCeCPzkigjGxKm4cYXWtATQJQIDAQABAoIBAHFDsgrrJca+NKEan77ycwx3jnKx4WrWjOF4zVKL9AQjiSYDNgvKknJyPb3kpC/lEoHdxGERHSzJoxib7DkoIqRQYhPxj73pxj5QfYk3P7LLJNNg/LTrpXDb3nL8JV9wIflGf87qQvstZTDJEyFWE4jBs7Hr0BxovWvri8InnzkmERJ1cbGJgNHe1Y3Zo2tw0yaHxQCxLuajP+notRZhD9bEp7uKeI0w9AvlW6k8m/7y10F0BK/TlyW8rQiEC391yOiRYoMcUh4hd2Q9bMx3jngZgX8PXIvZZcup4/pvWlv1alwhB2tsnLdazP62r1MO80vLyLunzGO+7WwCjEYlVaECgYEA+lQRFmbhKaPuAuXMtY31Fbga8nedka5TjnEV7+/kX+yowE2OlNujF+ZG8UTddTxAGv56yVNi/mjRlgD74j8z0eOsgvOq9mwbCrgLhLo51H9O/wAxtb+hBKtC5l50pBr4gER6d8W6EQNTSGojnMIaLXTkAZ5Qf6Z8e2HFVdOn0X0CgYEA7SSrTokwzukt5KldNu5ukyyd+C3D1i6orbg6qD73EP9CfNMfGSBn7dDv9wMSJH01+Ty+RgTROgtjGRDbMJWnfbdt/61NePr9ar5sb6Nbsf7/I0w7cZF5dsaFYgzaOfQYquzXPbLQHkpMT64bqpv/Mwy4F2lFvaYWY5fA4pC2uckCgYEAg75Ym9ybJaoTqky8ttQ2Jy8UZ4VSVQhVC0My02sCWwWXLlXi8y7An+Rec73Ve0yxREOn5WrQT6pkmzh7V/ABWrYi5WxODpCIjtSbo0fLBa3Wqle00b0/hdCITetqIa/cFs1zUrOqICgK3bKWeXqiAkhhcwSZwwSgwOKM04Wn7ZUCgYBvhHX2mbdVJfyJ8kc+hMOE/E9RHRxiBVEXWHJlGi8PVCqNDq8qHr4g7Mdbzprig+s0yKblwHAvrpkseWvKHiZEjVTyDipHgShY4TGXEigVvUd37uppTrLi8xpYcJjS9gH/px7VCdiq1d+q/MJP6coJ1KphgATm2UrgDMYNBWaYWQKBgEHRxrmER7btUF60/YgcqPHFc8RpYQB2ZZE0kyKGDqk2Data1XYUY6vsPAU28yRLAaWr/D2H17iyLkxP80VLm6QhifxCadv90Q/Wl1DFfOJQMW6avyQ0so6G0wFq/LJxaFK4iLXQn1RJnmTp6BYiJMmK2BhFbRzw8ssMoF6ad2rr',1,1,NULL,'2019-04-03 19:54:05','2019-04-03 20:11:58'), + (10,'easyopen_test','G9w0BAQEFAAOCAQ8AMIIBCgKCA','','',1,2,NULL,'2019-04-19 17:19:34','2019-04-19 17:30:09'), + (11,'20190513577548661718777856','','MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCdGjz3IE2VQteDjQaFacXLCAIqdEIIVcf7LW7f142V55Q0xtcjDTOHjBHExZsG64/Y5WKz4oQVWGnXwtrled8Qg0YAA7ueat8mE8NzJSm9txbfU9hwXB77nJxVkFyaSG1p0IZFrNQpbbUxTX9755deP7DdcSF148LLr091V++S3QIDAQAB','MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAJ0aPPcgTZVC14ONBoVpxcsIAip0QghVx/stbt/XjZXnlDTG1yMNM4eMEcTFmwbrj9jlYrPihBVYadfC2uV53xCDRgADu55q3yYTw3MlKb23Ft9T2HBcHvucnFWQXJpIbWnQhkWs1ClttTFNf3vnl14/sN1xIXXjwsuvT3VX75LdAgMBAAECgYB68z/nQDa3q/oykDocS21qujfHtfi/wTKjVylAsdezC+wnab6RRhGf8XUuhGARiGWpn8whcBNjCTC8lVju4vQ5IIx4Hb74vwDDMtNXeqwkLmARLYu2ELibauezSeqom8/J8cR3ho7Hr4VHPTiC8qvePRmu8AvXVQz2T7SOhEjDGQJBAOm8XOivr+atiknLbQhmo508ON3sjoN9VMwK9cmnup+ZPCsurJTHRja0MJQNdOXObUVJ6wJhs1PHWT+vITfXGJ8CQQCsESzxOYTkZaqBUFjbWVf1rSwjOOsylweTuq44YIJkHhwMjHf3kN/UTXbxsBPUGeT7/+2K5UwQ9snUPr0yTBcDAkA0FMezBWqxgNu+g7iA1bYBVCjrskkzHVsmuA56Z4hbBZ71lEnaQOjxSYdFhhYVGsEYXlciSbjWoyXM3e4N7jzLAkB0ejv+H33CTsAZQZalBdnxSQTz4vf0CyDp9BkzuMELnQZHyF79i2i5gqbd/N+vWMgVfq4CtC3F3gnKT54rii6ZAkAMBIvHriT5Zbs1fW+oxBP1rHqdsRvqs1zEyIadvJgKAFwFEisryfdw2mWm3vxQQ22RlOquBiZEDIlyM0z2m9PJ',1,1,NULL,'2019-05-13 17:24:17','2019-05-13 17:24:17'), + (12,'20190513577548661718777857','','MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCdGjz3IE2VQteDjQaFacXLCAIqdEIIVcf7LW7f142V55Q0xtcjDTOHjBHExZsG64/Y5WKz4oQVWGnXwtrled8Qg0YAA7ueat8mE8NzJSm9txbfU9hwXB77nJxVkFyaSG1p0IZFrNQpbbUxTX9755deP7DdcSF148LLr091V++S3QIDAQAB','MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAJ0aPPcgTZVC14ONBoVpxcsIAip0QghVx/stbt/XjZXnlDTG1yMNM4eMEcTFmwbrj9jlYrPihBVYadfC2uV53xCDRgADu55q3yYTw3MlKb23Ft9T2HBcHvucnFWQXJpIbWnQhkWs1ClttTFNf3vnl14/sN1xIXXjwsuvT3VX75LdAgMBAAECgYB68z/nQDa3q/oykDocS21qujfHtfi/wTKjVylAsdezC+wnab6RRhGf8XUuhGARiGWpn8whcBNjCTC8lVju4vQ5IIx4Hb74vwDDMtNXeqwkLmARLYu2ELibauezSeqom8/J8cR3ho7Hr4VHPTiC8qvePRmu8AvXVQz2T7SOhEjDGQJBAOm8XOivr+atiknLbQhmo508ON3sjoN9VMwK9cmnup+ZPCsurJTHRja0MJQNdOXObUVJ6wJhs1PHWT+vITfXGJ8CQQCsESzxOYTkZaqBUFjbWVf1rSwjOOsylweTuq44YIJkHhwMjHf3kN/UTXbxsBPUGeT7/+2K5UwQ9snUPr0yTBcDAkA0FMezBWqxgNu+g7iA1bYBVCjrskkzHVsmuA56Z4hbBZ71lEnaQOjxSYdFhhYVGsEYXlciSbjWoyXM3e4N7jzLAkB0ejv+H33CTsAZQZalBdnxSQTz4vf0CyDp9BkzuMELnQZHyF79i2i5gqbd/N+vWMgVfq4CtC3F3gnKT54rii6ZAkAMBIvHriT5Zbs1fW+oxBP1rHqdsRvqs1zEyIadvJgKAFwFEisryfdw2mWm3vxQQ22RlOquBiZEDIlyM0z2m9PJ',1,1,NULL,'2019-05-13 17:24:17','2019-05-13 17:24:17'); +ALTER TABLE `isv_info` ENABLE KEYS; +UNLOCK TABLES; + + +LOCK TABLES `isv_keys` WRITE; +ALTER TABLE `isv_keys` DISABLE KEYS; +INSERT INTO `isv_keys` (`id`, `app_key`, `sign_type`, `secret`, `key_format`, `public_key_isv`, `private_key_isv`, `public_key_platform`, `private_key_platform`, `gmt_create`, `gmt_modified`) VALUES + (1,'2019032617262200001',1,'',1,'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlyb9aUBaljQP/vjmBFe1mF8HsWSvyfC2NTlpT/V9E+sBxTr8TSkbzJCeeeOEm4LCaVXL0Qz63MZoT24v7AIXTuMdj4jyiM/WJ4tjrWAgnmohNOegfntTto16C3l234vXz4ryWZMR/7W+MXy5B92wPGQEJ0LKFwNEoLspDEWZ7RdE53VH7w6y6sIZUfK+YkXWSwehfKPKlx+lDw3zRJ3/yvMF+U+BAdW/MfECe1GuBnCFKnlMRh3UKczWyXWkL6ItOpYHHJi/jx85op5BWDje2pY9QowzfN94+0DB3T7UvZeweu3zlP6diwAJDzLaFQX8ULfWhY+wfKxIRgs9NoiSAQIDAQAB','MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXJv1pQFqWNA/++OYEV7WYXwexZK/J8LY1OWlP9X0T6wHFOvxNKRvMkJ5544SbgsJpVcvRDPrcxmhPbi/sAhdO4x2PiPKIz9Yni2OtYCCeaiE056B+e1O2jXoLeXbfi9fPivJZkxH/tb4xfLkH3bA8ZAQnQsoXA0SguykMRZntF0TndUfvDrLqwhlR8r5iRdZLB6F8o8qXH6UPDfNEnf/K8wX5T4EB1b8x8QJ7Ua4GcIUqeUxGHdQpzNbJdaQvoi06lgccmL+PHzminkFYON7alj1CjDN833j7QMHdPtS9l7B67fOU/p2LAAkPMtoVBfxQt9aFj7B8rEhGCz02iJIBAgMBAAECggEARqOuIpY0v6WtJBfmR3lGIOOokLrhfJrGTLF8CiZMQha+SRJ7/wOLPlsH9SbjPlopyViTXCuYwbzn2tdABigkBHYXxpDV6CJZjzmRZ+FY3S/0POlTFElGojYUJ3CooWiVfyUMhdg5vSuOq0oCny53woFrf32zPHYGiKdvU5Djku1onbDU0Lw8w+5tguuEZ76kZ/lUcccGy5978FFmYpzY/65RHCpvLiLqYyWTtaNT1aQ/9pw4jX9HO9NfdJ9gYFK8r/2f36ZE4hxluAfeOXQfRC/WhPmiw/ReUhxPznG/WgKaa/OaRtAx3inbQ+JuCND7uuKeRe4osP2jLPHPP6AUwQKBgQDUNu3BkLoKaimjGOjCTAwtp71g1oo+k5/uEInAo7lyEwpV0EuUMwLA/HCqUgR4K9pyYV+Oyb8d6f0+Hz0BMD92I2pqlXrD7xV2WzDvyXM3s63NvorRooKcyfd9i6ccMjAyTR2qfLkxv0hlbBbsPHz4BbU63xhTJp3Ghi0/ey/1HQKBgQC2VsgqC6ykfSidZUNLmQZe3J0p/Qf9VLkfrQ+xaHapOs6AzDU2H2osuysqXTLJHsGfrwVaTs00ER2z8ljTJPBUtNtOLrwNRlvgdnzyVAKHfOgDBGwJgiwpeE9voB1oAV/mXqSaUWNnuwlOIhvQEBwekqNyWvhLqC7nCAIhj3yvNQKBgQCqYbeec56LAhWP903Zwcj9VvG7sESqXUhIkUqoOkuIBTWFFIm54QLTA1tJxDQGb98heoCIWf5x/A3xNI98RsqNBX5JON6qNWjb7/dobitti3t99v/ptDp9u8JTMC7penoryLKK0Ty3bkan95Kn9SC42YxaSghzqkt+uvfVQgiNGQKBgGxU6P2aDAt6VNwWosHSe+d2WWXt8IZBhO9d6dn0f7ORvcjmCqNKTNGgrkewMZEuVcliueJquR47IROdY8qmwqcBAN7Vg2K7r7CPlTKAWTRYMJxCT1Hi5gwJb+CZF3+IeYqsJk2NF2s0w5WJTE70k1BSvQsfIzAIDz2yE1oPHvwVAoGAA6e+xQkVH4fMEph55RJIZ5goI4Y76BSvt2N5OKZKd4HtaV+eIhM3SDsVYRLIm9ZquJHMiZQGyUGnsvrKL6AAVNK7eQZCRDk9KQz+0GKOGqku0nOZjUbAu6A2/vtXAaAuFSFx1rUQVVjFulLexkXR3KcztL1Qu2k5pB6Si0K/uwQ=','','','2019-08-13 15:18:39','2019-08-13 15:18:39'), + (2,'asdfasdf',1,'',1,'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOJTAsGoAsNGJdzaWm835mtpcY2YxGr4NPjnhQmfUrPdT+Zgi6jmm+olYuygNeB8cOSALmTzgXlef+6SdwfRJVEMYVAO7hqF0Ood9zTOc+kolgnWJtqX54CoodfABt0SNS/bsr6hCAWu17RGnbgBaK+ZaJD3NVqXVXE8E30cYHiQIDAQAB','MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAM4lMCwagCw0Yl3Npabzfma2lxjZjEavg0+OeFCZ9Ss91P5mCLqOab6iVi7KA14Hxw5IAuZPOBeV5/7pJ3B9ElUQxhUA7uGoXQ6h33NM5z6SiWCdYm2pfngKih18AG3RI1L9uyvqEIBa7XtEaduAFor5lokPc1WpdVcTwTfRxgeJAgMBAAECgYAM3XFGL1k0aQiChiUCaEvJKTgAywLgHm/5dRC5JwKP8knqnn+I9P5QcV0jimPvaFjZ4VCdAvCjOC3EUNSvRn7wR2Lb1+BGZZePTdxtHWE2aqJ1W1SvgQTqMsLlPBRPnXo5XH/ng3WEH15ynd5NR035xAluaI0X/y+PsRxE6TlfIQJBAPSYUyXa2yaEqmvIN+ECKALCLLeDdi2YW3Kjahgz0X9V4Y4aTdrHh8y603zXC0Wy8HeOhwGoyciaS8SmjxCMn4UCQQDXweW8xsUreLH8hfVUtyiY/KgUz+R5foJDNXD7TLE9CDoPSHy09qBe99HyVCZg/gNJH4O+tNr6C4916dYaVk01AkBYZ2HOEc8ZmeOaty/zJHtfm9zbqykgi6upwISNINV8Z4bxfHJdO7bKeVANFBBf7a/aFmqXX/EmjxYJioW03o6dAkEAp7ViXJCtJpNU1pNSFZ2hgvmxtSu7zuyVWKSrw8rjYiuI5eRUe13RXsCHgzQB+Ra5exdyEsUGCaL+yosPD73RmQJBALGuM8EQUcBgrpgpeLZ39Ni1DYXYG9aj+u+ar/UL6kI1mCNFgwroO4EVIvXPVxikMxUgiE2tVaBML5nm8VDNJ7s=','','','2019-08-13 15:18:39','2019-08-13 15:18:39'), + (3,'20190331562013861008375808',2,'29864b93427447f5ac6c44df746f84ef',1,'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC4aMIx2q8rCVu5z6dgNQQSX2vwhvIcRb7FaqSk0ZK8AV9qQeE1TvfFVlAzOHlysE1yTRb0Mb6W2aw7IAS7Bkc3onYBQR4zNQYjYoDBzLukjF8o84hoVFRnh7sV8zszid2vb5H/YQr3M+5sYhlXY8KfILk3vhdbWpHM/umplcrxlwIDAQAB','MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALhowjHarysJW7nPp2A1BBJfa/CG8hxFvsVqpKTRkrwBX2pB4TVO98VWUDM4eXKwTXJNFvQxvpbZrDsgBLsGRzeidgFBHjM1BiNigMHMu6SMXyjziGhUVGeHuxXzOzOJ3a9vkf9hCvcz7mxiGVdjwp8guTe+F1takcz+6amVyvGXAgMBAAECgYBj40LFVGoryp7n0CYeg7kX5p4GJGKCk/jY4IIcUPTFZ4zydorxoDuvpag9hmqqh/r7XeyAC23sMi4LvLUzRRxPh+7PuwL6nLce7vytsMCZQTPpBgz7dUfbi2HAxsuMOLjH3sVGycutARJsz6bT+9PyBEuVtUqwBrDGpFvwT0z6yQJBAOANC8nysb+O4rn/fbJtHIhtQoV74yu00mLnfwv8/J1+WyAEc32WZ4KYINqCe8ft1UknhPQx9UV6JaPCnlpF6w0CQQDStJhd38uQ7dVUQZHGP24xS/K38AYiSheEr7uewhkJfC2cKqE/lBk3oEG4s7asjhwlFLWLWSBLVM/Ta9Yj0hYzAkBe82hxl1bY9bcEWFBu02rqLlOouk4V8bXPkIf5DqgIHsqDkR9Ys+r+H3ac4/uNSS/ApuzjiGCHpzJYalwtqb/pAkEAvAKlSm5dCC8QAaSYXJtQyfAI3hPwhTwzjBP6iAiNqqcBU62+QCr37Wiz/Alv4LzVZEj8TSDz7gP5hZ9dbo0RfQJBALJ7NhTaeMN4jxBJ6Xg4rNZPb4yhAXuFxCp+a+FyXTsbWnW/ar8KJ3LHox0GOao6wne4qN3h7eqLOrYnnvOSFl0=','','','2019-08-13 15:18:39','2019-08-13 15:18:39'), + (4,'20190331562037310372184064',2,'d6b2a6603236491f87eed958292be136',1,'','','','','2019-08-13 15:18:39','2019-08-13 15:18:39'), + (5,'20190401562373672858288128',1,'',1,'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCOgSo6GfwM8a+rUT4m61nZa7q5cJpjL7luxQKuJvkMl/QydVrzRPinCh6zqgBJDa7YY2X6z5TyTkjYuDkYMrdyHcYvCDuJ0NpRPZMR+QDE70yp9wJ+kOqTa8bepJtxzuCYLxFz+wRsIFt0nIxC6ttCaM9OEw/Bflhws3xczw4uqQIDAQAB','MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAI6BKjoZ/Azxr6tRPibrWdlrurlwmmMvuW7FAq4m+QyX9DJ1WvNE+KcKHrOqAEkNrthjZfrPlPJOSNi4ORgyt3Idxi8IO4nQ2lE9kxH5AMTvTKn3An6Q6pNrxt6km3HO4JgvEXP7BGwgW3ScjELq20Joz04TD8F+WHCzfFzPDi6pAgMBAAECgYA/zuQ6ieILZbjUDBe5U46yxQMh/6KRoQ/14m81zauckPm+EkA8R3jTSru+lPN1wpO0vqUuLf6ylI0XxT2DcUokOgY07ZdE54pu5XAsyY0eanFwt6C1LrHYpORV3Mp9XuI3fXrxYqVlxLuj1N7MGinXUuW7aZCHaEuSnZ55OL9dkQJBANKOeFiYDISSuIFHSrndSgr+a8E44jS/2/7lE49p5l3WVOFTHh0IZQNTs/IKsKJkUnYDE4W/Ab7NTnWZpXYeM30CQQCtQrtxPEzLl4dtupPOBJJoApj2lq7Q6tIGx178K6wS9Rz3GEvkA7fz1Tpm+nmPZflWZ9mVmEaVuMTMpl3HN/edAkBylyzx8lYltIALg5QskT1hvFNChkW9tYjyMROzIkxIV4Q8WPLzlAT9iYlOOfkld/nU1hnC2VAG2k9P+z2sigU1AkBAl1AptsEqZSMn1RalBy9NdypvQ12IpQIHZOwUNnO/3YEe3P/t0TUSwbs0CMyomOuLOsvy6QHnbypu4Na1HjhBAkAWjtdhuvU15HAa5jMgiUVfQM3YFuz2k3QkRagtZZ33bqnYs4wNxEZqB5t+vEj+8r3fmSN0BpNR1VW71j53Ir0H','','','2019-08-13 15:18:39','2019-08-13 15:18:39'), + (6,'20190401562373796095328256',1,'',1,'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCbxn0En2cj29t8i+m1dT7seF9XQx3cEaoClXFTlmc78Y1ZuQFbyY99VSwv8JbtpGxPvBSVD9SPmXWNBRBbSHT+gxStcdXD9QeD3knGFy/1V+z3E2Rj/6LIlFmgTlGh1fmEgttPjsUW1Dj704/ydyIrw+CAyAMKQyrFg0wYmNW+UQIDAQAB','MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAJvGfQSfZyPb23yL6bV1Pux4X1dDHdwRqgKVcVOWZzvxjVm5AVvJj31VLC/wlu2kbE+8FJUP1I+ZdY0FEFtIdP6DFK1x1cP1B4PeScYXL/VX7PcTZGP/osiUWaBOUaHV+YSC20+OxRbUOPvTj/J3IivD4IDIAwpDKsWDTBiY1b5RAgMBAAECgYAE27/ycPZKjATgcYyseCeqQGbY1eMMhhCDXB3YuYwmtnXuInMEZdjv08Q5CovqhYJLSlZp/8BlaifcahgEgNIFQXmxAF0U0HsNC6W4Dk1gGgQaVmYaZv5ex7uIcFB1qFvlO60kWf82YeRnO5KsFBODOJ1XSNwqjL2GeLSHBSVyQQJBAOsvDmClBsETSdiNSFMz+D9WCnCh1Ip4AoCzA/yG+PRSwYjZDdceP2DXieiZXPlxTFZ7MIXxAafgeyeQA2hpkQUCQQCpkCUSbrZ+nd4BYdnxZOSf0//cUT0o6+3kROX7gsXV7zRAWWxojT6DkGVlduDLZM/hjWeHRjWUxKC/jgbzvundAkBckhUSrWJPNQxoFJRXS6l3JKLPWqOSLVKu3ce/6lCrurc66lSsS9eegrhhuZwDAzmNAMhEsGx6a72OAP2WZ5cRAkBd8cT4X2qw4BpePa6YdcPNYZHCqSfvgje9XwbkwGGH1A3pESJlEsxt7BShkKmfRu1+E/AmHJoXIJHHT5M+fKnpAkA+VfyAAviKeCwUSq+5oUa0B+ozEA3frp/40cKQP7k02aamocAQCDRaC1ZlWffeQqYMnYe1/Mjr/SdX/Ut3X0CC','','','2019-08-13 15:18:39','2019-08-13 15:18:39'), + (7,'201904035630907729292csharp',1,'',1,'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5+OvJxeSzf44NxQ/cl7Ii+BzPg2k6sRcvH4ffOtU5Dzq1/oEvg02nxIhmwOHBZmjbmuUu0aLsfglUTAwqfXftfAKZidshsgj9NNh0/kxk0avRZ1UoljWGz/FxVZA0ogbxxhohPZ9jWcD+eBQcIwF2DtHfAJqWWZrYFnCMeHD8mPzxo2kwXSvDzi0vf9I2tKiYvNG26a9FqeYtPOoi81sdS3+70HOMdxP8ejXtyfnKpKz7Dx506LCIRS5moWS3Q5eTLV3NGX/1CSJ8wpQA2DAQTjVhX5eVu7Yqz12t8W+sjWM/tHUR6cgwYYR10p7tSCeCPzkigjGxKm4cYXWtATQJQIDAQAB','MIIEowIBAAKCAQEA5+OvJxeSzf44NxQ/cl7Ii+BzPg2k6sRcvH4ffOtU5Dzq1/oEvg02nxIhmwOHBZmjbmuUu0aLsfglUTAwqfXftfAKZidshsgj9NNh0/kxk0avRZ1UoljWGz/FxVZA0ogbxxhohPZ9jWcD+eBQcIwF2DtHfAJqWWZrYFnCMeHD8mPzxo2kwXSvDzi0vf9I2tKiYvNG26a9FqeYtPOoi81sdS3+70HOMdxP8ejXtyfnKpKz7Dx506LCIRS5moWS3Q5eTLV3NGX/1CSJ8wpQA2DAQTjVhX5eVu7Yqz12t8W+sjWM/tHUR6cgwYYR10p7tSCeCPzkigjGxKm4cYXWtATQJQIDAQABAoIBAHFDsgrrJca+NKEan77ycwx3jnKx4WrWjOF4zVKL9AQjiSYDNgvKknJyPb3kpC/lEoHdxGERHSzJoxib7DkoIqRQYhPxj73pxj5QfYk3P7LLJNNg/LTrpXDb3nL8JV9wIflGf87qQvstZTDJEyFWE4jBs7Hr0BxovWvri8InnzkmERJ1cbGJgNHe1Y3Zo2tw0yaHxQCxLuajP+notRZhD9bEp7uKeI0w9AvlW6k8m/7y10F0BK/TlyW8rQiEC391yOiRYoMcUh4hd2Q9bMx3jngZgX8PXIvZZcup4/pvWlv1alwhB2tsnLdazP62r1MO80vLyLunzGO+7WwCjEYlVaECgYEA+lQRFmbhKaPuAuXMtY31Fbga8nedka5TjnEV7+/kX+yowE2OlNujF+ZG8UTddTxAGv56yVNi/mjRlgD74j8z0eOsgvOq9mwbCrgLhLo51H9O/wAxtb+hBKtC5l50pBr4gER6d8W6EQNTSGojnMIaLXTkAZ5Qf6Z8e2HFVdOn0X0CgYEA7SSrTokwzukt5KldNu5ukyyd+C3D1i6orbg6qD73EP9CfNMfGSBn7dDv9wMSJH01+Ty+RgTROgtjGRDbMJWnfbdt/61NePr9ar5sb6Nbsf7/I0w7cZF5dsaFYgzaOfQYquzXPbLQHkpMT64bqpv/Mwy4F2lFvaYWY5fA4pC2uckCgYEAg75Ym9ybJaoTqky8ttQ2Jy8UZ4VSVQhVC0My02sCWwWXLlXi8y7An+Rec73Ve0yxREOn5WrQT6pkmzh7V/ABWrYi5WxODpCIjtSbo0fLBa3Wqle00b0/hdCITetqIa/cFs1zUrOqICgK3bKWeXqiAkhhcwSZwwSgwOKM04Wn7ZUCgYBvhHX2mbdVJfyJ8kc+hMOE/E9RHRxiBVEXWHJlGi8PVCqNDq8qHr4g7Mdbzprig+s0yKblwHAvrpkseWvKHiZEjVTyDipHgShY4TGXEigVvUd37uppTrLi8xpYcJjS9gH/px7VCdiq1d+q/MJP6coJ1KphgATm2UrgDMYNBWaYWQKBgEHRxrmER7btUF60/YgcqPHFc8RpYQB2ZZE0kyKGDqk2Data1XYUY6vsPAU28yRLAaWr/D2H17iyLkxP80VLm6QhifxCadv90Q/Wl1DFfOJQMW6avyQ0so6G0wFq/LJxaFK4iLXQn1RJnmTp6BYiJMmK2BhFbRzw8ssMoF6ad2rr','','','2019-08-13 15:18:39','2019-08-13 15:18:39'), + (8,'easyopen_test',2,'G9w0BAQEFAAOCAQ8AMIIBCgKCA',1,'','','','','2019-08-13 15:18:39','2019-08-13 15:18:39'), + (9,'20190513577548661718777856',1,'',1,'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCdGjz3IE2VQteDjQaFacXLCAIqdEIIVcf7LW7f142V55Q0xtcjDTOHjBHExZsG64/Y5WKz4oQVWGnXwtrled8Qg0YAA7ueat8mE8NzJSm9txbfU9hwXB77nJxVkFyaSG1p0IZFrNQpbbUxTX9755deP7DdcSF148LLr091V++S3QIDAQAB','MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAJ0aPPcgTZVC14ONBoVpxcsIAip0QghVx/stbt/XjZXnlDTG1yMNM4eMEcTFmwbrj9jlYrPihBVYadfC2uV53xCDRgADu55q3yYTw3MlKb23Ft9T2HBcHvucnFWQXJpIbWnQhkWs1ClttTFNf3vnl14/sN1xIXXjwsuvT3VX75LdAgMBAAECgYB68z/nQDa3q/oykDocS21qujfHtfi/wTKjVylAsdezC+wnab6RRhGf8XUuhGARiGWpn8whcBNjCTC8lVju4vQ5IIx4Hb74vwDDMtNXeqwkLmARLYu2ELibauezSeqom8/J8cR3ho7Hr4VHPTiC8qvePRmu8AvXVQz2T7SOhEjDGQJBAOm8XOivr+atiknLbQhmo508ON3sjoN9VMwK9cmnup+ZPCsurJTHRja0MJQNdOXObUVJ6wJhs1PHWT+vITfXGJ8CQQCsESzxOYTkZaqBUFjbWVf1rSwjOOsylweTuq44YIJkHhwMjHf3kN/UTXbxsBPUGeT7/+2K5UwQ9snUPr0yTBcDAkA0FMezBWqxgNu+g7iA1bYBVCjrskkzHVsmuA56Z4hbBZ71lEnaQOjxSYdFhhYVGsEYXlciSbjWoyXM3e4N7jzLAkB0ejv+H33CTsAZQZalBdnxSQTz4vf0CyDp9BkzuMELnQZHyF79i2i5gqbd/N+vWMgVfq4CtC3F3gnKT54rii6ZAkAMBIvHriT5Zbs1fW+oxBP1rHqdsRvqs1zEyIadvJgKAFwFEisryfdw2mWm3vxQQ22RlOquBiZEDIlyM0z2m9PJ','','','2019-08-13 15:18:39','2019-08-13 15:18:39'), + (10,'20190513577548661718777857',1,'',1,'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCdGjz3IE2VQteDjQaFacXLCAIqdEIIVcf7LW7f142V55Q0xtcjDTOHjBHExZsG64/Y5WKz4oQVWGnXwtrled8Qg0YAA7ueat8mE8NzJSm9txbfU9hwXB77nJxVkFyaSG1p0IZFrNQpbbUxTX9755deP7DdcSF148LLr091V++S3QIDAQAB','MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAJ0aPPcgTZVC14ONBoVpxcsIAip0QghVx/stbt/XjZXnlDTG1yMNM4eMEcTFmwbrj9jlYrPihBVYadfC2uV53xCDRgADu55q3yYTw3MlKb23Ft9T2HBcHvucnFWQXJpIbWnQhkWs1ClttTFNf3vnl14/sN1xIXXjwsuvT3VX75LdAgMBAAECgYB68z/nQDa3q/oykDocS21qujfHtfi/wTKjVylAsdezC+wnab6RRhGf8XUuhGARiGWpn8whcBNjCTC8lVju4vQ5IIx4Hb74vwDDMtNXeqwkLmARLYu2ELibauezSeqom8/J8cR3ho7Hr4VHPTiC8qvePRmu8AvXVQz2T7SOhEjDGQJBAOm8XOivr+atiknLbQhmo508ON3sjoN9VMwK9cmnup+ZPCsurJTHRja0MJQNdOXObUVJ6wJhs1PHWT+vITfXGJ8CQQCsESzxOYTkZaqBUFjbWVf1rSwjOOsylweTuq44YIJkHhwMjHf3kN/UTXbxsBPUGeT7/+2K5UwQ9snUPr0yTBcDAkA0FMezBWqxgNu+g7iA1bYBVCjrskkzHVsmuA56Z4hbBZ71lEnaQOjxSYdFhhYVGsEYXlciSbjWoyXM3e4N7jzLAkB0ejv+H33CTsAZQZalBdnxSQTz4vf0CyDp9BkzuMELnQZHyF79i2i5gqbd/N+vWMgVfq4CtC3F3gnKT54rii6ZAkAMBIvHriT5Zbs1fW+oxBP1rHqdsRvqs1zEyIadvJgKAFwFEisryfdw2mWm3vxQQ22RlOquBiZEDIlyM0z2m9PJ','','','2019-08-13 15:18:39','2019-08-13 15:18:39'); +ALTER TABLE `isv_keys` ENABLE KEYS; +UNLOCK TABLES; + + +LOCK TABLES `perm_isv_role` WRITE; +ALTER TABLE `perm_isv_role` DISABLE KEYS; +INSERT INTO `perm_isv_role` (`id`, `isv_id`, `role_code`, `gmt_create`, `gmt_modified`) VALUES + (18,5,'normal','2019-03-31 22:07:50','2019-03-31 22:07:50'), + (32,7,'normal','2019-04-01 20:27:33','2019-04-01 20:27:33'), + (36,9,'normal','2019-04-03 20:11:58','2019-04-03 20:11:58'), + (37,10,'normal','2019-04-19 17:19:34','2019-04-19 17:19:34'), + (38,10,'vip','2019-04-19 17:19:34','2019-04-19 17:19:34'), + (52,6,'normal','2019-05-07 18:14:53','2019-05-07 18:14:53'), + (57,1,'normal','2019-05-09 11:10:38','2019-05-09 11:10:38'), + (58,11,'normal','2019-05-13 17:24:17','2019-05-13 17:24:17'); +ALTER TABLE `perm_isv_role` ENABLE KEYS; +UNLOCK TABLES; + + +LOCK TABLES `perm_role` WRITE; +ALTER TABLE `perm_role` DISABLE KEYS; +INSERT INTO `perm_role` (`id`, `role_code`, `description`, `gmt_create`, `gmt_modified`) VALUES + (1,'normal','普通权限','2019-03-29 15:00:10','2019-03-29 15:00:10'), + (2,'vip','VIP权限','2019-03-29 15:00:27','2019-03-29 15:00:27'); +ALTER TABLE `perm_role` ENABLE KEYS; +UNLOCK TABLES; + + +LOCK TABLES `perm_role_permission` WRITE; +ALTER TABLE `perm_role_permission` DISABLE KEYS; +INSERT INTO `perm_role_permission` (`id`, `role_code`, `route_id`, `gmt_create`, `gmt_modified`) VALUES + (29,'normal','permission.story.get1.0','2019-05-06 18:29:16','2019-05-06 18:29:16'); +ALTER TABLE `perm_role_permission` ENABLE KEYS; +UNLOCK TABLES; + + +LOCK TABLES `user_info` WRITE; +ALTER TABLE `user_info` DISABLE KEYS; +INSERT INTO `user_info` (`id`, `username`, `password`, `nickname`, `gmt_create`, `gmt_modified`) VALUES + (1,'zhangsan','123456','张三','2019-04-27 08:32:57','2019-04-27 08:32:57'); +ALTER TABLE `user_info` ENABLE KEYS; +UNLOCK TABLES; + + + + +SET FOREIGN_KEY_CHECKS = @PREVIOUS_FOREIGN_KEY_CHECKS; + + diff --git a/sop-sdk/sdk-java/pom.xml b/sop-sdk/sdk-java/pom.xml index 12def640..d7f37743 100644 --- a/sop-sdk/sdk-java/pom.xml +++ b/sop-sdk/sdk-java/pom.xml @@ -24,7 +24,7 @@ com.alibaba fastjson - 1.2.60 + 1.2.62 diff --git a/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/client/OpenRequest.java b/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/client/OpenRequest.java index c04f2792..6f55ddf5 100644 --- a/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/client/OpenRequest.java +++ b/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/client/OpenRequest.java @@ -42,7 +42,7 @@ public class OpenRequest { } return openHttp.get(url, header); } else { - return openHttp.requestJson(url, JSON.toJSONString(form), header); + return openHttp.request(url, form, header, requestMethod); } } } catch (IOException e) { diff --git a/sop-test/pom.xml b/sop-test/pom.xml index 0f8040be..c6e167f5 100644 --- a/sop-test/pom.xml +++ b/sop-test/pom.xml @@ -33,7 +33,7 @@ com.alibaba fastjson - 1.2.60 + 1.2.62 commons-codec diff --git a/sop-test/src/main/java/com/gitee/sop/test/Client.java b/sop-test/src/main/java/com/gitee/sop/test/Client.java index 3d641df8..ec25a65d 100644 --- a/sop-test/src/main/java/com/gitee/sop/test/Client.java +++ b/sop-test/src/main/java/com/gitee/sop/test/Client.java @@ -117,6 +117,40 @@ public class Client { return responseData; } + /** + * 发送请求 + * + * @param requestBuilder 请求信息 + * @return 返回结果 + */ + public InputStream download(RequestBuilder requestBuilder) { + RequestInfo requestInfo = requestBuilder.build(appId, privateKey); + HttpTool.HTTPMethod httpMethod = requestInfo.getHttpMethod(); + boolean postJson = requestInfo.isPostJson(); + Map form = requestInfo.getForm(); + Map header = requestInfo.getHeader(); + String requestUrl = requestInfo.getUrl() != null ? requestInfo.getUrl() : url; + List uploadFileList = requestBuilder.getUploadFileList(); + InputStream responseData = null; + // 发送请求 + try { + // 如果有上传文件 + if (uploadFileList != null && uploadFileList.size() > 0) { + responseData = httpTool.downloadByRequestFile(url, form, header, uploadFileList); + } else { + if (httpMethod == HttpTool.HTTPMethod.POST && postJson) { + responseData = httpTool.downloadJson(requestUrl, JSON.toJSONString(form), header); + } else { + responseData = httpTool.download(requestUrl, form, header, httpMethod); + } + } + + } catch (IOException e) { + throw new RuntimeException(e); + } + return responseData; + } + public interface Callback { /** * 请求成功后回调 @@ -127,6 +161,16 @@ public class Client { void callback(RequestInfo requestInfo, String responseData); } + public interface DownloadCallback { + /** + * 请求成功后回调 + * + * @param requestInfo 请求信息 + * @param responseData 返回结果 + */ + void callback(RequestInfo requestInfo, InputStream responseData); + } + public static class RequestBuilder { private static final String DEFAULT_VERSION = "1.0"; diff --git a/sop-test/src/main/java/com/gitee/sop/test/HttpTool.java b/sop-test/src/main/java/com/gitee/sop/test/HttpTool.java index 55145558..a8f78454 100644 --- a/sop-test/src/main/java/com/gitee/sop/test/HttpTool.java +++ b/sop-test/src/main/java/com/gitee/sop/test/HttpTool.java @@ -130,6 +130,28 @@ public class HttpTool { } } + /** + * 下载文件 + * + * @param url url + * @param form 参数 + * @param header header + * @param method 请求方式,post,get等 + * @return + * @throws IOException + */ + public InputStream download(String url, Map form, Map header, HTTPMethod method) throws IOException { + Request.Builder requestBuilder = buildRequestBuilder(url, form, method); + // 添加header + addHeader(requestBuilder, header); + + Request request = requestBuilder.build(); + Response response = httpClient + .newCall(request) + .execute(); + return response.body().byteStream(); + } + /** * 请求json数据,contentType=application/json * @param url 请求路径 @@ -157,6 +179,29 @@ public class HttpTool { } } + /** + * 请求json数据,contentType=application/json + * @param url 请求路径 + * @param json json数据 + * @param header header + * @return 返回响应结果 + * @throws IOException + */ + public InputStream downloadJson(String url, String json, Map header) throws IOException { + RequestBody body = RequestBody.create(MEDIA_TYPE_JSON, json); + Request.Builder requestBuilder = new Request.Builder() + .url(url) + .post(body); + // 添加header + addHeader(requestBuilder, header); + + Request request = requestBuilder.build(); + Response response = httpClient + .newCall(request) + .execute(); + return response.body().byteStream(); + } + public static Request.Builder buildRequestBuilder(String url, Map form, HTTPMethod method) { switch (method) { case GET: @@ -244,6 +289,48 @@ public class HttpTool { } } + /** + * 上传文件,然后下载文件 + * + * @param url + * @param form + * @param header + * @param files + * @return + * @throws IOException + */ + public InputStream downloadByRequestFile(String url, Map form, Map header, List files) + throws IOException { + // 创建MultipartBody.Builder,用于添加请求的数据 + MultipartBody.Builder bodyBuilder = new MultipartBody.Builder(); + bodyBuilder.setType(MultipartBody.FORM); + + for (UploadFile uploadFile : files) { + // 请求的名字 + bodyBuilder.addFormDataPart(uploadFile.getName(), + // 文件的文字,服务器端用来解析的 + uploadFile.getFileName(), + // 创建RequestBody,把上传的文件放入 + RequestBody.create(null, uploadFile.getFileData()) + ); + } + + for (Map.Entry entry : form.entrySet()) { + bodyBuilder.addFormDataPart(entry.getKey(), String.valueOf(entry.getValue())); + } + + RequestBody requestBody = bodyBuilder.build(); + + Request.Builder builder = new Request.Builder().url(url).post(requestBody); + + // 添加header + addHeader(builder, header); + + Request request = builder.build(); + Response response = httpClient.newCall(request).execute(); + return response.body().byteStream(); + } + /** * 请求数据 * @@ -318,7 +405,7 @@ public class HttpTool { /** http DELETE */ DELETE; - private HTTPMethod() { + HTTPMethod() { } public String value() { diff --git a/sop-test/src/test/java/com/gitee/sop/test/AlipayClientPostTest.java b/sop-test/src/test/java/com/gitee/sop/test/AlipayClientPostTest.java index 2dad56e1..fa3c93e2 100644 --- a/sop-test/src/test/java/com/gitee/sop/test/AlipayClientPostTest.java +++ b/sop-test/src/test/java/com/gitee/sop/test/AlipayClientPostTest.java @@ -63,6 +63,7 @@ public class AlipayClientPostTest extends TestBase { System.out.println("签名(sign):" + sign); params.put("sign", sign); + System.out.println("URL参数:" + buildUrlQuery(params)); System.out.println("----------- 返回结果 -----------"); String responseData = get(url, params);// 发送请求 diff --git a/sop-test/src/test/java/com/gitee/sop/test/AllInOneTest.java b/sop-test/src/test/java/com/gitee/sop/test/AllInOneTest.java index a6034f43..70c3d98e 100644 --- a/sop-test/src/test/java/com/gitee/sop/test/AllInOneTest.java +++ b/sop-test/src/test/java/com/gitee/sop/test/AllInOneTest.java @@ -2,9 +2,12 @@ package com.gitee.sop.test; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; +import org.apache.commons.io.IOUtils; import org.junit.Assert; import java.io.File; +import java.io.IOException; +import java.io.InputStream; import java.util.HashMap; import java.util.Optional; import java.util.concurrent.CountDownLatch; @@ -247,6 +250,23 @@ public class AllInOneTest extends TestBase { client.execute(requestBuilder); } + /** + * 下载文件 + */ + public void testDownloadFile() throws IOException { + Client.RequestBuilder requestBuilder = new Client.RequestBuilder() + .method("story.download") + .version("1.0") + .bizContent(new BizContent().add("id",1).add("name","Jim")) + .httpMethod(HttpTool.HTTPMethod.GET); + + // 文件流 + InputStream download = client.download(requestBuilder); + String content = IOUtils.toString(download, "UTF-8"); + System.out.println("下载文件内容:" + content); + Assert.assertEquals(content, "spring.profiles.active=dev"); + } + /** * 验证中文乱码问题 */ diff --git a/sop-test/src/test/java/com/gitee/sop/test/TestBase.java b/sop-test/src/test/java/com/gitee/sop/test/TestBase.java index adbb3538..d6e37210 100644 --- a/sop-test/src/test/java/com/gitee/sop/test/TestBase.java +++ b/sop-test/src/test/java/com/gitee/sop/test/TestBase.java @@ -5,6 +5,8 @@ import junit.framework.TestCase; import java.io.IOException; import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; import java.util.Collections; import java.util.Map; @@ -73,4 +75,16 @@ public class TestBase extends TestCase { return sb.toString().substring(1); } + protected static String buildUrlQuery(Map params) { + StringBuilder sb = new StringBuilder(); + for (Map.Entry entry : params.entrySet()) { + try { + sb.append("&").append(entry.getKey()).append("=").append(URLEncoder.encode(entry.getValue(), "UTF-8")); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + return sb.toString().substring(1); + } + } diff --git a/sop-website/pom.xml b/sop-website/pom.xml index 9075bfd5..fcbaaef5 100644 --- a/sop-website/pom.xml +++ b/sop-website/pom.xml @@ -35,7 +35,7 @@ com.gitee.sop sop-gateway-common - 2.5.12-SNAPSHOT + 3.0.0-SNAPSHOT @@ -65,7 +65,7 @@ com.alibaba fastjson - 1.2.60 + 1.2.62 compile diff --git a/sop-website/src/main/resources/application-dev.properties b/sop-website/src/main/resources/application-dev.properties index 72235474..cce940d2 100644 --- a/sop-website/src/main/resources/application-dev.properties +++ b/sop-website/src/main/resources/application-dev.properties @@ -1,5 +1,5 @@ server.port=8083 -spring.application.name=website-server +spring.application.name=sop-website # ------- 需要改的配置 ------- # nacos地址 diff --git a/sop-website/src/main/resources/public/pages/doc/doc.html b/sop-website/src/main/resources/public/pages/doc/doc.html index b3ed78b8..65d0b6d5 100644 --- a/sop-website/src/main/resources/public/pages/doc/doc.html +++ b/sop-website/src/main/resources/public/pages/doc/doc.html @@ -96,12 +96,30 @@

-

() +

+

-
-
+
+ + + + + + + + + +
接口名版本号
+
+ +
+
+ 接口描述 +
+
+ +
diff --git a/sop-website/src/main/resources/public/pages/doc/doc.js b/sop-website/src/main/resources/public/pages/doc/doc.js index f7b0db2b..e64d2538 100644 --- a/sop-website/src/main/resources/public/pages/doc/doc.js +++ b/sop-website/src/main/resources/public/pages/doc/doc.js @@ -3,7 +3,7 @@ var docEvent = { this.onSelectDocItem = fn; }, onSelectDocItem: function () {} -} +}; layui.config({ base: '../../assets/lib/layuiext/module/' }).extend({ @@ -61,7 +61,7 @@ layui.config({ */ html.push('
  • '); - html.push('' + docItem['summary'] + '') + html.push('' + docItem['summary'] + ' ' + docItem.version + '') } } @@ -75,7 +75,7 @@ layui.config({ function initEvent() { form.on('select(moduleListFilter)', function (data) { selectDocInfo(data.value); - }) + }); $('#docItemTree').on('click', 'li', function () { var $li = $(this); selectDocItem($li.attr('nameversion')); diff --git a/sop-website/src/main/resources/public/pages/sandbox/sandbox.html b/sop-website/src/main/resources/public/pages/sandbox/sandbox.html index db969c7d..3514d938 100644 --- a/sop-website/src/main/resources/public/pages/sandbox/sandbox.html +++ b/sop-website/src/main/resources/public/pages/sandbox/sandbox.html @@ -77,11 +77,30 @@
  • -

    () +

    +

    -
    -
    + +
    + + + + + + + + + +
    接口名版本号
    +
    + +
    +
    + 接口描述 +
    +
    + +
    diff --git a/sop.sql b/sop.sql index 06acf0b0..4df5d424 100644 --- a/sop.sql +++ b/sop.sql @@ -1,4 +1,6 @@ - +/** +适用于MYSQL 5.6+版本 +**/ CREATE DATABASE IF NOT EXISTS `sop` DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci; USE `sop`;