Merge branch 'master' into pr

# Conflicts:
#	pom.xml
#	sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/configuration/BaseGatewayConfiguration.java
#	sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/interceptor/MonitorRouteInterceptor.java
#	sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/monitor/MonitorInfo.java
#	sop-common/sop-service-common/pom.xml
#	sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/ServiceConfiguration.java
#	sop-gateway/pom.xml
#	sop-gateway/src/main/resources/META-INF/gateway.properties
pull/10/MERGE
tanghc 4 years ago
commit c596290aaa
  1. 2
      Dockerfile
  2. 26
      README.md
  3. 60
      changelog.md
  4. 4
      doc/docs/README.md
  5. 66
      doc/docs/_sidebar.md
  6. 8
      doc/docs/files/10010_快速体验.md
  7. 12
      doc/docs/files/10011_项目接入到SOP.md
  8. 2
      doc/docs/files/10020_新增接口.md
  9. 8
      doc/docs/files/10021_开发流程.md
  10. 11
      doc/docs/files/10022_用户注册.md
  11. 33
      doc/docs/files/10087_自定义返回结果.md
  12. 94
      doc/docs/files/10088_自定义过滤器.md
  13. 4
      doc/docs/files/10091_路由授权.md
  14. 27
      doc/docs/files/10093_路由监控.md
  15. 8
      doc/docs/files/10095_SDK开发.md
  16. 8
      doc/docs/files/10110_预发布灰度发布.md
  17. 14
      doc/docs/files/10113_超时设置.md
  18. 6
      doc/docs/files/90001_网关性能测试.md
  19. 86
      doc/docs/files/90012_原理分析之如何路由.md
  20. 23
      doc/docs/files/90100_常见问题.md
  21. BIN
      doc/docs/files/images/10090_1.png
  22. BIN
      doc/docs/files/images/10090_2.png
  23. BIN
      doc/docs/files/images/10093_1.png
  24. BIN
      doc/docs/files/images/10093_2.png
  25. BIN
      doc/docs/files/images/90012_1.png
  26. 2
      doc/pom.xml
  27. 40
      pom.xml
  28. 3
      sop-admin/pom.xml
  29. 22
      sop-admin/sop-admin-server/pom.xml
  30. 8
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/param/IsvKeysFormUpdate.java
  31. 2
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/result/IsvInfoVO.java
  32. 5
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/MonitorApi.java
  33. 80
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/MonitorNewApi.java
  34. 54
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/ResourceApi.java
  35. 15
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/InstanceMonitorSearchParam.java
  36. 21
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/MonitorErrorMsgParam.java
  37. 19
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/MonitorInfoErrorSolveParam.java
  38. 23
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/MonitorSearchParam.java
  39. 26
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/result/MonitorInfoErrorMsgResult.java
  40. 9
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/system/param/IspPageParam.java
  41. 27
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/system/param/IspResourceParam.java
  42. 27
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/system/result/IspResourceResult.java
  43. 13
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/RouteErrorCount.java
  44. 75
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/common/CopyUtil.java
  45. 50
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/IspResource.java
  46. 3
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/IsvInfo.java
  47. 66
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/MonitorInfo.java
  48. 54
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/MonitorInfoError.java
  49. 61
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/MonitorSummary.java
  50. 11
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/mapper/IspResourceMapper.java
  51. 22
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/mapper/MonitorInfoErrorMapper.java
  52. 19
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/mapper/MonitorInfoMapper.java
  53. 3
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/ConfigPushService.java
  54. 7
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/impl/RegistryServiceNacosImpl.java
  55. 1
      sop-admin/sop-admin-server/src/main/resources/META-INF/sop-admin.properties
  56. 63
      sop-admin/sop-admin-server/src/main/resources/mybatis/mapper/MonitorInfoMapper.xml
  57. 10
      sop-admin/sop-admin-server/src/main/resources/mybatis/mybatisConfig.xml
  58. 2
      sop-admin/sop-admin-server/src/main/resources/public/index.html
  59. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/app.7297829f.js
  60. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/app.f323bdd7.js
  61. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-25908fca.cc77b01c.js
  62. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-25908fca.eac43cde.js
  63. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d0d32e7.e7c489be.js
  64. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d0d6219.18813e69.js
  65. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d238661.23693be8.js
  66. 24
      sop-admin/sop-admin-server/src/main/resources/public/static/sdkConfig.json
  67. 4
      sop-admin/sop-admin-server/src/test/java/com/gitee/sop/adminserver/AccountTest.java
  68. 5
      sop-admin/sop-admin-server/src/test/java/com/gitee/sop/adminserver/SopAdminServerApplicationTests.java
  69. 4
      sop-admin/sop-admin-vue/.env.development
  70. 6
      sop-admin/sop-admin-vue/.env.production
  71. 10
      sop-admin/sop-admin-vue/README.md
  72. 24
      sop-admin/sop-admin-vue/public/static/sdkConfig.json
  73. 8
      sop-admin/sop-admin-vue/src/layout/components/Sidebar/index.vue
  74. 12
      sop-admin/sop-admin-vue/src/router/index.js
  75. 13
      sop-admin/sop-admin-vue/src/utils/global.js
  76. 46
      sop-admin/sop-admin-vue/src/views/isv/index.vue
  77. 283
      sop-admin/sop-admin-vue/src/views/service/monitorNew.vue
  78. 179
      sop-admin/sop-admin-vue/src/views/service/sdk.vue
  79. 6
      sop-auth/pom.xml
  80. 2
      sop-common/pom.xml
  81. 11
      sop-common/sop-bridge-eureka/pom.xml
  82. 11
      sop-common/sop-bridge-nacos/pom.xml
  83. 8
      sop-common/sop-bridge-nacos/src/main/java/com/gitee/sop/bridge/route/NacosRegistryListener.java
  84. 12
      sop-common/sop-gateway-common/pom.xml
  85. 4
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiConfig.java
  86. 3
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/GatewayPredicateDefinition.java
  87. 43
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/LRUCache.java
  88. 2
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/TargetRoute.java
  89. 16
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/config/AbstractConfiguration.java
  90. 1
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/config/SopGatewayEnvironmentPostProcessor.java
  91. 12
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/ServerWebExchangeUtil.java
  92. 46
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/codec/MessageReaderFactory.java
  93. 6
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/configuration/AlipayGatewayConfiguration.java
  94. 24
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/configuration/BaseGatewayConfiguration.java
  95. 7
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/ConfigChannelController.java
  96. 21
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/ErrorLogController.java
  97. 4
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/GatewayController.java
  98. 21
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/GatewayMonitorController.java
  99. 58
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/controller/RestfulController.java
  100. 107
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/GatewayModifyResponseGatewayFilter.java
  101. Some files were not shown because too many files have changed in this diff Show More

