Merge branch 'master' into SpringCloudGateway

# Conflicts:
#	sop-gateway/pom.xml
tanghc 6 years ago
commit 51e17486dd
  1. 7
      README.md
  2. 12
      changelog.md
  3. 55
      doc/docs/_sidebar.md
  4. 10
      doc/docs/files/10085_ISV管理.md
  5. 23
      doc/docs/files/10086_自定义路由.md
  6. 2
      doc/docs/files/10090_路由授权.md
  7. 2
      doc/docs/files/10095_SDK开发.md
  8. 90
      doc/docs/files/10106_nacos注册中心.md
  9. 112
      doc/docs/files/10107_扩展其它注册中心.md
  10. 20
      doc/docs/files/90100_常见问题.md
  11. BIN
      doc/docs/files/images/10085_1.png
  12. BIN
      doc/docs/files/images/10085_2.png
  13. BIN
      doc/docs/files/images/10086_1.png
  14. BIN
      doc/docs/files/images/10086_2.png
  15. 20
      sop-1.11.0.sql
  16. 2
      sop-admin/readme.md
  17. 2
      sop-admin/sop-admin-front/README.md
  18. 22
      sop-admin/sop-admin-server/pom.xml
  19. 12
      sop-admin/sop-admin-server/readme.md
  20. 158
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/IsvApi.java
  21. 46
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/RoleApi.java
  22. 18
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/param/IsvInfoForm.java
  23. 9
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/param/IsvInfoFormAdd.java
  24. 47
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/param/IsvKeysFormUpdate.java
  25. 18
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/param/IsvKeysGen.java
  26. 24
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/param/RoleForm.java
  27. 19
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/param/RolePageParam.java
  28. 11
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/result/IsvAppKeyGenVO.java
  29. 34
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/result/IsvDetailDTO.java
  30. 23
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/result/IsvFormVO.java
  31. 14
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/result/IsvInfoVO.java
  32. 25
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/result/IsvKeysGenVO.java
  33. 38
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/result/IsvKeysVO.java
  34. 8
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/result/RoleVO.java
  35. 4
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/LimitApi.java
  36. 6
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/LimitNewApi.java
  37. 14
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/LogApi.java
  38. 150
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/RouteApi.java
  39. 173
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/ServiceApi.java
  40. 2
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/LimitNewAddParam.java
  41. 2
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/LimitNewUpdateParam.java
  42. 6
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/LogMonitorInstanceAddParam.java
  43. 76
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/RouteAddParam.java
  44. 22
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/RouteDeleteParam.java
  45. 31
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/RouteUpdateParam.java
  46. 21
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/ServiceAddParam.java
  47. 6
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/result/RouteServiceInfo.java
  48. 54
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/result/ServiceInstanceVO.java
  49. 15
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/GatewayRouteDefinition.java
  50. 1
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/IsvRoutePermission.java
  51. 3
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/ServiceRouteInfo.java
  52. 7
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/SopAdminConstants.java
  53. 252
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/ZookeeperContext.java
  54. 33
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/common/BizException.java
  55. 410
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/common/RSATool.java
  56. 26
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/common/ZookeeperOperationException.java
  57. 10
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/common/ZookeeperPathExistException.java
  58. 10
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/common/ZookeeperPathNotExistException.java
  59. 12
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/config/RegistryConfig.java
  60. 12
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/config/WebConfig.java
  61. 6
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/AdminUserInfo.java
  62. 11
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/IsvInfo.java
  63. 57
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/IsvKeys.java
  64. 16
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/mapper/IsvInfoMapper.java
  65. 12
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/mapper/IsvKeysMapper.java
  66. 2
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/RouteConfigService.java
  67. 40
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/RoutePermissionService.java
  68. 4
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/RouteService.java
  69. 21
      sop-admin/sop-admin-server/src/main/resources/application-dev.yml
  70. 2
      sop-admin/sop-admin-server/src/main/resources/public/index.html
  71. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/css/chunk-009073d4.0af16c7e.css
  72. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/css/chunk-25908fca.89ab33e8.css
  73. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/css/chunk-29b24c82.857979fb.css
  74. 0
      sop-admin/sop-admin-server/src/main/resources/public/static/css/chunk-29e7142c.d10599db.css
  75. 0
      sop-admin/sop-admin-server/src/main/resources/public/static/css/chunk-37401378.a43114f3.css
  76. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/css/chunk-6a68a33e.3b12267b.css
  77. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/css/chunk-73b2dcec.99cf6327.css
  78. 2
      sop-admin/sop-admin-server/src/main/resources/public/static/js/app.095e8b9c.js
  79. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-009073d4.28312808.js
  80. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-25908fca.9d8bc0b6.js
  81. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-29b24c82.2b3ac7ca.js
  82. 4
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-29e7142c.994a3ac0.js
  83. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d0aa95b.447435b3.js
  84. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d2085ef.a63a74dc.js
  85. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d221836.574c9ab6.js
  86. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d22c2e3.46f37153.js
  87. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d22c2e3.4a098244.js
  88. 2
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-37401378.4e39ec9b.js
  89. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-4a59cbe4.a6360c68.js
  90. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-4a59cbe4.b5e74cc4.js
  91. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-6a68a33e.b6685cb9.js
  92. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-73b2dcec.14f248eb.js
  93. 45
      sop-admin/sop-admin-server/src/test/java/com/gitee/sop/adminserver/CuratorTest.java
  94. 21
      sop-admin/sop-admin-server/src/test/java/com/gitee/sop/adminserver/IsvInfoMapperTest.java
  95. 2
      sop-admin/sop-admin-server/src/test/java/com/gitee/sop/adminserver/PubPriKeyTest.java
  96. 2
      sop-admin/sop-admin-server/src/test/java/com/gitee/sop/adminserver/SignTest.java
  97. 2
      sop-admin/sop-admin-server/src/test/java/com/gitee/sop/adminserver/SopAdminServerApplicationTests.java
  98. 14
      sop-admin/sop-admin-vue/src/router/index.js
  99. 10
      sop-admin/sop-admin-vue/src/utils/global.js
  100. 210
      sop-admin/sop-admin-vue/src/views/isv/index.vue
  101. Some files were not shown because too many files have changed in this diff Show More

@ -45,9 +45,6 @@ SOP封装了开放平台大部分功能包括:签名验证、统一异常处
- 应用授权 - 应用授权
- 监控日志 - 监控日志
## 后期规划
- 调试工具
## admin界面预览 ## admin界面预览
@ -73,6 +70,10 @@ SOP封装了开放平台大部分功能包括:签名验证、统一异常处
- sop-test:接口调用测试用例 - sop-test:接口调用测试用例
- sop-website:开放平台对应网站,提供文档API等内容 - sop-website:开放平台对应网站,提供文档API等内容
## 分支说明
- develop:日常开发分支
- registry-nacos:nacos作为注册中心
## 相关文档 ## 相关文档

@ -1,5 +1,17 @@
# changelog # changelog
## 1.12.0
- admin后台新增角色管理
- 支持nacos作为注册中心
## 1.11.0
- 秘钥管理改造
- 服务端返回sign
- 新增SDK返回sign处理
- 新增沙箱环境
## 1.10.0 ## 1.10.0
- 新增监控日志 - 新增监控日志

@ -1,28 +1,31 @@
* [首页](/?t=1559803778913) * [首页](/?t=1562549680042)
* 开发文档 * 开发文档
* [快速体验](files/10010_快速体验.md?t=1559803778920) * [快速体验](files/10010_快速体验.md?t=1562549680042)
* [项目接入到SOP](files/10011_项目接入到SOP.md?t=1559803778940) * [项目接入到SOP](files/10011_项目接入到SOP.md?t=1562549680065)
* [新增接口](files/10020_新增接口.md?t=1559803778940) * [新增接口](files/10020_新增接口.md?t=1562549680065)
* [业务参数校验](files/10030_业务参数校验.md?t=1559803778940) * [业务参数校验](files/10030_业务参数校验.md?t=1562549680065)
* [错误处理](files/10040_错误处理.md?t=1559803778940) * [错误处理](files/10040_错误处理.md?t=1562549680065)
* [编写文档](files/10041_编写文档.md?t=1559803778941) * [编写文档](files/10041_编写文档.md?t=1562549680065)
* [接口交互详解](files/10050_接口交互详解.md?t=1559803778941) * [接口交互详解](files/10050_接口交互详解.md?t=1562549680065)
* [easyopen支持](files/10070_easyopen支持.md?t=1559803778941) * [easyopen支持](files/10070_easyopen支持.md?t=1562549680065)
* [使用签名校验工具](files/10080_使用签名校验工具.md?t=1559803778941) * [使用签名校验工具](files/10080_使用签名校验工具.md?t=1562549680066)
* [ISV管理](files/10085_ISV管理.md?t=1559803778941) * [ISV管理](files/10085_ISV管理.md?t=1562549680066)
* [路由授权](files/10090_路由授权.md?t=1559803778941) * [自定义路由](files/10086_自定义路由.md?t=1562549680066)
* [接口限流](files/10092_接口限流.md?t=1559803778941) * [路由授权](files/10090_路由授权.md?t=1562549680066)
* [监控日志](files/10093_监控日志.md?t=1559803778941) * [接口限流](files/10092_接口限流.md?t=1562549680066)
* [SDK开发](files/10095_SDK开发.md?t=1559803778941) * [监控日志](files/10093_监控日志.md?t=1562549680066)
* [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1559803778942) * [SDK开发](files/10095_SDK开发.md?t=1562549680067)
* [应用授权](files/10097_应用授权.md?t=1559803778942) * [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1562549680067)
* [更改数据节点名称](files/10099_更改数据节点名称.md?t=1559803778942) * [应用授权](files/10097_应用授权.md?t=1562549680067)
* [对接前端](files/10100_对接前端.md?t=1559803778942) * [更改数据节点名称](files/10099_更改数据节点名称.md?t=1562549680067)
* [自定义过滤器](files/10102_自定义过滤器.md?t=1559803778942) * [对接前端](files/10100_对接前端.md?t=1562549680067)
* [文件上传](files/10104_文件上传.md?t=1559803778942) * [自定义过滤器](files/10102_自定义过滤器.md?t=1562549680067)
* [文件上传](files/10104_文件上传.md?t=1562549680068)
* [nacos注册中心](files/10106_nacos注册中心.md?t=1562549680068)
* [扩展其它注册中心](files/10107_扩展其它注册中心.md?t=1562549680069)
* 原理分析 * 原理分析
* [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1559803778942) * [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1562549680069)
* [原理分析之路由存储](files/90011_原理分析之路由存储.md?t=1559803778942) * [原理分析之路由存储](files/90011_原理分析之路由存储.md?t=1562549680069)
* [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1559803778942) * [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1562549680069)
* [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1559803778942) * [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1562549680069)
* [常见问题](files/90100_常见问题.md?t=1559803778942) * [常见问题](files/90100_常见问题.md?t=1562549680069)

@ -12,3 +12,13 @@ ISV:独立软体开发商(independent software vendor),即接入方或
界面如下图所示: 界面如下图所示:
![admin预览](images/10085_1.png "10085_1.png") ![admin预览](images/10085_1.png "10085_1.png")
## 秘钥管理
点击操作列的`秘钥管理`,可对ISV的秘钥进行设置。
- 如果采用淘宝开放平台签名方式,签名方式选择`MD5`,如果采用支付宝开放平台签名方式,选择`RSA`
- 如果对接的开发者使用非Java语言,秘钥格式选择`PKCS1`
- 带 ★ 的分配给开发者
![admin预览](images/10085_2.png "10085_2.png")

@ -0,0 +1,23 @@
# 自定义路由
假设有一个非java开发的接口,比如php开发的接口,然后需要接入到SOP,使用接口名版本号进行路由跳转。
操作方式如下:
- 登录sop-admin,服务管理--路由管理
- 新建一个自定义服务:`php-service`
![新建一个自定义服务](images/10086_1.png "10086_1.png")
- 选中php-service,新建一个路由,输入接口名、版本号、uri
![新建一个路由](images/10086_2.png "10086_2.png")
- 请求网关
`http://localhost:8081/api?method=php.goods.list&version=1.0`会跳转到`http://www.xxx.com/api/listGoods.php`
注意:
- php接口返回数据必须是json格式,返回其它内容可能会调用失败
- 只有自定义服务、路由可以删除

@ -9,7 +9,7 @@
假设把路由a,b,c分配给了`VIP角色`,那么具有VIP角色的ISV可以访问a,b,c三个路由。 假设把路由a,b,c分配给了`VIP角色`,那么具有VIP角色的ISV可以访问a,b,c三个路由。
默认情况下,接口访问时公开的,ISV都能访问。如果要设置某个接口需要授权,在`@ApiMapping`注解中指定permission=true。 默认情况下,接口访问时公开的,ISV都能访问。如果要设置某个接口访问权限,在`@ApiMapping`注解中指定permission=true。
如:`@ApiMapping(value = "permission.story.get", permission = true)`。这样该接口是需要经过授权给ISV才能访问的。 如:`@ApiMapping(value = "permission.story.get", permission = true)`。这样该接口是需要经过授权给ISV才能访问的。
重启服务后,登录admin,服务管理-路由列表界面中,操作操作列会出现一个授权按钮,点击出现授权窗口,勾选对应的角色即可完成授权。 重启服务后,登录admin,服务管理-路由列表界面中,操作操作列会出现一个授权按钮,点击出现授权窗口,勾选对应的角色即可完成授权。

@ -86,6 +86,8 @@ protected RequestMethod getRequestMethod() {
} }
``` ```
**建议读请求用GET,写请求用POST**,
### 使用方式 ### 使用方式
```java ```java

