Compare commits

..

3 Commits

  1. 1
      .gitignore
  2. 16
      Dockerfile
  3. 84
      README.md
  4. 113
      changelog.md
  5. 27
      doc/.gitignore
  6. 4
      doc/docs/README.md
  7. 71
      doc/docs/_sidebar.md
  8. 43
      doc/docs/files/10010_快速体验.md
  9. 72
      doc/docs/files/10011_项目接入到SOP.md
  10. 109
      doc/docs/files/10020_新增接口.md
  11. 10
      doc/docs/files/10021_开发流程.md
  12. 11
      doc/docs/files/10022_用户注册.md
  13. 3
      doc/docs/files/10030_业务参数校验.md
  14. 11
      doc/docs/files/10041_编写文档.md
  15. 78
      doc/docs/files/10070_easyopen支持.md
  16. 9
      doc/docs/files/10080_使用签名校验工具.md
  17. 35
      doc/docs/files/10087_自定义返回结果.md
  18. 94
      doc/docs/files/10088_自定义过滤器.md
  19. 19
      doc/docs/files/10089_自定义校验token.md
  20. 8
      doc/docs/files/10090_路由授权.md
  21. 17
      doc/docs/files/10092_接口限流.md
  22. 27
      doc/docs/files/10093_路由监控.md
  23. 10
      doc/docs/files/10095_SDK开发.md
  24. 16
      doc/docs/files/10096_使用SpringCloudGateway.md
  25. 37
      doc/docs/files/10100_提供restful接口.md
  26. 6
      doc/docs/files/10104_文件上传.md
  27. 8
      doc/docs/files/10110_预发布灰度发布.md
  28. 52
      doc/docs/files/10112_使用eureka.md
  29. 86
      doc/docs/files/10113_扩展其它注册中心.md
  30. 14
      doc/docs/files/10113_超时设置.md
  31. 6
      doc/docs/files/90001_网关性能测试.md
  32. 21
      doc/docs/files/90010_原理分析之@ApiMapping.md
  33. 65
      doc/docs/files/90011_原理分析之如何存储路由.md
  34. 86
      doc/docs/files/90012_原理分析之如何路由.md
  35. 23
      doc/docs/files/90099_2.x升3.x注意事项.md
  36. 78
      doc/docs/files/90100_常见问题.md
  37. BIN
      doc/docs/files/images/10041_4.png
  38. BIN
      doc/docs/files/images/10090_1.png
  39. BIN
      doc/docs/files/images/10090_2.png
  40. BIN
      doc/docs/files/images/10093_1.png
  41. BIN
      doc/docs/files/images/10093_2.png
  42. BIN
      doc/docs/files/images/90012_1.png
  43. 2
      doc/pom.xml
  44. 12
      docker-build.sh
  45. 13
      docker-entrypoint.sh
  46. 70
      pom.xml
  47. 3
      sop-admin/pom.xml
  48. 21
      sop-admin/sop-admin-server/pom.xml
  49. 11
      sop-admin/sop-admin-server/readme.md
  50. 13
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/config/ConfigListApi.java
  51. 8
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/param/IsvKeysFormUpdate.java
  52. 2
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/result/IsvInfoVO.java
  53. 5
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/MonitorApi.java
  54. 80
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/MonitorNewApi.java
  55. 54
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/ResourceApi.java
  56. 21
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/RouteApi.java
  57. 1
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/ServiceApi.java
  58. 15
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/InstanceMonitorSearchParam.java
  59. 4
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/LimitNewAddParam.java
  60. 4
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/LimitNewUpdateParam.java
  61. 4
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/LimitParam.java
  62. 21
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/MonitorErrorMsgParam.java
  63. 19
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/MonitorInfoErrorSolveParam.java
  64. 23
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/MonitorSearchParam.java
  65. 32
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/RouteStatusUpdateParam.java
  66. 4
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/result/LimitNewVO.java
  67. 4
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/result/LimitVO.java
  68. 26
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/result/MonitorInfoErrorMsgResult.java
  69. 2
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/system/SystemApi.java
  70. 9
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/system/param/IspPageParam.java
  71. 27
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/system/param/IspResourceParam.java
  72. 27
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/system/result/IspResourceResult.java
  73. 2
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/ConfigLimitDto.java
  74. 89
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/RouteDefinition.java
  75. 13
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/RouteErrorCount.java
  76. 19
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/ServiceInfo.java
  77. 26
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/ServiceRouteInfo.java
  78. 75
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/common/CopyUtil.java
  79. 2
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/common/LimitEnum.java
  80. 2
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/ConfigLimit.java
  81. 58
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/ConfigRouteLimit.java
  82. 50
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/IspResource.java
  83. 12
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/IsvInfo.java
  84. 66
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/MonitorInfo.java
  85. 54
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/MonitorInfoError.java
  86. 61
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/MonitorSummary.java
  87. 12
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/mapper/ConfigRouteLimitMapper.java
  88. 11
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/mapper/IspResourceMapper.java
  89. 22
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/mapper/MonitorInfoErrorMapper.java
  90. 19
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/mapper/MonitorInfoMapper.java
  91. 3
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/ConfigPushService.java
  92. 14
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/impl/EurekaUri.java
  93. 26
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/impl/RegistryServiceEurekaImpl.java
  94. 7
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/impl/RegistryServiceNacosImpl.java
  95. 17
      sop-admin/sop-admin-server/src/main/resources/META-INF/sop-admin.properties
  96. 5
      sop-admin/sop-admin-server/src/main/resources/application-dev.properties
  97. 63
      sop-admin/sop-admin-server/src/main/resources/mybatis/mapper/MonitorInfoMapper.xml
  98. 10
      sop-admin/sop-admin-server/src/main/resources/mybatis/mybatisConfig.xml
  99. 2
      sop-admin/sop-admin-server/src/main/resources/public/index.html
  100. 2
      sop-admin/sop-admin-server/src/main/resources/public/static/css/app.c6dfb7ee.css
  101. Some files were not shown because too many files have changed in this diff Show More

1
.gitignore vendored

@ -15,7 +15,6 @@
*.iws
*.iml
*.ipr
*.png
### NetBeans ###
/nbproject/private/