@ -5,7 +5,7 @@ VOLUME /sop
# 将所有应用放到一个镜像当中
ADD sop-gateway/target/*.jar sop/sop-gateway/sop-gateway.jar
ADD sop-admin/sop-admin-server/target/*.jar sop/sop-admin/sop-admin.jar
ADD sop-website/target/*.jar sop/sop-website/sop-website.jar
ADD sop-website/sop-website-server/target/*.jar sop/sop-website/sop-website.jar
ADD sop-auth/target/*.jar sop/sop-auth/sop-auth.jar
ADD sop-example/sop-story/target/*.jar sop/sop-story/sop-story.jar

@ -2,7 +2,6 @@
一个开放平台解决方案项目,基于Spring Cloud实现,目标让用户快速搭建自己的开放平台。
SOP提供了两种接口调用方式,分别是:[支付宝开放平台](https://docs.open.alipay.com/api)的调用方式和[淘宝开放平台](http://open.taobao.com/api.htm?docId=285&docType=2)的调用方式。
通过简单的配置后,你的项目就具备了和支付宝开放平台的一样的接口提供能力。
SOP封装了开放平台大部分功能包括:签名验证、统一异常处理、统一返回内容 、业务参数验证(JSR-303)、秘钥管理等,未来还会实现更多功能。
@ -78,6 +77,7 @@ System.out.println(responseData);
> 如上图所示,整个系统运行后,开发者只需关注微服务中的业务代码,接口变更后重新部署微服务应用即可
## 已完成列表
- 签名验证
@ -86,20 +86,19 @@ System.out.println(responseData);
- session管理
- 秘钥管理
- 微服务端自动验证(JSR-303)
- 支持Spring Cloud Gateway
- Admin管理平台,统一管理微服务配置,管理路由管理,微服务上下线
- 门户网站,提供用户注册账号
- 接入方管理+秘钥管理
- 接口权限分配
- 文件上传/下载
- 提供基础SDK(含:Java,C#,Python,Go)
- 提供基础SDK(含:Java,C++,C#,Python,Go,Rust,Nodejs
- 接口限流
- 文档整合
- 应用授权
- 监控日志
- 支持nacos
- 注册中心支持nacos/eureka
- 网关动态修改参数
- 预发布/灰度环境切换
- 支持eureka注册中心
## 界面预览
@ -111,9 +110,11 @@ System.out.println(responseData);
![秘钥信息](https://images.gitee.com/uploads/images/2019/0711/174921_bd817533_332975.png "秘钥信息")
![API文档](https://images.gitee.com/uploads/images/2019/0711/174939_97886883_332975.png "API文档")
- 门户网站
![首页2](https://images.gitee.com/uploads/images/2021/0318/195935_1d610da8_332975.png "portal-vue.png")
![沙箱环境](https://images.gitee.com/uploads/images/2019/0711/175226_3f69346a_332975.png "沙箱环境")
![文档页](https://images.gitee.com/uploads/images/2020/1107/104342_d44849a9_332975.png "portal1.png")
## 工程说明
@ -124,16 +125,19 @@ System.out.println(responseData);
- sop-gateway:网关,统一访问入口,含`Spring Cloud Zuul`和`Spring Cloud Gateway`实现
- sop-example:微服务示例,含springboot,springmvc示例
- sop-website:开放平台对应网站,提供文档API、沙箱测试等内容
- sop-auth:应用授权服务
- sop-auth:应用授权服务示例
- sop-admin:后台管理
- sop-sdk:基础sdk,含Java、C#版本
- sop-test:接口调用测试用例
## 分支说明
- master:发版分支(当前为4.0版本)
- master:发版分支
- develop:日常开发分支
- eureka:使用eureka注册中心
- pr:接受PR的分支,提交PR请提交到此分支
[更新说明](./changelog.md)
## 相关文档
@ -141,6 +145,6 @@ System.out.println(responseData);
## 沟通交流
Q群:167643071
- QQ群
![SOP2群](https://images.gitee.com/uploads/images/2020/0203/103119_bdf84eb6_332975.png "SOP2群.png")
1群:167643071(满)、2群:167643071(满)、3群:565894770

@ -1,5 +1,65 @@
# changelog
## 4.3.2
- 修复微服务方法获取不到OpenContext问题
## 4.3.1
- 修复serviceId有大小写出现404问题
- 修复路有监控错误面板分页问题
## 4.3.0
- 升级`spring-boot/spring-cloud/spring-cloud-alibaba`版本
- 修复`DataBufferLimitException: Exceeded limit on max bytes to buffer : 262144`问题
- 修复压测出现`ClosedChannelException`,`Connection has been closed BEFORE response`问题
## 4.2.7
- 修复两个微服务相同path问题
- 修复Python-SDK下参数传递问题
## 4.2.6
- 优化网关超时处理
## 4.2.5
- 修复restful负载均衡问题
## 4.2.4
- 修复sop-auth模块启动失败问题
## 4.2.3
- 修复nacos的group不生效问题
## 4.2.2
- 优化版本号
## 4.2.1
- 可指定nacos的group,(`spring.cloud.nacos.discovery.group=xx`)
- 修复spring循环依赖问题
## 4.2.0
需要执行`sop-upgrade-4.2.0.sql`
- 新增ISV用户平台
- 新增门户网站(portal)
- 新增`C++`,`Rust`语言SDK
## 4.1.0
需要执行`sop-upgrade-4.1.0.sql`
- 重构路由监控功能
- 升级SpringBoot,SpringCloud,SpringCloudAlibaba版本
## 4.0.3
- 可定义业务错误码(见`@Open`注解中的`bizCode`属性)

@ -2,6 +2,6 @@
Git地址:[SOP](https://gitee.com/durcframework/SOP)
Q群:167643071
- QQ群
![SOP2群](https://images.gitee.com/uploads/images/2020/0203/103119_bdf84eb6_332975.png "SOP2群.png")
1群:167643071(满)、2群:167643071(满)、3群:565894770

@ -1,34 +1,36 @@
* [首页](/?t=1595931646391)
* [首页](/?t=1616211903021)
* 开发文档
* [快速体验](files/10010_快速体验.md?t=1595931646394)
* [项目接入到SOP](files/10011_项目接入到SOP.md?t=1595931646412)
* [新增接口](files/10020_新增接口.md?t=1595931646412)
* [开发流程](files/10021_开发流程.md?t=1595931646412)
* [业务参数校验](files/10030_业务参数校验.md?t=1595931646412)
* [错误处理](files/10040_错误处理.md?t=1595931646412)
* [编写文档](files/10041_编写文档.md?t=1595931646412)
* [接口交互详解](files/10050_接口交互详解.md?t=1595931646412)
* [使用签名校验工具](files/10080_使用签名校验工具.md?t=1595931646413)
* [ISV管理](files/10085_ISV管理.md?t=1595931646413)
* [自定义返回结果](files/10087_自定义返回结果.md?t=1595931646413)
* [自定义过滤器](files/10088_自定义过滤器.md?t=1595931646413)
* [自定义校验token](files/10089_自定义校验token.md?t=1595931646413)
* [网关拦截器](files/10090_网关拦截器.md?t=1595931646413)
* [路由授权](files/10090_路由授权.md?t=1595931646413)
* [接口限流](files/10092_接口限流.md?t=1595931646413)
* [路由监控](files/10093_路由监控.md?t=1595931646413)
* [SDK开发](files/10095_SDK开发.md?t=1595931646413)
* [应用授权](files/10097_应用授权.md?t=1595931646414)
* [提供restful接口](files/10100_提供restful接口.md?t=1595931646414)
* [文件上传](files/10104_文件上传.md?t=1595931646414)
* [配置Sleuth链路追踪](files/10109_配置Sleuth链路追踪.md?t=1595931646414)
* [预发布灰度发布](files/10110_预发布灰度发布.md?t=1595931646414)
* [动态修改请求参数](files/10111_动态修改请求参数.md?t=1595931646414)
* [使用eureka](files/10112_使用eureka.md?t=1595931646414)
* [快速体验](files/10010_快速体验.md?t=1616211903027)
* [项目接入到SOP](files/10011_项目接入到SOP.md?t=1616211903048)
* [新增接口](files/10020_新增接口.md?t=1616211903048)
* [开发流程](files/10021_开发流程.md?t=1616211903048)
* [用户注册](files/10022_用户注册.md?t=1616211903049)
* [业务参数校验](files/10030_业务参数校验.md?t=1616211903049)
* [错误处理](files/10040_错误处理.md?t=1616211903049)
* [编写文档](files/10041_编写文档.md?t=1616211903049)
* [接口交互详解](files/10050_接口交互详解.md?t=1616211903049)
* [使用签名校验工具](files/10080_使用签名校验工具.md?t=1616211903049)
* [ISV管理](files/10085_ISV管理.md?t=1616211903050)
* [自定义返回结果](files/10087_自定义返回结果.md?t=1616211903050)
* [自定义过滤器](files/10088_自定义过滤器.md?t=1616211903050)
* [自定义校验token](files/10089_自定义校验token.md?t=1616211903050)
* [网关拦截器](files/10090_网关拦截器.md?t=1616211903050)
* [路由授权](files/10091_路由授权.md?t=1616211903050)
* [接口限流](files/10092_接口限流.md?t=1616211903051)
* [路由监控](files/10093_路由监控.md?t=1616211903051)
* [SDK开发](files/10095_SDK开发.md?t=1616211903051)
* [应用授权](files/10097_应用授权.md?t=1616211903051)
* [提供restful接口](files/10100_提供restful接口.md?t=1616211903051)
* [文件上传](files/10104_文件上传.md?t=1616211903051)
* [配置Sleuth链路追踪](files/10109_配置Sleuth链路追踪.md?t=1616211903052)
* [预发布灰度发布](files/10110_预发布灰度发布.md?t=1616211903052)
* [动态修改请求参数](files/10111_动态修改请求参数.md?t=1616211903052)
* [使用eureka](files/10112_使用eureka.md?t=1616211903052)
* [超时设置](files/10113_超时设置.md?t=1616211903052)
* 原理分析
* [网关性能测试](files/90001_网关性能测试.md?t=1595931646414)
* [原理分析之如何存储路由](files/90011_原理分析之如何存储路由.md?t=1595931646415)
* [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1595931646415)
* [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1595931646415)
* [原理分析之预发布灰度发布](files/90014_原理分析之预发布灰度发布.md?t=1595931646415)
* [常见问题](files/90100_常见问题.md?t=1595931646415)
* [网关性能测试](files/90001_网关性能测试.md?t=1616211903052)
* [原理分析之如何存储路由](files/90011_原理分析之如何存储路由.md?t=1616211903052)
* [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1616211903052)
* [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1616211903052)
* [原理分析之预发布灰度发布](files/90014_原理分析之预发布灰度发布.md?t=1616211903052)
* [常见问题](files/90100_常见问题.md?t=1616211903053)

@ -38,11 +38,15 @@
登录账号:admin/123456
## 启动文档中心
## 启动门户网站
见:`sop-website/sop-portal/README.md`
## 启动文档中心/ISV后台
文档中心代码在sop-website工程中
- 确保注册中心、网关、微服务正常启动
- 修改sop-website下的application-dev.properties相关配置
- 修改sop-website-server下的application-dev.properties相关配置
- 运行WebsiteServerApplication.java
- 访问http://localhost:8083

@ -5,15 +5,13 @@
- pom.xml添加版本配置
```xml
<properties>
<!-- springboot 版本-->
<spring-boot.version>2.2.5.RELEASE</spring-boot.version>
<spring-boot.version>2.3.2.RELEASE</spring-boot.version>
<!-- spring cloud 版本 -->
<spring-cloud.version>Hoxton.SR3</spring-cloud.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
<!-- spring cloud alibaba 版本 -->
<!-- 具体版本对应关系见:https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E -->
<spring-cloud-alibaba.version>2.2.1.RELEASE</spring-cloud-alibaba.version>
</properties>
<spring-cloud-alibaba.version>2.2.5.RELEASE</spring-cloud-alibaba.version>
```
- pom.xml添加`<dependencyManagement>`控制版本
@ -95,6 +93,8 @@ public class OpenServiceConfig extends AlipayServiceConfiguration {
php应用提供的接口需要返回如下json内容:
假设请求的接口为:`http://open.xxx.com/get_routes`
```json
{
"serviceId": "goods-service",
@ -159,7 +159,7 @@ instance.setServiceName("goods-service");
instance.setIp("192.168.0.11");
instance.setPort(8080);
// 在nacos的metadata中指定接口路径
instance.getMetadata().put("sop.routes.path", "/goods/list_goods");
instance.getMetadata().put("sop.routes.path", "http://open.xxx.com/get_routes");
namingService.registerInstance(serviceId, instance);
```

@ -44,6 +44,8 @@ public StoryResult getStory() {
其中biz_content部分是我们想要的,在方法上申明一个对象,对应biz_content中的内容即可完成参数绑定,并且对参数进行JSR-303校验。
**注意:接口方法必须有且只有一个对象参数,如果申明多个会出现参数绑定失败**
```java
@Open("goods.add")
@RequestMapping("/goods/add")

@ -9,12 +9,12 @@
- 你的项目接入到SOP,参考[项目接入到SOP](10011_项目接入到SOP.md),在微服务端开发接口,编写swagger注解文档
- 接口开发完成,启动微服务,注册到注册中心。
- 【可选】编写sdk
- 【可选】编写sdk,在`SDK管理`页发布SDK,提供下载地址
## ISV对接流程
假设接口开发完毕,开始对接ISV,大致步骤如下:
1. 在admin新建一个ISV,然后`导出秘钥`,文件给到对方
2. 告知对方接口文档,开始接口调用。
3. 如果有SDK,把SDK给到对方,让其直接调用
1. ISV访问门户网站,注册账号并登陆
2. 上传应用公钥
3. 如果有SDK,下载SDK,进行接口调用

@ -0,0 +1,11 @@
# 用户(ISV)注册
新增ISV有两种方式
- 方式1
启动sop-admin,在admin后台`ISV管理添加`
- 方式2
启动`sop-website-server`,用户访问自主注册

@ -44,7 +44,7 @@ return method.replace('.', '_') + "_response";
```java
@Configuration
public class ZuulConfig extends AlipayZuulConfiguration {
public class MyConfig {
static {
...
@ -84,23 +84,18 @@ public class ZuulConfig extends AlipayZuulConfiguration {
如果想要对微服务结果做更深一步处理,步骤如下:
1. 新增一个类,继承`ZuulResultExecutor.java`,并重写`public String merge(T exchange, JSONObject responseData)`方法
方法merge参数说明如下:
exchange:RequestContext对象
responseData:微服务端返回的结果
方法返回最终结果
1. 新增一个类,继承`GatewayResultExecutor.java`,并重写`String mergeResult(T request, String serviceResult)`方法
2. 配置自定义类
```java
public class SopGatewayApplication {
@Configuration
public class MyConfig {
public static void main(String[] args) {
ApiConfig.getInstance().setZuulResultExecutor(new MyzuulResultExecutor());
SpringApplication.run(SopGatewayApplication.class, args);
static {
...
ApiConfig.getInstance().setGatewayResultExecutor(new MyGatewayResultExecutor());
...
}
}
@ -109,17 +104,7 @@ public class SopGatewayApplication {
## 不合并结果
如果不希望对结果进行合并,可设置`ApiConfig.getInstance().setMergeResult(false);`
如果不希望对结果进行合并,可在application.properties中设置`sop.api-config.merge-result=false`
```java
public class SopGatewayApplication {
public static void main(String[] args) {
ApiConfig.getInstance().setMergeResult(false);
SpringApplication.run(SopGatewayApplication.class, args);
}
}
```
这样,网关最终返回结果即为微服务端的返回结果。

@ -1,103 +1,39 @@
# 自定义过滤器
## zuul过滤器
zuul过滤器列表如下:
| 类型 | 顺序 | 过滤器 | 功能 |
| ----- | ---- | ----------------------- | ---------------------------- |
| pre | -1000 | PreValidateFilter (SOP自带) | 校验签名 |
| pre | -998 | PreLimitFilter (SOP自带) | 限流拦截器 |
| pre | -3 | ServletDetectionFilter | 标记处理 Servlet 的类型 |
| pre | -2 | Servlet30WrapperFilter | 包装 HttpServletRequest 请求 |
| pre | -1 | FormBodyWrapperFilter | 包装请求体 Servlet30WrapperFilter |
| pre | 1 | DebugFilter | 标记调试标志 |
| pre | 5 | PreDecorationFilter | 决定路由转发过滤器 |
| route | 10 | RibbonRoutingFilter | serviceId 请求转发 |
| route | 100 | SimpleHostRoutingFilter | url 请求转发 |
| route | 500 | SendForwardFilter | forward 请求转发 |
| post | 0 | SendErrorFilter | 处理有错误的请求响应 |
| post | 1000 | SendResponseFilter | 处理正常的请求响应 |
顺序值小的优先执行,`-3`之前是sop自带的过滤器,`-3`开始是zuul自带的过滤器。
创建自定义过滤器可以从`-500`开始(-1000 ~ -501留给SOP)。下面是一个自定义过虑器的例子:
演示在网关追加一个header
```java
public class PreXXXFilter extends BaseZuulFilter {
@Override
protected FilterType getFilterType() {
return FilterType.PRE;
}
@Override
protected int getFilterOrder() {
return -500;
}
@Override
protected Object doRun(RequestContext requestContext) throws ZuulException {
HttpServletRequest request = requestContext.getRequest();
ApiParam apiParam = ZuulContext.getApiParam();
String appKey = apiParam.fetchAppKey();
// ...业务处理
// 固定返回null
return null;
}
}
```
过滤器编写完毕后,在Config中使用:
```java
@Configuration
public class ZuulConfig extends AlipayZuulConfiguration {
...
@Bean
PreXXXFilter preXXXFilter() {
return new PreXXXFilter();
}
...
}
```
## spring cloud gateway
跟zuul同理
```java
public class XXXFilter implements GlobalFilter, Ordered {
public class CustomFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ApiParam apiParam = (ApiParam)exchange.getAttribute(SopConstants.CACHE_API_PARAM);
String appKey = apiParam.fetchAppKey();
// ...业务处理
...
return chain.filter(exchange);
// 演示在网关追加header
ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange);
String token = apiParam.fetchAccessToken();
ServerWebExchange serverWebExchange = ServerWebExchangeUtil.addHeaders(exchange, httpHeaders -> {
httpHeaders.add("token", token);
});
return chain.filter(serverWebExchange);
}
@Override
public int getOrder() {
return -500;
// 自定义过滤器可以从0开始
return 0;
}
}
```
使用过滤器:
使用过滤器,在sop-gateway中找到MyConfig,添加:
```java
@Configuration
public class GatewayConfig extends AlipayGatewayConfiguration {
public class MyConfig {
...
@Bean
XXXFilter xxxFilter() {
return new XXXFilter();
CustomFilter customFilter() {
return new CustomFilter();
}
...
}

@ -12,9 +12,9 @@
默认情况下,接口访问时公开的,ISV都能访问。如果要设置某个接口访问权限,在`@Open`注解中指定permission=true。
如:`@Open(value = "permission.story.get", permission = true)`。这样该接口是需要经过授权给ISV才能访问的。
重启服务后,登录admin,服务管理-路由列表界面中,操作操作列会出现一个授权按钮,点击出现授权窗口,勾选对应的角色即可完成授权。
重启服务后,登录admin,服务管理-路由列表界面中,`访问权限`列会出现一个点击授权,点击出现授权窗口,勾选对应的角色即可完成授权。
- 点击`授权`按钮,进行角色授权
- `点击授权`,进行角色授权
![admin预览](images/10090_1.png "10090_1.png")

@ -1,15 +1,6 @@
# 路由监控
路由监控功能可以查看各个接口的调用情况,监控信息收集采用拦截器实现。
- 统计各个接口的调用次数、耗时等信息
- 错误日志统一在网关负责收集
- 只收集未知类型的错误日志,开发人员主动throw的异常不收集
- 收集的日志存放在内存中,重启网关日志会消失
## 永久保存日志
默认收集的日志存放在内存中,重启网关日志会消失(见:`com.gitee.sop.gatewaycommon.monitor.MonitorManager.java`)。如果要永久保存日志内容,需要自己修改`MonitorManager`
路由监控功能可以查看各个接口的调用情况,监控信息收集采用拦截器实现,前往【服务管理】-【路由监控】查看
- 后台预览
@ -17,6 +8,20 @@
![监控日志](images/10093_2.png "10093_2.png")
- 注意事项
处理完错误后,请及时`标记解决`,一个接口默认保存50条错误信息,采用LRU机制,淘汰老的。标记解决后则会空出一个位置存放新的错误信息。
重复的错误只会存放一条记录,然后累加错误次数,重复错误定义如下:
`instanceId + routeId + errorMsg`,即一个实例 + 路由id + 错误信息确定一个错误
可在网关设置`sop.monitor.error-count-capacity=50`参数调整错误容量
考虑到数据库压力,网关收到错误信息后并不会立即保存到数据库,而是先保存到内容中,然后定时保存到时间,默认时间隔为30秒
可通过`sop.monitor.flush-period-seconds=30`调整间隔时间。
相关类:
- com.gitee.sop.gatewaycommon.interceptor.MonitorRouteInterceptor
- com.gitee.sop.gateway.interceptor.MonitorRouteInterceptor

@ -333,3 +333,11 @@ private static void TestCommon()
}
}
```
## 发布SDK
将编写好的SDK打包后上传到云服务器,如百度网盘
前往sop-admin,点击`SDK管理`菜单,点击`发布SDK`,填写SDK语言、版本、下载地址(网盘地址)、调用示例
保存后,ISV端会看到发布的SDK

@ -40,6 +40,14 @@ pre.domain=openpre.domain.com
## 使用灰度发布
- 灰度接口定义
接口名相同,版本号不同。比如接口`user.get`,version:1.0
新建一个接口`user.get`,version:2.0
那么这个2.0接口就是灰度接口
灰度发布可允许指定的用户访问灰度服务器,其它用户还是走正常流程。
登录SOP-Admin,前往`服务列表`。

@ -0,0 +1,14 @@
# 超时设置
当微服务处理业务逻辑时间过长,网关会报超时错误,默认等待时间是5秒。
可在网关指定`spring.cloud.gateway.httpclient.response-timeout`参数设置超时时间,单位毫秒
```properties
# 设置响应超时10秒
spring.cloud.gateway.httpclient.response-timeout=10000
```
更多配置参见:`org.springframework.cloud.gateway.config.HttpClientProperties`
测试用例参见:`com.gitee.sop.test.TimeoutTest`

@ -133,8 +133,8 @@ Transfer/sec: 226.50KB
`restart.sh`
```bash
echo "Stopping sop-gateway-4.0.3-SNAPSHOT.jar"
pid=`ps -ef | grep sop-gateway-4.0.3-SNAPSHOT.jar | grep -v grep | awk '{print $2}'`
echo "Stopping sop-gateway-4.2.4-SNAPSHOT.jar"
pid=`ps -ef | grep sop-gateway-4.2.4-SNAPSHOT.jar | grep -v grep | awk '{print $2}'`
if [ -n "$pid" ]
then
echo "kill -9 的id:" $pid
@ -142,7 +142,7 @@ then
fi
nohup java -jar -verbose:gc -XX:+PrintGCDetails -XX:+PrintHeapAtGC -Xloggc:gc.log \
-XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m -Xms1024m -Xmx1024m -Xmn256m -Xss256k -XX:SurvivorRatio=8\
-XX:+UseConcMarkSweepGC sop-gateway-4.0.3-SNAPSHOT.jar\
-XX:+UseConcMarkSweepGC sop-gateway-4.2.4-SNAPSHOT.jar\
--spring.profiles.active=dev --server.port=8081 &
tail -f nohup.out

@ -1,77 +1,33 @@
# 原理分析之如何路由
## zuul如何路由
Spring Cloud Gateway通过一系列的Filter来进行数据的传输,如下图所示:
SOP网关默认使用zuul,当然也默认使用了zuul提供的路由功能。zuul默认使用过滤器来实现路由转发,
我们看下zuul中自带的过滤器:
![流程图](images/90012_1.png)
| 类型 | 顺序 | 过滤器 | 功能 |
| ----- | ---- | ----------------------- | ---------------------------- |
| pre | -3 | ServletDetectionFilter | 标记处理 Servlet 的类型 |
| pre | -2 | Servlet30WrapperFilter | 包装 HttpServletRequest 请求 |
| pre | -1 | FormBodyWrapperFilter | 包装请求体 |
| pre | 1 | DebugFilter | 标记调试标志 |
| pre | 5 | PreDecorationFilter | 决定路由转发过滤器 |
| route | 10 | RibbonRoutingFilter | serviceId 请求转发 |
| route | 100 | SimpleHostRoutingFilter | url 请求转发 |
| route | 500 | SendForwardFilter | forward 请求转发 |
| post | 0 | SendErrorFilter | 处理有错误的请求响应 |
| post | 1000 | SendResponseFilter | 处理正常的请求响应 |
上图就是zuul提供的默认过滤器,可在org.springframework.cloud.netflix.zuul.filters下查看。
zuul的过滤器顺序值小的优先执行,其中的`PreDecorationFilter`是我们重点关注的类,由它来决定路由转发去向。
打开PreDecorationFilter类,看到类注释有一句话:`that determines where and how to route based on the supplied`
翻译过来就是说,决定从哪里获取路由,然后怎样去路由。
PreDecorationFilter类的核心方法是run()方法。找到run方法中这一句代码:
SOP网关在此基础上新增了几个Filter用来处理自己的逻辑,如:前置校验、结果返回。
`Route route = this.routeLocator.getMatchingRoute(requestURI);`
这句代码很重要,表示路由从哪里获取,如果我们能够重写getMatchingRoute方法那就可以返回自己定义的路由了。
接下来找到RouteLocator类的定义,发现是通过构造方法传进来的,那么我们就去找使用构造方法的类。(IDEA下右键构造方法--Find Usage)
在org.springframework.cloud.netflix.zuul.ZuulProxyAutoConfiguration类中找到了定义
```java
// pre filters
@Bean
@ConditionalOnMissingBean(PreDecorationFilter.class)
public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator, ProxyRequestHelper proxyRequestHelper) {
return new PreDecorationFilter(routeLocator, this.server.getServlet().getContextPath(), this.zuulProperties,
proxyRequestHelper);
}
```
| 过滤器 | 类型 | Order | 功能 |
| ----- | ---- | ----------------------- | ---------------------------- |
|IndexFilter| `自定义` | -2147483648 | 入口过滤器,获取参数、签名校验 |
|ParameterFormatterFilter | `自定义` | -2147482647 | 格式化参数 |
|LimitFilter|`自定义`|-2147482447|限流|
|ForwardPathFilter|系统自带|0 |设置转发的path|
|RouteToRequestUrlFilter|系统自带|10000|设置转发host|
|SopLoadBalancerClientFilter|`自定义`|10100|LoadBalance获取转发实例|
|NettyRoutingFilter|系统自带|2147483647|获取httpclient发送请求|
|ForwardRoutingFilter|系统自带|2147483647|请求分发|
|GatewayModifyResponseGatewayFilter|`自定义`|-2|处理响应结果|
方法默认注入了RouteLocator类,默认注入的实现是CompositeRouteLocator类(通过打断点可以查看)
一个完整的请求会自上而下经过这些Filter,下面讲解如何动态设置路由。
同时方法上用了`@ConditionalOnMissingBean`注解,表示如果其它地方没有声明,则默认使用这个。
## 动态设置路由
因此我们可以自己声明一个PreDecorationFilter,然后注入自定义的RouteLocator就行了
网关启动后会从注册中心拉取微服务实例,然后请求微服务提供的一个接口(`/sop/routes`),获取开放接口信息(被`@Open`注解的接口)。
SOP自定义的RouteLocator为:`com.gitee.sop.gatewaycommon.zuul.route.SopRouteLocator`,可自行前往查看。
监听处理类在:`com.gitee.sop.bridge.route.NacosRegistryListener`
然后再我们的Config中定义:
获取到路由信息后,将路由信息缓存到本地,并保存到数据库,代码在:`com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteCache.load`
```java
/**
* 选取路由
* @param zuulRouteRepository
* @param proxyRequestHelper
* @return
*/
@Bean
public PreDecorationFilter preDecorationFilter(ZuulRouteRepository zuulRouteRepository, ProxyRequestHelper proxyRequestHelper) {
// 自定义路由
RouteLocator routeLocator = new SopRouteLocator(zuulRouteRepository);
return new PreDecorationFilter(routeLocator,
this.server.getServlet().getContextPath(),
this.zuulProperties,
proxyRequestHelper);
}
```
然后动态设置Gateway路由,代码在:`com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteRepository.refresh`
到此,我们只需要实现RouteLocator接口,就能使用zuul默认的路由功能,非常方便
当有微服务重新启动时,网关会监听到微服务实例有变更,会重复上述步骤,确保网关存有最新的路由。

@ -1,5 +1,28 @@
# 常见问题
## Connection has been closed BEFORE send operation
压测时出现`Connection has been closed BEFORE send operation`,`java.nio.channels.ClosedChannelException`之类的错误
- 解决办法
尝试调大内存分配1G或2G,`-Xms1g -Xmx1g`
## spring cloud gateway [DataBufferLimitException: Exceeded limit on max bytes to buffer : 262144]
如果POST请求body内容太大可能会报这个错误
- 解决版办法
网关指定配置`spring.codec.max-in-memory-size=xx`,xx默认是262144,即256K(262144=256*1024)
## primordials is not defined
node版本太高导致与gulp版本不兼容,解决方法:node退回11版本。参考https://blog.csdn.net/zxxzxx23/article/details/103000393
## Nacos指定group
可在配置文件中添加:`spring.cloud.nacos.discovery.group=xxx`指定group,不加默认是:DEFAULT_GROUP
## 在SpringCloudGateway中获取请求参数

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.gitee.sop</groupId>
<artifactId>doc</artifactId>
<version>4.0.3-SNAPSHOT</version>
<version>4.3.2-SNAPSHOT</version>
<properties>
<!-- Generic properties -->

@ -5,14 +5,13 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<version>2.3.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-parent</artifactId>
<version>4.0.3-SNAPSHOT</version>
<version>4.3.2-SNAPSHOT</version>
<packaging>pom</packaging>
<description>一个开放平台解决方案项目,基于Spring Cloud实现,目标是能够让用户快速得搭建起自己的开放平台</description>
@ -36,17 +35,16 @@
<maven.compiler.target>1.8</maven.compiler.target>
<!-- springboot 版本-->
<spring-boot.version>2.3.4.RELEASE</spring-boot.version>
<spring-boot.version>2.3.2.RELEASE</spring-boot.version>
<!-- spring cloud 版本 -->
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
<!-- spring cloud alibaba 版本 -->
<!-- 具体版本对应关系见:https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E -->
<spring-cloud-alibaba.version>2.2.3.RELEASE</spring-cloud-alibaba.version>
<spring-cloud-alibaba.version>2.2.5.RELEASE</spring-cloud-alibaba.version>
<!-- Logging -->
<logback.version>1.2.3</logback.version>
<!-- <slf4j.version>1.7.5</slf4j.version>-->
<!-- Test -->
<junit.version>4.11</junit.version>
@ -58,16 +56,16 @@
<commons-lang3.version>3.8.1</commons-lang3.version>
<commons-codec.version>1.11</commons-codec.version>
<commons-logging.version>1.2</commons-logging.version>
<!-- <validation-api.version>2.0.1.Final</validation-api.version>-->
<!-- <hibernate-validator.version>6.0.13.Final</hibernate-validator.version>-->
<validation-api.version>2.0.1.Final</validation-api.version>
<hibernate-validator.version>6.0.13.Final</hibernate-validator.version>
<fastmybatis.version>1.9.1</fastmybatis.version>
<spring-data-redis.version>2.3.0.RELEASE</spring-data-redis.version>
<guava.version>29.0-jre</guava.version>
<swagger.version>1.5.21</swagger.version>
<springfox-spring-web.version>2.9.2</springfox-spring-web.version>
<springfox-swagger2.version>2.9.2</springfox-swagger2.version>
<easyopen.version>1.16.9</easyopen.version>
<asm.version>6.2</asm.version>
<pagehelper.version>5.2.0</pagehelper.version>
</properties>
<dependencyManagement>
@ -135,6 +133,12 @@
<version>${easyopen.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.14.7</version>
</dependency>
<dependency>
<groupId>net.oschina.durcframework</groupId>
<artifactId>easyopen-spring-boot-starter</artifactId>
@ -158,12 +162,6 @@
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>${spring-data-redis.version}</version>
</dependency>
<!-- commons -->
<dependency>
<groupId>org.apache.commons</groupId>
@ -202,6 +200,12 @@
<version>${asm.version}</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>${pagehelper.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
@ -217,9 +221,9 @@
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<id>aliyun</id>
<name>aliyun</name>
<url>https://maven.aliyun.com/repository/public</url>
</repository>
</repositories>

@ -6,13 +6,12 @@
<parent>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-parent</artifactId>
<version>4.0.3-SNAPSHOT</version>
<version>4.3.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sop-admin</artifactId>
<version>4.0.3-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>

@ -5,13 +5,13 @@
<parent>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-parent</artifactId>
<version>4.0.3-SNAPSHOT</version>
<version>4.3.2-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sop-admin-server</artifactId>
<version>4.0.3-SNAPSHOT</version>
<version>4.3.2-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
@ -21,11 +21,6 @@
</properties>
<dependencies>
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-bridge-nacos</artifactId>
<version>4.0.3-SNAPSHOT</version>
</dependency>
<!-- easyopen starter -->
<dependency>
@ -64,6 +59,11 @@
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
</dependency>
<!-- optional-->
<dependency>
<groupId>com.alibaba.nacos</groupId>
@ -76,6 +76,14 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>

@ -29,14 +29,6 @@ public class IsvKeysFormUpdate {
@Max(value = 2, message = "秘钥格式错误")
private Byte keyFormat;
/** 开发者生成的公钥, 数据库字段:public_key_isv */
@ApiDocField(description = "开发者生成的公钥")
private String publicKeyIsv;
/** 开发者生成的私钥(交给开发者), 数据库字段:private_key_isv */
@ApiDocField(description = "开发者生成的私钥")
private String privateKeyIsv;
/** 平台生成的公钥(交给开发者), 数据库字段:public_key_platform */
@ApiDocField(description = "平台生成的公钥")
private String publicKeyPlatform;

@ -29,6 +29,8 @@ public class IsvInfoVO {
@ApiDocField(description = "备注")
private String remark;
private Long userId;
/** 数据库字段:gmt_create */
@ApiDocField(description = "添加时间")
private Date gmtCreate;

@ -29,8 +29,11 @@ import java.util.stream.Collectors;
/**
* @author tanghc
* @deprecated use com.gitee.sop.adminserver.api.service.MonitorNewApi
* @see MonitorNewApi
*/
@ApiService
@Deprecated
//@ApiService
@ApiDoc("服务管理-监控")
@Slf4j
public class MonitorApi {

@ -0,0 +1,80 @@
package com.gitee.sop.adminserver.api.service;
import com.gitee.easyopen.annotation.Api;
import com.gitee.easyopen.annotation.ApiService;
import com.gitee.fastmybatis.core.query.Query;
import com.gitee.fastmybatis.core.query.Sort;
import com.gitee.fastmybatis.core.support.PageEasyui;
import com.gitee.fastmybatis.core.util.MapperUtil;
import com.gitee.sop.adminserver.api.service.param.InstanceMonitorSearchParam;
import com.gitee.sop.adminserver.api.service.param.MonitorErrorMsgParam;
import com.gitee.sop.adminserver.api.service.param.MonitorInfoErrorSolveParam;
import com.gitee.sop.adminserver.api.service.param.MonitorSearchParam;
import com.gitee.sop.adminserver.bean.RouteErrorCount;
import com.gitee.sop.adminserver.entity.MonitorInfoError;
import com.gitee.sop.adminserver.entity.MonitorSummary;
import com.gitee.sop.adminserver.mapper.MonitorInfoErrorMapper;
import com.gitee.sop.adminserver.mapper.MonitorInfoMapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 路由监控
* @author tanghc
*/
@ApiService
public class MonitorNewApi {
@Autowired
private MonitorInfoMapper monitorInfoMapper;
@Autowired
private MonitorInfoErrorMapper monitorInfoErrorMapper;
@Api(name = "monitornew.data.page")
PageInfo<Object> listMonitor(MonitorSearchParam param) {
Query query = Query.build(param);
query.orderby("errorCount", Sort.DESC)
.orderby("avgTime", Sort.DESC);
return PageHelper.offsetPage(query.getStart(), query.getLimit())
.doSelectPage(() -> monitorInfoMapper.listMonitorSummary(query))
.toPageInfo();
}
@Api(name = "monitornew.routeid.data.get")
List<MonitorSummary> listInstanceMonitor(InstanceMonitorSearchParam param) {
Query query = Query.build(param);
query.orderby("errorCount", Sort.DESC)
.orderby("avgTime", Sort.DESC);
return monitorInfoMapper.listInstanceMonitorInfo(query);
}
private Map<String, Integer> getRouteErrorCount() {
List<RouteErrorCount> routeErrorCounts = monitorInfoErrorMapper.listRouteErrorCount();
return routeErrorCounts.stream()
.collect(Collectors.toMap(RouteErrorCount::getRouteId, RouteErrorCount::getCount));
}
@Api(name = "monitornew.error.page")
PageEasyui<MonitorInfoError> listError(MonitorErrorMsgParam param) {
Query query = param.toQuery()
.orderby("gmt_modified", Sort.DESC);
return MapperUtil.queryForEasyuiDatagrid(monitorInfoErrorMapper, query);
}
@Api(name = "monitornew.error.solve")
void solve(MonitorInfoErrorSolveParam param) {
Query query = Query.build(param);
Map<String, Object> set = new HashMap<>(4);
set.put("is_deleted", 1);
set.put("count", 0);
monitorInfoErrorMapper.updateByMap(set, query);
}
}

@ -0,0 +1,54 @@
package com.gitee.sop.adminserver.api.service;
import com.gitee.easyopen.annotation.Api;
import com.gitee.easyopen.annotation.ApiService;
import com.gitee.sop.adminserver.api.system.param.IspPageParam;
import com.gitee.sop.adminserver.api.system.param.IspResourceParam;
import com.gitee.sop.adminserver.common.CopyUtil;
import com.gitee.sop.adminserver.entity.IspResource;
import com.gitee.sop.adminserver.mapper.IspResourceMapper;
import org.springframework.beans.factory.annotation.Autowired;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* @author tanghc
*/
@ApiService
public class ResourceApi {
public static final byte RESOURCE_TYPE_SDK = (byte) 0;
@Autowired
private IspResourceMapper ispResourceMapper;
@Api(name = "isp.sdk.list")
List<IspResource> list(IspPageParam param) {
return ispResourceMapper.list(param.toQuery());
}
@Api(name = "isp.sdk.add")
void addSdk(IspResourceParam param) {
IspResource ispResource = CopyUtil.copyBean(param, IspResource::new);
ispResource.setType(RESOURCE_TYPE_SDK);
ispResourceMapper.saveIgnoreNull(ispResource);
}
@Api(name = "isp.sdk.update")
void updateSdk(IspResourceParam param) {
IspResource resource = ispResourceMapper.getById(param.getId());
CopyUtil.copyProperties(param, resource);
ispResourceMapper.update(resource);
}
@Api(name = "isp.sdk.delete")
void deleteSdk(@NotNull Long id) {
IspResource resource = ispResourceMapper.getById(id);
if (resource != null) {
ispResourceMapper.deleteById(id);
}
}
}

@ -0,0 +1,15 @@
package com.gitee.sop.adminserver.api.service.param;
import com.gitee.fastmybatis.core.query.annotation.Condition;
import lombok.Getter;
import lombok.Setter;
/**
* @author tanghc
*/
@Getter
@Setter
public class InstanceMonitorSearchParam {
@Condition(column = "t.route_id")
private String routeId;
}

@ -0,0 +1,21 @@
package com.gitee.sop.adminserver.api.service.param;
import com.gitee.fastmybatis.core.query.annotation.Condition;
import com.gitee.fastmybatis.core.query.param.PageParam;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
/**
* @author tanghc
*/
@Getter
@Setter
public class MonitorErrorMsgParam extends PageParam {
@NotBlank(message = "routeId不能为空")
private String routeId;
@Condition(ignoreEmptyString = true)
private String instanceId;
}

@ -0,0 +1,19 @@
package com.gitee.sop.adminserver.api.service.param;
import com.gitee.fastmybatis.core.query.annotation.Condition;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* @author tanghc
*/
@Data
public class MonitorInfoErrorSolveParam {
/** 错误id,md5(error_msg), 数据库字段:error_id */
@NotBlank
@Condition(index = 1)
private String errorId;
}

@ -0,0 +1,23 @@
package com.gitee.sop.adminserver.api.service.param;
import com.gitee.easyopen.doc.annotation.ApiDocField;
import com.gitee.fastmybatis.core.query.Operator;
import com.gitee.fastmybatis.core.query.annotation.Condition;
import com.gitee.fastmybatis.core.query.param.PageParam;
import lombok.Getter;
import lombok.Setter;
/**
* @author tanghc
*/
@Getter
@Setter
public class MonitorSearchParam extends PageParam {
@ApiDocField(description = "服务名serviceId")
@Condition(column = "service_id", operator = Operator.like, ignoreEmptyString = true)
private String serviceId;
@ApiDocField(description = "路由id")
@Condition(column = "route_id", operator = Operator.like, ignoreEmptyString = true)
private String routeId;
}

@ -0,0 +1,26 @@
package com.gitee.sop.adminserver.api.service.result;
import lombok.Data;
import java.util.Date;
/**
* @author tanghc
*/
@Data
public class MonitorInfoErrorMsgResult {
private String routeId;
private String errorId;
/** 错误信息, 数据库字段:error_msg */
private String errorMsg;
private Integer errorStatus;
private Integer count;
/** 数据库字段:gmt_modified */
private Date gmtModified;
}

@ -0,0 +1,9 @@
package com.gitee.sop.adminserver.api.system.param;
import com.gitee.fastmybatis.core.query.param.PageParam;
/**
* @author tanghc
*/
public class IspPageParam extends PageParam {
}

@ -0,0 +1,27 @@
package com.gitee.sop.adminserver.api.system.param;
import lombok.Data;
/**
* 表名isp_resource
* 备注ISP资源表
*
* @author tanghc
*/
@Data
public class IspResourceParam {
private Long id;
/** 资源名称, 数据库字段:name */
private String name;
private String version;
/** 资源内容(URL), 数据库字段:content */
private String content;
private String extContent;
}

@ -0,0 +1,27 @@
package com.gitee.sop.adminserver.api.system.result;
import lombok.Data;
/**
* 表名isp_resource
* 备注ISP资源表
*
* @author tanghc
*/
@Data
public class IspResourceResult {
private Long id;
/** 资源名称, 数据库字段:name */
private String name;
private String version;
/** 资源内容(URL), 数据库字段:content */
private String content;
private String extContent;
}

@ -0,0 +1,13 @@
package com.gitee.sop.adminserver.bean;
import lombok.Data;
/**
* @author tanghc
*/
@Data
public class RouteErrorCount {
private String routeId;
private Integer count;
}

@ -0,0 +1,75 @@
package com.gitee.sop.adminserver.common;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
* @author tanghc
*/
@Slf4j
public class CopyUtil {
public static void copyProperties(Object from, Object to) {
BeanUtils.copyProperties(from, to);
}
public static <T> T copyBean(Object from, Supplier<T> supplier) {
Objects.requireNonNull(from);
T to = supplier.get();
BeanUtils.copyProperties(from, to);
return to;
}
public static <T> T copyBeanNullable(Object from, Supplier<T> supplier) {
if (from == null) {
return supplier.get();
}
T to = supplier.get();
BeanUtils.copyProperties(from, to);
return to;
}
public static <T> T copyBean(Object from, Supplier<T> supplier, Consumer<T> after) {
if (from == null) {
return null;
}
T to = supplier.get();
BeanUtils.copyProperties(from, to);
after.accept(to);
return to;
}
public static <T> List<T> copyList(List<?> fromList, Supplier<T> toElement) {
if (fromList == null) {
return Collections.emptyList();
}
return fromList.stream()
.map(source -> {
T target = toElement.get();
BeanUtils.copyProperties(source, target);
return target;
})
.collect(Collectors.toList());
}
public static <T> List<T> copyList(List<?> fromList, Supplier<T> toElement, Consumer<T> after) {
if (fromList == null) {
return Collections.emptyList();
}
return fromList.stream()
.map(source -> {
T target = toElement.get();
BeanUtils.copyProperties(source, target);
after.accept(target);
return target;
})
.collect(Collectors.toList());
}
}

@ -0,0 +1,50 @@
package com.gitee.sop.adminserver.entity;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
/**
* 表名isp_resource
* 备注ISP资源表
*
* @author tanghc
*/
@Table(name = "isp_resource")
@Data
public class IspResource {
/** 数据库字段:id */
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/** 资源名称, 数据库字段:name */
private String name;
private String version;
/** 资源内容(URL), 数据库字段:content */
private String content;
private String extContent;
/** 资源类型:0:SDK链接, 数据库字段:type */
private Byte type;
/** 数据库字段:is_deleted */
@com.gitee.fastmybatis.core.annotation.LogicDelete
private Byte isDeleted;
/** 数据库字段:gmt_create */
private Date gmtCreate;
/** 数据库字段:gmt_modified */
private Date gmtModified;
}

@ -31,6 +31,9 @@ public class IsvInfo {
/** 1启用,2禁用, 数据库字段:status */
private Byte status;
/** user_account.id */
private Long userId;
/** 备注,数据库字段:remark */
private String remark;

@ -0,0 +1,66 @@
package com.gitee.sop.adminserver.entity;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
/**
* 表名monitor_info
* 备注接口监控信息
*
* @author tanghc
*/
@Table(name = "monitor_info")
@Data
public class MonitorInfo {
/** 数据库字段:id */
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/** 路由id, 数据库字段:route_id */
private String routeId;
/** 接口名, 数据库字段:name */
private String name;
/** 版本号, 数据库字段:version */
private String version;
/** 数据库字段:service_id */
private String serviceId;
/** 数据库字段:instance_id */
private String instanceId;
/** 请求耗时最长时间, 数据库字段:max_time */
private Integer maxTime;
/** 请求耗时最小时间, 数据库字段:min_time */
private Integer minTime;
/** 总时长,毫秒, 数据库字段:total_time */
private Long totalTime;
/** 总调用次数, 数据库字段:total_request_count */
private Long totalRequestCount;
/** 成功次数, 数据库字段:success_count */
private Long successCount;
/** 失败次数(业务主动抛出的异常算作成功,如参数校验,未知的错误算失败), 数据库字段:error_count */
private Long errorCount;
/** 数据库字段:gmt_create */
private Date gmtCreate;
/** 数据库字段:gmt_modified */
private Date gmtModified;
}

@ -0,0 +1,54 @@
package com.gitee.sop.adminserver.entity;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
/**
* 表名monitor_info_error
*
* @author tanghc
*/
@Table(name = "monitor_info_error")
@Data
public class MonitorInfoError {
/** 数据库字段:id */
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/** 错误id,md5Hex(instanceId + routeId + errorMsg), 数据库字段:error_id */
private String errorId;
/** 实例id, 数据库字段:instance_id */
private String instanceId;
/** 数据库字段:route_id */
private String routeId;
/** 数据库字段:error_msg */
private String errorMsg;
/** http status,非200错误, 数据库字段:error_status */
private Integer errorStatus;
/** 错误次数, 数据库字段:count */
private Integer count;
/** 数据库字段:is_deleted */
@com.gitee.fastmybatis.core.annotation.LogicDelete
private Byte isDeleted;
/** 数据库字段:gmt_create */
private Date gmtCreate;
/** 数据库字段:gmt_modified */
private Date gmtModified;
}

@ -0,0 +1,61 @@
package com.gitee.sop.adminserver.entity;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author tanghc
*/
@Data
public class MonitorSummary {
private static final AtomicInteger i = new AtomicInteger();
private Integer id = i.incrementAndGet();
private String routeId;
private String name;
private String version;
/** 数据库字段:service_id */
private String serviceId;
/** 数据库字段:instance_id */
private String instanceId;
/** 请求耗时最长时间, 数据库字段:max_time */
private Integer maxTime;
/** 请求耗时最小时间, 数据库字段:min_time */
private Integer minTime;
/** 总时长,毫秒, 数据库字段:total_time */
private Long totalTime;
/** 总调用次数, 数据库字段:total_request_count */
private Long totalRequestCount;
/** 成功次数, 数据库字段:success_count */
private Long successCount;
/** 失败次数(业务主动抛出的异常算作成功,如参数校验,未知的错误算失败), 数据库字段:error_count */
private Long errorCount;
/** 未解决的错误数量 */
private Long unsolvedErrorCount;
private Float avgTime;
private Boolean hasChildren;
}

@ -0,0 +1,11 @@
package com.gitee.sop.adminserver.mapper;
import com.gitee.fastmybatis.core.mapper.CrudMapper;
import com.gitee.sop.adminserver.entity.IspResource;
/**
* @author tanghc
*/
public interface IspResourceMapper extends CrudMapper<IspResource, Long> {
}

@ -0,0 +1,22 @@
package com.gitee.sop.adminserver.mapper;
import com.gitee.fastmybatis.core.mapper.CrudMapper;
import com.gitee.sop.adminserver.bean.RouteErrorCount;
import com.gitee.sop.adminserver.entity.MonitorInfoError;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* @author tanghc
*/
public interface MonitorInfoErrorMapper extends CrudMapper<MonitorInfoError, Long> {
@Select("SELECT route_id routeId, count(*) `count` FROM monitor_info_error \n" +
"WHERE is_deleted=0 \n" +
"GROUP BY route_id")
List<RouteErrorCount> listRouteErrorCount();
}

@ -0,0 +1,19 @@
package com.gitee.sop.adminserver.mapper;
import com.gitee.fastmybatis.core.mapper.CrudMapper;
import com.gitee.fastmybatis.core.query.Query;
import com.gitee.sop.adminserver.entity.MonitorInfo;
import com.gitee.sop.adminserver.entity.MonitorSummary;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* @author tanghc
*/
public interface MonitorInfoMapper extends CrudMapper<MonitorInfo, Long> {
List<MonitorSummary> listMonitorSummary(@Param("query") Query query);
List<MonitorSummary> listInstanceMonitorInfo(@Param("query") Query query);
}

@ -37,9 +37,6 @@ public class ConfigPushService {
@Autowired
private ServerService serverService;
@Value("${gateway.host:}")
private String gatewayHost;
@Value("${sop.secret}")
private String secret;

@ -36,6 +36,9 @@ public class RegistryServiceNacosImpl implements RegistryService {
@Value("${nacos.discovery.namespace:${spring.cloud.nacos.discovery.namespace:}}")
private String nacosNamespace;
@Value("${nacos.discovery.group:${spring.cloud.nacos.discovery.group:DEFAULT_GROUP}}")
private String nacosGroup;
private NamingService namingService;
@PostConstruct
@ -53,13 +56,13 @@ public class RegistryServiceNacosImpl implements RegistryService {
@Override
public List<ServiceInfo> listAllService(int pageNo, int pageSize) throws Exception {
ListView<String> servicesOfServer = namingService.getServicesOfServer(pageNo, pageSize);
ListView<String> servicesOfServer = namingService.getServicesOfServer(pageNo, pageSize, nacosGroup);
List<String> serverList = servicesOfServer.getData();
List<ServiceInfo> serviceInfoList = new ArrayList<>();
for (String serviceName : serverList) {
ServiceInfo serviceInfo = new ServiceInfo();
serviceInfo.setServiceId(serviceName);
List<Instance> instanceList = namingService.getAllInstances(serviceName);
List<Instance> instanceList = namingService.getAllInstances(serviceName, nacosGroup);
if (CollectionUtils.isEmpty(instanceList)) {
serviceInfo.setInstances(Collections.emptyList());
} else {

@ -30,6 +30,7 @@ spring.datasource.hikari.pool-name=HikariCP
spring.datasource.hikari.max-lifetime=500000
# 固定不用改
mybatis.config-location=classpath:mybatis/mybatisConfig.xml
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
easyopen.show-doc=false
easyopen.ignore-validate=true

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 注意:文件名必须跟Dao类名字一致,因为是根据文件名做关联。 -->
<mapper namespace="com.gitee.sop.adminserver.mapper.MonitorInfoMapper">
<select id="listMonitorSummary" resultType="com.gitee.sop.adminserver.entity.MonitorSummary">
SELECT t.*, IFNULL(t2.unsolvedErrorCount, 0) unsolvedErrorCount
FROM (
SELECT
t.`service_id` serviceId,
CONCAT(t.name, t.version) routeId,
t.`name` name,
t.`version` version,
SUM(t.`max_time`) maxTime,
SUM(t.`min_time`) minTime,
SUM(t.`total_time`) totalTime,
SUM(t.`total_request_count`) totalRequestCount,
SUM(t.`success_count`) successCount,
SUM(t.`error_count`) errorCount,
SUM(t.`total_time`)/SUM(t.`total_request_count`) avgTime,
1 hasChildren
FROM
`monitor_info` t
<include refid="common.where"/>
GROUP BY t.service_id, t.name, t.version
<include refid="common.orderBy"/>
) t
LEFT JOIN
(
SELECT route_id, count(*) unsolvedErrorCount
FROM monitor_info_error WHERE is_deleted=0 GROUP BY route_id
) t2 on t.routeId = t2.route_id
</select>
<select id="listInstanceMonitorInfo" resultType="com.gitee.sop.adminserver.entity.MonitorSummary">
SELECT
t.`service_id` serviceId,
t.`instance_id` instanceId,
t.route_id routeId,
t.`name` name,
t.`version` version,
t.`max_time` maxTime,
t.`min_time` minTime,
t.`total_time` totalTime,
t.`total_request_count` totalRequestCount,
t.`success_count` successCount,
t.`error_count` errorCount,
IFNULL(t2.unsolvedErrorCount, 0) unsolvedErrorCount,
t.`total_time`/t.`total_request_count` avgTime,
0 hasChildren
FROM
`monitor_info` t
LEFT JOIN
(
SELECT instance_id, route_id, count(*) unsolvedErrorCount
FROM monitor_info_error WHERE is_deleted=0 GROUP BY instance_id, route_id
) t2 on t.route_id = t2.route_id AND t.instance_id = t2.instance_id
<include refid="common.where" />
<include refid="common.orderBy" />
</select>
</mapper>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor" />
</plugins>
</configuration>

@ -1 +1 @@
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><link rel=icon href=favicon.ico><title>SOP Admin</title><link href=static/css/chunk-elementUI.81cf475c.css rel=stylesheet><link href=static/css/chunk-libs.3dfb7769.css rel=stylesheet><link href=static/css/app.6095bfbf.css rel=stylesheet></head><body><noscript><strong>We're sorry but SOP Admin doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script>(function(e){function n(n){for(var r,c,a=n[0],f=n[1],i=n[2],d=0,l=[];d<a.length;d++)c=a[d],u[c]&&l.push(u[c][0]),u[c]=0;for(r in f)Object.prototype.hasOwnProperty.call(f,r)&&(e[r]=f[r]);h&&h(n);while(l.length)l.shift()();return o.push.apply(o,i||[]),t()}function t(){for(var e,n=0;n<o.length;n++){for(var t=o[n],r=!0,c=1;c<t.length;c++){var a=t[c];0!==u[a]&&(r=!1)}r&&(o.splice(n--,1),e=f(f.s=t[0]))}return e}var r={},c={runtime:0},u={runtime:0},o=[];function a(e){return f.p+"static/js/"+({}[e]||e)+"."+{"chunk-25908fca":"eac43cde","chunk-2c1f2e8f":"f092c0a0","chunk-2d0d32e7":"e7c489be","chunk-2d2085ef":"91d75f3c","chunk-2d221c34":"20057287","chunk-30c6c34f":"b288bbf5","chunk-4de1c2b6":"e74e3d03","chunk-73b2dcec":"60c5d8e9","chunk-9b31c83a":"494fc338","chunk-9f479afe":"2093f9d0","chunk-c3ce42fe":"9517b588"}[e]+".js"}function f(n){if(r[n])return r[n].exports;var t=r[n]={i:n,l:!1,exports:{}};return e[n].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.e=function(e){var n=[],t={"chunk-25908fca":1,"chunk-2c1f2e8f":1,"chunk-30c6c34f":1,"chunk-4de1c2b6":1,"chunk-73b2dcec":1,"chunk-9b31c83a":1,"chunk-c3ce42fe":1};c[e]?n.push(c[e]):0!==c[e]&&t[e]&&n.push(c[e]=new Promise(function(n,t){for(var r="static/css/"+({}[e]||e)+"."+{"chunk-25908fca":"a66354ec","chunk-2c1f2e8f":"0314067f","chunk-2d0d32e7":"31d6cfe0","chunk-2d2085ef":"31d6cfe0","chunk-2d221c34":"31d6cfe0","chunk-30c6c34f":"3b12267b","chunk-4de1c2b6":"a37cd815","chunk-73b2dcec":"ed391cc5","chunk-9b31c83a":"c4612b4a","chunk-9f479afe":"31d6cfe0","chunk-c3ce42fe":"6b789903"}[e]+".css",u=f.p+r,o=document.getElementsByTagName("link"),a=0;a<o.length;a++){var i=o[a],d=i.getAttribute("data-href")||i.getAttribute("href");if("stylesheet"===i.rel&&(d===r||d===u))return n()}var l=document.getElementsByTagName("style");for(a=0;a<l.length;a++){i=l[a],d=i.getAttribute("data-href");if(d===r||d===u)return n()}var h=document.createElement("link");h.rel="stylesheet",h.type="text/css",h.onload=n,h.onerror=function(n){var r=n&&n.target&&n.target.src||u,o=new Error("Loading CSS chunk "+e+" failed.\n("+r+")");o.code="CSS_CHUNK_LOAD_FAILED",o.request=r,delete c[e],h.parentNode.removeChild(h),t(o)},h.href=u;var s=document.getElementsByTagName("head")[0];s.appendChild(h)}).then(function(){c[e]=0}));var r=u[e];if(0!==r)if(r)n.push(r[2]);else{var o=new Promise(function(n,t){r=u[e]=[n,t]});n.push(r[2]=o);var i,d=document.createElement("script");d.charset="utf-8",d.timeout=120,f.nc&&d.setAttribute("nonce",f.nc),d.src=a(e),i=function(n){d.onerror=d.onload=null,clearTimeout(l);var t=u[e];if(0!==t){if(t){var r=n&&("load"===n.type?"missing":n.type),c=n&&n.target&&n.target.src,o=new Error("Loading chunk "+e+" failed.\n("+r+": "+c+")");o.type=r,o.request=c,t[1](o)}u[e]=void 0}};var l=setTimeout(function(){i({type:"timeout",target:d})},12e4);d.onerror=d.onload=i,document.head.appendChild(d)}return Promise.all(n)},f.m=e,f.c=r,f.d=function(e,n,t){f.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:t})},f.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,n){if(1&n&&(e=f(e)),8&n)return e;if(4&n&&"object"===typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var r in e)f.d(t,r,function(n){return e[n]}.bind(null,r));return t},f.n=function(e){var n=e&&e.__esModule?function(){return e["default"]}:function(){return e};return f.d(n,"a",n),n},f.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},f.p="",f.oe=function(e){throw console.error(e),e};var i=window["webpackJsonp"]=window["webpackJsonp"]||[],d=i.push.bind(i);i.push=n,i=i.slice();for(var l=0;l<i.length;l++)n(i[l]);var h=d;t()})([]);</script><script src=static/js/chunk-elementUI.298ac98c.js></script><script src=static/js/chunk-libs.75deb05f.js></script><script src=static/js/app.f323bdd7.js></script></body></html>
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><link rel=icon href=favicon.ico><title>SOP Admin</title><link href=static/css/chunk-elementUI.81cf475c.css rel=stylesheet><link href=static/css/chunk-libs.3dfb7769.css rel=stylesheet><link href=static/css/app.6095bfbf.css rel=stylesheet></head><body><noscript><strong>We're sorry but SOP Admin doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script>(function(e){function n(n){for(var c,r,a=n[0],f=n[1],i=n[2],d=0,h=[];d<a.length;d++)r=a[d],u[r]&&h.push(u[r][0]),u[r]=0;for(c in f)Object.prototype.hasOwnProperty.call(f,c)&&(e[c]=f[c]);l&&l(n);while(h.length)h.shift()();return o.push.apply(o,i||[]),t()}function t(){for(var e,n=0;n<o.length;n++){for(var t=o[n],c=!0,r=1;r<t.length;r++){var a=t[r];0!==u[a]&&(c=!1)}c&&(o.splice(n--,1),e=f(f.s=t[0]))}return e}var c={},r={runtime:0},u={runtime:0},o=[];function a(e){return f.p+"static/js/"+({}[e]||e)+"."+{"chunk-25908fca":"cc77b01c","chunk-2c1f2e8f":"f092c0a0","chunk-2d0d6219":"18813e69","chunk-2d2085ef":"91d75f3c","chunk-2d221c34":"20057287","chunk-2d238661":"23693be8","chunk-30c6c34f":"b288bbf5","chunk-4de1c2b6":"e74e3d03","chunk-73b2dcec":"60c5d8e9","chunk-9b31c83a":"494fc338","chunk-9f479afe":"2093f9d0","chunk-c3ce42fe":"9517b588"}[e]+".js"}function f(n){if(c[n])return c[n].exports;var t=c[n]={i:n,l:!1,exports:{}};return e[n].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.e=function(e){var n=[],t={"chunk-25908fca":1,"chunk-2c1f2e8f":1,"chunk-30c6c34f":1,"chunk-4de1c2b6":1,"chunk-73b2dcec":1,"chunk-9b31c83a":1,"chunk-c3ce42fe":1};r[e]?n.push(r[e]):0!==r[e]&&t[e]&&n.push(r[e]=new Promise(function(n,t){for(var c="static/css/"+({}[e]||e)+"."+{"chunk-25908fca":"a66354ec","chunk-2c1f2e8f":"0314067f","chunk-2d0d6219":"31d6cfe0","chunk-2d2085ef":"31d6cfe0","chunk-2d221c34":"31d6cfe0","chunk-2d238661":"31d6cfe0","chunk-30c6c34f":"3b12267b","chunk-4de1c2b6":"a37cd815","chunk-73b2dcec":"ed391cc5","chunk-9b31c83a":"c4612b4a","chunk-9f479afe":"31d6cfe0","chunk-c3ce42fe":"6b789903"}[e]+".css",u=f.p+c,o=document.getElementsByTagName("link"),a=0;a<o.length;a++){var i=o[a],d=i.getAttribute("data-href")||i.getAttribute("href");if("stylesheet"===i.rel&&(d===c||d===u))return n()}var h=document.getElementsByTagName("style");for(a=0;a<h.length;a++){i=h[a],d=i.getAttribute("data-href");if(d===c||d===u)return n()}var l=document.createElement("link");l.rel="stylesheet",l.type="text/css",l.onload=n,l.onerror=function(n){var c=n&&n.target&&n.target.src||u,o=new Error("Loading CSS chunk "+e+" failed.\n("+c+")");o.code="CSS_CHUNK_LOAD_FAILED",o.request=c,delete r[e],l.parentNode.removeChild(l),t(o)},l.href=u;var s=document.getElementsByTagName("head")[0];s.appendChild(l)}).then(function(){r[e]=0}));var c=u[e];if(0!==c)if(c)n.push(c[2]);else{var o=new Promise(function(n,t){c=u[e]=[n,t]});n.push(c[2]=o);var i,d=document.createElement("script");d.charset="utf-8",d.timeout=120,f.nc&&d.setAttribute("nonce",f.nc),d.src=a(e),i=function(n){d.onerror=d.onload=null,clearTimeout(h);var t=u[e];if(0!==t){if(t){var c=n&&("load"===n.type?"missing":n.type),r=n&&n.target&&n.target.src,o=new Error("Loading chunk "+e+" failed.\n("+c+": "+r+")");o.type=c,o.request=r,t[1](o)}u[e]=void 0}};var h=setTimeout(function(){i({type:"timeout",target:d})},12e4);d.onerror=d.onload=i,document.head.appendChild(d)}return Promise.all(n)},f.m=e,f.c=c,f.d=function(e,n,t){f.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:t})},f.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,n){if(1&n&&(e=f(e)),8&n)return e;if(4&n&&"object"===typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var c in e)f.d(t,c,function(n){return e[n]}.bind(null,c));return t},f.n=function(e){var n=e&&e.__esModule?function(){return e["default"]}:function(){return e};return f.d(n,"a",n),n},f.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},f.p="",f.oe=function(e){throw console.error(e),e};var i=window["webpackJsonp"]=window["webpackJsonp"]||[],d=i.push.bind(i);i.push=n,i=i.slice();for(var h=0;h<i.length;h++)n(i[h]);var l=d;t()})([]);</script><script src=static/js/chunk-elementUI.298ac98c.js></script><script src=static/js/chunk-libs.75deb05f.js></script><script src=static/js/app.7297829f.js></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1 +0,0 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0d32e7"],{"5c58":function(t,e,a){"use strict";a.r(e);var l=function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("div",{staticClass:"app-container"},[a("el-form",{staticClass:"demo-form-inline",attrs:{inline:!0,model:t.searchFormData,size:"mini"}},[a("el-form-item",{attrs:{label:"接口名"}},[a("el-input",{staticStyle:{width:"250px"},attrs:{clearable:!0,placeholder:"输入接口名或版本号"},model:{value:t.searchFormData.routeId,callback:function(e){t.$set(t.searchFormData,"routeId",e)},expression:"searchFormData.routeId"}})],1),t._v(" "),a("el-form-item",[a("el-button",{attrs:{type:"primary",icon:"el-icon-search"},on:{click:t.loadTable}},[t._v("搜索")])],1)],1),t._v(" "),a("el-alert",{staticStyle:{"margin-bottom":"10px"},attrs:{title:"监控数据保存在网关服务器,重启网关数据会清空。",type:"info",closable:!1}}),t._v(" "),a("el-table",{attrs:{data:t.tableData,border:"","default-expand-all":!1,"row-key":"id",height:"500","empty-text":"无数据"}},[a("el-table-column",{attrs:{fixed:"",prop:"instanceId",label:"网关实例",width:"200"},scopedSlots:t._u([{key:"default",fn:function(e){return[e.row.children?t._e():a("span",[t._v(t._s(e.row.instanceId))])]}}])}),t._v(" "),a("el-table-column",{attrs:{fixed:"",prop:"name",label:"接口名 (版本号)",width:"280"},scopedSlots:t._u([{key:"default",fn:function(e){return[t._v("\n "+t._s(e.row.name+(e.row.version?" ("+e.row.version+")":""))+"\n ")]}}])}),t._v(" "),a("el-table-column",{attrs:{prop:"serviceId",label:"serviceId",width:"170"}}),t._v(" "),a("el-table-column",{attrs:{prop:"maxTime",label:"最大耗时(ms)",width:"125"}},[a("template",{slot:"header"},[t._v("\n 最大耗时(ms)\n "),a("el-tooltip",{attrs:{effect:"dark",content:"耗时计算:签名验证成功后开始,微服务返回结果后结束",placement:"top"}},[a("i",{staticClass:"el-icon-question",staticStyle:{cursor:"pointer"}})])],1)],2),t._v(" "),a("el-table-column",{attrs:{prop:"minTime",label:"最小耗时(ms)",width:"120"}}),t._v(" "),a("el-table-column",{attrs:{prop:"avgTime",label:"平均耗时(ms)",width:"120"}}),t._v(" "),a("el-table-column",{attrs:{prop:"totalCount",label:"总调用次数",width:"100"}}),t._v(" "),a("el-table-column",{attrs:{prop:"successCount",label:"成功次数",width:"100"}}),t._v(" "),a("el-table-column",{attrs:{prop:"errorCount",label:"失败次数",width:"100"},scopedSlots:t._u([{key:"default",fn:function(e){return[e.row.errorCount>0?a("el-link",{staticStyle:{"text-decoration":"underline"},attrs:{underline:!1,type:"danger"},on:{click:function(a){return t.onShowErrorDetail(e.row)}}},[t._v("\n "+t._s(e.row.errorCount)+"\n ")]):t._e(),t._v(" "),0===e.row.errorCount?a("span",[t._v("0")]):t._e()]}}])},[a("template",{slot:"header"},[t._v("\n 失败次数\n "),a("el-tooltip",{attrs:{effect:"dark",content:"只统计微服务返回的未知错误,JSR-303验证错误算作成功",placement:"top-end"}},[a("i",{staticClass:"el-icon-question",staticStyle:{cursor:"pointer"}})])],1)],2)],1),t._v(" "),a("el-dialog",{attrs:{title:"错误详情",visible:t.logDetailVisible,width:"60%"},on:{"update:visible":function(e){t.logDetailVisible=e}}},[a("div",{staticStyle:{"overflow-x":"auto"},domProps:{innerHTML:t._s(t.errorMsgDetail)}}),t._v(" "),a("div",{staticClass:"dialog-footer",attrs:{slot:"footer"},slot:"footer"},[a("el-button",{attrs:{type:"primary"},on:{click:function(e){t.logDetailVisible=!1}}},[t._v("关 闭")])],1)])],1)},o=[],r={data:function(){return{searchFormData:{routeId:""},tableData:[],logDetailVisible:!1,errorMsgDetail:""}},created:function(){this.loadTable()},methods:{loadTable:function(){this.post("monitor.data.list",this.searchFormData,function(t){var e=t.data;this.tableData=e.monitorInfoData})},onShowErrorDetail:function(t){var e=t.errorMsgList;this.errorMsgDetail=e.length>0?e.join("<br>"):"无内容",this.logDetailVisible=!0}}},i=r,n=a("2877"),s=Object(n["a"])(i,l,o,!1,null,null,null);e["default"]=s.exports}}]);

@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0d6219"],{"70f0":function(t,e,o){"use strict";o.r(e);var a=function(){var t=this,e=t.$createElement,o=t._self._c||e;return o("div",{staticClass:"app-container"},[o("el-form",{attrs:{size:"mini"}},[o("el-form-item",[o("el-button",{attrs:{type:"primary",icon:"el-icon-upload"},on:{click:t.onAddSdk}},[t._v("发布SDK")])],1)],1),t._v(" "),o("el-table",{attrs:{data:t.list,border:""}},[o("el-table-column",{attrs:{prop:"name",label:"SDK",width:"120"}}),t._v(" "),o("el-table-column",{attrs:{prop:"version",label:"版本",width:"120"}}),t._v(" "),o("el-table-column",{attrs:{prop:"content",label:"下载地址"},scopedSlots:t._u([{key:"default",fn:function(e){return[o("el-link",{attrs:{type:"primary",href:e.row.content,target:"_blank"}},[t._v(t._s(e.row.content))])]}}])}),t._v(" "),o("el-table-column",{attrs:{label:"操作",width:"150",align:"center"},scopedSlots:t._u([{key:"default",fn:function(e){return[o("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(o){return t.onSdkUpdate(e.row)}}},[t._v("编辑")]),t._v(" "),o("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(o){return t.onSdkDelete(e.row)}}},[t._v("删除")])]}}])})],1),t._v(" "),o("el-dialog",{attrs:{title:t.sdkDlgTitle,visible:t.sdkDlgAddShow,"close-on-click-modal":!1},on:{"update:visible":function(e){t.sdkDlgAddShow=e},close:function(e){return t.resetForm("sdkAddForm")}}},[o("el-form",{ref:"sdkAddForm",attrs:{model:t.sdkFormAddData,rules:t.sdkFormRule,"label-width":"100px"}},[o("el-form-item",{attrs:{prop:"name",label:"选择语言"}},[o("el-select",{attrs:{placeholder:"请选择"},model:{value:t.sdkFormAddData.name,callback:function(e){t.$set(t.sdkFormAddData,"name",e)},expression:"sdkFormAddData.name"}},t._l(t.sdkConfigs,function(t){return o("el-option",{key:t.name,attrs:{label:t.name,value:t.name}})}),1)],1),t._v(" "),o("el-form-item",{attrs:{prop:"version",label:"版本"}},[o("el-input",{attrs:{maxlength:"30","show-word-limit":"",placeholder:"如:1.0"},model:{value:t.sdkFormAddData.version,callback:function(e){t.$set(t.sdkFormAddData,"version",e)},expression:"sdkFormAddData.version"}})],1),t._v(" "),o("el-form-item",{attrs:{prop:"content",label:"下载地址"}},[o("el-input",{attrs:{maxlength:"100","show-word-limit":""},model:{value:t.sdkFormAddData.content,callback:function(e){t.$set(t.sdkFormAddData,"content",e)},expression:"sdkFormAddData.content"}})],1),t._v(" "),o("el-form-item",{attrs:{prop:"extContent",label:"调用示例"}},[o("el-input",{attrs:{type:"textarea",rows:12,placeholder:"填写SDK调用示例代码,支持markdown语法"},model:{value:t.sdkFormAddData.extContent,callback:function(e){t.$set(t.sdkFormAddData,"extContent",e)},expression:"sdkFormAddData.extContent"}})],1)],1),t._v(" "),o("div",{staticClass:"dialog-footer",attrs:{slot:"footer"},slot:"footer"},[o("el-button",{on:{click:function(e){t.sdkDlgAddShow=!1}}},[t._v("取 消")]),t._v(" "),o("el-button",{attrs:{type:"primary"},on:{click:t.onSubmitForm}},[t._v("保 存")])],1)],1)],1)},n=[],d=(o("7f7f"),function(){return{id:0,name:"",version:"",content:"",extContent:""}}),s={data:function(){return{searchFormData:{},sdkDownloadConfig:[],sdkConfigs:[],sdkDlgTitle:"",sdkDlgAddShow:!1,sdkFormUpdateData:d(),sdkFormAddData:d(),sdkFormLoading:!1,sdkFormRule:{name:[{required:!0,message:"请选择语言",trigger:"blur"}],version:[{required:!0,message:"请填版本",trigger:"blur"}],content:[{required:!0,message:"请填写URL",trigger:"blur"}],extContent:[{required:!0,message:"请填写调用示例",trigger:"blur"}]},downloadUrl:"",list:[]}},created:function(){this.loadLangSelector(),this.loadTable()},methods:{loadLangSelector:function(){var t=this;this.getFile("static/sdkConfig.json?q=".concat((new Date).getTime()),function(e){t.sdkConfigs=e.langList})},loadTable:function(){var t=this;this.post("isp.sdk.list",this.searchFormData,function(e){t.list=e.data})},onSizeChange:function(t){this.searchFormData.pageSize=t,this.loadTable()},onPageIndexChange:function(t){this.searchFormData.pageIndex=t,this.loadTable()},onAddSdk:function(){this.sdkDlgTitle="添加SDK",this.sdkFormAddData=d(),this.sdkDlgAddShow=!0},onSdkUpdate:function(t){this.sdkDlgTitle="修改SDK",this.sdkFormAddData=d(),Object.assign(this.sdkFormAddData,t),this.sdkDlgAddShow=!0},onSdkDelete:function(t){var e=this;this.confirm("确认要删除【".concat(t.name,"】吗?"),function(o){e.post("isp.sdk.delete",{id:t.id},function(t){o(),e.tip("删除成功"),e.loadTable()})})},onSubmitForm:function(){var t=this;this.$refs.sdkAddForm.validate(function(e){if(e){var o=t.sdkFormAddData.id?"isp.sdk.update":"isp.sdk.add";t.post(o,t.sdkFormAddData,function(){this.sdkDlgAddShow=!1,this.loadTable()})}})}}},l=s,r=o("2877"),i=Object(r["a"])(l,a,n,!1,null,null,null);e["default"]=i.exports}}]);

@ -0,0 +1,24 @@
{
"langList": [
{
"name": "Java"
},
{
"name": "C#"
},
{
"name": "C++"
},
{
"name": "Go"
},{
"name": "NodeJS"
},
{
"name": "Python"
},
{
"name": "Rust"
}
]
}

@ -2,7 +2,6 @@ package com.gitee.sop.adminserver;
import junit.framework.TestCase;
import org.apache.commons.codec.digest.DigestUtils;
import org.junit.jupiter.api.Test;
/**
* @author tanghc
@ -12,8 +11,7 @@ public class AccountTest extends TestCase {
/*
生成密码
*/
@Test
public void genPwd() {
public void testGen() {
String username = "admin";
String password = "123456";
String save_to_db = DigestUtils.md5Hex(username + DigestUtils.md5Hex(password) + username);

@ -1,6 +1,5 @@
package com.gitee.sop.adminserver;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@ -9,8 +8,4 @@ import org.springframework.test.context.junit4.SpringRunner;
@SpringBootTest
public class SopAdminServerApplicationTests {
@Test
public void contextLoads() {
}
}

@ -1,8 +1,8 @@
# just a flag
ENV = 'development'
# base api
VUE_APP_BASE_API = 'http://localhost:8082/api'
# base api,末尾没有/
VUE_APP_BASE_API = 'http://localhost:8082'
# vue-cli uses the VUE_CLI_BABEL_TRANSPILE_MODULES environment variable,
# to control whether the babel-plugin-dynamic-import-node plugin is enabled.

@ -1,6 +1,8 @@
# just a flag
ENV = 'production'
# base api
VUE_APP_BASE_API = '/api'
# base api,
# 如果要前后端分离,这里填 http(s)://ip:port 末尾没有/
# 如:http://www.aaa.com, http://www.bbb.com:7777
VUE_APP_BASE_API = ''

@ -1,5 +1,7 @@
# SOP Admin 前端vue实现
此工程基于前端脚手架:`https://github.com/PanJiaChen/vue-element-admin`
前提:先安装好npm,[npm安装教程](https://blog.csdn.net/zhangwenwu2/article/details/52778521)
1. 启动服务端程序,运行`SopAdminServerApplication.java`
@ -14,5 +16,11 @@
如果想要把vue打包放到服务端,步骤如下:
- 执行`npm run build:prod`进行打包,结果在dest
- 执行`npm run build:prod`进行打包,结果在`dist`
- 打包完成后,把dest中的所有文件,放到`sop-admin-server/src/main/resources/public`下
## 前后端分离部署
- 修改`.env.production`,指定服务端地址
- 执行`npm run build:prod`进行打包,结果在`dist`下
- 把`dist`中的文件放到静态服务器上

@ -0,0 +1,24 @@
{
"langList": [
{
"name": "Java"
},
{
"name": "C#"
},
{
"name": "C++"
},
{
"name": "Go"
},{
"name": "NodeJS"
},
{
"name": "Python"
},
{
"name": "Rust"
}
]
}

@ -10,6 +10,7 @@
:unique-opened="false"
:active-text-color="variables.menuActiveText"
:collapse-transition="false"
:default-openeds="opened"
mode="vertical"
>
<sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path" />
@ -33,6 +34,13 @@ export default {
routes() {
return this.$router.options.routes
},
opened() {
return this.routes.filter(route => {
return route.meta && route.meta.open
}).map(route => {
return route.path
})
},
activeMenu() {
const route = this.$route
const { meta, path } = route

@ -59,7 +59,7 @@ export const constantRoutes = [
path: '/service',
component: Layout,
name: 'Service',
meta: { title: '服务管理', icon: 'example' },
meta: { title: '服务管理', icon: 'example', open: true },
children: [
{
path: 'list',
@ -76,7 +76,7 @@ export const constantRoutes = [
{
path: 'monitor',
name: 'Monitor',
component: () => import('@/views/service/monitor'),
component: () => import('@/views/service/monitorNew'),
meta: { title: '路由监控' }
},
{
@ -90,6 +90,12 @@ export const constantRoutes = [
name: 'Blacklist',
component: () => import('@/views/service/ipBlacklist'),
meta: { title: 'IP黑名单' }
},
{
path: 'sdk',
name: 'Sdk',
component: () => import('@/views/service/sdk'),
meta: { title: 'SDK管理' }
}
]
},
@ -98,7 +104,7 @@ export const constantRoutes = [
path: '/isv',
component: Layout,
name: 'Isv',
meta: { title: 'ISV管理', icon: 'user' },
meta: { title: 'ISV管理', icon: 'user', open: true },
children: [
{
path: 'list',

@ -7,7 +7,7 @@ import { getToken, removeToken } from './auth'
// 创建axios实例
const client = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // api 的 base_url
baseURL: process.env.VUE_APP_BASE_API + '/api', // api 的 base_url
timeout: 60000 // 请求超时时间,60秒
})
@ -94,6 +94,17 @@ Object.assign(Vue.prototype, {
}
}).catch(function() {})
},
/**
* 文件必须放在public下面
* @param path 相对于public文件夹路径如文件在public/static/sign.mdstatic/sign.md
* @param callback 回调函数函数参数是文件内容
*/
getFile: function(path, callback) {
axios.get(path)
.then(function(response) {
callback.call(this, response.data)
})
},
downloadText(filename, text) {
const element = document.createElement('a')
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text))

@ -32,13 +32,28 @@
<el-table-column
prop="roleList"
label="角色"
width="150"
:show-overflow-tooltip="true"
>
<template slot-scope="scope">
<span v-html="roleRender(scope.row)"></span>
</template>
</el-table-column>
<el-table-column
prop="userId"
label="注册用户"
width="100"
>
<template slot="header">
注册用户
<el-tooltip content="注册用户自行管理秘钥" placement="top">
<i class="el-icon-question" style="cursor: pointer"></i>
</el-tooltip>
</template>
<template slot-scope="scope">
<span v-if="scope.row.userId" style="font-weight: bold;"></span>
<span v-else></span>
</template>
</el-table-column>
<el-table-column
prop="status"
label="状态"
@ -57,7 +72,6 @@
<el-table-column
prop="remark"
label="备注"
width="200"
:show-overflow-tooltip="true"
/>
<el-table-column
@ -66,8 +80,8 @@
>
<template slot-scope="scope">
<el-button type="text" size="mini" @click="onTableUpdate(scope.row)">修改</el-button>
<el-button type="text" size="mini" @click="onKeysUpdate(scope.row)">秘钥管理</el-button>
<el-button type="text" size="mini" @click="onExportKeys(scope.row)">导出秘钥</el-button>
<el-button v-if="!scope.row.userId" type="text" size="mini" @click="onKeysUpdate(scope.row)">秘钥管理</el-button>
<el-button v-if="!scope.row.userId" type="text" size="mini" @click="onExportKeys(scope.row)">导出秘钥</el-button>
</template>
</el-table-column>
</el-table>
@ -133,33 +147,26 @@
size="mini"
class="key-view"
>
<el-form-item label="">
<el-alert
title="带 ★ 的分配给开发者"
type="warning"
:closable="false"
/>
</el-form-item>
<el-form-item :label="selfLabel('appId')">
<el-form-item label="appId">
<span>{{ isvKeysFormData.appKey }}</span>
</el-form-item>
<el-form-item v-show="showKeys()" label="秘钥格式">
<span v-if="isvKeysFormData.keyFormat === 1">PKCS8(JAVA适用)</span>
<span v-if="isvKeysFormData.keyFormat === 2">PKCS1(非JAVA适用)</span>
</el-form-item>
<el-form-item v-show="isvKeysFormData.signType === 2" :label="selfLabel('secret')">
<el-form-item v-show="isvKeysFormData.signType === 2" label="secret">
<span>{{ isvKeysFormData.secret }}</span>
</el-form-item>
<el-tabs v-show="showKeys()" v-model="activeName" type="card" class="keyTabs">
<el-tab-pane label="ISV公私钥" name="first">
<el-form-item label="ISV公钥">
<el-input v-model="isvKeysFormData.publicKeyIsv" type="textarea" readonly />
<el-input v-model="isvKeysFormData.publicKeyIsv" type="textarea" placeholder="未上传" readonly />
</el-form-item>
<el-form-item :label="selfLabel('ISV私钥')">
<el-form-item v-show="isvKeysFormData.userId === 0" label="ISV私钥">
<el-input v-model="isvKeysFormData.privateKeyIsv" type="textarea" readonly />
</el-form-item>
</el-tab-pane>
<el-tab-pane label="平台公私钥[可选]" name="second">
<el-tab-pane label="平台公私钥" name="second">
<el-form-item label="平台公钥">
<el-input v-model="isvKeysFormData.publicKeyPlatform" type="textarea" readonly />
</el-form-item>
@ -221,7 +228,8 @@ export default {
privateKeyIsv: '',
publicKeyPlatform: '',
privateKeyPlatform: '',
signType: ''
signType: '',
userId: 0
}
}
},
@ -247,6 +255,7 @@ export default {
this.isvKeysDialogVisible = true
this.$nextTick(() => {
Object.assign(this.isvKeysFormData, resp.data)
this.isvKeysFormData.userId = row.userId
})
})
},
@ -319,9 +328,6 @@ export default {
this.isvDialogFormData.status = 1
this.isvDialogFormData.roleCode = []
},
selfLabel: function(lab) {
return '★ ' + lab
},
roleRender: function(row) {
const html = []
const roleList = row.roleList

@ -0,0 +1,283 @@
<template>
<div class="app-container">
<el-form :inline="true" :model="searchFormData" class="demo-form-inline" size="mini" @submit.native.prevent>
<el-form-item label="接口名">
<el-input v-model="searchFormData.routeId" :clearable="true" style="width: 250px;" />
</el-form-item>
<el-form-item label="serviceId">
<el-input v-model="searchFormData.serviceId" :clearable="true" style="width: 250px;" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" native-type="submit" @click="loadTable">查询</el-button>
</el-form-item>
</el-form>
<el-table
:data="pageInfo.list"
row-key="id"
lazy
empty-text="无数据"
:load="loadInstanceMonitorInfo"
>
<el-table-column
fixed
prop="instanceId"
label="网关实例"
width="200"
>
<template slot-scope="scope">
<span v-if="!scope.row.children">{{ scope.row.instanceId }}</span>
</template>
</el-table-column>
<el-table-column
fixed
prop="name"
label="接口名 (版本号)"
width="200"
>
<template slot-scope="scope">
{{ scope.row.name + (scope.row.version ? ' (' + scope.row.version + ')' : '') }}
</template>
</el-table-column>
<el-table-column
prop="serviceId"
label="serviceId"
width="150"
/>
<el-table-column
prop="maxTime"
label="最大耗时(ms)"
>
<template slot="header">
最大耗时(ms)
<el-tooltip content="耗时计算:签名验证成功后开始,应用返回结果后结束" placement="top">
<i class="el-icon-question" style="cursor: pointer"></i>
</el-tooltip>
</template>
</el-table-column>
<el-table-column
prop="minTime"
label="最小耗时(ms)"
/>
<el-table-column
prop="avgTime"
label="平均耗时(ms)"
>
<template slot-scope="scope">
{{ scope.row.avgTime.toFixed(1) }}
</template>
</el-table-column>
<el-table-column
prop="totalRequestCount"
label="总调用次数"
/>
<el-table-column
prop="successCount"
label="成功次数"
/>
<el-table-column
prop="errorCount"
label="失败次数"
/>
<el-table-column
prop="unsolvedErrorCount"
label="未解决错误"
>
<template slot-scope="scope">
<el-link
v-if="scope.row.unsolvedErrorCount > 0"
:underline="false"
type="danger"
style="text-decoration: underline;"
@click="onShowErrorDetail(scope.row)"
>
{{ scope.row.unsolvedErrorCount }}
</el-link>
<span v-if="scope.row.unsolvedErrorCount === 0">0</span>
</template>
</el-table-column>
</el-table>
<el-pagination
background
style="margin-top: 5px"
:current-page="searchFormData.pageIndex"
:page-size="searchFormData.pageSize"
:page-sizes="[5, 10, 20, 40]"
:total="pageInfo.total"
layout="total, sizes, prev, pager, next"
@size-change="onSizeChange"
@current-change="onPageIndexChange"
/>
<!-- dialog -->
<el-dialog
:title="errorMsgData.title"
:visible.sync="logDetailVisible"
:close-on-click-modal="false"
width="70%"
@close="onCloseErrorDlg"
>
<el-alert
title="修复错误后请标记解决"
:closable="false"
class="el-alert-tip"
/>
<el-table
:data="errorMsgData.pageInfo.rows"
empty-text="无错误日志"
>
<el-table-column
type="expand"
>
<template slot-scope="props">
<el-input v-model="props.row.errorMsg" type="textarea" :rows="8" readonly />
</template>
</el-table-column>
<el-table-column
prop="errorMsg"
label="错误内容"
>
<template slot-scope="props">
<span v-if="props.row.errorMsg.length > 50">{{ props.row.errorMsg.substring(0, 50) }}...</span>
<span v-else>{{ props.row.errorMsg }}</span>
</template>
</el-table-column>
<el-table-column
prop="instanceId"
label="实例ID"
width="150px"
/>
<el-table-column
prop="count"
label="报错次数"
width="80px"
/>
<el-table-column
prop="gmtModified"
label="报错时间"
width="160px"
/>
<el-table-column
label="操作"
width="120"
>
<template slot-scope="scope">
<el-link type="primary" @click="onSolve(scope.row)">标记解决</el-link>
</template>
</el-table-column>
</el-table>
<el-pagination
background
style="margin-top: 5px"
:current-page="errorMsgFormData.pageIndex"
:page-size="errorMsgFormData.pageSize"
:page-sizes="[5, 10, 20, 40]"
:total="errorMsgData.pageInfo.total"
layout="total, sizes, prev, pager, next"
@size-change="onErrorSizeChange"
@current-change="onErrorPageIndexChange"
/>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="logDetailVisible = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
export default {
data() {
return {
searchFormData: {
routeId: '',
serviceId: '',
pageIndex: 1,
pageSize: 20
},
pageInfo: {
list: [],
total: 0
},
logDetailVisible: false,
errorMsgFormData: {
routeId: '',
instanceId: '',
pageIndex: 1,
pageSize: 5
},
errorMsgData: {
title: '',
name: '',
version: '',
pageInfo: {
rows: [],
total: 0
}
}
}
},
created() {
this.loadTable()
},
methods: {
loadTable: function() {
this.post('monitornew.data.page', this.searchFormData, function(resp) {
this.pageInfo = resp.data
})
},
loadErrorData: function() {
this.post('monitornew.error.page', this.errorMsgFormData, function(resp) {
this.errorMsgData.pageInfo = resp.data
this.logDetailVisible = true
})
},
loadInstanceMonitorInfo(row, treeNode, resolve) {
this.post('monitornew.routeid.data.get', { routeId: row.routeId }, resp => {
const children = resp.data
row.children = children
resolve(children)
})
},
onShowErrorDetail: function(row) {
this.errorMsgData.title = `错误日志 ${row.name}${row.version}`
this.errorMsgData.name = row.name
this.errorMsgData.version = row.version
this.errorMsgFormData.routeId = row.routeId
this.errorMsgFormData.instanceId = row.instanceId
this.loadErrorData()
},
onSolve: function(row) {
this.confirm('确认标记为已解决吗?', function(done) {
this.post('monitornew.error.solve', { routeId: row.routeId, errorId: row.errorId }, function(resp) {
done()
this.errorMsgFormData.pageIndex = 1
this.loadErrorData()
})
})
},
onCloseErrorDlg: function() {
this.loadTable()
},
onErrorSizeChange: function(size) {
this.errorMsgFormData.pageSize = size
this.loadErrorData()
},
onErrorPageIndexChange: function(pageIndex) {
this.errorMsgFormData.pageIndex = pageIndex
this.loadErrorData()
},
onSizeChange: function(size) {
this.searchFormData.pageSize = size
this.loadTable()
},
onPageIndexChange: function(pageIndex) {
this.searchFormData.pageIndex = pageIndex
this.loadTable()
},
onAdd: function() {
this.dialogTitle = '新增IP'
this.dialogVisible = true
this.dialogFormData.id = 0
}
}
}
</script>

@ -0,0 +1,179 @@
<template>
<div class="app-container">
<el-form size="mini">
<el-form-item>
<el-button type="primary" icon="el-icon-upload" @click="onAddSdk">发布SDK</el-button>
</el-form-item>
</el-form>
<el-table
:data="list"
border
>
<el-table-column
prop="name"
label="SDK"
width="120"
/>
<el-table-column
prop="version"
label="版本"
width="120"
/>
<el-table-column
prop="content"
label="下载地址"
>
<template slot-scope="scope">
<el-link type="primary" :href="scope.row.content" target="_blank">{{ scope.row.content }}</el-link>
</template>
</el-table-column>
<el-table-column
label="操作"
width="150"
align="center"
>
<template slot-scope="scope">
<el-button type="text" size="mini" @click="onSdkUpdate(scope.row)">编辑</el-button>
<el-button type="text" size="mini" @click="onSdkDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--dialog-->
<el-dialog
:title="sdkDlgTitle"
:visible.sync="sdkDlgAddShow"
:close-on-click-modal="false"
@close="resetForm('sdkAddForm')"
>
<el-form
ref="sdkAddForm"
:model="sdkFormAddData"
:rules="sdkFormRule"
label-width="100px"
>
<el-form-item prop="name" label="选择语言">
<el-select
v-model="sdkFormAddData.name"
placeholder="请选择"
>
<el-option
v-for="item in sdkConfigs"
:key="item.name"
:label="item.name"
:value="item.name"
/>
</el-select>
</el-form-item>
<el-form-item prop="version" label="版本">
<el-input v-model="sdkFormAddData.version" maxlength="30" show-word-limit placeholder="如:1.0" />
</el-form-item>
<el-form-item prop="content" label="下载地址">
<el-input v-model="sdkFormAddData.content" maxlength="100" show-word-limit />
</el-form-item>
<el-form-item prop="extContent" label="调用示例">
<el-input v-model="sdkFormAddData.extContent" type="textarea" :rows="12" placeholder="填写SDK调用示例代码,支持markdown语法" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="sdkDlgAddShow = false"> </el-button>
<el-button type="primary" @click="onSubmitForm"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
const appFormDataInit = function() {
return {
id: 0,
name: '',
version: '',
content: '',
extContent: ''
}
}
export default {
data() {
return {
searchFormData: {},
sdkDownloadConfig: [],
sdkConfigs: [],
sdkDlgTitle: '',
sdkDlgAddShow: false,
sdkFormUpdateData: appFormDataInit(),
sdkFormAddData: appFormDataInit(),
sdkFormLoading: false,
sdkFormRule: {
name: [
{ required: true, message: '请选择语言', trigger: 'blur' }
],
version: [
{ required: true, message: '请填版本', trigger: 'blur' }
],
content: [
{ required: true, message: '请填写URL', trigger: 'blur' }
],
extContent: [
{ required: true, message: '请填写调用示例', trigger: 'blur' }
]
},
downloadUrl: '',
list: []
}
},
created() {
this.loadLangSelector()
this.loadTable()
},
methods: {
loadLangSelector: function() {
this.getFile(`static/sdkConfig.json?q=${new Date().getTime()}`, (content) => {
this.sdkConfigs = content.langList
})
},
loadTable: function() {
this.post('isp.sdk.list', this.searchFormData, resp => {
this.list = resp.data
})
},
onSizeChange: function(size) {
this.searchFormData.pageSize = size
this.loadTable()
},
onPageIndexChange: function(pageIndex) {
this.searchFormData.pageIndex = pageIndex
this.loadTable()
},
onAddSdk: function() {
this.sdkDlgTitle = '添加SDK'
this.sdkFormAddData = appFormDataInit()
this.sdkDlgAddShow = true
},
onSdkUpdate: function(row) {
this.sdkDlgTitle = '修改SDK'
this.sdkFormAddData = appFormDataInit()
Object.assign(this.sdkFormAddData, row)
this.sdkDlgAddShow = true
},
onSdkDelete: function(row) {
this.confirm(`确认要删除【${row.name}】吗?`, (done) => {
this.post('isp.sdk.delete', { id: row.id }, resp => {
done()
this.tip('删除成功')
this.loadTable()
})
})
},
onSubmitForm: function() {
this.$refs.sdkAddForm.validate((valid) => {
if (valid) {
const uri = this.sdkFormAddData.id ? 'isp.sdk.update' : 'isp.sdk.add'
this.post(uri, this.sdkFormAddData, function() {
this.sdkDlgAddShow = false
this.loadTable()
})
}
})
}
}
}
</script>

@ -5,7 +5,7 @@
<parent>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-parent</artifactId>
<version>4.0.3-SNAPSHOT</version>
<version>4.3.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent>
@ -25,7 +25,7 @@
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-service-common</artifactId>
<version>4.0.3-SNAPSHOT</version>
<version>${project.version}</version>
</dependency>
<!-- sop相关配置 end-->
@ -38,7 +38,7 @@
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sdk-java</artifactId>
<version>4.0.3-SNAPSHOT</version>
<version>4.3.2-SNAPSHOT</version>
</dependency>
<!-- http请求 -->
<dependency>

@ -6,7 +6,7 @@
<parent>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-parent</artifactId>
<version>4.0.3-SNAPSHOT</version>
<version>4.3.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent>

@ -4,21 +4,18 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-parent</artifactId>
<version>4.0.3-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath> <!-- lookup parent from repository -->
<artifactId>sop-common</artifactId>
<version>4.3.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<version>4.0.3-SNAPSHOT</version>
<artifactId>sop-bridge-eureka</artifactId>
<dependencies>
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-gateway-common</artifactId>
<version>4.0.3-SNAPSHOT</version>
<version>${project.version}</version>
</dependency>
<dependency>

@ -4,21 +4,18 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-parent</artifactId>
<version>4.0.3-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath> <!-- lookup parent from repository -->
<artifactId>sop-common</artifactId>
<version>4.3.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<version>4.0.3-SNAPSHOT</version>
<artifactId>sop-bridge-nacos</artifactId>
<dependencies>
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-gateway-common</artifactId>
<version>4.0.3-SNAPSHOT</version>
<version>${project.version}</version>
</dependency>
<dependency>

@ -13,6 +13,7 @@ import com.gitee.sop.gatewaycommon.route.ServiceHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.math.NumberUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEvent;
import org.springframework.util.CollectionUtils;
@ -34,6 +35,9 @@ public class NacosRegistryListener extends BaseRegistryListener {
private volatile Set<NacosServiceHolder> cacheServices = new HashSet<>();
@Value("${nacos.discovery.group:${spring.cloud.nacos.discovery.group:DEFAULT_GROUP}}")
private String nacosGroup;
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@ -81,7 +85,7 @@ public class NacosRegistryListener extends BaseRegistryListener {
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
ListView<String> servicesOfServer = null;
try {
servicesOfServer = namingService.getServicesOfServer(1, Integer.MAX_VALUE);
servicesOfServer = namingService.getServicesOfServer(1, Integer.MAX_VALUE, nacosGroup);
} catch (NacosException e) {
log.error("namingService.getServicesOfServer()错误", e);
}
@ -95,7 +99,7 @@ public class NacosRegistryListener extends BaseRegistryListener {
List<Instance> allInstances;
try {
// 获取服务实例
allInstances = namingService.getAllInstances(serviceName);
allInstances = namingService.getAllInstances(serviceName, nacosGroup);
} catch (NacosException e) {
log.error("namingService.getAllInstances(serviceName)错误,serviceName:{}", serviceName, e);
return null;

@ -4,14 +4,13 @@
<parent>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-parent</artifactId>
<version>4.0.3-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath> <!-- lookup parent from repository -->
<artifactId>sop-common</artifactId>
<version>4.3.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sop-gateway-common</artifactId>
<version>4.0.3-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
@ -54,6 +53,11 @@
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-webflux</artifactId>
</dependency>
<!-- optional -->
<dependency>
<groupId>com.alibaba.cloud</groupId>

@ -28,12 +28,12 @@ import com.gitee.sop.gatewaycommon.result.ResultExecutorForGateway;
import com.gitee.sop.gatewaycommon.secret.CacheIsvManager;
import com.gitee.sop.gatewaycommon.secret.IsvManager;
import com.gitee.sop.gatewaycommon.validate.ApiEncrypter;
import com.gitee.sop.gatewaycommon.validate.ApiSigner;
import com.gitee.sop.gatewaycommon.validate.ApiValidator;
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.validate.alipay.AlipaySigner;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
@ -74,7 +74,7 @@ public class ApiConfig {
/**
* 签名工具
*/
private Signer signer = new ApiSigner();
private Signer signer = new AlipaySigner();
/**
* 验证

@ -3,7 +3,6 @@ package com.gitee.sop.gatewaycommon.bean;
import lombok.Data;
import org.springframework.util.StringUtils;
import javax.validation.ValidationException;
import java.util.LinkedHashMap;
import java.util.Map;
@ -24,7 +23,7 @@ public class GatewayPredicateDefinition {
public GatewayPredicateDefinition(String text) {
int eqIdx = text.indexOf(61);
if (eqIdx <= 0) {
throw new ValidationException("Unable to parse GatewayPredicateDefinition text '" + text + "', must be of the form name=value");
throw new RuntimeException("Unable to parse GatewayPredicateDefinition text '" + text + "', must be of the form name=value");
} else {
this.setName(text.substring(0, eqIdx));
String[] params = StringUtils.tokenizeToStringArray(text.substring(eqIdx + 1), ",");

@ -0,0 +1,43 @@
package com.gitee.sop.gatewaycommon.bean;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Function;
/**
* LRU缓存
* LinkedHashMap 本身内部有一个触发条件则自动执行的方法删除最老元素最近最少使用的元素
* 由于最近最少使用元素是 LinkedHashMap 内部处理
* 故我们不再需要维护 最近访问元素放在链尾get 时直接访问/ put 时直接存储
* created by Ethan-Walker on 2019/2/16
*/
public class LRUCache<K, V> {
private final Map<K, V> map;
public LRUCache(int capacity) {
map = new LinkedHashMap<K, V>(capacity, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
// 容量大于capacity 时就删除
return size() > capacity;
}
};
}
public V get(K key) {
return map.get(key);
}
public V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
return map.computeIfAbsent(key, mappingFunction);
}
public V put(K key, V value) {
return map.put(key, value);
}
public Collection<V> values() {
return map.values();
}
}

@ -19,4 +19,6 @@ public interface TargetRoute {
*/
RouteDefinition getRouteDefinition();
String getFullPath();
}

@ -5,7 +5,6 @@ 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.interceptor.MonitorRouteInterceptor;
import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptor;
import com.gitee.sop.gatewaycommon.limit.LimitManager;
import com.gitee.sop.gatewaycommon.manager.EnvGrayManager;
@ -17,17 +16,20 @@ import com.gitee.sop.gatewaycommon.manager.LimitConfigManager;
import com.gitee.sop.gatewaycommon.manager.RouteConfigManager;
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
import com.gitee.sop.gatewaycommon.message.ErrorFactory;
import com.gitee.sop.gatewaycommon.monitor.MonitorManager;
import com.gitee.sop.gatewaycommon.param.ParameterFormatter;
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.sync.SopAsyncConfigurer;
import com.gitee.sop.gatewaycommon.util.RouteInterceptorUtil;
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.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@ -156,6 +158,17 @@ public class AbstractConfiguration implements ApplicationContextAware, Applicati
return ApiConfig.getInstance().getParameterFormatter();
}
@Bean
public SopAsyncConfigurer sopAsyncConfigurer(@Value("${sop.monitor-route-interceptor.thread-pool-size:4}")
int threadPoolSize) {
return new SopAsyncConfigurer("gatewayAsync", threadPoolSize);
}
@Bean
@ConditionalOnMissingBean
public MonitorManager monitorManager() {
return new MonitorManager();
}
/**
* 跨域过滤器gateway采用react形式需要使用reactive包下的UrlBasedCorsConfigurationSource
@ -231,7 +244,6 @@ public class AbstractConfiguration implements ApplicationContextAware, Applicati
protected void initRouteInterceptor() {
Map<String, RouteInterceptor> routeInterceptorMap = applicationContext.getBeansOfType(RouteInterceptor.class);
Collection<RouteInterceptor> routeInterceptors = new ArrayList<>(routeInterceptorMap.values());
routeInterceptors.add(new MonitorRouteInterceptor());
RouteInterceptorUtil.addInterceptors(routeInterceptors);
}

@ -36,4 +36,5 @@ public class SopGatewayEnvironmentPostProcessor implements EnvironmentPostProces
throw new IllegalStateException("加载配置文件失败" + resource, ex);
}
}
}

@ -3,7 +3,6 @@ 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.codec.MessageReaderFactory;
import com.gitee.sop.gatewaycommon.gateway.common.FileUploadHttpServletRequest;
import com.gitee.sop.gatewaycommon.gateway.common.RequestContentDataExtractor;
import com.gitee.sop.gatewaycommon.gateway.common.SopServerHttpRequestDecorator;
@ -17,7 +16,7 @@ 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.codec.ServerCodecConfigurer;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.MultiValueMap;
@ -47,8 +46,6 @@ public class ServerWebExchangeUtil {
private static final FormHttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter();
private static final List<HttpMessageReader<?>> messageReaders = MessageReaderFactory.build();
/**
* 重定向
*
@ -61,7 +58,7 @@ public class ServerWebExchangeUtil {
.mutate();
ServerHttpRequest newRequest = builder
.header(ParamNames.HEADER_VERSION_NAME, forwardInfo.getVersion())
.path(forwardInfo.getPath()).build();
.path(forwardInfo.getFullPath()).build();
return exchange.mutate().request(newRequest).build();
}
@ -70,10 +67,11 @@ public class ServerWebExchangeUtil {
* 构建一个接受请求体的request
*
* @param exchange exchange
* @param codecConfigurer codecConfigurer
* @return 返回ServerRequest
*/
public static ServerRequest createReadBodyRequest(ServerWebExchange exchange) {
return ServerRequest.create(exchange, messageReaders);
public static ServerRequest createReadBodyRequest(ServerWebExchange exchange, ServerCodecConfigurer codecConfigurer) {
return ServerRequest.create(exchange, codecConfigurer.getReaders());
}
public static String getRestfulPath(String path, String prefix) {

@ -1,46 +0,0 @@
package com.gitee.sop.gatewaycommon.gateway.codec;
import com.gitee.sop.gatewaycommon.manager.EnvironmentKeys;
import org.springframework.core.codec.AbstractDataBufferDecoder;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import java.lang.reflect.Method;
import java.util.List;
/**
* @author tanghc
*/
public class MessageReaderFactory {
public static final String METHOD_SET_MAX_IN_MEMORY_SIZE = "setMaxInMemorySize";
public static final String METHOD_GET_DECODER = "getDecoder";
public static final int DEFAULT_SIZE = 256 * 1024;
public static List<HttpMessageReader<?>> build() {
String maxInMemorySizeValueStr = EnvironmentKeys.MAX_IN_MEMORY_SIZE.getValue();
int maxInMemorySizeValue = Integer.parseInt(maxInMemorySizeValueStr);
List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders();
if (DEFAULT_SIZE == maxInMemorySizeValue) {
return messageReaders;
}
// 设置POST缓存大小
for (HttpMessageReader<?> httpMessageReader : messageReaders) {
Method[] methods = ReflectionUtils.getDeclaredMethods(httpMessageReader.getClass());
for (Method method : methods) {
String methodName = method.getName();
if (METHOD_SET_MAX_IN_MEMORY_SIZE.equals(methodName)) {
ReflectionUtils.invokeMethod(method, httpMessageReader, maxInMemorySizeValue);
} else if (METHOD_GET_DECODER.equals(methodName)) {
Object decoder = ReflectionUtils.invokeMethod(method, httpMessageReader);
if (decoder instanceof AbstractDataBufferDecoder) {
AbstractDataBufferDecoder<?> bufferDecoder = (AbstractDataBufferDecoder<?>) decoder;
bufferDecoder.setMaxInMemorySize(maxInMemorySizeValue);
}
}
}
}
return messageReaders;
}
}

@ -1,8 +1,5 @@
package com.gitee.sop.gatewaycommon.gateway.configuration;
import com.gitee.sop.gatewaycommon.bean.ApiContext;
import com.gitee.sop.gatewaycommon.validate.alipay.AlipaySigner;
/**
* 具备支付宝开放平台能力配置 https://docs.open.alipay.com/api
*
@ -10,7 +7,4 @@ import com.gitee.sop.gatewaycommon.validate.alipay.AlipaySigner;
*/
public class AlipayGatewayConfiguration extends BaseGatewayConfiguration {
static {
ApiContext.getApiConfig().setSigner(new AlipaySigner());
}
}

@ -1,10 +1,6 @@
package com.gitee.sop.gatewaycommon.gateway.configuration;
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
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.controller.GatewayMonitorController;
import com.gitee.sop.gatewaycommon.gateway.filter.GatewayModifyResponseGatewayFilter;
import com.gitee.sop.gatewaycommon.gateway.filter.IndexFilter;
import com.gitee.sop.gatewaycommon.gateway.filter.LimitFilter;
@ -50,26 +46,6 @@ public class BaseGatewayConfiguration extends AbstractConfiguration {
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
// public GatewayMonitorController gatewayMonitorController() {
// return new GatewayMonitorController();
// }
/**
* 自定义异常处理[@@]注册Bean时依赖的Bean会从容器中直接获取所以直接注入即可
*

@ -14,7 +14,9 @@ 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.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.server.ServerRequest;
@ -45,9 +47,12 @@ public class ConfigChannelController {
@Value("${sop.secret}")
private String secret;
@Autowired
private ServerCodecConfigurer codecConfigurer;
@PostMapping("/sop/configChannelMsg")
public Mono<String> configChannel(ServerWebExchange exchange) {
ServerRequest serverRequest = ServerWebExchangeUtil.createReadBodyRequest(exchange);
ServerRequest serverRequest = ServerWebExchangeUtil.createReadBodyRequest(exchange, codecConfigurer);
// 读取请求体中的内容
return serverRequest.bodyToMono(String.class)
.flatMap(requestJson -> {

@ -1,21 +0,0 @@
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<ServerWebExchange> {
@Override
protected ApiParam getApiParam(ServerWebExchange request) {
Map<String, String> params = request.getRequest().getQueryParams().toSingleValueMap();
return ApiParam.build(params);
}
}

@ -5,6 +5,8 @@ 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.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ServerWebExchange;
@ -28,6 +30,7 @@ public class GatewayController {
// 合并微服务传递过来的结果,变成最终结果
ResultExecutor<ServerWebExchange, String> resultExecutor = ApiContext.getApiConfig().getGatewayResultExecutor();
String gatewayResult = resultExecutor.buildErrorResult(exchange, throwable);
exchange.getResponse().getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
return Mono.just(gatewayResult);
}
@ -36,6 +39,7 @@ public class GatewayController {
ApiException exception = ErrorEnum.ISV_INVALID_METHOD.getErrorMeta().getException();
ResultExecutor<ServerWebExchange, String> resultExecutor = ApiContext.getApiConfig().getGatewayResultExecutor();
String gatewayResult = resultExecutor.buildErrorResult(exchange, exception);
exchange.getResponse().getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
return Mono.just(gatewayResult);
}

@ -1,21 +0,0 @@
package com.gitee.sop.gatewaycommon.gateway.controller;
import com.gitee.sop.gatewaycommon.support.BaseMonitorController;
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 GatewayMonitorController extends BaseMonitorController<ServerWebExchange> {
@Override
protected ApiParam getApiParam(ServerWebExchange request) {
Map<String, String> params = request.getRequest().getQueryParams().toSingleValueMap();
return ApiParam.build(params);
}
}

@ -0,0 +1,58 @@
package com.gitee.sop.gatewaycommon.gateway.controller;
import com.gitee.sop.gatewaycommon.bean.SpringContext;
import com.gitee.sop.gatewaycommon.gateway.loadbalancer.SopLoadBalancerClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.gateway.webflux.ProxyExchange;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 处理restful请求
* @author tanghc
*/
@Controller
public class RestfulController {
@Value("${sop.restful.path:/rest}")
private String prefix;
@RequestMapping("${sop.restful.path:/rest}/**")
public Mono<ResponseEntity<byte[]>> proxy(ProxyExchange<byte[]> proxy, ServerWebExchange exchange) {
String path = proxy.path();
String serviceId = getServiceId(path);
String targetPath = getTargetPath(serviceId, path);
String rawQuery = exchange.getRequest().getURI().getRawQuery();
if (StringUtils.hasLength(rawQuery)) {
targetPath = targetPath + "?" + rawQuery;
}
// 负载均衡
ServiceInstance serviceInstance = SpringContext.getBean(SopLoadBalancerClient.class).choose(serviceId, exchange);
String uri = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + targetPath;
return proxy.uri(uri).forward();
}
/**
* 从path中解析出serviceId
* @param path 格式/rest/<serviceId><real_path>
* @return 返回serviceId
*/
private String getServiceId(String path) {
int length = prefix.length() + 1;
path = path.substring(length);
int index = path.indexOf('/');
path = path.substring(0, index);
return path;
}
private String getTargetPath(String serviceId, String path) {
int length = prefix.length() + 1;
int len = length + serviceId.length();
return path.substring(len);
}
}

@ -5,43 +5,57 @@ import com.gitee.sop.gatewaycommon.bean.SopConstants;
import com.gitee.sop.gatewaycommon.result.ResultExecutor;
import org.apache.commons.lang3.StringUtils;
import org.reactivestreams.Publisher;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.filter.factory.rewrite.MessageBodyEncoder;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.cloud.gateway.support.DefaultClientResponse;
import org.springframework.core.Ordered;
import org.springframework.core.codec.AbstractDataBufferDecoder;
import org.springframework.core.codec.Decoder;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseCookie;
import org.springframework.http.client.reactive.ClientHttpResponse;
import org.springframework.http.codec.DecoderHttpMessageReader;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static java.util.function.Function.identity;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR;
/**
* 修改返回结果
*
* @author tanghc
*/
public class GatewayModifyResponseGatewayFilter implements GlobalFilter, Ordered {
@Value("${spring.codec.max-in-memory-size:262144}")
private int maxInMemorySize;
@Autowired
private ServerCodecConfigurer codecConfigurer;
@Autowired
private Set<MessageBodyEncoder> bodyEncoders;
private Map<String, MessageBodyEncoder> messageBodyEncoders;
@Override
@SuppressWarnings("unchecked")
@ -62,12 +76,14 @@ public class GatewayModifyResponseGatewayFilter implements GlobalFilter, Ordered
}
Class inClass = String.class;
Class outClass = String.class;
HttpHeaders httpHeaders = new HttpHeaders();
//explicitly add it in this way instead of 'httpHeaders.setContentType(originalResponseContentType)'
//this will prevent exception in case of using non-standard media types like "Content-Type: image"
// explicitly add it in this way instead of
// 'httpHeaders.setContentType(originalResponseContentType)'
// this will prevent exception in case of using non-standard media
// types like "Content-Type: image"
httpHeaders.add(HttpHeaders.CONTENT_TYPE, originalResponseContentType);
ResponseAdapter responseAdapter = new ResponseAdapter(body, httpHeaders);
DefaultClientResponse clientResponse = new DefaultClientResponse(responseAdapter, getExchangeStrategies());
ClientResponse clientResponse = prepareClientResponse(exchange, body, httpHeaders);
//TODO: flux or mono
Mono modifiedBody = clientResponse.bodyToMono(inClass)
@ -80,16 +96,21 @@ public class GatewayModifyResponseGatewayFilter implements GlobalFilter, Ordered
return Mono.just(ret);
});
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, outClass);
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, exchange.getResponse().getHeaders());
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody,
outClass);
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange,
exchange.getResponse().getHeaders());
return bodyInserter.insert(outputMessage, new BodyInserterContext())
.then(Mono.defer(() -> {
Flux<DataBuffer> messageBody = outputMessage.getBody();
Mono<DataBuffer> messageBody = writeBody(getDelegate(),
outputMessage, outClass);
HttpHeaders headers = getDelegate().getHeaders();
if (!headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) {
messageBody = messageBody.doOnNext(data -> headers.setContentLength(data.readableByteCount()));
if (!headers.containsKey(HttpHeaders.TRANSFER_ENCODING)
|| headers.containsKey(HttpHeaders.CONTENT_LENGTH)) {
messageBody = messageBody.doOnNext(data -> headers
.setContentLength(data.readableByteCount()));
}
//TODO: use isStreamingMediaType?
// TODO: fail if isStreamingMediaType?
return getDelegate().writeWith(messageBody);
}));
}
@ -104,20 +125,40 @@ public class GatewayModifyResponseGatewayFilter implements GlobalFilter, Ordered
return chain.filter(exchange.mutate().response(responseDecorator).build());
}
private ExchangeStrategies getExchangeStrategies() {
ExchangeStrategies exchangeStrategies = ExchangeStrategies.withDefaults();
// 修复返回大文本数据报org.springframework.core.io.buffer.DataBufferLimitException: Exceeded limit on max bytes to buffer : 262144
for (HttpMessageReader<?> messageReader : exchangeStrategies.messageReaders()) {
if (messageReader instanceof DecoderHttpMessageReader) {
DecoderHttpMessageReader reader = (DecoderHttpMessageReader) messageReader;
Decoder decoder = reader.getDecoder();
if (decoder instanceof AbstractDataBufferDecoder) {
AbstractDataBufferDecoder dataBufferDecoder = (AbstractDataBufferDecoder)decoder;
dataBufferDecoder.setMaxInMemorySize(maxInMemorySize);
private ClientResponse prepareClientResponse(
ServerWebExchange exchange,
Publisher<? extends DataBuffer> body,
HttpHeaders httpHeaders
) {
ClientResponse.Builder builder;
builder = ClientResponse.create(exchange.getResponse().getStatusCode(), codecConfigurer.getReaders());
return builder.headers(headers -> headers.putAll(httpHeaders))
.body(Flux.from(body)).build();
}
private Mono<DataBuffer> writeBody(ServerHttpResponse httpResponse,
CachedBodyOutputMessage message, Class<?> outClass) {
Mono<DataBuffer> response = DataBufferUtils.join(message.getBody());
if (byte[].class.isAssignableFrom(outClass)) {
return response;
}
List<String> encodingHeaders = httpResponse.getHeaders()
.getOrEmpty(HttpHeaders.CONTENT_ENCODING);
for (String encoding : encodingHeaders) {
MessageBodyEncoder encoder = messageBodyEncoders.get(encoding);
if (encoder != null) {
DataBufferFactory dataBufferFactory = httpResponse.bufferFactory();
response = response.publishOn(Schedulers.parallel()).map(buffer -> {
byte[] encodedResponse = encoder.encode(buffer);
DataBufferUtils.release(buffer);
return encodedResponse;
}).map(dataBufferFactory::wrap);
break;
}
return exchangeStrategies;
}
return response;
}
@Override
@ -164,4 +205,10 @@ public class GatewayModifyResponseGatewayFilter implements GlobalFilter, Ordered
return null;
}
}
@PostConstruct
public void after() {
this.messageBodyEncoders = bodyEncoders == null ? Collections.emptyMap() : bodyEncoders.stream()
.collect(Collectors.toMap(MessageBodyEncoder::encodingType, identity()));
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save