@ -0,0 +1,90 @@
# nacos注册中心
使用nacos作为注册中心,源码在`registry-nacos`分支
这里演示如何将默认的eureka注册中心替换成nacos,步骤如下:
- 准备工作
1.安装nacos,前往[最新稳定版本](https://github.com/alibaba/nacos/releases),下载最新版nacos
2.启动nacos服务器,cd nacos/bin
Linux/Unix/Mac,启动命令(standalone代表着单机模式运行,非集群模式):
`sh startup.sh -m standalone`
Windows,启动命令:
`cmd startup.cmd`
或者双击startup.cmd运行文件。
更多访问:https://nacos.io/zh-cn/docs/quick-start.html
- 微服务端修改
1.修改微服务应用pom,打开`sop-example/sop-story/sop-story-web/pom.xml`,注释eureka服务发现依赖,添加nacos服务发现依赖
```xml
<!-- 注册中心【只能用一个,不用的注释掉】 -->
<!-- 使用eureka注册中心
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
-->
<!-- 使用nacos注册中心
版本 0.2.x.RELEASE 对应的是 Spring Boot 2.x 版本,版本 0.1.x.RELEASE 对应的是 Spring Boot 1.x 版本。
https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-alibaba-nacos-discovery
-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>0.2.2.RELEASE</version>
</dependency>
<!-- 注册中心end -->
```
2.yml文件新增nacos配置,并注释掉eureka相关配置
```yaml
spring:
cloud:
# nacos注册中心,和eureka只能用一个
nacos:
discovery:
server-addr: 127.0.0.1:8848
```
- 网关修改
找到`sop-gateway`工程,步骤同上
- SOP-admin修改
修改yml文件,设置nacos服务器地址,`registry.name`填nacos
```yaml
# 注册中心地址,根据实际情况改,这里只是参数,并不会去注册
registry:
eureka-server-addr: http://localhost:1111/eureka/
# nacos服务器地址
nacos-server-addr: 127.0.0.1:8848
# 使用eureka,填:eureka,使用nacos填:nacos
name: nacos
```
- website-server修改
步骤同`SOP-admin修改`
如果要改成consul注册中心,可参照以上步骤。
- 参考资料
1.[nacos介绍及安装](https://nacos.io/zh-cn/docs/quick-start.html)
2.[nacos spring cloud注册发现](https://nacos.io/zh-cn/docs/quick-start-spring-cloud.html)

@ -0,0 +1,112 @@
# 扩展其它注册中心
**注: nacos注册中心已经实现,本篇以nacos为例介绍如何扩展,如果要改成consul,可按照此方式进行修改**
SOP默认使用eureka注册中心,如果要换成nacos注册中心,步骤如下:
- 实现`com.gitee.sop.registryapi.service.RegistryService`接口
1.找到`SOP/sop-common/sop-registry-api`工程,在service.impl包下新建一个类,实现RegistryService接口
```java
public class RegistryServiceNacos implements RegistryService {
@Override
public List<ServiceInfo> listAllService(int pageNo, int pageSize) throws Exception {
// TODO: 返回服务实例
}
@Override
public void onlineInstance(ServiceInstance serviceInstance) throws Exception {
// TODO: 实例上线
}
@Override
public void offlineInstance(ServiceInstance serviceInstance) throws Exception {
// TODO: 实例下线
}
}
```
2.在`com.gitee.sop.registryapi.config.BaseRegistryConfig`中新增
```java
/**
* 当配置了registry.name=nacos生效
*
* @return
*/
@Bean
@ConditionalOnProperty(prefix = "registry", name = "name", havingValue = "nacos")
RegistryService registryServiceNacos() {
return new RegistryServiceNacos();
}
```
其中`@ConditionalOnProperty(prefix = "registry", name = "name", havingValue = "nacos")`
表示`application.properties`配置了`registry.name=nacos`参数才能生效,registry.name=nacos下文会讲到。
- 微服务端修改
1.修改微服务应用pom.xml,注释eureka服务发现依赖,添加nacos服务发现依赖
```xml
<!-- 注册中心【只能用一个,不用的注释掉】 -->
<!-- 使用eureka注册中心
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
-->
<!-- 使用nacos注册中心
版本 0.2.x.RELEASE 对应的是 Spring Boot 2.x 版本,版本 0.1.x.RELEASE 对应的是 Spring Boot 1.x 版本。
https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-alibaba-nacos-discovery
-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>0.2.2.RELEASE</version>
</dependency>
<!-- 注册中心end -->
```
2.yml文件新增nacos配置,并注释掉eureka相关配置
```yaml
spring:
cloud:
# nacos注册中心,和eureka只能用一个
nacos:
discovery:
server-addr: 127.0.0.1:8848
```
- 网关修改
找到`sop-gateway`工程,步骤同上
- SOP-admin修改
修改yml文件,新增nacos服务器地址,`registry.name`填nacos
```yaml
# 注册中心地址,根据实际情况改,这里只是参数,并不会去注册
registry:
# 使用eureka,填:eureka,使用nacos填:nacos
name: nacos
eureka-server-addr: http://localhost:1111/eureka/
nacos-server-addr: 127.0.0.1:8848
```
- website-server修改
步骤同`SOP-admin修改`
- 参考资料
1.[nacos介绍及安装](https://nacos.io/zh-cn/docs/quick-start.html)
2.[nacos spring cloud注册发现](https://nacos.io/zh-cn/docs/quick-start-spring-cloud.html)

@ -28,4 +28,22 @@ public class ZuulConfig extends AlipayZuulConfiguration {
} }
} }
``` ```
## 注册到eureka显示hostname,非ip
```yaml
eureka:
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
```
```xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
</dependency>
```
参考:https://www.jianshu.com/p/5ad8317961b7

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

@ -0,0 +1,20 @@
DROP TABLE IF EXISTS `isv_keys`;
CREATE TABLE `isv_keys` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`app_key` varchar(128) NOT NULL DEFAULT '应用id(交给开发者)',
`sign_type` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1:RSA2,2:MD5',
`secret` varchar(200) NOT NULL DEFAULT '' COMMENT 'sign_type=2时使用',
`key_format` tinyint(4) NOT NULL DEFAULT '1' COMMENT '秘钥格式,1:PKCS8(JAVA适用),2:PKCS1(非JAVA适用)',
`public_key_isv` text NOT NULL COMMENT '开发者生成的公钥',
`private_key_isv` text NOT NULL COMMENT '开发者生成的私钥(交给开发者)',
`public_key_platform` text NOT NULL COMMENT '平台生成的公钥(交给开发者)',
`private_key_platform` text NOT NULL COMMENT '平台生成的私钥',
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_appkey` (`app_key`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='ISV秘钥';
INSERT INTO `sop`.`isv_keys` (`app_key`,`sign_type`, `secret`,`key_format`, `public_key_isv`, `private_key_isv`, `public_key_platform`, `private_key_platform`)
select app_key, sign_type, secret, 1, pub_key,pri_key,'','' from isv_info;

@ -1,5 +1,5 @@
# 后台admin # 后台admin
- sop-admin-front: admin前端layui实现 - sop-admin-front: admin前端layui实现(停止维护,改用sop-admin-vue)
- sop-admin-server: admin服务端 - sop-admin-server: admin服务端
- sop-admin-vue: admin前端vue实现 - sop-admin-vue: admin前端vue实现

@ -1,4 +1,4 @@
# admin前端页面 # admin前端页面(停止维护,改用sop-admin-vue)
做到前后端分离。 做到前后端分离。

@ -13,7 +13,7 @@
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version> <version>2.1.4.RELEASE</version>
<relativePath /> <!-- lookup parent from repository --> <relativePath /> <!-- lookup parent from repository -->
</parent> </parent>
@ -27,12 +27,12 @@
</properties> </properties>
<dependencies> <dependencies>
<!-- 将前端页面打包成webjars,然后打开注释
<dependency> <dependency>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sop-admin-front</artifactId> <artifactId>sop-registry-api</artifactId>
<version>1.0.0-SNAPSHOT</version> <version>1.12.0-SNAPSHOT</version>
</dependency>--> </dependency>
<!-- easyopen starter --> <!-- easyopen starter -->
<dependency> <dependency>
<groupId>net.oschina.durcframework</groupId> <groupId>net.oschina.durcframework</groupId>
@ -78,12 +78,6 @@
</exclusions> </exclusions>
</dependency> </dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>
@ -109,6 +103,12 @@
<version>1.18.4</version> <version>1.18.4</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
<version>2.1.0.RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies> </dependencies>

@ -1,9 +1,7 @@
# easyopen-server # sop admin服务端
启动后,接口url:http://ip:port/api
如果有contextPath的话则是:http://ip:port/contextPath/api
业务类在api下,重点关注这里即可。
- 打开`application-dev.yml`,修改数据库`username/password`
- 运行`SopAdminServerApplication.java`
- 访问:`http://localhost:8082`
登录账号:admin/123456

@ -7,40 +7,50 @@ import com.gitee.easyopen.doc.DataType;
import com.gitee.easyopen.doc.annotation.ApiDoc; import com.gitee.easyopen.doc.annotation.ApiDoc;
import com.gitee.easyopen.doc.annotation.ApiDocField; import com.gitee.easyopen.doc.annotation.ApiDocField;
import com.gitee.easyopen.doc.annotation.ApiDocMethod; import com.gitee.easyopen.doc.annotation.ApiDocMethod;
import com.gitee.easyopen.exception.ApiException;
import com.gitee.easyopen.util.CopyUtil; import com.gitee.easyopen.util.CopyUtil;
import com.gitee.easyopen.util.KeyStore;
import com.gitee.easyopen.util.RSAUtil;
import com.gitee.fastmybatis.core.PageInfo; import com.gitee.fastmybatis.core.PageInfo;
import com.gitee.fastmybatis.core.query.Query; import com.gitee.fastmybatis.core.query.Query;
import com.gitee.fastmybatis.core.query.Sort;
import com.gitee.fastmybatis.core.util.MapperUtil; import com.gitee.fastmybatis.core.util.MapperUtil;
import com.gitee.sop.adminserver.api.IdParam; import com.gitee.sop.adminserver.api.IdParam;
import com.gitee.sop.adminserver.api.isv.param.IsvInfoForm;
import com.gitee.sop.adminserver.api.isv.param.IsvInfoFormAdd; import com.gitee.sop.adminserver.api.isv.param.IsvInfoFormAdd;
import com.gitee.sop.adminserver.api.isv.param.IsvInfoFormUpdate; import com.gitee.sop.adminserver.api.isv.param.IsvInfoFormUpdate;
import com.gitee.sop.adminserver.api.isv.param.IsvKeysFormUpdate;
import com.gitee.sop.adminserver.api.isv.param.IsvKeysGen;
import com.gitee.sop.adminserver.api.isv.param.IsvPageParam; import com.gitee.sop.adminserver.api.isv.param.IsvPageParam;
import com.gitee.sop.adminserver.api.isv.result.IsvFormVO; import com.gitee.sop.adminserver.api.isv.result.IsvDetailDTO;
import com.gitee.sop.adminserver.api.isv.result.IsvVO; import com.gitee.sop.adminserver.api.isv.result.IsvInfoVO;
import com.gitee.sop.adminserver.api.isv.result.IsvKeysGenVO;
import com.gitee.sop.adminserver.api.isv.result.IsvKeysVO;
import com.gitee.sop.adminserver.api.isv.result.RoleVO; import com.gitee.sop.adminserver.api.isv.result.RoleVO;
import com.gitee.sop.adminserver.bean.ChannelMsg; import com.gitee.sop.adminserver.bean.ChannelMsg;
import com.gitee.sop.adminserver.bean.ZookeeperContext; import com.gitee.sop.adminserver.bean.ZookeeperContext;
import com.gitee.sop.adminserver.common.BizException;
import com.gitee.sop.adminserver.common.IdGen; import com.gitee.sop.adminserver.common.IdGen;
import com.gitee.sop.adminserver.common.RSATool;
import com.gitee.sop.adminserver.common.ZookeeperPathNotExistException;
import com.gitee.sop.adminserver.entity.IsvInfo; import com.gitee.sop.adminserver.entity.IsvInfo;
import com.gitee.sop.adminserver.entity.IsvKeys;
import com.gitee.sop.adminserver.entity.PermIsvRole; import com.gitee.sop.adminserver.entity.PermIsvRole;
import com.gitee.sop.adminserver.entity.PermRole; import com.gitee.sop.adminserver.entity.PermRole;
import com.gitee.sop.adminserver.mapper.IsvInfoMapper; import com.gitee.sop.adminserver.mapper.IsvInfoMapper;
import com.gitee.sop.adminserver.mapper.IsvKeysMapper;
import com.gitee.sop.adminserver.mapper.PermIsvRoleMapper; import com.gitee.sop.adminserver.mapper.PermIsvRoleMapper;
import com.gitee.sop.adminserver.mapper.PermRoleMapper; import com.gitee.sop.adminserver.mapper.PermRoleMapper;
import com.gitee.sop.adminserver.service.RoutePermissionService; import com.gitee.sop.adminserver.service.RoutePermissionService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import javax.validation.constraints.NotBlank;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -51,10 +61,21 @@ import java.util.stream.Collectors;
@Slf4j @Slf4j
public class IsvApi { public class IsvApi {
public static final int SIGN_TYPE_RSA2 = 1; public static final byte SIGN_TYPE_RSA = 1;
public static final byte SIGN_TYPE_MD5 = 2;
static Map<String, Byte> SIGN_TYPE_MAP = new HashMap<>();
static {
SIGN_TYPE_MAP.put("rsa", (byte) SIGN_TYPE_RSA);
SIGN_TYPE_MAP.put("md5", (byte) SIGN_TYPE_MD5);
}
@Autowired @Autowired
IsvInfoMapper isvInfoMapper; IsvInfoMapper isvInfoMapper;
@Autowired
IsvKeysMapper isvKeysMapper;
@Autowired @Autowired
PermIsvRoleMapper permIsvRoleMapper; PermIsvRoleMapper permIsvRoleMapper;
@ -64,25 +85,29 @@ public class IsvApi {
@Autowired @Autowired
RoutePermissionService routePermissionService; RoutePermissionService routePermissionService;
@Value("${sop.sign-type}")
private String sopSignType;
@Api(name = "isv.info.page") @Api(name = "isv.info.page")
@ApiDocMethod(description = "isv列表", results = { @ApiDocMethod(description = "isv列表", results = {
@ApiDocField(name = "pageIndex", description = "第几页", dataType = DataType.INT, example = "1"), @ApiDocField(name = "pageIndex", description = "第几页", dataType = DataType.INT, example = "1"),
@ApiDocField(name = "pageSize", description = "每页几条数据", dataType = DataType.INT, example = "10"), @ApiDocField(name = "pageSize", description = "每页几条数据", dataType = DataType.INT, example = "10"),
@ApiDocField(name = "total", description = "每页几条数据", dataType = DataType.LONG, example = "100"), @ApiDocField(name = "total", description = "每页几条数据", dataType = DataType.LONG, example = "100"),
@ApiDocField(name = "rows", description = "数据", dataType = DataType.ARRAY, elementClass = IsvVO.class) @ApiDocField(name = "rows", description = "数据", dataType = DataType.ARRAY, elementClass = IsvInfoVO.class)
}) })
PageInfo<IsvVO> pageIsv(IsvPageParam param) { PageInfo<IsvInfoVO> pageIsv(IsvPageParam param) {
Query query = Query.build(param); Query query = Query.build(param);
query.orderby("id", Sort.DESC);
PageInfo<IsvInfo> pageInfo = MapperUtil.query(isvInfoMapper, query); PageInfo<IsvInfo> pageInfo = MapperUtil.query(isvInfoMapper, query);
List<IsvInfo> list = pageInfo.getList(); List<IsvInfo> list = pageInfo.getList();
List<IsvVO> retList = list.stream() List<IsvInfoVO> retList = list.stream()
.map(isvInfo -> { .map(isvInfo -> {
return buildIsvVO(isvInfo); return buildIsvVO(isvInfo);
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());
PageInfo<IsvVO> pageInfoRet = new PageInfo<>(); PageInfo<IsvInfoVO> pageInfoRet = new PageInfo<>();
pageInfoRet.setTotal(pageInfo.getTotal()); pageInfoRet.setTotal(pageInfo.getTotal());
pageInfoRet.setList(retList); pageInfoRet.setList(retList);
@ -91,16 +116,31 @@ public class IsvApi {
@Api(name = "isv.info.get") @Api(name = "isv.info.get")
@ApiDocMethod(description = "获取isv") @ApiDocMethod(description = "获取isv")
IsvVO getIsvVO(IdParam param) { IsvInfoVO getIsvVO(IdParam param) {
IsvInfo isvInfo = isvInfoMapper.getById(param.getId()); IsvInfo isvInfo = isvInfoMapper.getById(param.getId());
return buildIsvVO(isvInfo); return buildIsvVO(isvInfo);
} }
private IsvVO buildIsvVO(IsvInfo isvInfo) { @Api(name = "isv.keys.get")
@ApiDocMethod(description = "获取isv2")
IsvKeysVO getIsvKeys(@NotBlank(message = "appKey不能为空")
@ApiDocField(description = "appKey")
String appKey) {
IsvKeys isvKeys = isvKeysMapper.getByColumn("app_key", appKey);
IsvKeysVO isvDetailVO = new IsvKeysVO();
if (isvKeys != null) {
CopyUtil.copyProperties(isvKeys, isvDetailVO);
}
isvDetailVO.setAppKey(appKey);
isvDetailVO.setSignType(getSignType());
return isvDetailVO;
}
private IsvInfoVO buildIsvVO(IsvInfo isvInfo) {
if (isvInfo == null) { if (isvInfo == null) {
return null; return null;
} }
IsvVO vo = new IsvVO(); IsvInfoVO vo = new IsvInfoVO();
CopyUtil.copyProperties(isvInfo, vo); CopyUtil.copyProperties(isvInfo, vo);
vo.setRoleList(this.buildIsvRole(isvInfo)); vo.setRoleList(this.buildIsvRole(isvInfo));
return vo; return vo;
@ -132,72 +172,110 @@ public class IsvApi {
@ApiDocMethod(description = "添加isv") @ApiDocMethod(description = "添加isv")
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void addIsv(IsvInfoFormAdd param) throws Exception { public void addIsv(IsvInfoFormAdd param) throws Exception {
if (isvInfoMapper.getByColumn("app_key", param.getAppKey()) != null) { String appKey = new SimpleDateFormat("yyyyMMdd").format(new Date()) + IdGen.nextId();
throw new ApiException("appKey已存在");
}
formatForm(param);
IsvInfo rec = new IsvInfo(); IsvInfo rec = new IsvInfo();
rec.setAppKey(appKey);
CopyUtil.copyPropertiesIgnoreNull(param, rec); CopyUtil.copyPropertiesIgnoreNull(param, rec);
isvInfoMapper.saveIgnoreNull(rec); isvInfoMapper.saveIgnoreNull(rec);
if (CollectionUtils.isNotEmpty(param.getRoleCode())) { if (CollectionUtils.isNotEmpty(param.getRoleCode())) {
this.saveIsvRole(rec, param.getRoleCode()); this.saveIsvRole(rec, param.getRoleCode());
} }
IsvKeysGenVO isvKeysGenVO = this.createIsvKeys();
IsvKeys isvKeys = new IsvKeys();
isvKeys.setAppKey(appKey);
isvKeys.setSignType(getSignType());
CopyUtil.copyPropertiesIgnoreNull(isvKeysGenVO, isvKeys);
isvKeysMapper.saveIgnoreNull(isvKeys);
this.sendChannelMsg(rec); this.sendChannelMsg(rec.getAppKey());
}
private byte getSignType() {
return SIGN_TYPE_MAP.getOrDefault(sopSignType, SIGN_TYPE_RSA);
} }
@Api(name = "isv.info.update") @Api(name = "isv.info.update")
@ApiDocMethod(description = "修改isv") @ApiDocMethod(description = "修改isv")
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void updateIsv(IsvInfoFormUpdate param) { public void updateIsv(IsvInfoFormUpdate param) {
formatForm(param);
IsvInfo rec = isvInfoMapper.getById(param.getId()); IsvInfo rec = isvInfoMapper.getById(param.getId());
CopyUtil.copyPropertiesIgnoreNull(param, rec); CopyUtil.copyPropertiesIgnoreNull(param, rec);
isvInfoMapper.updateIgnoreNull(rec); isvInfoMapper.updateIgnoreNull(rec);
this.saveIsvRole(rec, param.getRoleCode()); this.saveIsvRole(rec, param.getRoleCode());
this.sendChannelMsg(rec); this.sendChannelMsg(rec.getAppKey());
} }
private void formatForm(IsvInfoForm form) { @Api(name = "isv.keys.update")
if (form.getSignType() == SIGN_TYPE_RSA2) { @ApiDocMethod(description = "修改isv")
form.setSecret(""); @Transactional(rollbackFor = Exception.class)
public void updateIsvKeys(IsvKeysFormUpdate param) {
IsvKeys isvKeys = isvKeysMapper.getByColumn("app_key", param.getAppKey());
if (isvKeys == null) {
isvKeys = new IsvKeys();
CopyUtil.copyPropertiesIgnoreNull(param, isvKeys);
isvKeys.setSignType(getSignType());
isvKeysMapper.saveIgnoreNull(isvKeys);
} else { } else {
form.setPubKey(""); CopyUtil.copyPropertiesIgnoreNull(param, isvKeys);
form.setPriKey(""); isvKeysMapper.updateIgnoreNull(isvKeys);
} }
this.sendChannelMsg(isvKeys.getAppKey());
} }
private void sendChannelMsg(IsvInfo rec) { private void sendChannelMsg(String appKey) {
ChannelMsg channelMsg = new ChannelMsg("update", rec); IsvDetailDTO isvDetail = isvInfoMapper.getIsvDetail(appKey);
if (isvDetail == null) {
return;
}
ChannelMsg channelMsg = new ChannelMsg("update", isvDetail);
String path = ZookeeperContext.getIsvInfoChannelPath(); String path = ZookeeperContext.getIsvInfoChannelPath();
String data = JSON.toJSONString(channelMsg); String data = JSON.toJSONString(channelMsg);
try { try {
log.info("消息推送--ISV信息(update), path:{}, data:{}", path, data); log.info("消息推送--ISV信息(update), path:{}, data:{}", path, data);
ZookeeperContext.updatePathData(path, data); ZookeeperContext.updatePathData(path, data);
} catch (Exception e) { } catch (ZookeeperPathNotExistException e) {
log.error("发送isvChannelMsg失败, path:{}, msg:{}", path, data, e); log.error("发送isvChannelMsg失败, path:{}, msg:{}", path, data, e);
throw new ApiException("保存失败,请查看日志"); throw new BizException("路径不存在");
} }
} }
@Api(name = "isv.form.gen") private IsvKeysGenVO createIsvKeys() throws Exception {
@ApiDocMethod(description = "isv表单内容一键生成") IsvKeysGenVO isvFormVO = new IsvKeysGenVO();
IsvFormVO createIsvForm() throws Exception {
IsvFormVO isvFormVO = new IsvFormVO();
String appKey = new SimpleDateFormat("yyyyMMdd").format(new Date()) + IdGen.nextId();
String secret = IdGen.uuid(); String secret = IdGen.uuid();
isvFormVO.setAppKey(appKey);
isvFormVO.setSecret(secret); isvFormVO.setSecret(secret);
KeyStore keyStore = RSAUtil.createKeys(); RSATool rsaToolIsv = new RSATool(RSATool.KeyFormat.PKCS8, RSATool.KeyLength.LENGTH_2048);
isvFormVO.setPubKey(keyStore.getPublicKey()); RSATool.KeyStore keyStoreIsv = rsaToolIsv.createKeys();
isvFormVO.setPriKey(keyStore.getPrivateKey()); isvFormVO.setPublicKeyIsv(keyStoreIsv.getPublicKey());
isvFormVO.setPrivateKeyIsv(keyStoreIsv.getPrivateKey());
RSATool rsaToolPlatform = new RSATool(RSATool.KeyFormat.PKCS8, RSATool.KeyLength.LENGTH_2048);
RSATool.KeyStore keyStorePlatform = rsaToolPlatform.createKeys();
isvFormVO.setPublicKeyPlatform(keyStorePlatform.getPublicKey());
isvFormVO.setPrivateKeyPlatform(keyStorePlatform.getPrivateKey());
return isvFormVO; return isvFormVO;
} }
@Api(name = "isv.keys.gen")
@ApiDocMethod(description = "生成公私钥")
RSATool.KeyStore createPubPriKey(IsvKeysGen param) throws Exception {
RSATool.KeyFormat format = RSATool.KeyFormat.PKCS8;
Byte keyFormat = param.getKeyFormat();
if (keyFormat != null && keyFormat == 2) {
format = RSATool.KeyFormat.PKCS1;
}
RSATool rsaTool = new RSATool(format, RSATool.KeyLength.LENGTH_2048);
return rsaTool.createKeys();
}
@Api(name = "isv.secret.gen")
@ApiDocMethod(description = "生成MD秘钥")
String createSecret() throws Exception {
return IdGen.uuid();
}
void saveIsvRole(IsvInfo isvInfo, List<String> roleCodeList) { void saveIsvRole(IsvInfo isvInfo, List<String> roleCodeList) {
Query query = new Query(); Query query = new Query();
@ -222,7 +300,7 @@ public class IsvApi {
routePermissionService.sendIsvRolePermissionToZookeeper(isvInfo.getAppKey(), roleCodeList); routePermissionService.sendIsvRolePermissionToZookeeper(isvInfo.getAppKey(), roleCodeList);
} catch (Exception e) { } catch (Exception e) {
log.error("保存到zookeeper中失败,isvInfo:{}, roleCodeList:{}", isvInfo, roleCodeList); log.error("保存到zookeeper中失败,isvInfo:{}, roleCodeList:{}", isvInfo, roleCodeList);
throw new ApiException("保存失败,请查看日志"); throw new BizException("同步zookeeper失败,请查看网关日志");
} }
} }
} }

@ -3,14 +3,20 @@ package com.gitee.sop.adminserver.api.isv;
import com.gitee.easyopen.annotation.Api; import com.gitee.easyopen.annotation.Api;
import com.gitee.easyopen.annotation.ApiService; import com.gitee.easyopen.annotation.ApiService;
import com.gitee.easyopen.doc.annotation.ApiDoc; import com.gitee.easyopen.doc.annotation.ApiDoc;
import com.gitee.easyopen.doc.annotation.ApiDocMethod;
import com.gitee.easyopen.util.CopyUtil; import com.gitee.easyopen.util.CopyUtil;
import com.gitee.fastmybatis.core.PageInfo;
import com.gitee.fastmybatis.core.query.Query; import com.gitee.fastmybatis.core.query.Query;
import com.gitee.fastmybatis.core.query.Sort; import com.gitee.fastmybatis.core.query.Sort;
import com.gitee.fastmybatis.core.support.PageEasyui; import com.gitee.fastmybatis.core.support.PageEasyui;
import com.gitee.fastmybatis.core.util.MapperUtil; import com.gitee.fastmybatis.core.util.MapperUtil;
import com.gitee.sop.adminserver.api.isv.param.RoleForm;
import com.gitee.sop.adminserver.api.isv.param.RolePageParam;
import com.gitee.sop.adminserver.api.isv.result.RoleVO; import com.gitee.sop.adminserver.api.isv.result.RoleVO;
import com.gitee.sop.adminserver.common.BizException;
import com.gitee.sop.adminserver.entity.PermRole;
import com.gitee.sop.adminserver.entity.PermRolePermission;
import com.gitee.sop.adminserver.mapper.PermRoleMapper; import com.gitee.sop.adminserver.mapper.PermRoleMapper;
import com.gitee.sop.adminserver.mapper.PermRolePermissionMapper;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -28,6 +34,9 @@ public class RoleApi {
@Autowired @Autowired
PermRoleMapper permRoleMapper; PermRoleMapper permRoleMapper;
@Autowired
PermRolePermissionMapper permRolePermissionMapper;
@Api(name = "role.listall") @Api(name = "role.listall")
List<RoleVO> roleListall() { List<RoleVO> roleListall() {
Query query = new Query(); Query query = new Query();
@ -40,4 +49,39 @@ public class RoleApi {
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@ApiDocMethod(description = "获取角色,分页")
@Api(name = "role.page")
PageEasyui<RoleVO> pageRole(RolePageParam rolePage) {
Query query = Query.build(rolePage);
return MapperUtil.queryForEasyuiDatagrid(permRoleMapper, query, RoleVO.class);
}
@Api(name = "role.add")
void addRole(RoleForm roleForm) {
PermRole rec = permRoleMapper.getByColumn("role_code", roleForm.getRoleCode());
if (rec != null) {
throw new BizException("角色码已存在");
}
PermRole permRole = new PermRole();
CopyUtil.copyPropertiesIgnoreNull(roleForm, permRole);
permRoleMapper.saveIgnoreNull(permRole);
}
@Api(name = "role.update")
void updateRole(RoleForm roleForm) {
PermRole rec = permRoleMapper.getById(roleForm.getId());
rec.setDescription(roleForm.getDescription());
permRoleMapper.updateIgnoreNull(rec);
}
@Api(name = "role.del")
void delRole(long id) {
PermRole rec = permRoleMapper.getById(id);
PermRolePermission rolePermission = permRolePermissionMapper.getByColumn("role_code", rec.getRoleCode());
if (rolePermission != null) {
throw new BizException("该角色已使用,无法删除");
}
permRoleMapper.deleteById(id);
}
} }

@ -2,9 +2,7 @@ package com.gitee.sop.adminserver.api.isv.param;
import com.gitee.easyopen.doc.annotation.ApiDocField; import com.gitee.easyopen.doc.annotation.ApiDocField;
import lombok.Data; import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotNull;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -14,22 +12,6 @@ import java.util.List;
@Data @Data
public class IsvInfoForm { public class IsvInfoForm {
@NotNull(message = "signType不能为null")
private Byte signType;
/** secret, 数据库字段:secret */
@ApiDocField(description = "secret")
@Length(max = 100,message = "secret长度不能超过100")
private String secret = "";
/** 公钥, 数据库字段:pub_key */
@ApiDocField(description = "pubKey")
private String pubKey;
/** 私钥, 数据库字段:pri_key */
@ApiDocField(description = "priKey")
private String priKey;
/** 0启用,1禁用, 数据库字段:status */ /** 0启用,1禁用, 数据库字段:status */
@ApiDocField(description = "状态:0:启用,1:禁用") @ApiDocField(description = "状态:0:启用,1:禁用")
private Byte status = 0; private Byte status = 0;

@ -1,11 +1,7 @@
package com.gitee.sop.adminserver.api.isv.param; package com.gitee.sop.adminserver.api.isv.param;
import com.gitee.easyopen.doc.annotation.ApiDocField;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotBlank;
/** /**
* @author tanghc * @author tanghc
@ -13,9 +9,4 @@ import javax.validation.constraints.NotBlank;
@Getter @Getter
@Setter @Setter
public class IsvInfoFormAdd extends IsvInfoForm { public class IsvInfoFormAdd extends IsvInfoForm {
/** appKey, 数据库字段:app_key */
@ApiDocField(description = "appKey", required = true)
@NotBlank(message = "appKey不能为空")
@Length(max = 100,message = "appKey长度不能超过100")
private String appKey;
} }

@ -0,0 +1,47 @@
package com.gitee.sop.adminserver.api.isv.param;
import com.gitee.easyopen.doc.annotation.ApiDocField;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
/**
* @author tanghc
*/
@Data
public class IsvKeysFormUpdate {
/** appKey, 数据库字段:app_key */
@ApiDocField(description = "appKey", example = "aaaa")
@NotBlank(message = "appKey不能为空")
@Length(max = 100,message = "appKey长度不能超过100")
private String appKey;
/** secret, 数据库字段:secret */
@ApiDocField(description = "secret", example = "bbbb")
private String secret;
/** 秘钥格式,1:PKCS8(JAVA适用),2:PKCS1(非JAVA适用), 数据库字段:key_format */
@ApiDocField(description = "秘钥格式,1:PKCS8(JAVA适用),2:PKCS1(非JAVA适用)", example = "1")
@Min(value = 1, message = "秘钥格式错误")
@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;
/** 平台生成的私钥, 数据库字段:private_key_platform */
@ApiDocField(description = "平台生成的私钥")
private String privateKeyPlatform;
}

@ -0,0 +1,18 @@
package com.gitee.sop.adminserver.api.isv.param;
import com.gitee.easyopen.doc.annotation.ApiDocField;
import lombok.Data;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
/**
* @author tanghc
*/
@Data
public class IsvKeysGen {
@ApiDocField(description = "秘钥格式,1:PKCS8(JAVA适用),2:PKCS1(非JAVA适用)", example = "1")
@Min(value = 1, message = "秘钥格式错误")
@Max(value = 2, message = "秘钥格式错误")
private Byte keyFormat;
}

@ -0,0 +1,24 @@
package com.gitee.sop.adminserver.api.isv.param;
import com.gitee.easyopen.doc.annotation.ApiDocField;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotBlank;
/**
* @author tanghc
*/
@Data
public class RoleForm {
@ApiDocField(description = "id")
private Long id;
@ApiDocField(description = "角色码")
@NotBlank(message = "roleCode不能为空")
@Length(max = 64)
private String roleCode;
@ApiDocField(description = "描述")
private String description;
}

@ -0,0 +1,19 @@
package com.gitee.sop.adminserver.api.isv.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 RolePageParam extends PageParam {
@ApiDocField(description = "角色码")
@Condition(operator = Operator.like)
private String roleCode;
}

@ -0,0 +1,11 @@
package com.gitee.sop.adminserver.api.isv.result;
import lombok.Data;
/**
* @author tanghc
*/
@Data
public class IsvAppKeyGenVO {
private String appKey;
}

@ -0,0 +1,34 @@
package com.gitee.sop.adminserver.api.isv.result;
import com.gitee.easyopen.doc.annotation.ApiDocField;
import lombok.Data;
/**
* @author tanghc
*/
@Data
public class IsvDetailDTO {
/** appKey, 数据库字段:app_key */
@ApiDocField(description = "appKey", example = "aaaa")
private String appKey;
/** 0启用,1禁用, 数据库字段:status */
@ApiDocField(description = "状态:0启用,1禁用")
private Byte status;
/** secret, 数据库字段:secret */
@ApiDocField(description = "secret", example = "bbbb")
private String secret;
/** 开发者生成的公钥, 数据库字段:public_key_isv */
@ApiDocField(description = "开发者生成的公钥")
private String publicKeyIsv;
/** 平台生成的私钥, 数据库字段:private_key_platform */
@ApiDocField(description = "平台生成的私钥")
private String privateKeyPlatform;
@ApiDocField(description = "签名类型:1:RSA2,2:MD5")
private Byte signType;
}

@ -1,23 +0,0 @@
package com.gitee.sop.adminserver.api.isv.result;
import com.gitee.easyopen.doc.annotation.ApiDocField;
import lombok.Data;
/**
* @author tanghc
*/
@Data
public class IsvFormVO {
@ApiDocField(description = "appKey", example = "aaaa")
private String appKey;
@ApiDocField(description = "secret", example = "bbbb")
private String secret;
@ApiDocField(description = "pubKey")
private String pubKey;
@ApiDocField(description = "priKey")
private String priKey;
}

@ -10,7 +10,7 @@ import java.util.List;
* @author thc * @author thc
*/ */
@Data @Data
public class IsvVO { public class IsvInfoVO {
/** 数据库字段:id */ /** 数据库字段:id */
@ApiDocField(description = "id", example = "1") @ApiDocField(description = "id", example = "1")
private Long id; private Long id;
@ -19,18 +19,6 @@ public class IsvVO {
@ApiDocField(description = "appKey", example = "aaaa") @ApiDocField(description = "appKey", example = "aaaa")
private String appKey; private String appKey;
/** secret, 数据库字段:secret */
@ApiDocField(description = "secret", example = "bbbb")
private String secret;
/** 公钥, 数据库字段:pub_key */
@ApiDocField(description = "pubKey")
private String pubKey;
/** 私钥, 数据库字段:pri_key */
@ApiDocField(description = "priKey")
private String priKey;
/** 0启用,1禁用, 数据库字段:status */ /** 0启用,1禁用, 数据库字段:status */
@ApiDocField(description = "状态:0启用,1禁用") @ApiDocField(description = "状态:0启用,1禁用")
private Byte status; private Byte status;

@ -0,0 +1,25 @@
package com.gitee.sop.adminserver.api.isv.result;
import lombok.Data;
/**
* @author tanghc
*/
@Data
public class IsvKeysGenVO {
/** secret, 数据库字段:secret */
private String secret;
/** 开发者生成的公钥, 数据库字段:public_key_isv */
private String publicKeyIsv;
/** 开发者生成的私钥(交给开发者), 数据库字段:private_key_isv */
private String privateKeyIsv;
/** 平台生成的公钥(交给开发者), 数据库字段:public_key_platform */
private String publicKeyPlatform;
/** 平台生成的私钥, 数据库字段:private_key_platform */
private String privateKeyPlatform;
}

@ -0,0 +1,38 @@
package com.gitee.sop.adminserver.api.isv.result;
import com.gitee.easyopen.doc.annotation.ApiDocField;
import lombok.Data;
/**
* @author thc
*/
@Data
public class IsvKeysVO {
/** appKey, 数据库字段:app_key */
@ApiDocField(description = "appKey", example = "aaaa")
private String appKey;
/** secret, 数据库字段:secret */
@ApiDocField(description = "secret", example = "bbbb")
private String secret;
/** 秘钥格式,1:PKCS8(JAVA适用),2:PKCS1(非JAVA适用), 数据库字段:key_format */
private Byte keyFormat = 1;
/** 开发者生成的公钥, 数据库字段:public_key_isv */
private String publicKeyIsv;
/** 开发者生成的私钥(交给开发者), 数据库字段:private_key_isv */
private String privateKeyIsv;
/** 平台生成的公钥(交给开发者), 数据库字段:public_key_platform */
private String publicKeyPlatform;
/** 平台生成的私钥, 数据库字段:private_key_platform */
private String privateKeyPlatform;
@ApiDocField(description = "签名类型:1:RSA2,2:MD5")
private Byte signType = 1;
}

@ -10,10 +10,18 @@ import java.util.Date;
*/ */
@Data @Data
public class RoleVO { public class RoleVO {
@ApiDocField(description = "id")
private Long id; private Long id;
@ApiDocField(description = "角色码") @ApiDocField(description = "角色码")
private String roleCode; private String roleCode;
@ApiDocField(description = "描述") @ApiDocField(description = "描述")
private String description; private String description;
@ApiDocField(description = "创建时间")
private Date gmtCreate; private Date gmtCreate;
@ApiDocField(description = "修改时间")
private Date gmtModified;
} }

@ -4,7 +4,6 @@ import com.gitee.easyopen.annotation.Api;
import com.gitee.easyopen.annotation.ApiService; import com.gitee.easyopen.annotation.ApiService;
import com.gitee.easyopen.doc.annotation.ApiDoc; import com.gitee.easyopen.doc.annotation.ApiDoc;
import com.gitee.easyopen.doc.annotation.ApiDocMethod; import com.gitee.easyopen.doc.annotation.ApiDocMethod;
import com.gitee.easyopen.exception.ApiException;
import com.gitee.easyopen.util.CopyUtil; import com.gitee.easyopen.util.CopyUtil;
import com.gitee.fastmybatis.core.query.Query; import com.gitee.fastmybatis.core.query.Query;
import com.gitee.sop.adminserver.api.service.param.LimitParam; import com.gitee.sop.adminserver.api.service.param.LimitParam;
@ -12,6 +11,7 @@ import com.gitee.sop.adminserver.api.service.param.RouteSearchParam;
import com.gitee.sop.adminserver.api.service.result.LimitVO; import com.gitee.sop.adminserver.api.service.result.LimitVO;
import com.gitee.sop.adminserver.bean.GatewayRouteDefinition; import com.gitee.sop.adminserver.bean.GatewayRouteDefinition;
import com.gitee.sop.adminserver.bean.RouteConfigDto; import com.gitee.sop.adminserver.bean.RouteConfigDto;
import com.gitee.sop.adminserver.common.BizException;
import com.gitee.sop.adminserver.common.LimitEnum; import com.gitee.sop.adminserver.common.LimitEnum;
import com.gitee.sop.adminserver.entity.ConfigRouteLimit; import com.gitee.sop.adminserver.entity.ConfigRouteLimit;
import com.gitee.sop.adminserver.mapper.ConfigRouteLimitMapper; import com.gitee.sop.adminserver.mapper.ConfigRouteLimitMapper;
@ -118,7 +118,7 @@ public class LimitApi {
routeConfigService.sendRouteConfigMsg(routeConfigDto); routeConfigService.sendRouteConfigMsg(routeConfigDto);
} catch (Exception e) { } catch (Exception e) {
log.error("推送限流消息错误, param:{}", param, e); log.error("推送限流消息错误, param:{}", param, e);
throw new ApiException("修改失败,请查看日志"); throw new BizException("修改失败,请查看日志");
} }
} }
} }

@ -4,7 +4,6 @@ import com.gitee.easyopen.annotation.Api;
import com.gitee.easyopen.annotation.ApiService; import com.gitee.easyopen.annotation.ApiService;
import com.gitee.easyopen.doc.annotation.ApiDoc; import com.gitee.easyopen.doc.annotation.ApiDoc;
import com.gitee.easyopen.doc.annotation.ApiDocMethod; import com.gitee.easyopen.doc.annotation.ApiDocMethod;
import com.gitee.easyopen.exception.ApiException;
import com.gitee.easyopen.util.CopyUtil; import com.gitee.easyopen.util.CopyUtil;
import com.gitee.fastmybatis.core.PageInfo; import com.gitee.fastmybatis.core.PageInfo;
import com.gitee.fastmybatis.core.query.Query; import com.gitee.fastmybatis.core.query.Query;
@ -15,6 +14,7 @@ import com.gitee.sop.adminserver.api.service.param.LimitNewParam;
import com.gitee.sop.adminserver.api.service.param.LimitNewUpdateParam; import com.gitee.sop.adminserver.api.service.param.LimitNewUpdateParam;
import com.gitee.sop.adminserver.api.service.result.LimitNewVO; import com.gitee.sop.adminserver.api.service.result.LimitNewVO;
import com.gitee.sop.adminserver.bean.ConfigLimitDto; import com.gitee.sop.adminserver.bean.ConfigLimitDto;
import com.gitee.sop.adminserver.common.BizException;
import com.gitee.sop.adminserver.entity.ConfigLimit; import com.gitee.sop.adminserver.entity.ConfigLimit;
import com.gitee.sop.adminserver.mapper.ConfigLimitMapper; import com.gitee.sop.adminserver.mapper.ConfigLimitMapper;
import com.gitee.sop.adminserver.service.RouteConfigService; import com.gitee.sop.adminserver.service.RouteConfigService;
@ -65,7 +65,7 @@ public class LimitNewApi {
routeConfigService.sendLimitConfigMsg(configLimitDto); routeConfigService.sendLimitConfigMsg(configLimitDto);
} catch (Exception e) { } catch (Exception e) {
log.error("推送限流消息错误, param:{}", param, e); log.error("推送限流消息错误, param:{}", param, e);
throw new ApiException("新增失败,请查看日志"); throw new BizException("新增失败,请查看日志");
} }
} }
@ -87,7 +87,7 @@ public class LimitNewApi {
routeConfigService.sendLimitConfigMsg(configLimitDto); routeConfigService.sendLimitConfigMsg(configLimitDto);
} catch (Exception e) { } catch (Exception e) {
log.error("推送限流消息错误, param:{}", param, e); log.error("推送限流消息错误, param:{}", param, e);
throw new ApiException("修改失败,请查看日志"); throw new BizException("修改失败,请查看日志");
} }
} }

@ -6,11 +6,11 @@ import com.gitee.easyopen.annotation.Api;
import com.gitee.easyopen.annotation.ApiService; import com.gitee.easyopen.annotation.ApiService;
import com.gitee.easyopen.doc.annotation.ApiDoc; import com.gitee.easyopen.doc.annotation.ApiDoc;
import com.gitee.easyopen.doc.annotation.ApiDocMethod; import com.gitee.easyopen.doc.annotation.ApiDocMethod;
import com.gitee.easyopen.exception.ApiException;
import com.gitee.easyopen.verify.DefaultMd5Verifier; import com.gitee.easyopen.verify.DefaultMd5Verifier;
import com.gitee.fastmybatis.core.query.Query; import com.gitee.fastmybatis.core.query.Query;
import com.gitee.sop.adminserver.api.service.param.LogMonitorInstanceAddParam; import com.gitee.sop.adminserver.api.service.param.LogMonitorInstanceAddParam;
import com.gitee.sop.adminserver.api.service.result.LogMonitorInstanceVO; import com.gitee.sop.adminserver.api.service.result.LogMonitorInstanceVO;
import com.gitee.sop.adminserver.common.BizException;
import com.gitee.sop.adminserver.common.QueryUtil; import com.gitee.sop.adminserver.common.QueryUtil;
import com.gitee.sop.adminserver.entity.ConfigCommon; import com.gitee.sop.adminserver.entity.ConfigCommon;
import com.gitee.sop.adminserver.mapper.ConfigCommonMapper; import com.gitee.sop.adminserver.mapper.ConfigCommonMapper;
@ -80,7 +80,7 @@ public class LogApi {
} }
} catch (Exception e) { } catch (Exception e) {
log.error("获取日志信息出错", e); log.error("获取日志信息出错", e);
throw new ApiException("获取日志信息出错"); logMonitorInstanceVOParent.setMonitorName(logMonitorInstanceVOParent.getMonitorName() + "(请求出错)");
} }
} }
Collections.sort(ret, Comparator.comparing(LogMonitorInstanceVO::getCount)); Collections.sort(ret, Comparator.comparing(LogMonitorInstanceVO::getCount));
@ -98,7 +98,7 @@ public class LogApi {
String ipPort = configCommon.getConfigKey(); String ipPort = configCommon.getConfigKey();
this.requestLogServer(ipPort, "clearErrors"); this.requestLogServer(ipPort, "clearErrors");
} catch (Exception e) { } catch (Exception e) {
throw new ApiException("清除失败"); throw new BizException("清除失败");
} }
} }
@ -114,7 +114,7 @@ public class LogApi {
@Api(name = "monitor.instance.add") @Api(name = "monitor.instance.add")
@ApiDocMethod(description = "添加监控实例") @ApiDocMethod(description = "添加监控实例")
void addServiceInstance(LogMonitorInstanceAddParam param) { void addServiceInstance(LogMonitorInstanceAddParam param) {
String ipPort = param.getIp() + ":" + param.getPort(); String ipPort = param.getIpPort();
this.checkInstance(ipPort); this.checkInstance(ipPort);
Query query = new Query(); Query query = new Query();
@ -122,7 +122,7 @@ public class LogApi {
.eq("config_key", ipPort); .eq("config_key", ipPort);
ConfigCommon rec = configCommonMapper.getByQuery(query); ConfigCommon rec = configCommonMapper.getByQuery(query);
if (rec != null) { if (rec != null) {
throw new ApiException("该实例已添加"); throw new BizException("该实例已添加");
} }
ConfigCommon configCommon = new ConfigCommon(); ConfigCommon configCommon = new ConfigCommon();
configCommon.setConfigGroup(LOG_MONITOR_INSTANCE); configCommon.setConfigGroup(LOG_MONITOR_INSTANCE);
@ -137,11 +137,11 @@ public class LogApi {
JSONObject jsonObject = JSON.parseObject(json); JSONObject jsonObject = JSON.parseObject(json);
if (!CODE_SUCCESS.equals(jsonObject.getString("code"))) { if (!CODE_SUCCESS.equals(jsonObject.getString("code"))) {
log.error("请求结果:{}", json); log.error("请求结果:{}", json);
throw new ApiException("添加失败"); throw new BizException("添加失败");
} }
} catch (Exception e) { } catch (Exception e) {
log.error("添加失败", e); log.error("添加失败", e);
throw new ApiException("添加失败"); throw new BizException("添加失败");
} }
} }

@ -5,21 +5,22 @@ import com.gitee.easyopen.annotation.Api;
import com.gitee.easyopen.annotation.ApiService; import com.gitee.easyopen.annotation.ApiService;
import com.gitee.easyopen.doc.annotation.ApiDoc; import com.gitee.easyopen.doc.annotation.ApiDoc;
import com.gitee.easyopen.doc.annotation.ApiDocMethod; import com.gitee.easyopen.doc.annotation.ApiDocMethod;
import com.gitee.easyopen.exception.ApiException;
import com.gitee.easyopen.util.CopyUtil; import com.gitee.easyopen.util.CopyUtil;
import com.gitee.fastmybatis.core.query.Query;
import com.gitee.sop.adminserver.api.isv.result.RoleVO; import com.gitee.sop.adminserver.api.isv.result.RoleVO;
import com.gitee.sop.adminserver.api.service.param.RouteParam; import com.gitee.sop.adminserver.api.service.param.RouteAddParam;
import com.gitee.sop.adminserver.api.service.param.RouteDeleteParam;
import com.gitee.sop.adminserver.api.service.param.RoutePermissionParam; import com.gitee.sop.adminserver.api.service.param.RoutePermissionParam;
import com.gitee.sop.adminserver.api.service.param.RouteSearchParam; import com.gitee.sop.adminserver.api.service.param.RouteSearchParam;
import com.gitee.sop.adminserver.api.service.param.RouteUpdateParam;
import com.gitee.sop.adminserver.api.service.result.RouteVO; import com.gitee.sop.adminserver.api.service.result.RouteVO;
import com.gitee.sop.adminserver.api.service.result.ServiceInfo;
import com.gitee.sop.adminserver.bean.GatewayRouteDefinition; import com.gitee.sop.adminserver.bean.GatewayRouteDefinition;
import com.gitee.sop.adminserver.bean.RouteConfigDto; import com.gitee.sop.adminserver.bean.RouteConfigDto;
import com.gitee.sop.adminserver.bean.ZookeeperContext; import com.gitee.sop.adminserver.bean.ZookeeperContext;
import com.gitee.sop.adminserver.common.BizException;
import com.gitee.sop.adminserver.common.ZookeeperPathExistException;
import com.gitee.sop.adminserver.common.ZookeeperPathNotExistException;
import com.gitee.sop.adminserver.entity.ConfigRouteBase; import com.gitee.sop.adminserver.entity.ConfigRouteBase;
import com.gitee.sop.adminserver.entity.PermRole; import com.gitee.sop.adminserver.entity.PermRole;
import com.gitee.sop.adminserver.entity.PermRolePermission;
import com.gitee.sop.adminserver.mapper.ConfigRouteBaseMapper; import com.gitee.sop.adminserver.mapper.ConfigRouteBaseMapper;
import com.gitee.sop.adminserver.mapper.PermRoleMapper; import com.gitee.sop.adminserver.mapper.PermRoleMapper;
import com.gitee.sop.adminserver.mapper.PermRolePermissionMapper; import com.gitee.sop.adminserver.mapper.PermRolePermissionMapper;
@ -27,15 +28,11 @@ import com.gitee.sop.adminserver.service.RouteConfigService;
import com.gitee.sop.adminserver.service.RoutePermissionService; import com.gitee.sop.adminserver.service.RoutePermissionService;
import com.gitee.sop.adminserver.service.RouteService; import com.gitee.sop.adminserver.service.RouteService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -96,67 +93,84 @@ public class RouteApi {
return routeDefinitionList; return routeDefinitionList;
} }
@Api(name = "route.add")
@ApiDocMethod(description = "新增路由")
void addRoute(RouteAddParam param) {
String id = param.getName() + param.getVersion();
String routePath = ZookeeperContext.buildRoutePath(param.getServiceId(), id);
GatewayRouteDefinition routeDefinition = new GatewayRouteDefinition();
CopyUtil.copyPropertiesIgnoreNull(param, routeDefinition);
routeDefinition.setId(id);
routeDefinition.setCustom(1);
try {
ZookeeperContext.addPath(routePath, JSON.toJSONString(routeDefinition));
} catch (ZookeeperPathExistException e) {
throw new BizException("路由已存在");
}
this.updateRouteConfig(routeDefinition);
}
@Api(name = "route.update") @Api(name = "route.update")
@ApiDocMethod(description = "修改路由") @ApiDocMethod(description = "修改路由")
void updateRoute(RouteParam param) throws Exception { void updateRoute(RouteUpdateParam param) {
String serviceIdPath = ZookeeperContext.getSopRouteRootPath() + "/" + param.getServiceId(); String routePath = ZookeeperContext.buildRoutePath(param.getServiceId(), param.getId());
String zookeeperRoutePath = serviceIdPath + "/" + param.getId(); GatewayRouteDefinition routeDefinition = this.getGatewayRouteDefinition(routePath);
String data = ZookeeperContext.getData(zookeeperRoutePath);
GatewayRouteDefinition routeDefinition = JSON.parseObject(data, GatewayRouteDefinition.class);
CopyUtil.copyPropertiesIgnoreNull(param, routeDefinition); CopyUtil.copyPropertiesIgnoreNull(param, routeDefinition);
ZookeeperContext.updatePathData(zookeeperRoutePath, JSON.toJSONString(routeDefinition)); try {
ZookeeperContext.updatePathData(routePath, JSON.toJSONString(routeDefinition));
} catch (ZookeeperPathNotExistException e) {
throw new BizException("路由不存在");
}
this.updateRouteConfig(routeDefinition);
}
this.updateRouteConfig(param); @Api(name = "route.del")
@ApiDocMethod(description = "删除路由")
void delRoute(RouteDeleteParam param) {
String routePath = ZookeeperContext.buildRoutePath(param.getServiceId(), param.getId());
GatewayRouteDefinition routeDefinition = this.getGatewayRouteDefinition(routePath);
if (!BooleanUtils.toBoolean(routeDefinition.getCustom())) {
throw new BizException("非自定义路由,无法删除");
}
ZookeeperContext.deletePathDeep(routePath);
} }
@Api(name = "route.add") private GatewayRouteDefinition getGatewayRouteDefinition(String zookeeperRoutePath) {
@ApiDocMethod(description = "新增路由") String data = null;
void addRoute(RouteParam param) throws Exception { try {
String serviceIdPath = ZookeeperContext.getSopRouteRootPath() + "/" + param.getServiceId(); data = ZookeeperContext.getData(zookeeperRoutePath);
String zookeeperRoutePath = serviceIdPath + "/" + param.getId(); } catch (ZookeeperPathNotExistException e) {
if (ZookeeperContext.isPathExist(zookeeperRoutePath)) { throw new BizException("路由不存在");
throw new ApiException("id已存在");
} }
GatewayRouteDefinition routeDefinition = new GatewayRouteDefinition(); return JSON.parseObject(data, GatewayRouteDefinition.class);
CopyUtil.copyPropertiesIgnoreNull(param, routeDefinition);
ZookeeperContext.createNewData(zookeeperRoutePath, JSON.toJSONString(routeDefinition));
ServiceInfo serviceInfo = new ServiceInfo();
serviceInfo.setServiceId(param.getServiceId());
serviceInfo.setDescription(param.getServiceId());
String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
serviceInfo.setCreateTime(now);
serviceInfo.setUpdateTime(now);
ZookeeperContext.updatePathData(serviceIdPath, JSON.toJSONString(serviceInfo));
this.updateRouteConfig(param);
} }
private void updateRouteConfig(RouteParam param) { private void updateRouteConfig(GatewayRouteDefinition routeDefinition) {
try { try {
String routeId = param.getId(); String routeId = routeDefinition.getId();
ConfigRouteBase configRouteBase = configRouteBaseMapper.getByColumn("route_id", routeId); ConfigRouteBase configRouteBase = configRouteBaseMapper.getByColumn("route_id", routeId);
boolean doSave = configRouteBase == null; boolean doSave = configRouteBase == null;
if (doSave) { if (doSave) {
configRouteBase = new ConfigRouteBase(); configRouteBase = new ConfigRouteBase();
configRouteBase.setRouteId(param.getId()); configRouteBase.setRouteId(routeId);
} }
configRouteBase.setStatus(param.getStatus().byteValue()); configRouteBase.setStatus((byte) routeDefinition.getStatus());
int i = doSave ? configRouteBaseMapper.save(configRouteBase) int i = doSave ? configRouteBaseMapper.save(configRouteBase)
: configRouteBaseMapper.update(configRouteBase); : configRouteBaseMapper.update(configRouteBase);
if (i > 0) { if (i > 0) {
this.sendMsg(param); this.sendMsg(routeDefinition);
} }
} catch (Exception e) { } catch (Exception e) {
log.error("发送msg失败", e); log.error("发送msg失败", e);
} }
} }
private void sendMsg(RouteParam param) throws Exception { private void sendMsg(GatewayRouteDefinition routeDefinition) {
RouteConfigDto routeConfigDto = new RouteConfigDto(); RouteConfigDto routeConfigDto = new RouteConfigDto();
routeConfigDto.setRouteId(param.getId()); routeConfigDto.setRouteId(routeDefinition.getId());
routeConfigDto.setStatus(param.getStatus()); routeConfigDto.setStatus(routeDefinition.getStatus());
routeConfigService.sendRouteConfigMsg(routeConfigDto); routeConfigService.sendRouteConfigMsg(routeConfigDto);
} }
@ -164,11 +178,16 @@ public class RouteApi {
@ApiDocMethod(description = "获取路由对应的角色", elementClass = RoleVO.class) @ApiDocMethod(description = "获取路由对应的角色", elementClass = RoleVO.class)
List<RoleVO> getRouteRole(RouteSearchParam param) { List<RoleVO> getRouteRole(RouteSearchParam param) {
if (StringUtils.isBlank(param.getId())) { if (StringUtils.isBlank(param.getId())) {
throw new ApiException("id不能为空"); throw new BizException("id不能为空");
} }
return this.getRouteRole(param.getId()); return this.getRouteRole(param.getId());
} }
/**
* 获取路由对应的角色
* @param id routeId
* @return
*/
private List<RoleVO> getRouteRole(String id) { private List<RoleVO> getRouteRole(String id) {
return permRolePermissionMapper.listByColumn("route_id", id) return permRolePermissionMapper.listByColumn("route_id", id)
.stream() .stream()
@ -184,33 +203,28 @@ public class RouteApi {
@Api(name = "route.role.update") @Api(name = "route.role.update")
@ApiDocMethod(description = "更新路由对应的角色") @ApiDocMethod(description = "更新路由对应的角色")
@Transactional(rollbackFor = Exception.class)
public void updateRouteRole(RoutePermissionParam param) { public void updateRouteRole(RoutePermissionParam param) {
String routeId = param.getRouteId(); RoutePermissionParam oldRoutePermission = this.buildOldRoutePermission(param.getRouteId());
// 删除所有数据 routePermissionService.updateRoutePermission(param);
Query delQuery = new Query();
delQuery.eq("route_id", routeId);
permRolePermissionMapper.deleteByQuery(delQuery);
List<String> roleCodes = param.getRoleCode();
if (CollectionUtils.isNotEmpty(roleCodes)) {
List<PermRolePermission> tobeSave = new ArrayList<>(roleCodes.size());
for (String roleCode : roleCodes) {
PermRolePermission permRolePermission = new PermRolePermission();
permRolePermission.setRoleCode(roleCode);
permRolePermission.setRouteId(routeId);
tobeSave.add(permRolePermission);
}
// 批量添加
permRolePermissionMapper.saveBatch(tobeSave);
}
try { try {
routePermissionService.sendRoutePermissionReloadMsg(); routePermissionService.sendRoutePermissionReloadMsg(oldRoutePermission);
} catch (Exception e) { } catch (Exception e) {
log.info("消息推送--路由权限(reload)失败", e); log.error("消息推送--路由权限(reload)失败", e);
throw new ApiException("修改失败,请查看日志"); // 回滚
routePermissionService.updateRoutePermission(oldRoutePermission);
throw new BizException(e.getMessage());
} }
} }
private RoutePermissionParam buildOldRoutePermission(String routeId) {
List<RoleVO> routeRole = this.getRouteRole(routeId);
List<String> roleCodeList = routeRole.stream()
.map(RoleVO::getRoleCode)
.collect(Collectors.toList());
RoutePermissionParam routePermissionParam = new RoutePermissionParam();
routePermissionParam.setRouteId(routeId);
routePermissionParam.setRoleCode(roleCodeList);
return routePermissionParam;
}
} }

@ -5,29 +5,30 @@ import com.gitee.easyopen.annotation.Api;
import com.gitee.easyopen.annotation.ApiService; import com.gitee.easyopen.annotation.ApiService;
import com.gitee.easyopen.doc.annotation.ApiDoc; import com.gitee.easyopen.doc.annotation.ApiDoc;
import com.gitee.easyopen.doc.annotation.ApiDocMethod; import com.gitee.easyopen.doc.annotation.ApiDocMethod;
import com.gitee.easyopen.exception.ApiException; import com.gitee.sop.adminserver.api.service.param.ServiceAddParam;
import com.gitee.sop.adminserver.api.service.param.ServiceInstanceParam;
import com.gitee.sop.adminserver.api.service.param.ServiceSearchParam; import com.gitee.sop.adminserver.api.service.param.ServiceSearchParam;
import com.gitee.sop.adminserver.api.service.result.ServiceInfo; import com.gitee.sop.adminserver.api.service.result.RouteServiceInfo;
import com.gitee.sop.adminserver.api.service.result.ServiceInfoVo; import com.gitee.sop.adminserver.api.service.result.ServiceInfoVo;
import com.gitee.sop.adminserver.bean.EurekaApplication; import com.gitee.sop.adminserver.api.service.result.ServiceInstanceVO;
import com.gitee.sop.adminserver.bean.EurekaApps; import com.gitee.sop.adminserver.bean.ServiceRouteInfo;
import com.gitee.sop.adminserver.bean.EurekaInstance;
import com.gitee.sop.adminserver.bean.EurekaUri;
import com.gitee.sop.adminserver.bean.ZookeeperContext; import com.gitee.sop.adminserver.bean.ZookeeperContext;
import com.gitee.sop.adminserver.common.BizException;
import com.gitee.sop.adminserver.common.ZookeeperPathExistException;
import com.gitee.sop.adminserver.common.ZookeeperPathNotExistException;
import com.gitee.sop.registryapi.bean.ServiceInfo;
import com.gitee.sop.registryapi.bean.ServiceInstance;
import com.gitee.sop.registryapi.service.RegistryService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient; import org.apache.commons.lang.BooleanUtils;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.curator.framework.recipes.cache.ChildData; import org.apache.curator.framework.recipes.cache.ChildData;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import javax.annotation.PostConstruct;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -40,26 +41,19 @@ import java.util.stream.Collectors;
@Slf4j @Slf4j
public class ServiceApi { public class ServiceApi {
OkHttpClient client = new OkHttpClient();
@Autowired @Autowired
private Environment environment; private RegistryService registryService;
private String eurekaUrl;
// eureka.client.serviceUrl.defaultZone @Api(name = "zookeeper.service.list")
@ApiDocMethod(description = "zk中的服务列表", elementClass = RouteServiceInfo.class)
@Api(name = "service.list") List<RouteServiceInfo> listServiceInfo(ServiceSearchParam param) {
@ApiDocMethod(description = "服务列表(旧)", elementClass = ServiceInfo.class)
List<ServiceInfo> listServiceInfo(ServiceSearchParam param) throws Exception {
String routeRootPath = ZookeeperContext.getSopRouteRootPath(); String routeRootPath = ZookeeperContext.getSopRouteRootPath();
List<ChildData> childDataList = ZookeeperContext.getChildrenData(routeRootPath); List<ChildData> childDataList = ZookeeperContext.getChildrenData(routeRootPath);
List<ServiceInfo> serviceInfoList = childDataList.stream() List<RouteServiceInfo> serviceInfoList = childDataList.stream()
.filter(childData -> childData.getData() != null && childData.getData().length > 0) .filter(childData -> childData.getData() != null && childData.getData().length > 0)
.map(childData -> { .map(childData -> {
String serviceNodeData = new String(childData.getData()); String serviceNodeData = new String(childData.getData());
ServiceInfo serviceInfo = JSON.parseObject(serviceNodeData, ServiceInfo.class); RouteServiceInfo serviceInfo = JSON.parseObject(serviceNodeData, RouteServiceInfo.class);
return serviceInfo; return serviceInfo;
}) })
.filter(serviceInfo -> { .filter(serviceInfo -> {
@ -74,40 +68,83 @@ public class ServiceApi {
return serviceInfoList; return serviceInfoList;
} }
@Api(name = "service.instance.list") @Api(name = "service.custom.add")
@ApiDocMethod(description = "服务列表", elementClass = ServiceInfoVo.class) @ApiDocMethod(description = "添加服务")
List<ServiceInfoVo> listService(ServiceSearchParam param) throws Exception { void addService(ServiceAddParam param) {
String json = this.requestEurekaServer(EurekaUri.QUERY_APPS); String serviceId = param.getServiceId();
EurekaApps eurekaApps = JSON.parseObject(json, EurekaApps.class); String servicePath = ZookeeperContext.buildServiceIdPath(serviceId);
ServiceRouteInfo serviceRouteInfo = new ServiceRouteInfo();
Date now = new Date();
serviceRouteInfo.setServiceId(serviceId);
serviceRouteInfo.setDescription("自定义服务");
serviceRouteInfo.setCreateTime(now);
serviceRouteInfo.setUpdateTime(now);
serviceRouteInfo.setCustom(BooleanUtils.toInteger(true));
String serviceData = JSON.toJSONString(serviceRouteInfo);
try {
ZookeeperContext.addPath(servicePath, serviceData);
} catch (ZookeeperPathExistException e) {
throw new BizException("服务已存在");
}
}
@Api(name = "service.custom.del")
@ApiDocMethod(description = "删除自定义服务")
void delService(ServiceSearchParam param) {
String serviceId = param.getServiceId();
String servicePath = ZookeeperContext.buildServiceIdPath(serviceId);
String data = null;
try {
data = ZookeeperContext.getData(servicePath);
} catch (ZookeeperPathNotExistException e) {
throw new BizException("服务不存在");
}
if (StringUtils.isBlank(data)) {
throw new BizException("非自定义服务,无法删除");
}
ServiceRouteInfo serviceRouteInfo = JSON.parseObject(data, ServiceRouteInfo.class);
int custom = serviceRouteInfo.getCustom();
if (!BooleanUtils.toBoolean(custom)) {
throw new BizException("非自定义服务,无法删除");
}
ZookeeperContext.deletePathDeep(servicePath);
}
List<ServiceInfoVo> serviceInfoVoList = new ArrayList<>(); @Api(name = "service.instance.list")
List<EurekaApplication> applicationList = eurekaApps.getApplications().getApplication(); @ApiDocMethod(description = "获取注册中心的服务列表", elementClass = ServiceInfoVo.class)
List<ServiceInstanceVO> listService(ServiceSearchParam param) {
List<ServiceInfo> serviceInfos;
try {
serviceInfos = registryService.listAllService(1, 99999/* 获取所有实例 */);
} catch (Exception e) {
log.error("获取服务实例失败", e);
return Collections.emptyList();
}
List<ServiceInstanceVO> serviceInfoVoList = new ArrayList<>();
AtomicInteger idGen = new AtomicInteger(1); AtomicInteger idGen = new AtomicInteger(1);
applicationList.stream() serviceInfos.stream()
.filter(eurekaApplication -> { .filter(serviceInfo -> {
if (StringUtils.isBlank(param.getServiceId())) { if (StringUtils.isBlank(param.getServiceId())) {
return true; return true;
} }
return StringUtils.containsIgnoreCase(eurekaApplication.getName(), param.getServiceId()); return StringUtils.containsIgnoreCase(serviceInfo.getServiceId(), param.getServiceId());
}) })
.forEach(eurekaApplication -> { .forEach(serviceInfo -> {
int pid = idGen.getAndIncrement(); int pid = idGen.getAndIncrement();
String name = eurekaApplication.getName(); String serviceId = serviceInfo.getServiceId();
ServiceInfoVo parent = new ServiceInfoVo(); ServiceInstanceVO parent = new ServiceInstanceVO();
parent.setId(pid); parent.setId(pid);
parent.setName(name); parent.setServiceId(serviceId);
parent.setParentId(0); parent.setParentId(0);
serviceInfoVoList.add(parent); serviceInfoVoList.add(parent);
List<EurekaInstance> instanceList = eurekaApplication.getInstance(); List<ServiceInstance> instanceList = serviceInfo.getInstances();
for (EurekaInstance instance : instanceList) { for (ServiceInstance instance : instanceList) {
ServiceInfoVo vo = new ServiceInfoVo(); ServiceInstanceVO instanceVO = new ServiceInstanceVO();
BeanUtils.copyProperties(instance, vo); BeanUtils.copyProperties(instance, instanceVO);
int id = idGen.getAndIncrement(); int id = idGen.getAndIncrement();
vo.setId(id); instanceVO.setId(id);
vo.setName(name); instanceVO.setParentId(pid);
vo.setParentId(pid); serviceInfoVoList.add(instanceVO);
vo.setServerPort(instance.fetchPort());
serviceInfoVoList.add(vo);
} }
}); });
@ -116,38 +153,24 @@ public class ServiceApi {
@Api(name = "service.instance.offline") @Api(name = "service.instance.offline")
@ApiDocMethod(description = "服务下线") @ApiDocMethod(description = "服务下线")
void serviceOffline(ServiceInstanceParam param) throws IOException { void serviceOffline(ServiceInstance param) {
this.requestEurekaServer(EurekaUri.OFFLINE_SERVICE, param.getServiceId(), param.getInstanceId()); try {
registryService.offlineInstance(param);
} catch (Exception e) {
log.error("下线失败,param:{}", param, e);
throw new BizException("下线失败,请查看日志");
}
} }
@Api(name = "service.instance.online") @Api(name = "service.instance.online")
@ApiDocMethod(description = "服务上线") @ApiDocMethod(description = "服务上线")
void serviceOnline(ServiceInstanceParam param) throws IOException { void serviceOnline(ServiceInstance param) throws IOException {
this.requestEurekaServer(EurekaUri.ONLINE_SERVICE, param.getServiceId(), param.getInstanceId()); try {
} registryService.onlineInstance(param);
} catch (Exception e) {
private String requestEurekaServer(EurekaUri eurekaUri, String... args) throws IOException { log.error("上线失败,param:{}", param, e);
Request request = eurekaUri.getRequest(this.eurekaUrl, args); throw new BizException("上线失败,请查看日志");
Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
return response.body().string();
} else {
log.error("操作失败,url:{}, msg:{}, code:{}", eurekaUri.getUri(args), response.message(), response.code());
throw new ApiException("操作失败", String.valueOf(response.code()));
}
}
@PostConstruct
protected void after() {
String eurekaUrls = environment.getProperty("eureka.client.serviceUrl.defaultZone");
if (StringUtils.isBlank(eurekaUrls)) {
throw new ApiException("未指定eureka.client.serviceUrl.defaultZone参数");
}
String url = eurekaUrls.split("\\,")[0];
if (url.endsWith("/")) {
url = eurekaUrls.substring(0, eurekaUrls.length() - 1);
} }
this.eurekaUrl = url;
} }
} }

@ -41,10 +41,12 @@ public class LimitNewAddParam {
/** 返回的错误码, 数据库字段:limit_code */ /** 返回的错误码, 数据库字段:limit_code */
@ApiDocField(description = "返回的错误码") @ApiDocField(description = "返回的错误码")
@Length(max = 64, message = "limitCode长度不能超过64")
private String limitCode; private String limitCode;
/** 返回的错误信息, 数据库字段:limit_msg */ /** 返回的错误信息, 数据库字段:limit_msg */
@ApiDocField(description = "返回的错误信息") @ApiDocField(description = "返回的错误信息")
@Length(max = 100, message = "limitMsg长度不能超过100")
private String limitMsg; private String limitMsg;
/** 令牌桶容量, 数据库字段:token_bucket_count */ /** 令牌桶容量, 数据库字段:token_bucket_count */

@ -46,10 +46,12 @@ public class LimitNewUpdateParam {
/** 返回的错误码, 数据库字段:limit_code */ /** 返回的错误码, 数据库字段:limit_code */
@ApiDocField(description = "返回的错误码") @ApiDocField(description = "返回的错误码")
@Length(max = 64, message = "limitCode长度不能超过64")
private String limitCode; private String limitCode;
/** 返回的错误信息, 数据库字段:limit_msg */ /** 返回的错误信息, 数据库字段:limit_msg */
@ApiDocField(description = "返回的错误信息") @ApiDocField(description = "返回的错误信息")
@Length(max = 100, message = "limitMsg长度不能超过100")
private String limitMsg; private String limitMsg;
/** 令牌桶容量, 数据库字段:token_bucket_count */ /** 令牌桶容量, 数据库字段:token_bucket_count */

@ -12,8 +12,6 @@ public class LogMonitorInstanceAddParam {
@NotBlank(message = "serviceId不能为空") @NotBlank(message = "serviceId不能为空")
private String serviceId; private String serviceId;
@NotBlank(message = "ip不能为空") @NotBlank(message = "ipPort不能为空")
private String ip; private String ipPort;
private int port;
} }

@ -0,0 +1,76 @@
package com.gitee.sop.adminserver.api.service.param;
import com.gitee.easyopen.doc.annotation.ApiDocField;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* @author tanghc
*/
@Getter
@Setter
public class RouteAddParam {
@NotBlank(message = "serviceId不能为空")
@Length(max = 100, message = "serviceId长度不能超过100")
@ApiDocField(description = "serviceId")
private String serviceId;
/**
* 接口名
*/
@NotBlank(message = "接口名不能为空")
@Length(max = 100, message = "name长度不能超过100")
@ApiDocField(description = "接口名")
private String name;
/**
* 版本号
*/
@NotBlank(message = "版本号不能为空")
@Length(max = 100, message = "version长度不能超过100")
@ApiDocField(description = "版本号")
private String version;
/**
* 路由规则转发的目标uri
*/
@NotBlank(message = "uri不能为空")
@Length(max = 100, message = "uri长度不能超过100")
@ApiDocField(description = "路由uri")
private String uri;
/**
* uri后面跟的path
*/
@ApiDocField(description = "路由path")
@Length(max = 100, message = "path长度不能超过100")
private String path;
/**
* 是否忽略验证业务参数验证除外
*/
@ApiDocField(description = "是否忽略验证,业务参数验证除外,1:忽略,0:不忽略")
private Integer ignoreValidate;
/**
* 合并结果统一格式输出
*/
@ApiDocField(description = "合并结果,统一格式输出,1:合并,2:不合并")
private Integer mergeResult;
/**
* 状态
*/
@NotNull
@ApiDocField(description = "状态,0:审核,1:启用,2:禁用")
private Integer status;
@ApiDocField(description = "是否是自定义路由,1:是,0:否")
private Integer custom;
}

@ -0,0 +1,22 @@
package com.gitee.sop.adminserver.api.service.param;
import com.gitee.easyopen.doc.annotation.ApiDocField;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
/**
* @author tanghc
*/
@Getter
@Setter
public class RouteDeleteParam {
@ApiDocField(description = "服务名serviceId")
@NotBlank(message = "serviceId不能为空")
private String serviceId;
@ApiDocField(description = "路由id")
@NotBlank(message = "id不能为空")
private String id;
}

@ -3,6 +3,7 @@ package com.gitee.sop.adminserver.api.service.param;
import com.gitee.easyopen.doc.annotation.ApiDocField; import com.gitee.easyopen.doc.annotation.ApiDocField;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
@ -12,23 +13,25 @@ import javax.validation.constraints.NotNull;
*/ */
@Getter @Getter
@Setter @Setter
public class RouteParam { public class RouteUpdateParam {
@NotBlank(message = "serviceId不能为空")
@ApiDocField(description = "serviceId")
private String serviceId;
/** /**
* 路由的Id * 路由的Id
*/ */
@NotBlank(message = "id不能为空") @NotBlank(message = "id不能为空")
@ApiDocField(description = "路由id") @ApiDocField(description = "路由id")
private String id = ""; private String id;
@NotBlank(message = "serviceId不能为空")
@Length(max = 100, message = "serviceId长度不能超过100")
@ApiDocField(description = "serviceId")
private String serviceId;
/** /**
* 路由规则转发的目标uri * 路由规则转发的目标uri
*/ */
@NotBlank(message = "uri不能为空") @NotBlank(message = "uri不能为空")
@Length(max = 100, message = "uri长度不能超过100")
@ApiDocField(description = "路由uri") @ApiDocField(description = "路由uri")
private String uri; private String uri;
@ -36,8 +39,21 @@ public class RouteParam {
* uri后面跟的path * uri后面跟的path
*/ */
@ApiDocField(description = "路由path") @ApiDocField(description = "路由path")
@Length(max = 100, message = "path长度不能超过100")
private String path; private String path;
/**
* 是否忽略验证业务参数验证除外
*/
@ApiDocField(description = "是否忽略验证,业务参数验证除外,1:忽略,0:不忽略")
private Integer ignoreValidate;
/**
* 合并结果统一格式输出
*/
@ApiDocField(description = "合并结果,统一格式输出,1:合并,2:不合并")
private Integer mergeResult;
/** /**
* 状态 * 状态
*/ */
@ -45,4 +61,7 @@ public class RouteParam {
@ApiDocField(description = "状态,0:审核,1:启用,2:禁用") @ApiDocField(description = "状态,0:审核,1:启用,2:禁用")
private Integer status; private Integer status;
@ApiDocField(description = "是否是自定义路由,1:是,0:否")
private Integer custom;
} }

@ -0,0 +1,21 @@
package com.gitee.sop.adminserver.api.service.param;
import com.gitee.easyopen.doc.annotation.ApiDocField;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotBlank;
/**
* @author tanghc
*/
@Getter
@Setter
public class ServiceAddParam {
@ApiDocField(description = "服务名serviceId")
@Length(max = 100, message = "长度不能超过100")
@NotBlank(message = "serviceId不能为空")
private String serviceId;
}

@ -7,7 +7,7 @@ import lombok.Data;
* @author tanghc * @author tanghc
*/ */
@Data @Data
public class ServiceInfo { public class RouteServiceInfo {
@ApiDocField(description = "serviceId") @ApiDocField(description = "serviceId")
private String serviceId; private String serviceId;
@ -19,4 +19,8 @@ public class ServiceInfo {
@ApiDocField(description = "描述") @ApiDocField(description = "描述")
private String description; private String description;
/** 是否是自定义服务,1:是,0:否 */
@ApiDocField(description = "是否是自定义服务,1:是,0:否")
private Integer custom;
} }

@ -0,0 +1,54 @@
package com.gitee.sop.adminserver.api.service.result;
import com.alibaba.fastjson.annotation.JSONField;
import com.gitee.easyopen.doc.annotation.ApiDocField;
import lombok.Data;
import org.apache.commons.lang.StringUtils;
import java.util.Date;
/**
* @author tanghc
*/
@Data
public class ServiceInstanceVO {
@ApiDocField(description = "id")
private Integer id;
@ApiDocField(description = "服务名称(serviceId)")
private String serviceId;
@ApiDocField(description = "instanceId")
private String instanceId;
@ApiDocField(description = "ipPort")
private String ipPort;
@ApiDocField(description = "ip")
private String ip;
@ApiDocField(description = "port")
private int port;
@ApiDocField(description = "status,服务状态,UP:已上线,OUT_OF_SERVICE:已下线")
private String status;
@ApiDocField(description = "最后更新时间")
private String lastUpdatedTimestamp;
@ApiDocField(description = "parentId")
private Integer parentId;
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
public Date getUpdateTime() {
if (StringUtils.isBlank(lastUpdatedTimestamp)) {
return null;
}
return new Date(Long.valueOf(lastUpdatedTimestamp));
}
public String getIpPort() {
return ip != null && port > 0 ? ip + ":" + port : "";
}
}

@ -1,5 +1,6 @@
package com.gitee.sop.adminserver.bean; package com.gitee.sop.adminserver.bean;
import com.alibaba.fastjson.annotation.JSONField;
import com.gitee.easyopen.doc.annotation.ApiDocField; import com.gitee.easyopen.doc.annotation.ApiDocField;
import lombok.Data; import lombok.Data;
@ -30,11 +31,13 @@ public class GatewayRouteDefinition {
/** /**
* 路由断言集合配置 * 路由断言集合配置
*/ */
@JSONField(serialize = false)
private List<GatewayPredicateDefinition> predicates = new ArrayList<>(); private List<GatewayPredicateDefinition> predicates = new ArrayList<>();
/** /**
* 路由过滤器集合配置 * 路由过滤器集合配置
*/ */
@JSONField(serialize = false)
private List<GatewayFilterDefinition> filters = new ArrayList<>(); private List<GatewayFilterDefinition> filters = new ArrayList<>();
/** /**
@ -67,14 +70,20 @@ public class GatewayRouteDefinition {
private int status = 1; private int status = 1;
/** /**
* 合并结果 * 合并结果统一格式输出
*/ */
@ApiDocField(description = "合并结果,1:合并,2:不合并") @ApiDocField(description = "合并结果,统一格式输出,1:合并,0:不合并")
private int mergeResult = 1; private int mergeResult = 1;
/** /**
* 是否需要授权才能访问 * 是否需要授权才能访问
*/ */
@ApiDocField(description = "是否需要授权才能访问,1:是,2:否") @ApiDocField(description = "是否需要授权才能访问,1:是,0:否")
private int permission; private int permission;
/**
* 是否是自定义路由
*/
@ApiDocField(description = "是否是自定义路由,1:是,0:否")
private int custom;
} }

@ -13,4 +13,5 @@ public class IsvRoutePermission {
private String appKey; private String appKey;
private List<String> routeIdList; private List<String> routeIdList;
private String routeIdListMd5; private String routeIdListMd5;
private String listenPath;
} }

@ -24,4 +24,7 @@ public class ServiceRouteInfo {
@JSONField(serialize = false) @JSONField(serialize = false)
private List<GatewayRouteDefinition> routeDefinitionList; private List<GatewayRouteDefinition> routeDefinitionList;
/** 是否是自定义服务,1:是,0:否 */
private int custom;
} }

@ -9,14 +9,11 @@ public class SopAdminConstants {
*/ */
public static final String SOP_SERVICE_ROUTE_PATH = "/com.gitee.sop.route"; public static final String SOP_SERVICE_ROUTE_PATH = "/com.gitee.sop.route";
/**
* zookeeper存放路由授权信息根目录
*/
public static final String SOP_ROUTE_PERMISSION_PATH = "/com.gitee.sop.isv.route.permission";
/** /**
* 消息监听路径 * 消息监听路径
*/ */
public static final String SOP_MSG_CHANNEL_PATH = "/com.gitee.sop.channel"; public static final String SOP_MSG_CHANNEL_PATH = "/com.gitee.sop.channel";
public static final String RELOAD_ROUTE_PERMISSION_PATH = "/com.gitee.sop.route.permission.reload";
} }

@ -1,62 +1,91 @@
package com.gitee.sop.adminserver.bean; package com.gitee.sop.adminserver.bean;
import com.gitee.easyopen.exception.ApiException; import com.gitee.sop.adminserver.common.ZookeeperOperationException;
import com.gitee.sop.adminserver.common.ZookeeperPathExistException;
import com.gitee.sop.adminserver.common.ZookeeperPathNotExistException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils; import org.apache.commons.lang.math.NumberUtils;
import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.ChildData; import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.curator.framework.recipes.cache.NodeCacheListener;
import org.apache.curator.framework.recipes.cache.PathChildrenCache; import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.retry.ExponentialBackoffRetry; import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat; import org.apache.zookeeper.data.Stat;
import org.springframework.beans.factory.annotation.Value; import org.springframework.core.env.Environment;
import org.springframework.context.annotation.Configuration; import org.springframework.util.Assert;
import javax.annotation.PostConstruct; import java.io.Closeable;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import static com.gitee.sop.adminserver.bean.SopAdminConstants.SOP_MSG_CHANNEL_PATH; import static com.gitee.sop.adminserver.bean.SopAdminConstants.SOP_MSG_CHANNEL_PATH;
import static com.gitee.sop.adminserver.bean.SopAdminConstants.SOP_ROUTE_PERMISSION_PATH;
/** /**
* @author tanghc * @author tanghc
*/ */
@Configuration @Slf4j
public class ZookeeperContext { public class ZookeeperContext {
private static CuratorFramework client; private static CuratorFramework client;
@Value("${spring.cloud.zookeeper.connect-string}") private static Environment environment;
private String zookeeperServerAddr;
@Value("${spring.cloud.zookeeper.baseSleepTimeMs}") public static void setEnvironment(Environment environment) {
private String baseSleepTimeMs; Assert.notNull(environment, "environment不能为null");
ZookeeperContext.environment = environment;
initZookeeperClient();
}
@Value("${spring.cloud.zookeeper.maxRetries}") public synchronized static void initZookeeperClient() {
private String maxRetries; if (client != null) {
return;
}
setClient(createClient());
}
@PostConstruct public static CuratorFramework createClient() {
protected void after() { String zookeeperServerAddr = environment.getProperty("spring.cloud.zookeeper.connect-string");
if (StringUtils.isBlank(zookeeperServerAddr)) { if (StringUtils.isBlank(zookeeperServerAddr)) {
throw new IllegalArgumentException("未指定spring.cloud.zookeeper.connect-string参数"); throw new RuntimeException("未指定spring.cloud.zookeeper.connect-string参数");
} }
String baseSleepTimeMs = environment.getProperty("spring.cloud.zookeeper.baseSleepTimeMs");
String maxRetries = environment.getProperty("spring.cloud.zookeeper.maxRetries");
log.info("初始化zookeeper客户端,zookeeperServerAddr:{}, baseSleepTimeMs:{}, maxRetries:{}",
zookeeperServerAddr, baseSleepTimeMs, maxRetries);
CuratorFramework client = CuratorFrameworkFactory.builder() CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString(zookeeperServerAddr) .connectString(zookeeperServerAddr)
.retryPolicy(new ExponentialBackoffRetry(NumberUtils.toInt(baseSleepTimeMs, 3000), NumberUtils.toInt(maxRetries, 3))) .retryPolicy(new ExponentialBackoffRetry(NumberUtils.toInt(baseSleepTimeMs, 3000), NumberUtils.toInt(maxRetries, 3)))
.build(); .build();
client.start(); client.start();
return client;
setClient(client);
} }
public static String getSopRouteRootPath() { public static String getSopRouteRootPath() {
return SopAdminConstants.SOP_SERVICE_ROUTE_PATH; return SopAdminConstants.SOP_SERVICE_ROUTE_PATH;
} }
public static String getRoutePermissionPath() { public static String buildServiceIdPath(String serviceId) {
return SOP_ROUTE_PERMISSION_PATH; if (StringUtils.isBlank(serviceId)) {
throw new NullPointerException("serviceId不能为空");
}
return getSopRouteRootPath() + "/" + serviceId;
}
public static String buildRoutePath(String serviceId, String routeId) {
if (StringUtils.isBlank(serviceId)) {
throw new NullPointerException("serviceId不能为空");
}
if (StringUtils.isBlank(routeId)) {
throw new NullPointerException("routeId不能为空");
}
String serviceIdPath = getSopRouteRootPath() + "/" + serviceId;
return serviceIdPath + "/" + routeId;
} }
public static String getIsvInfoChannelPath() { public static String getIsvInfoChannelPath() {
@ -95,54 +124,128 @@ public class ZookeeperContext {
* 对已存在的path赋值如果path不存在抛异常 * 对已存在的path赋值如果path不存在抛异常
* *
* @param path 已存在的 * @param path 已存在的
* @param data * @param data 数据
* @return * @return
* @throws Exception * @throws ZookeeperPathNotExistException
*/ */
public static Stat updatePathData(String path, String data) throws Exception { public static Stat updatePathData(String path, String data) throws ZookeeperPathNotExistException {
if (!isPathExist(path)) { if (!isPathExist(path)) {
throw new ApiException("path " + path + " 不存在"); throw new ZookeeperPathNotExistException("path " + path + " 不存在");
}
try {
return getClient().setData().forPath(path, data.getBytes());
} catch (Exception e) {
throw new ZookeeperOperationException("updatePathData error, path=" + path, e);
} }
return getClient().setData().forPath(path, data.getBytes());
} }
/** /**
* 创建新的path并赋值如果path已存在抛异常 * 创建新的path并赋值如果path已存在抛异常
*
* @param path 待创建的path * @param path 待创建的path
* @param data * @param data
* @throws ZookeeperPathExistException
*/ */
public static String createNewData(String path, String data) throws Exception { public static String addPath(String path, String data) throws ZookeeperPathExistException {
if (isPathExist(path)) { if (isPathExist(path)) {
throw new ApiException("path " + path + " 已存在"); throw new ZookeeperPathExistException("path " + path + " 已存在");
}
try {
return addPath(path, CreateMode.PERSISTENT, data);
} catch (Exception e) {
throw new ZookeeperOperationException("addPath error path=" + path, e);
}
}
/**
* 添加节点
*
* @param path 待创建的path
* @param createMode 节点类型
* @param data 节点数据
* @return
*/
public static String addPath(String path, CreateMode createMode, String data) {
try {
return getClient().create()
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
.creatingParentContainersIfNeeded()
.withMode(createMode)
.forPath(path, data.getBytes());
} catch (Exception e) {
throw new ZookeeperOperationException("addPath error path=" + path, e);
}
}
/**
* 删除节点及子节点
*
* @param path
*/
public static void deletePathDeep(String path) {
try {
getClient().delete()
.deletingChildrenIfNeeded()
.forPath(path);
} catch (Exception e) {
}
}
/**
* 创建新的path并赋值如果path已存在则不创建
*
* @param path 待创建的path
* @param data
*/
public static String addPathQuietly(String path, String data) {
if (isPathExist(path)) {
return path;
}
try {
return addPath(path, data);
} catch (Exception e) {
throw new ZookeeperOperationException("addPathQuietly error path=" + path, e);
} }
return getClient().create()
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
.creatingParentContainersIfNeeded()
.forPath(path, data.getBytes());
} }
/** /**
* 新建或保存节点 * 新建或保存节点
*
* @param path * @param path
* @param data * @param data
* @return * @return
* @throws Exception
*/ */
public static String createOrUpdateData(String path, String data) throws Exception { public static String createOrUpdateData(String path, String data) {
return getClient().create() try {
// 如果节点存在则Curator将会使用给出的数据设置这个节点的值 return getClient().create()
.orSetData() // 如果节点存在则Curator将会使用给出的数据设置这个节点的值
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点 .orSetData()
.creatingParentContainersIfNeeded() // 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
.forPath(path, data.getBytes()); .creatingParentContainersIfNeeded()
.forPath(path, data.getBytes());
} catch (Exception e) {
throw new ZookeeperOperationException("createOrUpdateData error path=" + path, e);
}
} }
public static String getData(String path) throws Exception { /**
* 获取节点内容
*
* @param path
* @return
* @throws ZookeeperPathNotExistException
*/
public static String getData(String path) throws ZookeeperPathNotExistException {
if (!isPathExist(path)) { if (!isPathExist(path)) {
return null; throw new ZookeeperPathNotExistException("path 不存在, path=" + path);
}
try {
byte[] data = getClient().getData().forPath(path);
return new String(data);
} catch (Exception e) {
throw new ZookeeperOperationException("getData error path=" + path, e);
} }
byte[] data = getClient().getData().forPath(path);
return new String(data);
} }
/** /**
@ -150,9 +253,8 @@ public class ZookeeperContext {
* *
* @param parentPath 父节点 * @param parentPath 父节点
* @return * @return
* @throws Exception
*/ */
public static List<ChildData> getChildrenData(String parentPath) throws Exception { public static List<ChildData> getChildrenData(String parentPath) {
PathChildrenCache pathChildrenCache = buildPathChildrenCache(parentPath); PathChildrenCache pathChildrenCache = buildPathChildrenCache(parentPath);
if (pathChildrenCache == null) { if (pathChildrenCache == null) {
return Collections.emptyList(); return Collections.emptyList();
@ -160,7 +262,7 @@ public class ZookeeperContext {
return pathChildrenCache.getCurrentData(); return pathChildrenCache.getCurrentData();
} }
public static PathChildrenCache buildPathChildrenCache(String path) throws Exception { public static PathChildrenCache buildPathChildrenCache(String path) {
if (!isPathExist(path)) { if (!isPathExist(path)) {
return null; return null;
} }
@ -168,8 +270,66 @@ public class ZookeeperContext {
// 且第三个参数要设置为true,不然ChildData对象中的getData返回null // 且第三个参数要设置为true,不然ChildData对象中的getData返回null
PathChildrenCache childrenCache = new PathChildrenCache(client, path, true); PathChildrenCache childrenCache = new PathChildrenCache(client, path, true);
// 列出子节点数据列表,需要使用BUILD_INITIAL_CACHE同步初始化模式才能获得,异步是获取不到的 // 列出子节点数据列表,需要使用BUILD_INITIAL_CACHE同步初始化模式才能获得,异步是获取不到的
childrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE); try {
childrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
} catch (Exception e) {
throw new ZookeeperOperationException("buildPathChildrenCache error path=" + path, e);
}
return childrenCache; return childrenCache;
} }
/**
* 监听一个节点
*
* @param path
* @param listenCallback 回调
* @return 返回path
* @throws Exception
*/
public static void listenTempPath(String path, ListenCallback listenCallback) throws Exception {
String initData = "{}";
CuratorFramework client = createClient();
client.create()
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
.creatingParentContainersIfNeeded()
.withMode(CreateMode.EPHEMERAL)
.forPath(path, initData.getBytes());
final NodeCache cache = new NodeCache(client, path, false);
cache.getListenable().addListener(new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
byte[] nodeData = cache.getCurrentData().getData();
String data = new String(nodeData);
if (StringUtils.isNotBlank(data) && !initData.equals(data)) {
listenCallback.onError(data);
new Thread(new ZKClose(cache, client)).start();
}
}
});
cache.start();
}
public interface ListenCallback {
void onError(String errorMsg);
}
static class ZKClose implements Runnable {
Closeable[] closes;
public ZKClose(Closeable ...closes) {
this.closes = closes;
}
@Override
public void run() {
try {
Thread.sleep(2000);
IOUtils.closeQuietly(closes);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} }

@ -0,0 +1,33 @@
package com.gitee.sop.adminserver.common;
import com.gitee.easyopen.exception.ApiException;
import com.gitee.easyopen.message.Error;
/**
* @author tanghc
*/
public class BizException extends ApiException {
public BizException(String msg) {
super(msg, "4000");
}
public BizException(Exception e) {
super(e);
}
public BizException(Error<String> error) {
super(error);
}
public BizException(String msg, String code) {
super(msg, code);
}
public BizException(String msg, String code, Object data) {
super(msg, code, data);
}
public BizException(Error<String> error, Object data) {
super(error, data);
}
}

@ -0,0 +1,410 @@
package com.gitee.sop.adminserver.common;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import javax.crypto.Cipher;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
/**
* RSA加解密工具<br>
*
* @author tanghc
*/
public class RSATool {
public static String RSA_ALGORITHM = "RSA";
private KeyFormat keyFormat;
private KeyLength keyLength;
public RSATool(KeyFormat keyFormat, KeyLength keyLength) {
this.keyFormat = keyFormat;
this.keyLength = keyLength;
}
/**
* 创建公钥私钥
*
* @return 返回公私钥对
* @throws Exception
*/
public KeyStore createKeys() throws Exception {
KeyPairGenerator keyPairGeno = KeyPairGenerator.getInstance(RSA_ALGORITHM);
keyPairGeno.initialize(keyLength.getLength());
KeyPair keyPair = keyPairGeno.generateKeyPair();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
KeyStore keyStore = new KeyStore();
if (this.keyFormat == KeyFormat.PKCS1) {
keyStore.setPublicKey(Base64.encodeBase64String(publicKey.getEncoded()));
keyStore.setPrivateKey(convertPkcs8ToPkcs1(privateKey.getEncoded()));
} else {
keyStore.setPublicKey(Base64.encodeBase64String(publicKey.getEncoded()));
keyStore.setPrivateKey(Base64.encodeBase64String(privateKey.getEncoded()));
}
return keyStore;
}
/**
* 获取公钥对象
*
* @param pubKeyData 公钥
* @return 返回公钥对象
* @throws Exception
*/
public RSAPublicKey getPublicKey(byte[] pubKeyData) throws Exception {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(pubKeyData);
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
return (RSAPublicKey) keyFactory.generatePublic(keySpec);
}
/**
* 获取公钥对象
*
* @param pubKey 公钥
* @return 返回私钥对象
* @throws Exception
*/
public RSAPublicKey getPublicKey(String pubKey) throws Exception {
return getPublicKey(Base64.decodeBase64(pubKey));
}
/**
* 获取私钥对象
*
* @param priKey 私钥
* @return 私钥对象
* @throws Exception
*/
public RSAPrivateKey getPrivateKey(String priKey) throws Exception {
return getPrivateKey(Base64.decodeBase64(priKey));
}
/**
* 通过私钥byte[]将公钥还原适用于RSA算法
*
* @param keyBytes
* @return 返回私钥
* @throws Exception
*/
public RSAPrivateKey getPrivateKey(byte[] keyBytes) throws Exception {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
}
/**
* 公钥加密
*
* @param data 待加密内容
* @param publicKey 公钥
* @return 返回密文
* @throws Exception
*/
public String encryptByPublicKey(String data, RSAPublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance(keyFormat.getCipherAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
// 模长
int key_len = publicKey.getModulus().bitLength() / 8;
// 加密数据长度 <= 模长-11
String[] datas = splitString(data, key_len - 11);
String mi = "";
// 如果明文长度大于模长-11则要分组加密
for (String s : datas) {
mi += bcd2Str(cipher.doFinal(s.getBytes()));
}
return mi;
}
public String encryptByPrivateKey(String data, String privateKey) throws Exception {
return encryptByPrivateKey(data, getPrivateKey(privateKey));
}
/**
* 私钥加密
*
* @param data 待加密数据
* @param privateKey 私钥
* @return 返回密文
* @throws Exception
*/
public String encryptByPrivateKey(String data, RSAPrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance(keyFormat.getCipherAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
// 模长
int key_len = privateKey.getModulus().bitLength() / 8;
// 加密数据长度 <= 模长-11
String[] datas = splitString(data, key_len - 11);
String mi = "";
// 如果明文长度大于模长-11则要分组加密
for (String s : datas) {
mi += bcd2Str(cipher.doFinal(s.getBytes()));
}
return mi;
}
public String decryptByPrivateKey(String data, String privateKey) throws Exception {
return decryptByPrivateKey(data, getPrivateKey(privateKey));
}
/**
* 私钥解密
*
* @param data 待解密内容
* @param privateKey 私钥
* @return 返回明文
* @throws Exception
*/
public String decryptByPrivateKey(String data, RSAPrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance(keyFormat.getCipherAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateKey);
// 模长
int key_len = privateKey.getModulus().bitLength() / 8;
byte[] bytes = data.getBytes();
byte[] bcd = ASCII_To_BCD(bytes, bytes.length);
// 如果密文长度大于模长则要分组解密
String ming = "";
byte[][] arrays = splitArray(bcd, key_len);
for (byte[] arr : arrays) {
ming += new String(cipher.doFinal(arr));
}
return ming;
}
/**
* 公钥解密
*
* @param data 待解密内容
* @param rsaPublicKey 公钥
* @return 返回明文
* @throws Exception
*/
public String decryptByPublicKey(String data, RSAPublicKey rsaPublicKey) throws Exception {
Cipher cipher = Cipher.getInstance(keyFormat.getCipherAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, rsaPublicKey);
// 模长
int key_len = rsaPublicKey.getModulus().bitLength() / 8;
byte[] bytes = data.getBytes();
byte[] bcd = ASCII_To_BCD(bytes, bytes.length);
// 如果密文长度大于模长则要分组解密
String ming = "";
byte[][] arrays = splitArray(bcd, key_len);
for (byte[] arr : arrays) {
ming += new String(cipher.doFinal(arr));
}
return ming;
}
public static String convertPkcs8ToPkcs1(byte[] privateKeyData) throws Exception{
PrivateKeyInfo pkInfo = PrivateKeyInfo.getInstance(privateKeyData);
ASN1Encodable encodable = pkInfo.parsePrivateKey();
ASN1Primitive primitive = encodable.toASN1Primitive();
byte[] privateKeyPKCS1 = primitive.getEncoded();
return Base64.encodeBase64String(privateKeyPKCS1);
}
/**
* ASCII码转BCD码
*/
public static byte[] ASCII_To_BCD(byte[] ascii, int asc_len) {
byte[] bcd = new byte[asc_len / 2];
int j = 0;
for (int i = 0; i < (asc_len + 1) / 2; i++) {
bcd[i] = asc_to_bcd(ascii[j++]);
bcd[i] = (byte) (((j >= asc_len) ? 0x00 : asc_to_bcd(ascii[j++]) & 0xff) + (bcd[i] << 4));
}
return bcd;
}
public static byte asc_to_bcd(byte asc) {
byte bcd;
if ((asc >= '0') && (asc <= '9')) {
bcd = (byte) (asc - '0');
} else if ((asc >= 'A') && (asc <= 'F')) {
bcd = (byte) (asc - 'A' + 10);
} else if ((asc >= 'a') && (asc <= 'f')) {
bcd = (byte) (asc - 'a' + 10);
} else {
bcd = (byte) (asc - 48);
}
return bcd;
}
/**
* BCD转字符串
*/
public String bcd2Str(byte[] bytes) {
char[] temp = new char[bytes.length * 2];
char val;
for (int i = 0; i < bytes.length; i++) {
val = (char) (((bytes[i] & 0xf0) >> 4) & 0x0f);
temp[i * 2] = (char) (val > 9 ? val + 'A' - 10 : val + '0');
val = (char) (bytes[i] & 0x0f);
temp[i * 2 + 1] = (char) (val > 9 ? val + 'A' - 10 : val + '0');
}
return new String(temp);
}
/**
* 拆分字符串
*/
public String[] splitString(String string, int len) {
int x = string.length() / len;
int y = string.length() % len;
int z = 0;
if (y != 0) {
z = 1;
}
String[] strings = new String[x + z];
String str = "";
for (int i = 0; i < x + z; i++) {
if (i == x + z - 1 && y != 0) {
str = string.substring(i * len, i * len + y);
} else {
str = string.substring(i * len, i * len + len);
}
strings[i] = str;
}
return strings;
}
/**
* 拆分数组
*/
public byte[][] splitArray(byte[] data, int len) {
int x = data.length / len;
int y = data.length % len;
int z = 0;
if (y != 0) {
z = 1;
}
byte[][] arrays = new byte[x + z][];
byte[] arr;
for (int i = 0; i < x + z; i++) {
arr = new byte[len];
if (i == x + z - 1 && y != 0) {
System.arraycopy(data, i * len, arr, 0, y);
} else {
System.arraycopy(data, i * len, arr, 0, len);
}
arrays[i] = arr;
}
return arrays;
}
public enum KeyLength {
LENGTH_1024(1024), LENGTH_2048(2048);
private int length;
KeyLength(int length) {
this.length = length;
}
public int getLength() {
return length;
}
}
public static class KeyStore {
private String publicKey;
private String privateKey;
public String getPublicKey() {
return publicKey;
}
public void setPublicKey(String publicKey) {
this.publicKey = publicKey;
}
public String getPrivateKey() {
return privateKey;
}
public void setPrivateKey(String privateKey) {
this.privateKey = privateKey;
}
}
public enum KeyFormat {
PKCS1("RSA/ECB/PKCS1Padding"), PKCS8("RSA");
private String cipherAlgorithm;
KeyFormat(String cipherAlgorithm) {
this.cipherAlgorithm = cipherAlgorithm;
}
public String getCipherAlgorithm() {
return cipherAlgorithm;
}
}
public KeyFormat getKeyFormat() {
return keyFormat;
}
public KeyLength getKeyLength() {
return keyLength;
}
/* ------------ Test ------------
public static void main(String[] args) throws Exception {
RSATool rsa_pkcs8_1024 = new RSATool(KeyFormat.PKCS8, KeyLength.LENGTH_1024);
RSATool rsa_pkcs8_2048 = new RSATool(KeyFormat.PKCS8, KeyLength.LENGTH_2048);
doTest(rsa_pkcs8_1024);
doTest(rsa_pkcs8_2048);
// PKCS在Java环境无法测试,可以生成一对,到非java平台测试,如C#
}
private static void doTest(RSATool rsaTool) throws Exception {
System.out.println("秘钥格式:" + rsaTool.keyFormat.name() + ", 秘钥长度:" + rsaTool.keyLength.getLength());
KeyStore keys = rsaTool.createKeys();
String pubKey = keys.getPublicKey();
System.out.println("pubKey:");
System.out.println(pubKey);
String priKey = keys.getPrivateKey();
System.out.println("priKey:");
System.out.println(priKey);
System.out.println("--------");
String ming = "你好,abc123~!@=";
// 用公钥加密
String mi = rsaTool.encryptByPublicKey(ming, rsaTool.getPublicKey(pubKey));
System.out.println("mi : " + mi);
// 用私钥解密
String ming2 = rsaTool.decryptByPrivateKey(mi, rsaTool.getPrivateKey(priKey));
System.out.println("ming : " + ming2 + ", 结果:" + ming2.equals(ming));
// 用私钥加密
String mi2 = rsaTool.encryptByPrivateKey(ming, rsaTool.getPrivateKey(priKey));
System.out.println("mi2 : " + mi2);
// 用公钥解密
String ming3 = rsaTool.decryptByPublicKey(mi2, rsaTool.getPublicKey(pubKey));
System.out.println("ming3 : " + ming3 + ", 结果:" + ming3.equals(ming));
System.out.println("---------------------");
}
*/
}

@ -0,0 +1,26 @@
package com.gitee.sop.adminserver.common;
/**
* @author tanghc
*/
public class ZookeeperOperationException extends RuntimeException {
public ZookeeperOperationException() {
}
public ZookeeperOperationException(String message) {
super(message);
}
public ZookeeperOperationException(String message, Throwable cause) {
super(message, cause);
}
public ZookeeperOperationException(Throwable cause) {
super(cause);
}
public ZookeeperOperationException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

@ -0,0 +1,10 @@
package com.gitee.sop.adminserver.common;
/**
* @author tanghc
*/
public class ZookeeperPathExistException extends Exception {
public ZookeeperPathExistException(String message) {
super(message);
}
}

@ -0,0 +1,10 @@
package com.gitee.sop.adminserver.common;
/**
* @author tanghc
*/
public class ZookeeperPathNotExistException extends Exception {
public ZookeeperPathNotExistException(String message) {
super(message);
}
}

@ -0,0 +1,12 @@
package com.gitee.sop.adminserver.config;
import com.gitee.sop.registryapi.config.BaseRegistryConfig;
import org.springframework.context.annotation.Configuration;
/**
* @author tanghc
*/
@Configuration
public class RegistryConfig extends BaseRegistryConfig {
}

@ -8,13 +8,17 @@ import com.gitee.easyopen.ApiParamParser;
import com.gitee.easyopen.ParamNames; import com.gitee.easyopen.ParamNames;
import com.gitee.easyopen.interceptor.ApiInterceptor; import com.gitee.easyopen.interceptor.ApiInterceptor;
import com.gitee.easyopen.session.ApiSessionManager; import com.gitee.easyopen.session.ApiSessionManager;
import com.gitee.sop.adminserver.bean.ZookeeperContext;
import com.gitee.sop.adminserver.interceptor.LoginInterceptor; import com.gitee.sop.adminserver.interceptor.LoginInterceptor;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils; import org.apache.commons.lang.math.NumberUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -29,6 +33,9 @@ public class WebConfig {
@Value("${admin.access-token.timeout-minutes}") @Value("${admin.access-token.timeout-minutes}")
private String accessTokenTimeout; private String accessTokenTimeout;
@Autowired
private Environment environment;
@Bean @Bean
ApiConfig apiConfig() { ApiConfig apiConfig() {
ApiConfig apiConfig = new ApiConfig(); ApiConfig apiConfig = new ApiConfig();
@ -59,4 +66,9 @@ public class WebConfig {
return apiConfig; return apiConfig;
} }
@PostConstruct
public void after() {
ZookeeperContext.setEnvironment(environment);
}
} }

@ -2,13 +2,13 @@ package com.gitee.sop.adminserver.entity;
import lombok.Data; import lombok.Data;
import java.util.Date;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.GeneratedValue; import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType; import javax.persistence.GenerationType;
import javax.persistence.Id; import javax.persistence.Id;
import javax.persistence.Table; import javax.persistence.Table;
import java.io.Serializable;
import java.util.Date;
/** /**
@ -19,7 +19,7 @@ import javax.persistence.Table;
*/ */
@Table(name = "admin_user_info") @Table(name = "admin_user_info")
@Data @Data
public class AdminUserInfo { public class AdminUserInfo implements Serializable {
@Id @Id
@Column(name = "id") @Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)

@ -28,21 +28,18 @@ public class IsvInfo {
/** appKey, 数据库字段:app_key */ /** appKey, 数据库字段:app_key */
private String appKey; private String appKey;
/** secret, 数据库字段:secret */ // 不再使用,转移到isv_keys表
private String secret; private String secret = "";
/** 公钥, 数据库字段:pub_key */ /** 公钥,不再使用,转移到isv_keys表 数据库字段:pub_key */
private String pubKey; private String pubKey;
/** 私钥, 数据库字段:pri_key */ /** 私钥,不再使用,转移到isv_keys表 数据库字段:pri_key */
private String priKey; private String priKey;
/** 1启用,2禁用, 数据库字段:status */ /** 1启用,2禁用, 数据库字段:status */
private Byte status; private Byte status;
/** 1:RSA2,2:MD5, 数据库字段:sign_type */
private Byte signType;
/** 数据库字段:gmt_create */ /** 数据库字段:gmt_create */
private Date gmtCreate; private Date gmtCreate;

@ -0,0 +1,57 @@
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;
/**
* 表名isv_keys
* 备注ISV秘钥
*
* @author tanghc
*/
@Table(name = "isv_keys")
@Data
public class IsvKeys {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
/** 数据库字段:id */
private Long id;
/** 数据库字段:app_key */
private String appKey;
/** 1:RSA2,2:MD5, 数据库字段:sign_type */
private Byte signType;
/** isv_info.sign_type=2时使用, 数据库字段:secret */
private String secret;
/** 秘钥格式,1:PKCS8(JAVA适用),2:PKCS1(非JAVA适用), 数据库字段:key_format */
private Byte keyFormat;
/** 开发者生成的公钥, 数据库字段:public_key_isv */
private String publicKeyIsv;
/** 开发者生成的私钥(交给开发者), 数据库字段:private_key_isv */
private String privateKeyIsv;
/** 平台生成的公钥(交给开发者), 数据库字段:public_key_platform */
private String publicKeyPlatform;
/** 平台生成的私钥, 数据库字段:private_key_platform */
private String privateKeyPlatform;
/** 数据库字段:gmt_create */
private Date gmtCreate;
/** 数据库字段:gmt_modified */
private Date gmtModified;
}

@ -2,11 +2,27 @@ package com.gitee.sop.adminserver.mapper;
import com.gitee.fastmybatis.core.mapper.CrudMapper; import com.gitee.fastmybatis.core.mapper.CrudMapper;
import com.gitee.sop.adminserver.api.isv.result.IsvDetailDTO;
import com.gitee.sop.adminserver.entity.IsvInfo; import com.gitee.sop.adminserver.entity.IsvInfo;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
/** /**
* @author tanghc * @author tanghc
*/ */
public interface IsvInfoMapper extends CrudMapper<IsvInfo, Long> { public interface IsvInfoMapper extends CrudMapper<IsvInfo, Long> {
@Select("SELECT " +
" t.app_key appKey " +
" ,t.status " +
" ,t2.sign_type signType " +
" ,t2.secret " +
" ,t2.public_key_isv publicKeyIsv " +
" ,t2.private_key_platform privateKeyPlatform " +
"FROM isv_info t " +
"INNER JOIN isv_keys t2 ON t.app_key = t2.app_key " +
"WHERE t.app_key = #{appKey}")
IsvDetailDTO getIsvDetail(@Param("appKey") String appKey);
} }

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

@ -20,7 +20,7 @@ public class RouteConfigService {
* @param routeConfigDto * @param routeConfigDto
* @throws Exception * @throws Exception
*/ */
public void sendRouteConfigMsg(RouteConfigDto routeConfigDto) throws Exception { public void sendRouteConfigMsg(RouteConfigDto routeConfigDto) {
String configData = JSON.toJSONString(routeConfigDto); String configData = JSON.toJSONString(routeConfigDto);
ChannelMsg channelMsg = new ChannelMsg("update", configData); ChannelMsg channelMsg = new ChannelMsg("update", configData);
String jsonData = JSON.toJSONString(channelMsg); String jsonData = JSON.toJSONString(channelMsg);

@ -2,8 +2,10 @@ package com.gitee.sop.adminserver.service;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.gitee.fastmybatis.core.query.Query; import com.gitee.fastmybatis.core.query.Query;
import com.gitee.sop.adminserver.api.service.param.RoutePermissionParam;
import com.gitee.sop.adminserver.bean.ChannelMsg; import com.gitee.sop.adminserver.bean.ChannelMsg;
import com.gitee.sop.adminserver.bean.IsvRoutePermission; import com.gitee.sop.adminserver.bean.IsvRoutePermission;
import com.gitee.sop.adminserver.bean.SopAdminConstants;
import com.gitee.sop.adminserver.bean.ZookeeperContext; import com.gitee.sop.adminserver.bean.ZookeeperContext;
import com.gitee.sop.adminserver.entity.PermIsvRole; import com.gitee.sop.adminserver.entity.PermIsvRole;
import com.gitee.sop.adminserver.entity.PermRolePermission; import com.gitee.sop.adminserver.entity.PermRolePermission;
@ -16,6 +18,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -93,12 +96,45 @@ public class RoutePermissionService {
/** /**
* 推送所有路由权限到zookeeper * 推送所有路由权限到zookeeper
*/ */
public void sendRoutePermissionReloadMsg() throws Exception { public void sendRoutePermissionReloadMsg(RoutePermissionParam oldRoutePermission) throws Exception {
ChannelMsg channelMsg = new ChannelMsg("reload", null); String listenPath = SopAdminConstants.RELOAD_ROUTE_PERMISSION_PATH + "/" + System.currentTimeMillis();
ZookeeperContext.listenTempPath(listenPath, errorMsg -> {
log.error("推送所有路由权限到zookeeper失败,进行回滚,errorMsg: {},oldRoutePermission:{}", errorMsg, JSON.toJSONString(oldRoutePermission));
// 回滚
updateRoutePermission(oldRoutePermission);
});
IsvRoutePermission isvRoutePermission = new IsvRoutePermission();
isvRoutePermission.setListenPath(listenPath);
ChannelMsg channelMsg = new ChannelMsg("reload", isvRoutePermission);
String jsonData = JSON.toJSONString(channelMsg); String jsonData = JSON.toJSONString(channelMsg);
String path = ZookeeperContext.getIsvRoutePermissionChannelPath(); String path = ZookeeperContext.getIsvRoutePermissionChannelPath();
log.info("消息推送--路由权限(reload), path:{}, data:{}", path, jsonData); log.info("消息推送--路由权限(reload), path:{}, data:{}", path, jsonData);
ZookeeperContext.createOrUpdateData(path, jsonData); ZookeeperContext.createOrUpdateData(path, jsonData);
} }
/**
* 更新路由权限
*
* @param param
*/
public synchronized void updateRoutePermission(RoutePermissionParam param) {
String routeId = param.getRouteId();
// 删除所有数据
Query delQuery = new Query();
delQuery.eq("route_id", routeId);
permRolePermissionMapper.deleteByQuery(delQuery);
List<String> roleCodes = param.getRoleCode();
if (org.apache.commons.collections.CollectionUtils.isNotEmpty(roleCodes)) {
List<PermRolePermission> tobeSave = new ArrayList<>(roleCodes.size());
for (String roleCode : roleCodes) {
PermRolePermission permRolePermission = new PermRolePermission();
permRolePermission.setRoleCode(roleCode);
permRolePermission.setRouteId(routeId);
tobeSave.add(permRolePermission);
}
// 批量添加
permRolePermissionMapper.saveBatch(tobeSave);
}
}
} }

@ -10,7 +10,6 @@ import org.springframework.stereotype.Service;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toList;
@ -44,8 +43,7 @@ public class RouteService {
return isRoute && gatewayRouteDefinition.getId().contains(id); return isRoute && gatewayRouteDefinition.getId().contains(id);
} }
}) })
.collect(toList()) .collect(toList());
;
return routeDefinitionStream; return routeDefinitionStream;
} }

@ -6,12 +6,18 @@ admin:
access-token: access-token:
timeout-minutes: 30 timeout-minutes: 30
sop:
# 签名方式,rsa:支付宝开放平台签名方式,md5:淘宝开放平台签名方式
sign-type: rsa
# 不用改 # 不用改
spring: spring:
application: application:
name: sop-admin name: sop-admin
cloud: cloud:
# zookeeper地址,根据实际情况修改
zookeeper: zookeeper:
connect-string: localhost:2181 connect-string: localhost:2181
baseSleepTimeMs: 3000 baseSleepTimeMs: 3000
@ -31,12 +37,13 @@ easyopen:
ignore-validate: true ignore-validate: true
# 注册中心地址,根据实际情况改,这里只是参数,并不会去注册 # 注册中心地址,根据实际情况改,这里只是参数,并不会去注册
eureka: registry:
port: 1111 eureka-server-addr: http://localhost:1111/eureka/
host: localhost # nacos服务器地址
client: nacos-server-addr: 127.0.0.1:8848
serviceUrl: # 使用eureka,填:eureka,使用nacos填:nacos
defaultZone: http://${eureka.host}:${eureka.port}/eureka/ name: eureka
# 根据实际情况改 # 根据实际情况改
logging: logging:
@ -49,6 +56,6 @@ mybatis:
fill: {com.gitee.fastmybatis.core.support.DateFillInsert: gmt_create, fill: {com.gitee.fastmybatis.core.support.DateFillInsert: gmt_create,
com.gitee.fastmybatis.core.support.DateFillUpdate: gmt_modified} com.gitee.fastmybatis.core.support.DateFillUpdate: gmt_modified}
# 不用改,如果要改,建议全局替换修改 # 不用改,如果要改,全局替换修改
zuul: zuul:
secret: MZZOUSTua6LzApIWXCwEgbBmxSzpzC secret: MZZOUSTua6LzApIWXCwEgbBmxSzpzC

@ -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.4f0872ef.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 t(t){for(var r,o,a=t[0],i=t[1],f=t[2],l=0,d=[];l<a.length;l++)o=a[l],c[o]&&d.push(c[o][0]),c[o]=0;for(r in i)Object.prototype.hasOwnProperty.call(i,r)&&(e[r]=i[r]);s&&s(t);while(d.length)d.shift()();return u.push.apply(u,f||[]),n()}function n(){for(var e,t=0;t<u.length;t++){for(var n=u[t],r=!0,o=1;o<n.length;o++){var a=n[o];0!==c[a]&&(r=!1)}r&&(u.splice(t--,1),e=i(i.s=n[0]))}return e}var r={},o={runtime:0},c={runtime:0},u=[];function a(e){return i.p+"static/js/"+({}[e]||e)+"."+{"chunk-19100986":"3ec98327","chunk-238a81e9":"5955f13d","chunk-29b24c82":"2b3ac7ca","chunk-2d0aa95b":"447435b3","chunk-2d221836":"574c9ab6","chunk-2d22c2e3":"46f37153","chunk-4a59cbe4":"b5e74cc4","chunk-ea2e58a4":"f3f85b0e"}[e]+".js"}function i(t){if(r[t])return r[t].exports;var n=r[t]={i:t,l:!1,exports:{}};return e[t].call(n.exports,n,n.exports,i),n.l=!0,n.exports}i.e=function(e){var t=[],n={"chunk-19100986":1,"chunk-238a81e9":1,"chunk-29b24c82":1,"chunk-ea2e58a4":1};o[e]?t.push(o[e]):0!==o[e]&&n[e]&&t.push(o[e]=new Promise(function(t,n){for(var r="static/css/"+({}[e]||e)+"."+{"chunk-19100986":"a43114f3","chunk-238a81e9":"e8e2beee","chunk-29b24c82":"857979fb","chunk-2d0aa95b":"31d6cfe0","chunk-2d221836":"31d6cfe0","chunk-2d22c2e3":"31d6cfe0","chunk-4a59cbe4":"31d6cfe0","chunk-ea2e58a4":"d10599db"}[e]+".css",c=i.p+r,u=document.getElementsByTagName("link"),a=0;a<u.length;a++){var f=u[a],l=f.getAttribute("data-href")||f.getAttribute("href");if("stylesheet"===f.rel&&(l===r||l===c))return t()}var d=document.getElementsByTagName("style");for(a=0;a<d.length;a++){f=d[a],l=f.getAttribute("data-href");if(l===r||l===c)return t()}var s=document.createElement("link");s.rel="stylesheet",s.type="text/css",s.onload=t,s.onerror=function(t){var r=t&&t.target&&t.target.src||c,u=new Error("Loading CSS chunk "+e+" failed.\n("+r+")");u.code="CSS_CHUNK_LOAD_FAILED",u.request=r,delete o[e],s.parentNode.removeChild(s),n(u)},s.href=c;var h=document.getElementsByTagName("head")[0];h.appendChild(s)}).then(function(){o[e]=0}));var r=c[e];if(0!==r)if(r)t.push(r[2]);else{var u=new Promise(function(t,n){r=c[e]=[t,n]});t.push(r[2]=u);var f,l=document.createElement("script");l.charset="utf-8",l.timeout=120,i.nc&&l.setAttribute("nonce",i.nc),l.src=a(e),f=function(t){l.onerror=l.onload=null,clearTimeout(d);var n=c[e];if(0!==n){if(n){var r=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src,u=new Error("Loading chunk "+e+" failed.\n("+r+": "+o+")");u.type=r,u.request=o,n[1](u)}c[e]=void 0}};var d=setTimeout(function(){f({type:"timeout",target:l})},12e4);l.onerror=l.onload=f,document.head.appendChild(l)}return Promise.all(t)},i.m=e,i.c=r,i.d=function(e,t,n){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},i.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(i.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)i.d(n,r,function(t){return e[t]}.bind(null,r));return n},i.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="",i.oe=function(e){throw console.error(e),e};var f=window["webpackJsonp"]=window["webpackJsonp"]||[],l=f.push.bind(f);f.push=t,f=f.slice();for(var d=0;d<f.length;d++)t(f[d]);var s=l;n()})([]);</script><script src=static/js/chunk-elementUI.8ebdfbab.js></script><script src=static/js/chunk-libs.9cf9cc40.js></script><script src=static/js/app.36a3cb86.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.4f0872ef.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 t(t){for(var r,c,a=t[0],i=t[1],f=t[2],l=0,d=[];l<a.length;l++)c=a[l],u[c]&&d.push(u[c][0]),u[c]=0;for(r in i)Object.prototype.hasOwnProperty.call(i,r)&&(e[r]=i[r]);h&&h(t);while(d.length)d.shift()();return o.push.apply(o,f||[]),n()}function n(){for(var e,t=0;t<o.length;t++){for(var n=o[t],r=!0,c=1;c<n.length;c++){var a=n[c];0!==u[a]&&(r=!1)}r&&(o.splice(t--,1),e=i(i.s=n[0]))}return e}var r={},c={runtime:0},u={runtime:0},o=[];function a(e){return i.p+"static/js/"+({}[e]||e)+"."+{"chunk-009073d4":"28312808","chunk-238a81e9":"5955f13d","chunk-25908fca":"9d8bc0b6","chunk-29e7142c":"994a3ac0","chunk-2d2085ef":"a63a74dc","chunk-2d22c2e3":"4a098244","chunk-37401378":"4e39ec9b","chunk-4a59cbe4":"a6360c68","chunk-6a68a33e":"b6685cb9","chunk-73b2dcec":"14f248eb"}[e]+".js"}function i(t){if(r[t])return r[t].exports;var n=r[t]={i:t,l:!1,exports:{}};return e[t].call(n.exports,n,n.exports,i),n.l=!0,n.exports}i.e=function(e){var t=[],n={"chunk-009073d4":1,"chunk-238a81e9":1,"chunk-25908fca":1,"chunk-29e7142c":1,"chunk-37401378":1,"chunk-6a68a33e":1,"chunk-73b2dcec":1};c[e]?t.push(c[e]):0!==c[e]&&n[e]&&t.push(c[e]=new Promise(function(t,n){for(var r="static/css/"+({}[e]||e)+"."+{"chunk-009073d4":"0af16c7e","chunk-238a81e9":"e8e2beee","chunk-25908fca":"89ab33e8","chunk-29e7142c":"d10599db","chunk-2d2085ef":"31d6cfe0","chunk-2d22c2e3":"31d6cfe0","chunk-37401378":"a43114f3","chunk-4a59cbe4":"31d6cfe0","chunk-6a68a33e":"3b12267b","chunk-73b2dcec":"99cf6327"}[e]+".css",u=i.p+r,o=document.getElementsByTagName("link"),a=0;a<o.length;a++){var f=o[a],l=f.getAttribute("data-href")||f.getAttribute("href");if("stylesheet"===f.rel&&(l===r||l===u))return t()}var d=document.getElementsByTagName("style");for(a=0;a<d.length;a++){f=d[a],l=f.getAttribute("data-href");if(l===r||l===u)return t()}var h=document.createElement("link");h.rel="stylesheet",h.type="text/css",h.onload=t,h.onerror=function(t){var r=t&&t.target&&t.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),n(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)t.push(r[2]);else{var o=new Promise(function(t,n){r=u[e]=[t,n]});t.push(r[2]=o);var f,l=document.createElement("script");l.charset="utf-8",l.timeout=120,i.nc&&l.setAttribute("nonce",i.nc),l.src=a(e),f=function(t){l.onerror=l.onload=null,clearTimeout(d);var n=u[e];if(0!==n){if(n){var r=t&&("load"===t.type?"missing":t.type),c=t&&t.target&&t.target.src,o=new Error("Loading chunk "+e+" failed.\n("+r+": "+c+")");o.type=r,o.request=c,n[1](o)}u[e]=void 0}};var d=setTimeout(function(){f({type:"timeout",target:l})},12e4);l.onerror=l.onload=f,document.head.appendChild(l)}return Promise.all(t)},i.m=e,i.c=r,i.d=function(e,t,n){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},i.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(i.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)i.d(n,r,function(t){return e[t]}.bind(null,r));return n},i.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="",i.oe=function(e){throw console.error(e),e};var f=window["webpackJsonp"]=window["webpackJsonp"]||[],l=f.push.bind(f);f.push=t,f=f.slice();for(var d=0;d<f.length;d++)t(f[d]);var h=l;n()})([]);</script><script src=static/js/chunk-elementUI.8ebdfbab.js></script><script src=static/js/chunk-libs.9cf9cc40.js></script><script src=static/js/app.095e8b9c.js></script></body></html>

@ -0,0 +1 @@
.custom-tree-node{-webkit-box-flex:1;-ms-flex:1;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;font-size:14px;padding-right:8px}.el-input.is-disabled .el-input__inner,.el-radio__input.is-disabled+span.el-radio__label{color:#909399}.limit-tip[data-v-b81bfbde]{cursor:pointer;margin-left:10px}

@ -0,0 +1 @@
.gen-key{margin-bottom:0!important}fieldset{border:1px solid #ccc;color:grey;margin-left:40px;margin-bottom:20px}fieldset label{width:110px!important}fieldset .el-form-item__content{margin-left:110px!important}.key-view .el-form-item{margin-bottom:10px!important}

@ -1 +0,0 @@
.limit-tip[data-v-29df7fee]{cursor:pointer;margin-left:10px}

@ -0,0 +1 @@
.custom-tree-node{-webkit-box-flex:1;-ms-flex:1;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;font-size:14px;padding-right:8px}.el-input.is-disabled .el-input__inner,.el-radio__input.is-disabled+span.el-radio__label{color:#909399}

@ -0,0 +1 @@
.gen-key{margin-bottom:0!important}fieldset{border:1px solid #ccc;color:grey;margin-left:40px;margin-bottom:20px}fieldset label{width:110px!important}fieldset .el-form-item__content{margin-left:110px!important}

@ -1 +0,0 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d22c2e3"],{f1ac:function(t,e,a){"use strict";a.r(e);var n=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:"serviceId"}},[a("el-input",{staticStyle:{width:"250px"},attrs:{clearable:!0,placeholder:"serviceId"},model:{value:t.searchFormData.serviceId,callback:function(e){t.$set(t.searchFormData,"serviceId",e)},expression:"searchFormData.serviceId"}})],1),t._v(" "),a("el-form-item",[a("el-button",{attrs:{type:"primary",icon:"el-icon-search"},on:{click:t.onSearchTable}},[t._v("查询")])],1)],1),t._v(" "),a("el-table",{staticStyle:{width:"100%","margin-bottom":"20px"},attrs:{data:t.tableData,border:"","row-key":"id"}},[a("el-table-column",{attrs:{prop:"name",label:"服务名称(serviceId)",width:"200"}}),t._v(" "),a("el-table-column",{attrs:{prop:"instanceId",label:"instanceId",width:"250"}}),t._v(" "),a("el-table-column",{attrs:{prop:"ipAddr",label:"IP地址",width:"150"}}),t._v(" "),a("el-table-column",{attrs:{prop:"serverPort",label:"端口号",width:"100"}}),t._v(" "),a("el-table-column",{attrs:{prop:"status",label:"服务状态",width:"100"},scopedSlots:t._u([{key:"default",fn:function(e){return[e.row.parentId>0&&"UP"===e.row.status?a("el-tag",{attrs:{type:"success"}},[t._v("已上线")]):t._e(),t._v(" "),e.row.parentId>0&&"STARTING"===e.row.status?a("el-tag",{attrs:{type:"info"}},[t._v("正在启动")]):t._e(),t._v(" "),e.row.parentId>0&&"UNKNOWN"===e.row.status?a("el-tag",[t._v("未知")]):t._e(),t._v(" "),e.row.parentId>0&&("OUT_OF_SERVICE"===e.row.status||"DOWN"===e.row.status)?a("el-tag",{attrs:{type:"danger"}},[t._v("已下线")]):t._e()]}}])}),t._v(" "),a("el-table-column",{attrs:{prop:"updateTime",label:"最后更新时间",width:"160"}}),t._v(" "),a("el-table-column",{attrs:{label:"操作",width:"100"},scopedSlots:t._u([{key:"default",fn:function(e){return[e.row.parentId>0&&"UP"===e.row.status?a("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(a){return t.onOffline(e.row)}}},[t._v("下线")]):t._e(),t._v(" "),e.row.parentId>0&&"OUT_OF_SERVICE"===e.row.status?a("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(a){return t.onOnline(e.row)}}},[t._v("上线")]):t._e()]}}])})],1)],1)},r=[],i=(a("7f7f"),a("ac6a"),{data:function(){return{searchFormData:{serviceId:""},tableData:[]}},created:function(){this.loadTable()},methods:{loadTable:function(){this.post("service.instance.list",this.searchFormData,function(t){this.tableData=this.buildTreeData(t.data)})},buildTreeData:function(t){return t.forEach(function(e){var a=e.parentId;0===a||t.forEach(function(t){if(t.id===a){var n=t.children;n||(n=[]),n.push(e),t.children=n}})}),t=t.filter(function(t){return 0===t.parentId}),t},onSearchTable:function(){this.loadTable()},onOffline:function(t){this.confirm("确定要下线【"+t.name+"】吗?",function(e){var a={serviceId:t.name,instanceId:t.instanceId};this.post("service.instance.offline",a,function(){this.tip("下线成功"),e()})})},onOnline:function(t){this.confirm("确定要上线【"+t.name+"】吗?",function(e){var a={serviceId:t.name,instanceId:t.instanceId};this.post("service.instance.online",a,function(){this.tip("上线成功"),e()})})}}}),s=i,o=a("2877"),l=Object(o["a"])(s,n,r,!1,null,null,null);e["default"]=l.exports}}]);

@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d22c2e3"],{f1ac:function(t,e,n){"use strict";n.r(e);var a=function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"app-container"},[n("el-form",{staticClass:"demo-form-inline",attrs:{inline:!0,model:t.searchFormData,size:"mini"}},[n("el-form-item",{attrs:{label:"serviceId"}},[n("el-input",{staticStyle:{width:"250px"},attrs:{clearable:!0,placeholder:"serviceId"},model:{value:t.searchFormData.serviceId,callback:function(e){t.$set(t.searchFormData,"serviceId",e)},expression:"searchFormData.serviceId"}})],1),t._v(" "),n("el-form-item",[n("el-button",{attrs:{type:"primary",icon:"el-icon-search"},on:{click:t.onSearchTable}},[t._v("查询")])],1)],1),t._v(" "),n("el-table",{staticStyle:{width:"100%","margin-bottom":"20px"},attrs:{data:t.tableData,border:"","row-key":"id"}},[n("el-table-column",{attrs:{prop:"serviceId",label:"服务名称",width:"200"},scopedSlots:t._u([{key:"default",fn:function(e){return[n("span",{domProps:{innerHTML:t._s(t.renderServiceName(e.row))}})]}}])}),t._v(" "),n("el-table-column",{attrs:{prop:"ipPort",label:"IP端口",width:"250"}}),t._v(" "),n("el-table-column",{attrs:{prop:"status",label:"服务状态",width:"100"},scopedSlots:t._u([{key:"default",fn:function(e){return[e.row.parentId>0&&"UP"===e.row.status?n("el-tag",{attrs:{type:"success"}},[t._v("已上线")]):t._e(),t._v(" "),e.row.parentId>0&&"STARTING"===e.row.status?n("el-tag",{attrs:{type:"info"}},[t._v("正在启动")]):t._e(),t._v(" "),e.row.parentId>0&&"UNKNOWN"===e.row.status?n("el-tag",[t._v("未知")]):t._e(),t._v(" "),e.row.parentId>0&&("OUT_OF_SERVICE"===e.row.status||"DOWN"===e.row.status)?n("el-tag",{attrs:{type:"danger"}},[t._v("已下线")]):t._e()]}}])}),t._v(" "),n("el-table-column",{attrs:{prop:"updateTime",label:"最后更新时间",width:"160"}}),t._v(" "),n("el-table-column",{attrs:{label:"操作",width:"100"},scopedSlots:t._u([{key:"default",fn:function(e){return[e.row.parentId>0&&"UP"===e.row.status?n("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(n){return t.onOffline(e.row)}}},[t._v("下线")]):t._e(),t._v(" "),e.row.parentId>0&&"OUT_OF_SERVICE"===e.row.status?n("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(n){return t.onOnline(e.row)}}},[t._v("上线")]):t._e()]}}])})],1)],1)},r=[],i=(n("ac6a"),{data:function(){return{searchFormData:{serviceId:""},tableData:[]}},created:function(){this.loadTable()},methods:{loadTable:function(){this.post("service.instance.list",this.searchFormData,function(t){this.tableData=this.buildTreeData(t.data)})},buildTreeData:function(t){return t.forEach(function(e){var n=e.parentId;0===n||t.forEach(function(t){if(t.id===n){var a=t.children;a||(a=[]),a.push(e),t.children=a}})}),t=t.filter(function(t){return 0===t.parentId}),t},onSearchTable:function(){this.loadTable()},onOffline:function(t){this.confirm("确定要下线【"+t.serviceId+"】吗?",function(e){this.post("service.instance.offline",t,function(){this.tip("下线成功"),e()})})},onOnline:function(t){this.confirm("确定要上线【"+t.serviceId+"】吗?",function(e){this.post("service.instance.online",t,function(){this.tip("上线成功"),e()})})},renderServiceName:function(t){var e="";if(t.children&&t.children.length>0){var n=t.children.filter(function(t){return"UP"===t.status}).length;e=" (".concat(n,"/").concat(t.children.length,")")}return t.serviceId+e}}}),o=i,s=n("2877"),l=Object(s["a"])(o,a,r,!1,null,null,null);e["default"]=l.exports}}]);

@ -1 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-19100986"],{5040:function(t,n,a){"use strict";var e=a("a5a4"),s=a.n(e);s.a},9406:function(t,n,a){"use strict";a.r(n);var e=function(){var t=this,n=t.$createElement;t._self._c;return t._m(0)},s=[function(){var t=this,n=t.$createElement,a=t._self._c||n;return a("div",{staticClass:"dashboard-container"},[a("div",{staticClass:"dashboard-text"},[t._v("欢迎使用SOP Admin")])])}],i={created:function(){this.post("admin.userinfo.get",{},function(t){})}},c=i,r=(a("5040"),a("2877")),o=Object(r["a"])(c,e,s,!1,null,"b4bd5162",null);n["default"]=o.exports},a5a4:function(t,n,a){}}]); (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-37401378"],{5040:function(t,n,a){"use strict";var e=a("a5a4"),s=a.n(e);s.a},9406:function(t,n,a){"use strict";a.r(n);var e=function(){var t=this,n=t.$createElement;t._self._c;return t._m(0)},s=[function(){var t=this,n=t.$createElement,a=t._self._c||n;return a("div",{staticClass:"dashboard-container"},[a("div",{staticClass:"dashboard-text"},[t._v("欢迎使用SOP Admin")])])}],i={created:function(){this.post("admin.userinfo.get",{},function(t){})}},c=i,r=(a("5040"),a("2877")),o=Object(r["a"])(c,e,s,!1,null,"b4bd5162",null);n["default"]=o.exports},a5a4:function(t,n,a){}}]);

@ -0,0 +1,45 @@
package com.gitee.sop.adminserver;
import com.gitee.sop.adminserver.bean.SopAdminConstants;
import junit.framework.TestCase;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.data.Stat;
public class CuratorTest extends TestCase {
private static String zookeeperServerAddr = "localhost:2181";
static CuratorFramework client;
public CuratorTest() {
client = CuratorFrameworkFactory.builder()
.connectString(zookeeperServerAddr)
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
client.start();
}
/**
* 递归删除节点只能在测试环境用
*
* @throws Exception
*/
public void testDel() {
try {
client.delete()
.deletingChildrenIfNeeded()
.forPath(SopAdminConstants.RELOAD_ROUTE_PERMISSION_PATH);
} catch (Exception e) {
}
}
public void testCheck() throws Exception {
String path = SopAdminConstants.RELOAD_ROUTE_PERMISSION_PATH + "/1562231019332";
Stat stat = client.checkExists().forPath(path);
System.out.println(path + (stat == null ? "不存在" : "存在"));
}
}

@ -0,0 +1,21 @@
package com.gitee.sop.adminserver;
import com.gitee.sop.adminserver.api.isv.result.IsvDetailDTO;
import com.gitee.sop.adminserver.mapper.IsvInfoMapper;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @author tanghc
*/
public class IsvInfoMapperTest extends SopAdminServerApplicationTests {
@Autowired
IsvInfoMapper isvInfoMapper;
@Test
public void testGet() {
IsvDetailDTO isvDetail = isvInfoMapper.getIsvDetail("2019032617262200001");
System.out.println(isvDetail);
}
}

@ -1,4 +1,4 @@
package com.gitee.durcframework.easyopen.server; package com.gitee.sop.adminserver;
import org.junit.Test; import org.junit.Test;

@ -1,4 +1,4 @@
package com.gitee.durcframework.easyopen.server; package com.gitee.sop.adminserver;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;

@ -1,4 +1,4 @@
package com.gitee.durcframework.easyopen.server; package com.gitee.sop.adminserver;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;

@ -91,6 +91,7 @@ export const constantRoutes = [
{ {
path: '/isv', path: '/isv',
component: Layout, component: Layout,
name: 'Isv',
meta: { title: 'ISV管理', icon: 'user' }, meta: { title: 'ISV管理', icon: 'user' },
children: [ children: [
{ {
@ -98,6 +99,19 @@ export const constantRoutes = [
name: 'IsvList', name: 'IsvList',
component: () => import('@/views/isv/index'), component: () => import('@/views/isv/index'),
meta: { title: 'ISV列表' } meta: { title: 'ISV列表' }
},
{
path: 'role',
name: 'Role',
component: () => import('@/views/isv/role'),
meta: { title: '角色管理' }
},
{
path: 'keys',
name: 'Keys',
component: () => import('@/views/isv/keys'),
hidden: true,
meta: { title: '秘钥管理' }
} }
] ]
}, },

@ -8,7 +8,7 @@ import { getToken, removeToken } from './auth'
// 创建axios实例 // 创建axios实例
const client = axios.create({ const client = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // api 的 base_url baseURL: process.env.VUE_APP_BASE_API, // api 的 base_url
timeout: 5000 // 请求超时时间 timeout: 60000 // 请求超时时间,60秒
}) })
Object.assign(Vue.prototype, { Object.assign(Vue.prototype, {
@ -94,6 +94,14 @@ Object.assign(Vue.prototype, {
} }
}).catch(function() {}) }).catch(function() {})
}, },
/**
* 重置表单
* @param formName 表单元素的ref
*/
resetForm(formName) {
const frm = this.$refs[formName]
frm && frm.resetFields()
},
logout: function() { logout: function() {
removeToken() removeToken()
const fullPath = this.$route.fullPath const fullPath = this.$route.fullPath

@ -25,32 +25,13 @@
label="appKey" label="appKey"
width="250" width="250"
/> />
<el-table-column
prop="secret"
label="secret"
width="80"
>
<template slot-scope="scope">
<el-button v-if="scope.row.signType === 2" type="text" size="mini" @click="onShowSecret(scope.row)">查看</el-button>
</template>
</el-table-column>
<el-table-column <el-table-column
prop="" prop=""
label="公私钥" label="秘钥"
width="80" width="80"
> >
<template slot-scope="scope"> <template slot-scope="scope">
<el-button v-if="scope.row.signType === 1" type="text" size="mini" @click="onShowPriPubKey(scope.row)">查看</el-button> <el-button type="text" size="mini" @click="onShowKeys(scope.row)">查看</el-button>
</template>
</el-table-column>
<el-table-column
prop="signType"
label="签名类型"
width="80"
>
<template slot-scope="scope">
<span v-if="scope.row.signType === 1">RSA2</span>
<span v-if="scope.row.signType === 2">MD5</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
@ -83,11 +64,11 @@
/> />
<el-table-column <el-table-column
label="操作" label="操作"
fixed="right" width="150"
width="100"
> >
<template slot-scope="scope"> <template slot-scope="scope">
<el-button type="text" size="mini" @click="onTableUpdate(scope.row)">修改</el-button> <el-button type="text" size="mini" @click="onTableUpdate(scope.row)">修改</el-button>
<el-button type="text" size="mini" @click="onKeysUpdate(scope.row)">秘钥管理</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -116,26 +97,9 @@
label-width="120px" label-width="120px"
size="mini" size="mini"
> >
<el-form-item label=""> <el-form-item label="appKey">
<el-button size="mini" @click="onDataGen">一键生成数据</el-button> <span v-if="isvDialogFormData.id === 0" style="color: gray;">(系统自动生成)</span>
</el-form-item> <span v-else>{{ isvDialogFormData.appKey }}</span>
<el-form-item prop="appKey" label="appKey">
<el-input v-model="isvDialogFormData.appKey" size="mini" />
</el-form-item>
<el-form-item prop="signType" label="签名方式">
<el-radio-group v-model="isvDialogFormData.signType">
<el-radio :label="1" name="status">RSA2</el-radio>
<el-radio :label="2" name="status">MD5</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-show="isvDialogFormData.signType === 2" prop="secret" label="secret">
<el-input v-model="isvDialogFormData.secret" size="mini" />
</el-form-item>
<el-form-item v-show="isvDialogFormData.signType === 1" prop="pubKey" label="公钥">
<el-input v-model="isvDialogFormData.pubKey" type="textarea" />
</el-form-item>
<el-form-item v-show="isvDialogFormData.signType === 1" prop="priKey" label="私钥">
<el-input v-model="isvDialogFormData.priKey" type="textarea" />
</el-form-item> </el-form-item>
<el-form-item label="角色"> <el-form-item label="角色">
<el-checkbox-group v-model="isvDialogFormData.roleCode"> <el-checkbox-group v-model="isvDialogFormData.roleCode">
@ -154,31 +118,71 @@
<el-button type="primary" @click="onIsvDialogSave"> </el-button> <el-button type="primary" @click="onIsvDialogSave"> </el-button>
</div> </div>
</el-dialog> </el-dialog>
<!--view keys dialog-->
<el-dialog
title="秘钥信息"
:visible.sync="isvKeysDialogVisible"
@close="resetForm('isvKeysFrom')"
>
<el-form
ref="isvKeysFrom"
:model="isvKeysFormData"
label-width="160px"
size="mini"
class="key-view"
>
<el-form-item label="">
<el-alert
title="带 ★ 的分配给开发者"
type="warning"
:closable="false"
/>
</el-form-item>
<el-form-item :label="selfLabel('appKey')">
<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')">
<span>{{ isvKeysFormData.secret }}</span>
</el-form-item>
<fieldset v-show="showKeys()">
<legend>ISV公私钥</legend>
<el-form-item label="ISV公钥">
<el-input v-model="isvKeysFormData.publicKeyIsv" type="textarea" readonly />
</el-form-item>
<el-form-item :label="selfLabel('ISV私钥')">
<el-input v-model="isvKeysFormData.privateKeyIsv" type="textarea" readonly />
</el-form-item>
</fieldset>
<fieldset v-show="showKeys()">
<legend>平台公私钥</legend>
<el-form-item :label="selfLabel('平台公钥')">
<el-input v-model="isvKeysFormData.publicKeyPlatform" type="textarea" readonly />
</el-form-item>
<el-form-item prop="privateKeyPlatform" label="平台私钥">
<el-input v-model="isvKeysFormData.privateKeyPlatform" type="textarea" readonly />
</el-form-item>
</fieldset>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="isvKeysDialogVisible = false"> </el-button>
</span>
</el-dialog>
</div> </div>
</template> </template>
<style>
.gen-key {margin-bottom: 0px !important;}
fieldset {border: 1px solid #ccc; color: gray;margin-left: 40px;margin-bottom: 20px;}
fieldset label {width: 110px !important;}
fieldset .el-form-item__content {margin-left: 110px !important;}
.key-view .el-form-item {margin-bottom: 10px !important;}
</style>
<script> <script>
export default { export default {
data() { data() {
const validateSecret = (rule, value, callback) => {
if (this.isvDialogFormData.signType === 2) {
if (value === '') {
callback(new Error('不能为空'))
}
if (value.length > 200) {
callback(new Error('长度不能超过200'))
}
}
callback()
}
const validatePubPriKey = (rule, value, callback) => {
if (this.isvDialogFormData.signType === 1) {
if (value === '') {
callback(new Error('不能为空'))
}
}
callback()
}
return { return {
searchFormData: { searchFormData: {
appKey: '', appKey: '',
@ -195,11 +199,6 @@ export default {
isvDialogTitle: '新增ISV', isvDialogTitle: '新增ISV',
isvDialogFormData: { isvDialogFormData: {
id: 0, id: 0,
appKey: '',
secret: '',
pubKey: '',
priKey: '',
signType: 1,
status: 1, status: 1,
roleCode: [] roleCode: []
}, },
@ -207,16 +206,17 @@ export default {
appKey: [ appKey: [
{ required: true, message: '不能为空', trigger: 'blur' }, { required: true, message: '不能为空', trigger: 'blur' },
{ min: 1, max: 100, message: '长度在 1 到 100 个字符', trigger: 'blur' } { min: 1, max: 100, message: '长度在 1 到 100 个字符', trigger: 'blur' }
],
secret: [
{ validator: validateSecret, trigger: 'blur' }
],
pubKey: [
{ validator: validatePubPriKey, trigger: 'blur' }
],
priKey: [
{ validator: validatePubPriKey, trigger: 'blur' }
] ]
},
isvKeysDialogVisible: false,
isvKeysFormData: {
appKey: '',
secret: '',
publicKeyIsv: '',
privateKeyIsv: '',
publicKeyPlatform: '',
privateKeyPlatform: '',
signType: ''
} }
} }
}, },
@ -237,16 +237,12 @@ export default {
}) })
} }
}, },
onShowSecret: function(row) { onShowKeys: function(row) {
this.$alert(row.secret, 'secret') this.post('isv.keys.get', { appKey: row.appKey }, function(resp) {
}, this.isvKeysDialogVisible = true
onShowPriPubKey: function(row) { this.$nextTick(() => {
const pubKey = row.pubKey Object.assign(this.isvKeysFormData, resp.data)
const priKey = row.priKey })
const content = '<div>公钥:<textarea style="width: 380px;height: 100px;" readonly="readonly">' + pubKey + '</textarea><br>' +
'私钥:<textarea style="width: 380px;height: 100px;" readonly="readonly">' + priKey + '</textarea></div>'
this.$alert(content, '公私钥', {
dangerouslyUseHTMLString: true
}) })
}, },
onSearchTable: function() { onSearchTable: function() {
@ -268,6 +264,9 @@ export default {
}) })
}) })
}, },
onKeysUpdate: function(row) {
this.$router.push({ path: `keys?appKey=${row.appKey}` })
},
onSizeChange: function(size) { onSizeChange: function(size) {
this.searchFormData.pageSize = size this.searchFormData.pageSize = size
this.loadTable() this.loadTable()
@ -277,13 +276,15 @@ export default {
this.loadTable() this.loadTable()
}, },
onAdd: function() { onAdd: function() {
this.isvDialogFormData.id = 0
this.isvDialogTitle = '新增ISV' this.isvDialogTitle = '新增ISV'
this.isvDialogVisible = true this.isvDialogVisible = true
this.$nextTick(() => {
this.isvDialogFormData.id = 0
})
}, },
onIsvDialogSave: function() { onIsvDialogSave: function() {
const that = this const that = this
this.$refs['isvForm'].validate((valid) => { this.$refs.isvForm.validate((valid) => {
if (valid) { if (valid) {
const uri = this.isvDialogFormData.id === 0 ? 'isv.info.add' : 'isv.info.update' const uri = this.isvDialogFormData.id === 0 ? 'isv.info.add' : 'isv.info.update'
that.post(uri, that.isvDialogFormData, function() { that.post(uri, that.isvDialogFormData, function() {
@ -294,8 +295,12 @@ export default {
}) })
}, },
onIsvDialogClose: function() { onIsvDialogClose: function() {
this.$refs.isvForm.resetFields() this.resetForm('isvForm')
this.isvDialogVisible = false this.isvDialogFormData.status = 1
this.isvDialogFormData.roleCode = []
},
selfLabel: function(lab) {
return '★ ' + lab
}, },
roleRender: function(row) { roleRender: function(row) {
const html = [] const html = []
@ -305,27 +310,8 @@ export default {
} }
return html.join(', ') return html.join(', ')
}, },
onDataGen: function() { showKeys: function() {
this.post('isv.form.gen', {}, function(resp) { return this.isvKeysFormData.signType === 1
const data = resp.data
//
if (this.isvDialogFormData.id === 0) {
Object.assign(this.isvDialogFormData, data)
} else {
const signType = this.isvDialogFormData.signType
// RSA2
if (signType === 1) {
Object.assign(this.isvDialogFormData, {
pubKey: data.pubKey,
priKey: data.priKey
})
} else if (signType === 2) {
Object.assign(this.isvDialogFormData, {
secret: data.secret
})
}
}
})
} }
} }
} }

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

Loading…
Cancel
Save