@ -1,16 +0,0 @@
FROM java:8
VOLUME /tmp
VOLUME /sop
# 将所有应用放到一个镜像当中
ADD sop-gateway/target/*.jar sop/sop-gateway/sop-gateway.jar
ADD sop-admin/sop-admin-server/target/*.jar sop/sop-admin/sop-admin.jar
ADD sop-website/sop-website-server/target/*.jar sop/sop-website/sop-website.jar
ADD sop-auth/target/*.jar sop/sop-auth/sop-auth.jar
ADD sop-example/sop-story/target/*.jar sop/sop-story/sop-story.jar
# 拷贝启动脚本
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
ENTRYPOINT ["docker-entrypoint.sh"]

@ -2,6 +2,7 @@
一个开放平台解决方案项目,基于Spring Cloud实现,目标让用户快速搭建自己的开放平台。
SOP提供了两种接口调用方式,分别是:[支付宝开放平台](https://docs.open.alipay.com/api)的调用方式和[淘宝开放平台](http://open.taobao.com/api.htm?docId=285&docType=2)的调用方式。
通过简单的配置后,你的项目就具备了和支付宝开放平台的一样的接口提供能力。
SOP封装了开放平台大部分功能包括:签名验证、统一异常处理、统一返回内容 、业务参数验证(JSR-303)、秘钥管理等,未来还会实现更多功能。
@ -21,63 +22,12 @@ SOP封装了开放平台大部分功能包括:签名验证、统一异常处
以上情况都可以考虑使用SOP
## 例子
```java
// 加一个注解即可
@Open("story.get")
@RequestMapping("/get")
public StoryResult get() {
StoryResult result = new StoryResult();
result.setId(1L);
result.setName("海底小纵队");
return result;
}
```
调用:
```java
// 公共请求参数
Map<String, String> params = new HashMap<String, String>();
params.put("app_id", appId);
params.put("method", "story.get");
params.put("format", "json");
params.put("charset", "utf-8");
params.put("sign_type", "RSA2");
params.put("timestamp", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
params.put("version", "1.0");
// 业务参数
Map<String, String> bizContent = new HashMap<>();
bizContent.put("id", "1");
bizContent.put("name", "葫芦娃");
params.put("biz_content", JSON.toJSONString(bizContent));
System.out.println("----------- 请求信息 -----------");
System.out.println("请求参数:" + buildParamQuery(params));
System.out.println("商户秘钥:" + privateKey);
String content = AlipaySignature.getSignContent(params);
System.out.println("待签名内容:" + content);
String sign = AlipaySignature.rsa256Sign(content, privateKey, "utf-8");
System.out.println("签名(sign):" + sign);
params.put("sign", sign);
System.out.println("URL参数:" + buildUrlQuery(params));
System.out.println("----------- 返回结果 -----------");
String responseData = get(url, params);// 发送请求
System.out.println(responseData);
```
## 架构图
![架构图](https://images.gitee.com/uploads/images/2019/1227/145216_c9b45109_332975.png "sop3.png")
> 如上图所示,整个系统运行后,开发者只需关注微服务中的业务代码,接口变更后重新部署微服务应用即可
## 已完成列表
- 签名验证
@ -86,35 +36,34 @@ System.out.println(responseData);
- session管理
- 秘钥管理
- 微服务端自动验证(JSR-303)
- 支持Spring Cloud Gateway
- Admin管理平台,统一管理微服务配置,管理路由管理,微服务上下线
- 门户网站,提供用户注册账号
- 接入方管理+秘钥管理
- 接口权限分配
- 文件上传/下载
- 提供基础SDK(含:Java,C++,C#,Python,Go,Rust,Nodejs
- 提供基础SDK(含:Java,C#,Python,Go)
- 接口限流
- 文档整合
- 应用授权
- 监控日志
- 注册中心支持nacos/eureka
- 支持nacos
- 网关动态修改参数
- 预发布/灰度环境切换
- 支持eureka注册中心
## 界面预览
![服务列表](https://images.gitee.com/uploads/images/2020/1016/134354_c1915902_332975.png "service.png")
![服务列表](https://images.gitee.com/uploads/images/2019/0711/174825_2856281f_332975.png "服务列表")
![路由管理](https://images.gitee.com/uploads/images/2020/1016/134039_bed1608d_332975.png "route.png")
![路由管理](https://images.gitee.com/uploads/images/2019/0711/174843_1648591c_332975.png "路由管理")
![限流管理](https://images.gitee.com/uploads/images/2020/1016/134102_f2dcfb25_332975.png "limit.png")
![限流管理](https://images.gitee.com/uploads/images/2019/0711/174900_9315cc8e_332975.png "限流管理")
![秘钥信息](https://images.gitee.com/uploads/images/2019/0711/174921_bd817533_332975.png "秘钥信息")
- 门户网站
![API文档](https://images.gitee.com/uploads/images/2019/0711/174939_97886883_332975.png "API文档")
![首页2](https://images.gitee.com/uploads/images/2021/0318/195935_1d610da8_332975.png "portal-vue.png")
![文档页](https://images.gitee.com/uploads/images/2020/1107/104342_d44849a9_332975.png "portal1.png")
![沙箱环境](https://images.gitee.com/uploads/images/2019/0711/175226_3f69346a_332975.png "沙箱环境")
## 工程说明
@ -125,20 +74,23 @@ System.out.println(responseData);
- sop-gateway:网关,统一访问入口,含`Spring Cloud Zuul`和`Spring Cloud Gateway`实现
- sop-example:微服务示例,含springboot,springmvc示例
- sop-website:开放平台对应网站,提供文档API、沙箱测试等内容
- sop-auth:应用授权服务示例
- sop-auth:应用授权服务
- sop-admin:后台管理
- sop-sdk:基础sdk,含Java、C#版本
- sop-test:接口调用测试用例
## 分支说明
- master:发版分支
- master:发版分支(当前为3.0版本,2.x版本见`2.x`分支)
- develop:日常开发分支
- eureka:使用eureka注册中心
- pr:接受PR的分支,提交PR请提交到此分支
[更新说明](./changelog.md)
## 相关文档
[开发文档](http://durcframework.gitee.io/sop)
## 沟通交流
Q群:167643071
![SOP2群](https://images.gitee.com/uploads/images/2020/0203/103119_bdf84eb6_332975.png "SOP2群.png")

@ -1,118 +1,5 @@
# changelog
## 4.4.2
- 优化参数绑定
## 4.4.1
- 修复单值参数绑定问题
## 4.4.0
**【重要】:升级前请阅读 [升级到4.4.0注意事项](./升级到4.4.0注意事项.md)**
- 优化异常处理
- 优化网关多实例数据库重复保存问题
- 修复`IP`,`IP+路由ID`,`IP+APP_ID`限流不生效问题
## 4.3.4
- 修复Request参数在第一位导致绑定失败问题
## 4.3.3
- 修复获取eureka地址问题
## 4.3.2
- 修复微服务方法获取不到OpenContext问题
## 4.3.1
- 修复serviceId有大小写出现404问题
- 修复路有监控错误面板分页问题
## 4.3.0
- 升级`spring-boot/spring-cloud/spring-cloud-alibaba`版本
- 修复`DataBufferLimitException: Exceeded limit on max bytes to buffer : 262144`问题
- 修复压测出现`ClosedChannelException`,`Connection has been closed BEFORE response`问题
## 4.2.7
- 修复两个微服务相同path问题
- 修复Python-SDK下参数传递问题
## 4.2.6
- 优化网关超时处理
## 4.2.5
- 修复restful负载均衡问题
## 4.2.4
- 修复sop-auth模块启动失败问题
## 4.2.3
- 修复nacos的group不生效问题
## 4.2.2
- 优化版本号
## 4.2.1
- 可指定nacos的group,(`spring.cloud.nacos.discovery.group=xx`)
- 修复spring循环依赖问题
## 4.2.0
需要执行`sop-upgrade-4.2.0.sql`
- 新增ISV用户平台
- 新增门户网站(portal)
- 新增`C++`,`Rust`语言SDK
## 4.1.0
需要执行`sop-upgrade-4.1.0.sql`
- 重构路由监控功能
- 升级SpringBoot,SpringCloud,SpringCloudAlibaba版本
## 4.0.3
- 可定义业务错误码(见`@Open`注解中的`bizCode`属性)
- 文档参数可指定最大长度(使用`@Length(max = xx)`)
- 修复返回大文本导致的错误(Exceeded limit on max bytes to buffer : 262144)
- 增强参数绑定
## 4.0.2
- 支持swagger排序(position属性)
## 4.0.1
- 修复超大文本无法请求BUG(设置`spring.codec.max-in-memory-size`无效)
## 4.0.0(不兼容3.x)
- 新增@Open注解,代替ApiMapping和ApiAbility
- service接入减少代码入侵
- 修改admin密码存储规则
- 完善example
### 不兼容部分
- 移除ApiMapping和ApiAbility,改为Open注解
- 移除OpenContext
- 移除zuul
- 移除对easyopen支持
## 3.2.1
- 强化RouteInterceptorContext,可获取微服务信息

27
doc/.gitignore vendored

@ -1,27 +0,0 @@
/target/
!.mvn/wrapper/maven-wrapper.jar
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
*.png
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
/build/

@ -1,3 +1,7 @@
# SOP开发文档
Git地址:[SOP](https://gitee.com/durcframework/SOP)
Q群:167643071
![SOP2群](https://images.gitee.com/uploads/images/2020/0203/103119_bdf84eb6_332975.png "SOP2群.png")

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

@ -1,7 +1,5 @@
# 快速体验
## 方式1
> 运行环境:JDK8,Maven3,[Nacos](https://nacos.io/zh-cn/docs/what-is-nacos.html),Mysql
- 安装并启动Nacos,[安装教程](https://nacos.io/zh-cn/docs/quick-start.html)
@ -11,25 +9,11 @@
1. 修改数据库`username/password`
2. 指定nacos地址,如果nacos安装在本机则不用改
3. 运行`SopGatewayApplication.java`
- 启动微服务:打开`sop-example/sop-story`下的`application-dev.properties`文件
- 启动微服务:打开`sop-example/sop-story/sop-story-web`下的`application-dev.properties`文件
1. 指定nacos地址,如果nacos安装在本机则不用改
2. 运行`SopStoryApplication.java`
- 找到sop-test,运行`com.gitee.sop.test.AlipayClientPostTest.testGet`进行接口调用测试
## 方式2(docker)
> 前提:安装好docker
- 安装并启动Nacos,[安装教程](https://nacos.io/zh-cn/docs/quick-start.html)
- 执行Mysql脚本`sop.sql`(Mysql版本5.6+),5.6以下运行`sop-mysql5.6以下版本.sql`
- 打开`docker-entrypoint.sh`,修改mysql,nacos配置
- 执行`docker-build.sh`
- 找到sop-test,运行`com.gitee.sop.test.AlipayClientPostTest.testGet`进行接口调用测试
> - admin地址:http://ip:8082 登录账号:admin/123456
> - 文档地址:http://ip:8083/
## 使用admin
- 找到`sop-admin/sop-admin-server`工程,打开sop-admin-server下的`application-dev.properties`,修改相关配置
@ -38,15 +22,28 @@
登录账号:admin/123456
## 启动门户网站
见:`sop-website/sop-portal/README.md`
## 启动文档中心/ISV后台
## 启动文档中心
文档中心代码在sop-website工程中
- 确保注册中心、网关、微服务正常启动
- 修改sop-website-server下的application-dev.properties相关配置
- 修改sop-website下的application-dev.properties相关配置
- 运行WebsiteServerApplication.java
- 访问http://localhost:8083
## 基本配置
在`sop-gateway`下的application-dev.properties配置,各项配置说明如下
```properties
# 忽略验证,设置true,则所有接口不会进行签名校验,默认false
sop.api-config.ignore-validate=false
# 是否对结果进行合并,默认true
sop.api-config.merge-result=true
# 显示返回sign,默认true
sop.api-config.show-return-sign=true
# 是否开启限流功能,默认true
sop.api-config.open-limit=true
# 请求超时时间,默认5分钟,即允许在5分钟内重复请求,默认300
sop.api-config.timeout-seconds=300
```

@ -5,16 +5,18 @@
- pom.xml添加版本配置
```xml
<!-- springboot 版本-->
<spring-boot.version>2.3.2.RELEASE</spring-boot.version>
<!-- spring cloud 版本 -->
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
<!-- spring cloud alibaba 版本 -->
<!-- 具体版本对应关系见:https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E -->
<spring-cloud-alibaba.version>2.2.5.RELEASE</spring-cloud-alibaba.version>
<properties>
<!-- springboot 版本-->
<spring-boot.version>2.2.5.RELEASE</spring-boot.version>
<!-- spring cloud 版本 -->
<spring-cloud.version>Hoxton.SR3</spring-cloud.version>
<!-- spring cloud alibaba 版本 -->
<!-- 具体版本对应关系见:https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E -->
<spring-cloud-alibaba.version>2.2.1.RELEASE</spring-cloud-alibaba.version>
</properties>
```
- pom.xml添加`<dependencyManagement>`控制版本
- pom.xml添加SpringCloud支持
```xml
<dependencyManagement>
@ -79,56 +81,6 @@ public class OpenServiceConfig extends AlipayServiceConfiguration {
}
```
- 全局异常处理
在微服务项目的全局异常处理中添加一句:`ExceptionHolder.hold(request, response, exception);`
```java
@ExceptionHandler(Exception.class)
@ResponseBody
public Object exceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception exception) {
...
// 在返回前加这一句
ExceptionHolder.hold(request, response, exception);
...
return ..;
}
```
如果没有配置全局异常,可参考下面配置
```java
@ControllerAdvice
@Slf4j
public class StoryGlobalExceptionHandler {
/**
* 捕获手动抛出的异常
*
* @param request request
* @param response response
* @param exception 异常信息
* @return 返回提示信息
*/
@ExceptionHandler(Exception.class)
@ResponseBody
public Object exceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception exception) {
// 在返回前加这一句
ExceptionHolder.hold(request, response, exception);
// 下面可以实现自己的全局异常处理
return new ErrorResult(500, exception.getMessage());
}
@Data
@AllArgsConstructor
public static class ErrorResult {
private int code;
private String msg;
}
}
```
到此准备工作就完成了,接下来可前往`新增接口`查看如何新增接口。
## 非Java项目接入
@ -143,8 +95,6 @@ public class StoryGlobalExceptionHandler {
php应用提供的接口需要返回如下json内容:
假设请求的接口为:`http://open.xxx.com/get_routes`
```json
{
"serviceId": "goods-service",
@ -209,7 +159,7 @@ instance.setServiceName("goods-service");
instance.setIp("192.168.0.11");
instance.setPort(8080);
// 在nacos的metadata中指定接口路径
instance.getMetadata().put("sop.routes.path", "http://open.xxx.com/get_routes");
instance.getMetadata().put("sop.routes.path", "/goods/list_goods");
namingService.registerInstance(serviceId, instance);
```

@ -1,27 +1,21 @@
# 新增接口
假设要对下面这个接口提供开放能力。
以story服务为例,新增一个获取故事内容接口
- 在controller下新建一个类,StoryDemoController.java
- 加上`@RestController`注解
```java
@RestController
public class StoryDemoController {
@RequestMapping("/story/get")
public StoryResult getStory() {
StoryResult result = new StoryResult();
result.setId(1L);
result.setName("海底小纵队");
return result;
}
}
```
只需要在方法上新增一个`@Open`注解,指定接口名即可
- 新增一个接口
```java
// 添加一个@Open注解
@Open("story.demo.get")
@RequestMapping("/story/get")
@ApiMapping(value = "story.demo.get")
public StoryResult getStory() {
StoryResult result = new StoryResult();
result.setId(1L);
@ -30,9 +24,13 @@ public StoryResult getStory() {
}
```
如果要加上版本号,指定`version`参数:`@Open(value = "story.demo.get", version = "2.0")`
这里的`@ApiMapping`注解作用同`@RequestMapping`注解,可以理解为是它的扩展
value就是接口名,对应客户端的`method`参数
- 重启服务,这样接口就可以使用了。
如果要加上版本号,指定`version`参数:`@ApiMapping(value = "story.demo.get", version = "2.0")`
- 重启story服务,这样接口就可以使用了。
## 绑定业务参数
@ -44,11 +42,8 @@ public StoryResult getStory() {
其中biz_content部分是我们想要的,在方法上申明一个对象,对应biz_content中的内容即可完成参数绑定,并且对参数进行JSR-303校验。
**注意:接口方法必须有且只有一个对象参数,如果申明多个会出现参数绑定失败**
```java
@Open("goods.add")
@RequestMapping("/goods/add")
@ApiMapping(value = "goods.add")
public Object addGoods(GoodsParam param) {
return param;
}
@ -63,14 +58,40 @@ public class GoodsParam {
一般情况下,只需要获取业务参数即可,如果想要获取更多的参数,可在后面跟一个`HttpServletRequest`对象。
```java
@Open("goods.add")
@RequestMapping("/goods/add")
@ApiMapping(value = "goods.add")
public Object addGoods(GoodsParam param, HttpServletRequest request) {
System.out.println(request.getParameter("method"));
return param;
}
```
- 方式2
```java
@ApiMapping(value = "story.get", version = "2.2")
public Story getStory22(OpenContext<Story> openContext) {
// 业务参数
Story bizObject = openContext.getBizObject();
// 获取appid,更多方法查看OpenContext类
String appId = openContext.getAppId();
System.out.println(appId);
return bizObject;
}
```
另一种方式,OpenContext泛型参数填bizObject类,调用openContext.getBizObject()可直接获得对象
此方式等价于:
```java
@ApiMapping(value = "story.get", version = "2.2")
public Story getStory22(Story bizObject) {
OpenContext openContext = ServiceContext.getCurrentContext().getOpenContext();
String appId = openContext.getAppId();
System.out.println(appId);
return bizObject;
}
```
## 接口命名
@ -101,13 +122,13 @@ public class StoryDemoTest extends TestBase {
// 公共请求参数
Map<String, String> params = new HashMap<String, String>();
params.put("app_id", appId);
// 这里对应@Open.value属性
// 这里对应@ApiMapping.value属性
params.put("method", "story.demo.get");
params.put("format", "json");
params.put("charset", "utf-8");
params.put("sign_type", "RSA2");
params.put("timestamp", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
// 这里对应@Open.version属性
// 这里对应@ApiMapping.version属性
params.put("version", "1.0");
// 业务参数
@ -145,3 +166,47 @@ public class StoryDemoTest extends TestBase {
{"story_demo_get_response":{"msg":"Success","code":"10000","name":"白雪公主","id":1},"sign":"YMbxTPdovi6htcn1K3USTS6/Tbg6MOAMigG6x/kG0kQFCYH8ljvxXzcY86UT056nUG3OXxnj0xkw07eV6E03HMlu7bn3/jrT3PCcV3YguhA92aWz720x2xJWdfXY13OUPS9VOCC9zIVxu6EBD+PoZ7ojYChYvOfCR5I8bR/oOc0ZLjK63PWTBdf0eFS4sybXzRf81uNLMROsMhmBDDy0Fhml3ml77qzWBIpsmq5ECZ+89rMPbkNhAUcnFAe7ik7xZIL6WcUhAOhKVa8ZQK1GMjoGnAbGRed1FbuOHZGubgffg4/vMqrY10Bcy6h9jt/zK5w9L3HVgK3aPgQlfP16Gg=="}
```
## 开放现有接口
如果想把现有项目中的接口开放出去,提供给客户调用,具体操作如下:
- 将现有项目接入到SOP,前往`项目接入到SOP`文档页查看
- 在现有接口方法上加上一个注解`@ApiAbility`,如下面这个接口
```java
// 具备开放平台能力
@ApiAbility
@RequestMapping("getStory2")
public Story getStory2_0() {
Story story = new Story();
story.setId(1);
story.setName("海底小纵队(默认版本号)");
return story;
}
```
- 启动程序
这种情况下,老接口依然能正常访问,同时开放平台也能访问进来。
**注意** 此时的开放接口对应的接口名为:类@RequestMapping.value + "." + 方法@RequestMapping.value
举个列子:
```java
@RequestMapping("goods")
public class MyController {
@ApiAbility
@RequestMapping("listGoods")
public Object fun() {
}
}
```
fun接口对应的路径为:`/goods/listGoods`
那么对应开放平台的接口名会转换成:`goods.listGoods`,客户端的method参数要填`goods.listGoods`
当然也可以直接把@RequestMapping替换成`@ApiMapping`并指定接口名,这样的话不能兼容以前的访问形式。

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

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

@ -5,8 +5,7 @@
在参数中使用注解即可,框架会自动进行验证。如下面一个添加商品接口,它的参数是GoodsParam
```java
@Open("goods.add")
@RequestMapping("/goods/add")
@ApiMapping(value = "goods.add")
public void addGoods(GoodsParam param) {
...
}

@ -84,11 +84,7 @@ public class StoryResult {
* @return
*/
@ApiOperation(value = "获取故事信息", notes = "说明接口的详细信息,介绍,用途,注意事项等。")
@Open(value = "alipay.story.find", bizCode = {
// 定义业务错误码,用于文档显示
@BizCode(code = "100001", msg = "姓名错误", solution = "填写正确的姓名"),
@BizCode(code = "100002", msg = "备注错误", solution = "填写正确备注"),
})
@ApiMapping(value = "alipay.story.find")
public StoryResult getStory2(StoryParam story) {
log.info("获取故事信息参数, story: {}", story);
// 获取其它参数
@ -120,7 +116,4 @@ swagger注解和文档界面显示关系如下图所示:
![预览](images/10041_2.png "10041_2.png")
![预览](images/10041_3.png "10041_3.png")
![预览](images/10041_4.png "10041_4.png")
![预览](images/10041_3.png "10041_3.png")

@ -0,0 +1,78 @@
# easyopen支持
SOP对easyopen项目提供了很好的支持,如果您的服务端使用了easyopen框架,相关配置步骤如下:
## 服务端配置
首先是服务端相关配置
- pom添加依赖
```xml
<!-- sop接入依赖 -->
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-service-common</artifactId>
<version>最新版本</version>
</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>
<exclusions>
<exclusion>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>net.oschina.durcframework</groupId>
<artifactId>easyopen</artifactId>
<version>1.16.1</version>
</dependency>
<!-- sop接入依赖 end -->
```
easyopen版本必须升级到1.16.1
- 启动类上面添加注解@EnableDiscoveryClient,将自己注册到注册中心
- 新增一个配置类,继承EasyopenServiceConfiguration,内容为空
```java
@Configuration
public class SopConfig extends EasyopenServiceConfiguration {
}
```
服务端配置完毕,重启服务。
## 网关端配置
接下来是网关的配置
- 打开ZuulConfig.java,注释掉原本的@Configuration,新增如下Configuration
```java
@Configuration
public class ZuulConfig extends EasyopenZuulConfiguration {
}
```
配置完毕,重启网关服务,可运行测试用例`EasyopenClientPostTest.java`验证
**注:** 配置完成后easyopen签名校验将会关闭,改用网关端来校验;网关对easyopen返回的结果不进行处理,直接返回服务端的结果。
完整配置可查看sop-example/sop-easyopen项目

@ -13,7 +13,14 @@ SOP默认签名算法仿照的是支付宝开放平台,因此我们可以使
![示例图](https://gw.alipayobjects.com/zos/skylark/6dbc42cc-6b9b-4691-83f1-e7b875e1a602/2018/png/e6b725d0-8257-4a71-b7a0-f5479c9d43d0.png)
sop-admin创建一个新ISV,将公私钥放入对应文本框中,保存。
公钥给到开放平台,打开sop-gateway项目中的`ZuulConfig.java`,复制公钥
```java
appSecretStore.put(应用ID, 公钥内容);
```
- 应用ID(app_id):建议个格式为`yyyyMMddHHmmss+自增ID`,如2019032617262200001
- 公钥内容:刚刚生成的公钥字符串
接着私钥放入客户端进行调用。参见AlipayClientPostTest类

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

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

@ -1,6 +1,6 @@
# 自定义校验token
# 自定义校验token(2.5.0)
在`@Open`注解中有一个属性`needToken`,用来告诉网关是否校验token
从2.5.0开始在`@ApiMapping`注解中新增了一个属性`needToken`,用来告诉网关是否校验token
```java
/**
@ -12,13 +12,12 @@ boolean needToken() default false;
使用方式:
```java
@ApiOperation(value="传递token", notes = "传递token")
@Open(value = "story.get.token", needToken = true/* 设置true,网关会校验token是否存在 */)
@RequestMapping("token")
public StoryResult token(StoryParam story, HttpServletRequest request) {
@ApiMapping(value = "story.token.get", needToken = true/* 设置true,网关会校验token是否存在 */)
public StoryResult token(StoryParam story) {
OpenContext openContext = ServiceContext.getCurrentContext().getOpenContext();
String appAuthToken = openContext.getAppAuthToken();
StoryResult result = new StoryResult();
result.setName("appAuthToken:" + openContext.getAppAuthToken());
result.setName("appAuthToken:" + appAuthToken);
return result;
}
```
@ -29,6 +28,7 @@ public StoryResult token(StoryParam story, HttpServletRequest request) {
自己实现步骤:
- 在ZuulConfig类中重写`doAfter`方法
- 设置`ApiConfig中的tokenValidator属性`
`TokenValidator`是一个函数式接口,可以直接使用Lambda表达式,示例代码如下:
@ -42,10 +42,13 @@ public class MyConfig {
ApiConfig.getInstance().setTokenValidator(apiParam -> {
// 获取客户端传递过来的token
String token = apiParam.fetchAccessToken();
return !StringUtils.isBlank(token);
if (StringUtils.isBlank(token)) {
return false;
}
// TODO: 校验token有效性,可以从redis中读取
// 返回true表示这个token真实、有效
return true;
});
}
}

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

@ -2,16 +2,18 @@
SOP提供了简单的接口限流策略:
- 窗口策略:每秒处理固定数量的请求,超出请求返回错误信息。
- 漏桶策略:每秒处理固定数量的请求,超出请求返回错误信息。
- 令牌桶策略:每秒放置固定数量的令牌数,每个请求进来后先去拿令牌,拿到了令牌才能继续,拿不到则等候令牌重新生成了再拿。
如果一个接口设置了窗口策略,假设接口每秒可处理5个请求,一秒内同时有6个请求进来,前5个接口是能够访问的,第六个请求将返回错误信息。
如果一个接口设置了漏桶策略,假设接口每秒可处理5个请求,一秒内同时有6个请求进来,前5个接口是能够访问的,第六个请求将返回错误信息。
如果设置了令牌桶策略,桶的容量是5,那么每秒中生成5个令牌,同一时间有6个请求进来,那么前5个能成功拿到令牌继续,第六个则等待,令牌重新生成了再拿。
默认情况下接口的限流功能是关闭的,可在sop admin中配置并开启。功能在`路由管理-->限流管理`下。
## 多维度限流
## 新版限流(1.9.0)
1.9.0将之前的限流进行了改造,新的限流支持更多的限流方式。之前只能针对某个接口限流,新版限流可以在`路由ID(接口)、appKey、ip`上进行限流。
- 可针对接口进行限流,所有访问该接口的请求都被限流
- 可针对appKey进行限流,某个appKey请求过来后,对他限流
@ -30,12 +32,12 @@ SOP提供了简单的接口限流策略:
- 接口:`goods.get`, appKey:xxxx, 排序值:0, 每秒可处理请求数:5
- 接口:`goods.get`, ip:172.1.2.2, 排序值:2, 每秒可处理请求数:6
客户端调用接口:`http://open.domain.com?method=goods.get&app_id=xxxx`,客户端IP为`172.1.2.2`
客户端调用接口:`http://open.domain.com/api?method=goods.get&app_key=xxxx`,客户端IP为`172.1.2.2`
这种情况下上面三条限流规则都命中了,由于排序值小优先执行,因此第二条规则命中.
具体设置方式可在sop admin中配置,功能在`服务管理-->限流管理`下。执行`com.gitee.sop.test.LimitTest`测试用例验证限流情况
具体设置方式可在sop admin中配置,功能在`服务管理-->限流管理`下。执行`sop-test/src/test/java/com/gitee/sop/LimitDemoPostTest.java`测试用例验证限流情况
![限流配置](images/10092_1.png "10092_1.png")
@ -65,16 +67,15 @@ spring.redis.host=127.0.0.1
spring.redis.port=6379
```
- 在`com.gitee.sop.gateway.config.MyConfig`中添加如下代码:
- 在启动类中添加如下代码:
```java
@Autowired
private RedisTemplate redisTemplate;
@PostConstruct
public void after() {
public void doAfter() {
ApiConfig.getInstance().setLimitManager(new RedisLimitManager(redisTemplate));
...
}
```

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

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

@ -0,0 +1,16 @@
# 使用SpringCloudGateway
修改`sop-gateway/pom.xml`配置,artifactId部分改成`sop-bridge-gateway`即可
```xml
<dependency>
<groupId>com.gitee.sop</groupId>
<!-- 使用zuul作为网关 -->
<!--<artifactId>sop-bridge-zuul</artifactId>-->
<!-- 使用spring cloud gateway作为网关 -->
<artifactId>sop-bridge-gateway</artifactId>
<version>对应版本</version>
</dependency>
```
修改完毕,重启sop-gateway

@ -1,10 +1,29 @@
# 传统web开发
默认情况下SOP只提供开放接口,也可以同时提供restful接口,即程序提供一部分的开放接口,同时提供一部分restful接口。
默认情况下提供restful功能是关闭的,开启方式如下:
- 打开sop-gateway配置文件,新增一行配置:
```properties
# 提供restful接口
sop.restful.enable=true
```
有些接口没有被开放,但是也想要通过网关来访问,SOP提供一个固定的请求格式来访问。
- 前端app请求网关(`2.4.1之后有变动`)
请求格式:
`http://ip:port/rest/服务id/your_path`,其中`http://ip:port/rest/`为固定部分,后面跟微服务请求路径。
**2.4.1版本之前:** `http://ip:port/rest/your_path`,其中`http://ip:port/rest/`为固定部分,后面跟微服务请求路径。
**2.4.1之后:** `http://ip:port/rest/服务id/your_path`,其中`http://ip:port/rest/`为固定部分,后面跟微服务请求路径。
**注意,`2.4.1`开始多了一个服务id作为区分,这样做是为了避免各微服务之间url冲突,假如两个微服务都有一个叫`/getItems`这样的接口
那么调用`http://ip:port/rest/getItems`接口网关无法做出正确的路由,虽然可以在代码上进行规范,为了防止万一,还是强行加上了,避免采坑
。可以指定`sop.restful.old-model=true`强制使用老的调用方式**
> 可在微服务端指定一个配置:`sop.restful.prefix=xxx`。请求路径将变成:`http://ip:port/rest/xxx/your_path`
下面是一个微服务的接口例子
@ -29,24 +48,12 @@ public class TraditionalWebappController {
2. 本地访问:`http://10.0.1.22:2222/food/getFoodById/?id=2`
更多例子,可查看源码类:`TraditionalWebappController.java`
由此可见,对于前端调用者来说,它把网关看做一个大服务,只访问网关提供的请求,不需要关心网关后面的路由转发。网关后面各个微服务独自管理,
微服务之间的调用可以使用dubbo或feign,有了版本号的管理,可以做到服务的平滑升级,对用户来说都是无感知的。结合SOP-Admin提供的上下线功能,
可实现预发布环境功能。
默认情况下,`http://ip:port/rest/`为固定部分,如果想要更改`rest`,可在网关配置文件指定:`sop.restful.path=/aaa`
请求前缀将变成:`http://ip:port/aaa/`
- 关闭restful功能
如果不想用该功能,在配置文件指定:
```properties
# 开启restful请求,默认开启
sop.restful.enable=false
```
- 封装请求工具【可选】
封装请求,方便调用,针对vue的封装如下:

@ -61,8 +61,7 @@ public UploadFile(String name, File file) throws IOException {
* @param param
* @return
*/
@Open("file.upload")
@RequestMapping("file1")
@ApiMapping(value = "demo.file.upload")
public FileUploadVO file1(FileUploadParam param) {
System.out.println(param.getRemark());
// 获取上传的文件
@ -96,8 +95,7 @@ public class FileUploadParam {
* @param param
* @return
*/
@Open("file.upload2")
@RequestMapping("file2")
@ApiMapping(value = "demo.file.upload2")
public FileUploadVO file2(FileUploadParam2 param, HttpServletRequest request) {
System.out.println(param.getRemark());
FileUploadVO vo = new FileUploadVO();

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

@ -1,3 +1,53 @@
# 使用eureka
切换到`eureka`分支
## 修改网关
- 打开`/sop-gateway/pom.xml`
注释nacos相关依赖
添加eureka依赖:
```xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
```
- 打开sop-gateway下`application-dev.properties`
注释nacos配置
```properties
# nacos cloud配置
#spring.cloud.nacos.discovery.server-addr=${nacos.url}
#nacos.config.server-addr=${nacos.url}
```
添加eureka配置
```properties
# eureka注册中心
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
```
## 微服务端修改
步骤同上
## admin修改
打开`application-dev.properties`,新增配置
```properties
# 网关地址,多个用逗号隔开
# 在不使用nacos时有用,使用nacos时注释掉
gateway.host=127.0.0.1:8081
# eureka注册中心
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
# 如果使用eureka,填eureka,使用nacos,填eureka
registry.name=eureka
```

@ -0,0 +1,86 @@
# 扩展其它注册中心
SOP默认使用的注册中心是[nacos](https://nacos.io/),可以扩展实现其它注册中心,其中`eureka`分支是已经扩展好的,使用eureka注册中心。
现在以扩展[consul](https://www.consul.io/)为例,说下具体扩展步骤:
- 扩展注册中心监听
在`sop-gateway-common`工程下,找到com.gitee.sop.gatewaycommon.route包,可以看到有两个类
`EurekaRegistryListener`和`NacosRegistryListener`
这两个类的作用是监听注册中心服务注册,从而触发事件,然后获取新注册的服务。
新建一个类:`ConsulRegistryListener`,继承`BaseRegistryListener`
实现onEvent方法,具体内容可参考`EurekaRegistryListener`类
```java
public class ConsulRegistryListener extends BaseRegistryListener {
/**
* 注册中心触发事件,可以从中获取服务<br>
*
* 这个方法做的事情有2个:<br>
*
* 1. 找出新注册的服务,调用pullRoutes方法<br>
* 2. 找出删除的服务,调用removeRoutes方法<br>
*
* @param applicationEvent 事件体
*/
@Override
public void onEvent(ApplicationEvent applicationEvent) {
}
}
```
配置类中新增:
```java
@Bean
@ConditionalOnProperty("spring.cloud.consul.host")
RegistryListener registryListenerConsul() {
return new ConsulRegistryListener();
}
```
其中`@ConditionalOnProperty("spring.cloud.consul.host")`的意思是只有配置了`spring.cloud.consul.host`属性,这个Bean才会被Spring注入
`sop-gateway`工程添加`Spring Cloud Consul`相关依赖,配置文件新增consul配置
- 扩展admin实现
找到`sop-admin-server`工程下com.gitee.sop.adminserver.service包,可以看到有两个类,`RegistryServiceEurekaImpl`和`RegistryServiceNacosImpl`
它们实现了`com.gitee.sop.adminserver.service.RegistryService`接口,因此我们要新建一个consul对应的类
新建`RegistryServiceConsulImpl`,然后实现RegistryService接口中的方法,具体可参考RegistryServiceEurekaImpl
```java
public class RegistryServiceConsulImpl implements RegistryService {
}
```
打开`com.gitee.sop.adminserver.config.WebConfig`类
新增一条配置
```java
/**
* 当配置了registry.name=eureka生效。
*
* @return
*/
@Bean
@ConditionalOnProperty(value = "registry.name", havingValue = "consul")
RegistryService registryServiceEureka() {
return new RegistryServiceConsulImpl();
}
```
application配置文件新增一条配置:
```properties
registry.name=consul
```

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

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

@ -0,0 +1,21 @@
# 原理分析之@ApiMapping注解
@ApiMapping注解的使用方式参考了Spring自带的@PostMapping注解
查看org.springframework.web.bind.annotation.PostMapping的类注释,有这么一句话:
> Specifically, @PostMapping is a composed annotation that acts as a shortcut for @RequestMapping(method = RequestMethod.POST).
翻译过来就是说,@PostMapping是一个组合模式的注解,可以看成是@RequestMapping(method = RequestMethod.POST)快捷方式。
如果我们自己定义个Mapping,仿照@PostMapping的方式,然后作用在方法上面会不会成功呢?实践证明是可以的。
@ApiMapping注解正是仿照了@PostMapping注解,然后再添加了几个自己的属性,比如版本号字段。
那么如何才能通过path + 版本号来确定一个接口呢?
springmvc提供了RequestCondition接口来实现这个功能,具体的操作可参考这篇文章:[让SpringMVC支持可版本管理的Restful接口](http://www.cnblogs.com/jcli/p/springmvc_restful_version.html)
SOP对应的是`com.gitee.sop.servercommon.mapping.ApiMappingRequestCondition`,这个类在com.gitee.sop.servercommon.mapping下。可以从`ApiMappingHandlerMapping`类开始解读。

@ -1,37 +1,54 @@
# 原理分析之如何存储路由
SOP基于spring cloud,因此会涉及到网关路由。但是开发者不用去配置文件定义路由的隐射关系,SOP帮你解决了这个问题。
SOP基于spring cloud,因此会涉及到网关路由。但是开发者不用去配置文件定义路由的隐射关系,SOP自动帮你解决了这个问题。
## 获取路由信息
网关启动成后会触发一个事件,代码见:`com.gitee.sop.gatewaycommon.config.AbstractConfiguration.listenEvent`
首先明确一点,路由信息由各微服务提供,因此网关需要从注册中心获取各微服务实例,这个通过nacos提供的`watch`来实现。
当有新的微服务加入时,Nacos会触发一个事件推送,详见`NacosWatch.java`类
这个事件会取拉取微服务中提供的路由信息
下面以nacos为例,介绍拉取路由过程
1.从nacos中获取微服务实例
入口代码:`com.gitee.sop.bridge.route.NacosRegistryListener.onEvent`
2.拿到微服务信息,调用微服务提供的接口拉取路由数据
入口代码:`com.gitee.sop.gatewaycommon.route.BaseRegistryListener.pullRoutes`
最终落实到:`com.gitee.sop.gatewaycommon.route.ServiceRouteListener.onAddInstance`
微服务提供一个url:`http://ip:port/sop/routes`,对应Controller在:`com.gitee.sop.servercommon.route.ServiceRouteController.listRoutes`
```java
this.publisher.publishEvent(new HeartbeatEvent(this, this.nacosWatchIndex.getAndIncrement()));
```
微服务找到被`@Open`注解的方法,然后封装成一个路由对象,放到List中,最后返回给网关
因此,只要注册了`HeartbeatEvent`事件就能随时感知到微服务实例的变化。如何注册呢,config类实现`ApplicationListener`接口即可。
3.网关拿到路由信息,经过处理,转化成网关路由配置
```java
public class AbstractConfiguration implements ApplicationContextAware, ApplicationListener<HeartbeatEvent> {
/**
* nacos事件监听,每次微服务变化会触发这个方法
* @see org.springframework.cloud.alibaba.nacos.discovery.NacosWatch NacosWatch
* @param heartbeatEvent
*/
@Override
public void onApplicationEvent(HeartbeatEvent heartbeatEvent) {
...这里加载路由信息
}
}
```
关联方法:
然后,每个微服务提供一个restful接口,接口地址为:`http://ip:port/sop/routes`,用来返回自身路由信息。网关请求这个接口用来获取路由信息。
`com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteCache.add`
加载路由伪代码如下:
`com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteCache.refresh`
```java
public void onApplicationEvent(HeartbeatEvent heartbeatEvent) {
// 获取nacos中的服务实例列表
List<Instance> allInstances = namingService.getAllInstances(serviceName);
for(Instance instance : allInstances) {
// 微服务提供的接口
String url = "http://" + instance.getIp + ":" + instance.getPort + "/sop/routes";
ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
if (responseEntity.getStatusCode() == HttpStatus.OK) {
// 返回来的路由信息
String body = responseEntity.getBody();
...加载路由到本地
}
}
}
```
完整代码可参看`com.gitee.sop.gatewaycommon.manager.ServiceRoutesLoader.java`
路由的存储方式是一个Map,key为路由id,即接口名+版本号。
@ -39,8 +56,8 @@ SOP基于spring cloud,因此会涉及到网关路由。但是开发者不用
/**
* key:nameVersion
*/
private static final Map<String, GatewayTargetRoute> routes = synchronizedMap(new LinkedHashMap<>());
private Map<String, ZuulTargetRoute> nameVersionTargetRouteMap;
```
因为客户端调用接口都会传递一个接口名和版本号,因此通过这两个字段能够很快查询出路由信息,然后进行路由转发操作。
因为客户端调用接口都会传递一个接口名和版本号,因此通过这两个字段能够很快查询出路由信息,进行路由转发操作。

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

@ -0,0 +1,23 @@
# 2.x升3.x注意事项
升级到3.x后`本地访问接口`方式会有不同。
```java
@ApiMapping(value = "alipay.story.get")
public StoryResult getStory(StoryParam param) {
StoryResult story = new StoryResult();
story.setId(1L);
story.setName("海底小纵队(alipay.story.get1.0), port:" + environment.getProperty("server.port") + ", param:" + param);
return story;
}
```
- 2.x版本访问方式:
`http://localhost:2222/alipay.story.get/?name=Jim&version=1.0`
- 3.x版本访问方式:
`http://localhost:2222/alipay.story.get/1.0/?name=Jim`
3.x版本中把版本号融合在了url中,如果这个功能没有用到,可以放心升级。

@ -1,28 +1,10 @@
# 常见问题
## Connection has been closed BEFORE send operation
## 在zuul过滤器中获取请求参数
压测时出现`Connection has been closed BEFORE send operation`,`java.nio.channels.ClosedChannelException`之类的错误
- 解决办法
尝试调大内存分配1G或2G,`-Xms1g -Xmx1g`
## spring cloud gateway [DataBufferLimitException: Exceeded limit on max bytes to buffer : 262144]
如果POST请求body内容太大可能会报这个错误
- 解决版办法
网关指定配置`spring.codec.max-in-memory-size=xx`,xx默认是262144,即256K(262144=256*1024)
## primordials is not defined
node版本太高导致与gulp版本不兼容,解决方法:node退回11版本。参考https://blog.csdn.net/zxxzxx23/article/details/103000393
## Nacos指定group
可在配置文件中添加:`spring.cloud.nacos.discovery.group=xxx`指定group,不加默认是:DEFAULT_GROUP
```java
ApiParam param = ZuulContext.getApiParam();
```
## 在SpringCloudGateway中获取请求参数
@ -34,16 +16,40 @@ ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange);
```java
OpenContext openContext = ServiceContext.getCurrentContext().getOpenContext();
System.out.println("app_id:" + openContext.getAppId());
System.out.println("token:" + openContext.getAppAuthToken());
String appId = openContext.getAppId();
```
## 在其它地方获取业务参数
```java
OpenContext openContext = ServiceContext.getCurrentContext().getOpenContext();
Story bizObject = (Story)openContext.getBizObject();
OpenContext openContext = ServiceContext.getCurrentContext().getOpenContext();
Story bizObject = openContext.getBizObject(Story.class);
```
## 如何关闭签名验证
- 针对某一个接口关闭签名验证
`@Open(value = "alipay.story.get", ignoreValidate = true)`
`@ApiMapping(value = "alipay.story.get", ignoreValidate = true)`
- 针对所有接口关闭签名验证
```java
@Configuration
public class ZuulConfig extends AlipayZuulConfiguration {
static {
...
ApiConfig.getInstance().setIgnoreValidate(true);
...
}
}
```
## 注册到eureka显示hostname,非ip
@ -94,9 +100,29 @@ yml添加:
# https://blog.csdn.net/qq_36872046/article/details/81058045
# 路由转发超时时间,毫秒,默认值1000,详见:RibbonClientConfiguration.DEFAULT_READ_TIMEOUT。
# 如果微服务端 处理时间过长,会导致ribbon read超时,解决办法将这个值调大一点
ribbon.ReadTimeout= 60000
ribbon.ReadTimeout: 60000
```
## 指定了context-path(拉取路由404)
微服务项目定义了`server.servlet.context-path=/story-service`,同时必须指定:
```properties
spring.cloud.nacos.discovery.metadata.context-path=${server.servlet.context-path}
```
yml配置如下:
```yaml
spring:
cloud:
nacos:
discovery:
metadata:
context-path: ${server.servlet.context-path}
```
不然网关无法拉取路由信息
## 其它微服务没有开放接口,需要排除

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

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

@ -1,12 +0,0 @@
#!/bin/sh
# 打包
mvn clean package
# 创建镜像
docker build -t gitee.com/sop .
# 获取镜像id
image_id=`docker images gitee.com/sop --format "{{.ID}}" | awk '{print $1}'`
# 运行镜像
docker run --name sop -p 8081:8081 -p 8082:8082 -p 8083:8083 -p 8087:8087 -p 2222:2222 -d $image_id

@ -1,13 +0,0 @@
#!/bin/sh
JAVA_OPTS="-Xms128m -Xmx128m"
# mysql, nacos配置
args="--mysql.host=10.1.30.110:3306 --mysql.username=root --mysql.password=root --register.url=10.1.30.110:8848"
java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /sop/sop-gateway/sop-gateway.jar $args --logging.file.path=/sop/sop-gateway/log &
java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /sop/sop-admin/sop-admin.jar $args --logging.file.path=/sop/sop-admin/log &
java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /sop/sop-website/sop-website.jar $args --logging.file.path=/sop/sop-website/log &
java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /sop/sop-auth/sop-auth.jar $args --logging.file.path=/sop/sop-auth/log &
# 最后一条没有&
java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /sop/sop-story/sop-story.jar $args --logging.file.path=/sop/sop-story/log

@ -5,13 +5,14 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-parent</artifactId>
<version>4.4.2-SNAPSHOT</version>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<description>一个开放平台解决方案项目,基于Spring Cloud实现,目标是能够让用户快速得搭建起自己的开放平台</description>
@ -35,21 +36,22 @@
<maven.compiler.target>1.8</maven.compiler.target>
<!-- springboot 版本-->
<spring-boot.version>2.3.2.RELEASE</spring-boot.version>
<spring-boot.version>2.2.5.RELEASE</spring-boot.version>
<!-- spring cloud 版本 -->
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
<spring-cloud.version>Hoxton.SR3</spring-cloud.version>
<!-- spring cloud alibaba 版本 -->
<!-- 具体版本对应关系见:https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E -->
<spring-cloud-alibaba.version>2.2.5.RELEASE</spring-cloud-alibaba.version>
<spring-cloud-alibaba.version>2.2.1.RELEASE</spring-cloud-alibaba.version>
<!-- Logging -->
<logback.version>1.2.3</logback.version>
<slf4j.version>1.7.5</slf4j.version>
<!-- Test -->
<junit.version>4.11</junit.version>
<fastjson.version>1.2.73</fastjson.version>
<fastjson.version>1.2.70</fastjson.version>
<commons-io.version>2.5</commons-io.version>
<commons-fileupload.version>1.3.3</commons-fileupload.version>
<commons-collection.version>3.2.2</commons-collection.version>
@ -59,13 +61,11 @@
<validation-api.version>2.0.1.Final</validation-api.version>
<hibernate-validator.version>6.0.13.Final</hibernate-validator.version>
<fastmybatis.version>1.9.1</fastmybatis.version>
<guava.version>29.0-jre</guava.version>
<spring-data-redis.version>2.3.0.RELEASE</spring-data-redis.version>
<guava.version>27.1-jre</guava.version>
<swagger.version>1.5.21</swagger.version>
<springfox-spring-web.version>2.9.2</springfox-spring-web.version>
<springfox-swagger2.version>2.9.2</springfox-swagger2.version>
<easyopen.version>1.16.9</easyopen.version>
<asm.version>6.2</asm.version>
<pagehelper.version>5.2.0</pagehelper.version>
</properties>
<dependencyManagement>
@ -121,11 +121,6 @@
<artifactId>swagger-annotations</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${springfox-swagger2.version}</version>
</dependency>
<dependency>
<groupId>net.oschina.durcframework</groupId>
@ -133,12 +128,6 @@
<version>${easyopen.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.14.7</version>
</dependency>
<dependency>
<groupId>net.oschina.durcframework</groupId>
<artifactId>easyopen-spring-boot-starter</artifactId>
@ -162,6 +151,12 @@
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>${spring-data-redis.version}</version>
</dependency>
<!-- commons -->
<dependency>
<groupId>org.apache.commons</groupId>
@ -194,18 +189,6 @@
<version>${commons-logging.version}</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>${asm.version}</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>${pagehelper.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
@ -221,9 +204,9 @@
<repositories>
<repository>
<id>aliyun</id>
<name>aliyun</name>
<url>https://maven.aliyun.com/repository/public</url>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
@ -248,7 +231,20 @@
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
</project>

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

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

@ -10,13 +10,4 @@
后台用户表:admin_user_info
- 密码保存规则:`md5(username + md5(password) + username)`
```text
username: admin
password: 123456
md5("admin" + md5(123456) + "admin")
```
详见:com.gitee.sop.adminserver.AccountTest
密码保存规则:两次MD5,即`md5(md5("123456"))`

@ -0,0 +1,13 @@
package com.gitee.sop.adminserver.api.config;
import com.gitee.easyopen.annotation.ApiService;
import com.gitee.easyopen.doc.annotation.ApiDoc;
/**
* @author tanghc
*/
@ApiService
@ApiDoc("配置列表")
public class ConfigListApi {
}

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

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

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

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

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

@ -13,7 +13,6 @@ 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.RouteSearchParam;
import com.gitee.sop.adminserver.api.service.param.RouteStatusUpdateParam;
import com.gitee.sop.adminserver.api.service.param.RouteUpdateParam;
import com.gitee.sop.adminserver.api.service.result.RouteVO;
import com.gitee.sop.adminserver.bean.RouteConfigDto;
@ -124,26 +123,6 @@ public class RouteApi {
this.updateRouteConfig(param);
}
@Api(name = "route.status.update")
@ApiDocMethod(description = "修改路由状态")
void updateRouteStatus(RouteStatusUpdateParam param) {
String routeId = param.getId();
ConfigRouteBase configRouteBase = configRouteBaseMapper.getByColumn("route_id", routeId);
boolean doSave = configRouteBase == null;
if (doSave) {
configRouteBase = new ConfigRouteBase();
configRouteBase.setRouteId(routeId);
}
configRouteBase.setStatus(param.getStatus());
int i = doSave ? configRouteBaseMapper.save(configRouteBase)
: configRouteBaseMapper.update(configRouteBase);
if (i > 0) {
this.sendMsg(configRouteBase);
}
}
@Api(name = "route.del")
@ApiDocMethod(description = "删除路由")
void delRoute(RouteDeleteParam param) {

@ -75,7 +75,6 @@ public class ServiceApi {
return serviceId.contains(param.getServiceId());
}
})
.sorted()
.collect(Collectors.toList());
}

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

@ -30,8 +30,8 @@ public class LimitNewAddParam {
@NotBlank(message = "serviceId can not null")
private String serviceId;
/** 限流策略,1:窗口策略,2:令牌桶策略, 数据库字段:limit_type */
@ApiDocField(description = "限流策略,1:窗口策略,2:令牌桶策略")
/** 限流策略,1:漏桶策略,2:令牌桶策略, 数据库字段:limit_type */
@ApiDocField(description = "限流策略,1:漏桶策略,2:令牌桶策略")
@NotNull(message = "limitType不能为空")
private Byte limitType;

@ -35,8 +35,8 @@ public class LimitNewUpdateParam {
@NotBlank(message = "serviceId can not null")
private String serviceId;
/** 限流策略,1:窗口策略,2:令牌桶策略, 数据库字段:limit_type */
@ApiDocField(description = "限流策略,1:窗口策略,2:令牌桶策略")
/** 限流策略,1:漏桶策略,2:令牌桶策略, 数据库字段:limit_type */
@ApiDocField(description = "限流策略,1:漏桶策略,2:令牌桶策略")
@NotNull(message = "limitType不能为空")
private Byte limitType;

@ -20,8 +20,8 @@ public class LimitParam {
@NotBlank(message = "serviceId can not null")
private String serviceId;
/** 限流策略,1:窗口策略,2:令牌桶策略, 数据库字段:limit_type */
@ApiDocField(description = "限流策略,1:窗口策略,2:令牌桶策略")
/** 限流策略,1:漏桶策略,2:令牌桶策略, 数据库字段:limit_type */
@ApiDocField(description = "限流策略,1:漏桶策略,2:令牌桶策略")
@NotNull
private Byte limitType;

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

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

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

@ -1,32 +0,0 @@
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 RouteStatusUpdateParam {
/**
* 路由的Id
*/
@NotBlank(message = "id不能为空")
@ApiDocField(description = "路由id")
private String id;
/**
* 状态
*/
@NotNull
@ApiDocField(description = "状态,0:审核,1:启用,2:禁用")
private Byte status;
}

@ -41,9 +41,9 @@ public class LimitNewVO {
private String serviceId;
/**
* 限流策略1窗口策略2令牌桶策略, 数据库字段limit_type
* 限流策略1漏桶策略2令牌桶策略, 数据库字段limit_type
*/
@ApiDocField(description = "限流策略,1:窗口策略,2:令牌桶策略")
@ApiDocField(description = "限流策略,1:漏桶策略,2:令牌桶策略")
private Byte limitType;
/**

@ -32,9 +32,9 @@ public class LimitVO {
private String serviceId;
/**
* 限流策略1窗口策略2令牌桶策略, 数据库字段limit_type
* 限流策略1漏桶策略2令牌桶策略, 数据库字段limit_type
*/
@ApiDocField(description = "限流策略,1:窗口策略,2:令牌桶策略")
@ApiDocField(description = "限流策略,1:漏桶策略,2:令牌桶策略")
private Byte limitType;
/**

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

@ -36,7 +36,7 @@ public class SystemApi {
String adminLogin(LoginForm param) {
String username = param.getUsername();
String password = param.getPassword();
password = DigestUtils.md5Hex(username + password + username);
password = DigestUtils.md5Hex(password);
Query query = new Query()
.eq("username", username)

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

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

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

@ -21,7 +21,7 @@ public class ConfigLimitDto {
/** 服务id, 数据库字段:service_id */
private String serviceId;
/** 限流策略,1:窗口策略,2:令牌桶策略, 数据库字段:limit_type */
/** 限流策略,1:漏桶策略,2:令牌桶策略, 数据库字段:limit_type */
private Byte limitType;
/** 每秒可处理请求数, 数据库字段:exec_count_per_second */

@ -0,0 +1,89 @@
package com.gitee.sop.adminserver.bean;
import com.alibaba.fastjson.annotation.JSONField;
import com.gitee.easyopen.doc.annotation.ApiDocField;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* @author tanghc
*/
@Data
public class RouteDefinition {
/**
* 路由的Id
*/
@ApiDocField(description = "路由id")
private String id = "";
/**
* 接口名
*/
private String name;
/**
* 版本号
*/
private String version;
/**
* 路由断言集合配置
*/
@JSONField(serialize = false)
private List<GatewayPredicateDefinition> predicates = new ArrayList<>();
/**
* 路由过滤器集合配置
*/
@JSONField(serialize = false)
private List<GatewayFilterDefinition> filters = new ArrayList<>();
/**
* 路由规则转发的目标uri
*/
@ApiDocField(description = "uri")
private String uri;
/**
* uri后面跟的path
*/
@ApiDocField(description = "path")
private String path;
/**
* 路由执行的顺序
*/
private int order = 0;
/**
* 是否忽略验证业务参数验证除外
*/
@ApiDocField(description = "是否忽略验证,业务参数验证除外,1:忽略,0:不忽略")
private int ignoreValidate;
/**
* 状态0待审核1启用2禁用
*/
@ApiDocField(description = "状态,0:待审核,1:启用,2:禁用")
private int status = 1;
/**
* 合并结果统一格式输出
*/
@ApiDocField(description = "合并结果,统一格式输出,1:合并,0:不合并")
private int mergeResult = 1;
/**
* 是否需要授权才能访问
*/
@ApiDocField(description = "是否需要授权才能访问,1:是,0:否")
private int permission;
/**
* 是否是自定义路由
*/
@ApiDocField(description = "是否是自定义路由,1:是,0:否")
private int custom;
}

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

@ -1,29 +1,16 @@
package com.gitee.sop.adminserver.bean;
import lombok.Data;
import java.util.List;
/**
* @author tanghc
*/
@Data
public class ServiceInfo {
/** 服务名称 */
private String serviceId;
/** 实例列表 */
private List<ServiceInstance> instances;
public String getServiceId() {
return serviceId;
}
public void setServiceId(String serviceId) {
this.serviceId = serviceId;
}
public List<ServiceInstance> getInstances() {
return instances;
}
public void setInstances(List<ServiceInstance> instances) {
this.instances = instances;
}
}

@ -0,0 +1,26 @@
package com.gitee.sop.adminserver.bean;
import lombok.Data;
import java.util.Date;
import java.util.List;
/**
* @author tanghc
*/
@Data
public class ServiceRouteInfo {
/** 服务名称,对应spring.application.name */
private String serviceId;
private Date createTime = new Date();
private Date updateTime = new Date();
private String description;
private List<RouteDefinition> routeDefinitionList;
/** 是否是自定义服务,1:是,0:否 */
private int custom;
}

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

@ -6,7 +6,7 @@ package com.gitee.sop.adminserver.common;
*/
public enum LimitEnum {
/**
* 限流策略1窗口策略
* 限流策略1漏桶策略
*/
TYPE_LEAKY_BUCKET((byte) 1),
/**

@ -37,7 +37,7 @@ public class ConfigLimit {
/** 服务id, 数据库字段:service_id */
private String serviceId;
/** 限流策略,1:窗口策略,2:令牌桶策略, 数据库字段:limit_type */
/** 限流策略,1:漏桶策略,2:令牌桶策略, 数据库字段:limit_type */
private Byte limitType;
/** 每秒可处理请求数, 数据库字段:exec_count_per_second */

@ -0,0 +1,58 @@
package com.gitee.sop.adminserver.entity;
import lombok.Data;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
/**
* 表名config_route_limit
* 备注路由限流配置
*
* @author tanghc
*/
@Table(name = "config_route_limit")
@Data
public class ConfigRouteLimit {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
/** 数据库字段:id */
private Integer id;
/** 路由id, 数据库字段:route_id */
private String routeId;
/** serviceId, 数据库字段:service_id */
private String serviceId;
/** 限流策略,1:漏桶策略,2:令牌桶策略, 数据库字段:limit_type */
private Byte limitType;
/** 每秒可处理请求数, 数据库字段:exec_count_per_second */
private Integer execCountPerSecond;
/** 返回的错误码, 数据库字段:limit_code */
private String limitCode;
/** 返回的错误信息, 数据库字段:limit_msg */
private String limitMsg;
/** 令牌桶容量, 数据库字段:token_bucket_count */
private Integer tokenBucketCount;
/** 1:开启,0关闭, 数据库字段:limit_status */
private Byte limitStatus;
/** 数据库字段:gmt_create */
private Date gmtCreate;
/** 数据库字段:gmt_modified */
private Date gmtModified;
}

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

@ -28,12 +28,18 @@ public class IsvInfo {
/** appKey, 数据库字段:app_key */
private String appKey;
/** 不再使用,转移到isv_keys表 */
private String secret = "";
/** 公钥,不再使用,转移到isv_keys表 数据库字段:pub_key */
private String pubKey;
/** 私钥,不再使用,转移到isv_keys表 数据库字段:pri_key */
private String priKey;
/** 1启用,2禁用, 数据库字段:status */
private Byte status;
/** user_account.id */
private Long userId;
/** 备注,数据库字段:remark */
private String remark;

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

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

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

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

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

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

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

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

@ -5,8 +5,6 @@ import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.internal.http.HttpMethod;
import java.util.Base64;
/**
* https://github.com/Netflix/eureka/wiki/Eureka-REST-operations
*
@ -66,16 +64,12 @@ public enum EurekaUri {
url = url.substring(0, url.length() - 1);
}
String requestUrl = url + getUri(args);
Request.Builder builder = this.getBuilder()
Request request = this.getBuilder()
.url(requestUrl)
.addHeader("Content-Type", "application/json")
.addHeader("Accept", "application/json");
//是否开启 Basic
if(url.contains("@") && url.contains(":")){
String user = url.split("@")[0].replace("http://","").replace("https://","");
builder.addHeader("Authorization","Basic " + Base64.getUrlEncoder().encodeToString(user.getBytes()));
}
return builder.build();
.addHeader("Accept", "application/json")
.build();
return request;
}
public Request.Builder getBuilder() {

@ -18,8 +18,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* eureka接口实现
@ -31,11 +29,11 @@ public class RegistryServiceEurekaImpl implements RegistryService {
private OkHttpClient client = new OkHttpClient();
@Value("${eureka.client.serviceUrl.defaultZone:}")
private String eurekaUrls;
private String eurekaUrl;
@Override
public List<ServiceInfo> listAllService(int pageNo, int pageSize) throws Exception {
if (StringUtils.isBlank(eurekaUrls)) {
if (StringUtils.isBlank(eurekaUrl)) {
throw new IllegalArgumentException("未指定eureka.client.serviceUrl.defaultZone参数");
}
String json = this.requestEurekaServer(EurekaUri.QUERY_APPS);
@ -83,7 +81,7 @@ public class RegistryServiceEurekaImpl implements RegistryService {
}
private String requestEurekaServer(EurekaUri eurekaUri, String... args) throws IOException {
Request request = eurekaUri.getRequest(getFirstDefaultZoneServiceUrl(), args);
Request request = eurekaUri.getRequest(this.eurekaUrl, args);
Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
return response.body().string();
@ -93,22 +91,4 @@ public class RegistryServiceEurekaImpl implements RegistryService {
}
}
private List<String> getDefaultZoneServiceUrls() {
if (!StringUtils.isEmpty(eurekaUrls)) {
return Stream.of(org.springframework.util.StringUtils.commaDelimitedListToStringArray(eurekaUrls))
.map(url -> !url.endsWith("/") ? (url += "/").trim() : url.trim())
.collect(Collectors.toList());
} else {
return Collections.emptyList();
}
}
private String getFirstDefaultZoneServiceUrl() {
List<String> serviceUrls = getDefaultZoneServiceUrls();
if (CollectionUtils.isEmpty(serviceUrls)) {
throw new IllegalArgumentException("未指定eureka.client.serviceUrl.defaultZone参数");
}
return serviceUrls.get(0);
}
}

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

@ -10,27 +10,18 @@ admin.access-token.timeout-minutes=30
sop.sign-type=rsa
# nacos配置
nacos.config.server-addr=${register.url}
nacos.discovery.server-addr=${register.url}
nacos.config.server-addr=${nacos.url}
nacos.discovery.server-addr=${nacos.url}
# nacos cloud配置
spring.cloud.nacos.discovery.server-addr=${register.url}
# eureka地址
eureka.client.serviceUrl.defaultZone=${register.url}
spring.cloud.nacos.discovery.server-addr=${nacos.url}
# 数据库配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://${mysql.host}/sop?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
spring.datasource.url=jdbc:mysql://${mysql.host}/sop3?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
spring.datasource.username=${mysql.username}
spring.datasource.password=${mysql.password}
#连接池
spring.datasource.hikari.pool-name=HikariCP
spring.datasource.hikari.max-lifetime=500000
# 固定不用改
mybatis.config-location=classpath:mybatis/mybatisConfig.xml
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
easyopen.show-doc=false
easyopen.ignore-validate=true

@ -1,4 +1,3 @@
# 更多配置,见:META-INF/sop-admin.properties
server.port=8082
# mysql数据库账号
@ -6,7 +5,7 @@ mysql.host=localhost:3306
mysql.username=root
mysql.password=root
# 注册中心地址
register.url=127.0.0.1:8848
# nacos注册中心地址
nacos.url=127.0.0.1:8848
logging.level.com.gitee=debug

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

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

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

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

Loading…
Cancel
Save