Merge branch 'develop'

1.x
tanghc 5 years ago
commit b64539515e
  1. 5
      changelog.md
  2. 56
      doc/docs/_sidebar.md
  3. 88
      doc/docs/files/10106_nacos注册中心.md
  4. 112
      doc/docs/files/10107_扩展其它注册中心.md
  5. 2
      sop-admin/readme.md
  6. 2
      sop-admin/sop-admin-front/README.md
  7. 22
      sop-admin/sop-admin-server/pom.xml
  8. 46
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/RoleApi.java
  9. 24
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/param/RoleForm.java
  10. 19
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/param/RolePageParam.java
  11. 8
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/result/RoleVO.java
  12. 2
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/LogApi.java
  13. 121
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/ServiceApi.java
  14. 6
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/LogMonitorInstanceAddParam.java
  15. 2
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/result/RouteServiceInfo.java
  16. 54
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/result/ServiceInstanceVO.java
  17. 12
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/config/RegistryConfig.java
  18. 7
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/IsvInfo.java
  19. 15
      sop-admin/sop-admin-server/src/main/resources/application-dev.yml
  20. 2
      sop-admin/sop-admin-server/src/main/resources/public/index.html
  21. 2
      sop-admin/sop-admin-server/src/main/resources/public/static/css/chunk-009073d4.0af16c7e.css
  22. 2
      sop-admin/sop-admin-server/src/main/resources/public/static/js/app.095e8b9c.js
  23. 2
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-009073d4.28312808.js
  24. 2
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-25908fca.e7e4b6d5.js
  25. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d2085ef.a63a74dc.js
  26. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d22c2e3.46f37153.js
  27. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d22c2e3.4a098244.js
  28. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-4a59cbe4.a6360c68.js
  29. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-4a59cbe4.b5e74cc4.js
  30. 2
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-6a68a33e.15951b55.js
  31. 8
      sop-admin/sop-admin-vue/src/router/index.js
  32. 2
      sop-admin/sop-admin-vue/src/views/isv/index.vue
  33. 179
      sop-admin/sop-admin-vue/src/views/isv/role.vue
  34. 2
      sop-admin/sop-admin-vue/src/views/service/limit/index.vue
  35. 2
      sop-admin/sop-admin-vue/src/views/service/limit/index2.vue
  36. 50
      sop-admin/sop-admin-vue/src/views/service/list/index.vue
  37. 15
      sop-admin/sop-admin-vue/src/views/service/log/index.vue
  38. 2
      sop-admin/sop-admin-vue/src/views/service/route/index.vue
  39. 3
      sop-common/pom.xml
  40. 3
      sop-common/readme.md
  41. 4
      sop-common/sop-gateway-common/pom.xml
  42. 2
      sop-common/sop-gateway-common/readme.md
  43. 74
      sop-common/sop-registry-api/pom.xml
  44. 3
      sop-common/sop-registry-api/readme.md
  45. 2
      sop-common/sop-registry-api/src/main/java/com/gitee/sop/registryapi/bean/EurekaApplication.java
  46. 2
      sop-common/sop-registry-api/src/main/java/com/gitee/sop/registryapi/bean/EurekaApplications.java
  47. 2
      sop-common/sop-registry-api/src/main/java/com/gitee/sop/registryapi/bean/EurekaApps.java
  48. 2
      sop-common/sop-registry-api/src/main/java/com/gitee/sop/registryapi/bean/EurekaInstance.java
  49. 30
      sop-common/sop-registry-api/src/main/java/com/gitee/sop/registryapi/bean/EurekaUri.java
  50. 24
      sop-common/sop-registry-api/src/main/java/com/gitee/sop/registryapi/bean/ServiceInfo.java
  51. 32
      sop-common/sop-registry-api/src/main/java/com/gitee/sop/registryapi/bean/ServiceInstance.java
  52. 36
      sop-common/sop-registry-api/src/main/java/com/gitee/sop/registryapi/config/BaseRegistryConfig.java
  53. 36
      sop-common/sop-registry-api/src/main/java/com/gitee/sop/registryapi/service/RegistryService.java
  54. 90
      sop-common/sop-registry-api/src/main/java/com/gitee/sop/registryapi/service/impl/RegistryServiceEureka.java
  55. 106
      sop-common/sop-registry-api/src/main/java/com/gitee/sop/registryapi/service/impl/RegistryServiceNacos.java
  56. 4
      sop-common/sop-service-common/pom.xml
  57. 2
      sop-example/sop-auth/pom.xml
  58. 21
      sop-example/sop-book/sop-book-web/pom.xml
  59. 2
      sop-example/sop-book/sop-book-web/src/main/java/com/gitee/sop/bookweb/controller/ApiResult.java
  60. 10
      sop-example/sop-book/sop-book-web/src/main/resources/application-dev.yml
  61. 2
      sop-example/sop-easyopen/pom.xml
  62. 2
      sop-example/sop-springmvc/pom.xml
  63. 19
      sop-example/sop-story/sop-story-web/pom.xml
  64. 2
      sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/AlipayController.java
  65. 11
      sop-example/sop-story/sop-story-web/src/main/resources/application-dev.yml
  66. 17
      sop-gateway/pom.xml
  67. 9
      sop-gateway/src/main/resources/application-dev.yml
  68. 64
      sop-test/src/test/java/com/gitee/sop/LoadBalanceTest.java
  69. 7
      sop-website/website-server/pom.xml
  70. 12
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/config/RegistryConfig.java
  71. 13
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/controller/SandboxController.java
  72. 89
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/DocManagerImpl.java
  73. 13
      sop-website/website-server/src/main/resources/application-dev.yml
  74. 2
      sop.sql

@ -1,5 +1,10 @@
# changelog # changelog
## 1.12.0
- admin后台新增角色管理
- 支持nacos作为注册中心
## 1.11.0 ## 1.11.0
- 秘钥管理改造 - 秘钥管理改造

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

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

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

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

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

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

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

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

@ -0,0 +1,19 @@
package com.gitee.sop.adminserver.api.isv.param;
import com.gitee.easyopen.doc.annotation.ApiDocField;
import com.gitee.fastmybatis.core.query.Operator;
import com.gitee.fastmybatis.core.query.annotation.Condition;
import com.gitee.fastmybatis.core.query.param.PageParam;
import lombok.Getter;
import lombok.Setter;
/**
* @author tanghc
*/
@Getter
@Setter
public class RolePageParam extends PageParam {
@ApiDocField(description = "角色码")
@Condition(operator = Operator.like)
private String roleCode;
}

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

@ -114,7 +114,7 @@ public class LogApi {
@Api(name = "monitor.instance.add") @Api(name = "monitor.instance.add")
@ApiDocMethod(description = "添加监控实例") @ApiDocMethod(description = "添加监控实例")
void addServiceInstance(LogMonitorInstanceAddParam param) { void addServiceInstance(LogMonitorInstanceAddParam param) {
String ipPort = param.getIp() + ":" + param.getPort(); String ipPort = param.getIpPort();
this.checkInstance(ipPort); this.checkInstance(ipPort);
Query query = new Query(); Query query = new Query();

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

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

@ -7,7 +7,7 @@ import lombok.Data;
* @author tanghc * @author tanghc
*/ */
@Data @Data
public class ServiceInfo { public class RouteServiceInfo {
@ApiDocField(description = "serviceId") @ApiDocField(description = "serviceId")
private String serviceId; private String serviceId;

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

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

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

@ -12,6 +12,8 @@ spring:
name: sop-admin name: sop-admin
cloud: cloud:
# zookeeper地址,根据实际情况修改
zookeeper: zookeeper:
connect-string: localhost:2181 connect-string: localhost:2181
baseSleepTimeMs: 3000 baseSleepTimeMs: 3000
@ -31,12 +33,13 @@ easyopen:
ignore-validate: true ignore-validate: true
# 注册中心地址,根据实际情况改,这里只是参数,并不会去注册 # 注册中心地址,根据实际情况改,这里只是参数,并不会去注册
eureka: registry:
port: 1111 eureka-server-addr: http://localhost:1111/eureka/
host: localhost # nacos服务器地址
client: nacos-server-addr: 127.0.0.1:8848
serviceUrl: # 使用eureka,填:eureka,使用nacos填:nacos
defaultZone: http://${eureka.host}:${eureka.port}/eureka/ name: eureka
# 根据实际情况改 # 根据实际情况改
logging: logging:

@ -1 +1 @@
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><link rel=icon href=favicon.ico><title>SOP Admin</title><link href=static/css/chunk-elementUI.81cf475c.css rel=stylesheet><link href=static/css/chunk-libs.3dfb7769.css rel=stylesheet><link href=static/css/app.4f0872ef.css rel=stylesheet></head><body><noscript><strong>We're sorry but SOP Admin doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script>(function(e){function t(t){for(var r,c,a=t[0],i=t[1],f=t[2],l=0,h=[];l<a.length;l++)c=a[l],u[c]&&h.push(u[c][0]),u[c]=0;for(r in i)Object.prototype.hasOwnProperty.call(i,r)&&(e[r]=i[r]);s&&s(t);while(h.length)h.shift()();return o.push.apply(o,f||[]),n()}function n(){for(var e,t=0;t<o.length;t++){for(var n=o[t],r=!0,c=1;c<n.length;c++){var a=n[c];0!==u[a]&&(r=!1)}r&&(o.splice(t--,1),e=i(i.s=n[0]))}return e}var r={},c={runtime:0},u={runtime:0},o=[];function a(e){return i.p+"static/js/"+({}[e]||e)+"."+{"chunk-238a81e9":"5955f13d","chunk-25908fca":"3daff0c2","chunk-29e7142c":"994a3ac0","chunk-2d22c2e3":"46f37153","chunk-37401378":"4e39ec9b","chunk-4a59cbe4":"b5e74cc4","chunk-5f00e420":"5e409513","chunk-6a68a33e":"f59ae895","chunk-73b2dcec":"094bb2fa"}[e]+".js"}function i(t){if(r[t])return r[t].exports;var n=r[t]={i:t,l:!1,exports:{}};return e[t].call(n.exports,n,n.exports,i),n.l=!0,n.exports}i.e=function(e){var t=[],n={"chunk-238a81e9":1,"chunk-25908fca":1,"chunk-29e7142c":1,"chunk-37401378":1,"chunk-5f00e420":1,"chunk-6a68a33e":1,"chunk-73b2dcec":1};c[e]?t.push(c[e]):0!==c[e]&&n[e]&&t.push(c[e]=new Promise(function(t,n){for(var r="static/css/"+({}[e]||e)+"."+{"chunk-238a81e9":"e8e2beee","chunk-25908fca":"89ab33e8","chunk-29e7142c":"d10599db","chunk-2d22c2e3":"31d6cfe0","chunk-37401378":"a43114f3","chunk-4a59cbe4":"31d6cfe0","chunk-5f00e420":"aeebecd4","chunk-6a68a33e":"3b12267b","chunk-73b2dcec":"99cf6327"}[e]+".css",u=i.p+r,o=document.getElementsByTagName("link"),a=0;a<o.length;a++){var f=o[a],l=f.getAttribute("data-href")||f.getAttribute("href");if("stylesheet"===f.rel&&(l===r||l===u))return t()}var h=document.getElementsByTagName("style");for(a=0;a<h.length;a++){f=h[a],l=f.getAttribute("data-href");if(l===r||l===u)return t()}var s=document.createElement("link");s.rel="stylesheet",s.type="text/css",s.onload=t,s.onerror=function(t){var r=t&&t.target&&t.target.src||u,o=new Error("Loading CSS chunk "+e+" failed.\n("+r+")");o.code="CSS_CHUNK_LOAD_FAILED",o.request=r,delete c[e],s.parentNode.removeChild(s),n(o)},s.href=u;var d=document.getElementsByTagName("head")[0];d.appendChild(s)}).then(function(){c[e]=0}));var r=u[e];if(0!==r)if(r)t.push(r[2]);else{var o=new Promise(function(t,n){r=u[e]=[t,n]});t.push(r[2]=o);var f,l=document.createElement("script");l.charset="utf-8",l.timeout=120,i.nc&&l.setAttribute("nonce",i.nc),l.src=a(e),f=function(t){l.onerror=l.onload=null,clearTimeout(h);var n=u[e];if(0!==n){if(n){var r=t&&("load"===t.type?"missing":t.type),c=t&&t.target&&t.target.src,o=new Error("Loading chunk "+e+" failed.\n("+r+": "+c+")");o.type=r,o.request=c,n[1](o)}u[e]=void 0}};var h=setTimeout(function(){f({type:"timeout",target:l})},12e4);l.onerror=l.onload=f,document.head.appendChild(l)}return Promise.all(t)},i.m=e,i.c=r,i.d=function(e,t,n){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},i.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(i.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)i.d(n,r,function(t){return e[t]}.bind(null,r));return n},i.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="",i.oe=function(e){throw console.error(e),e};var f=window["webpackJsonp"]=window["webpackJsonp"]||[],l=f.push.bind(f);f.push=t,f=f.slice();for(var h=0;h<f.length;h++)t(f[h]);var s=l;n()})([]);</script><script src=static/js/chunk-elementUI.8ebdfbab.js></script><script src=static/js/chunk-libs.9cf9cc40.js></script><script src=static/js/app.3cc1fa6b.js></script></body></html> <!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><link rel=icon href=favicon.ico><title>SOP Admin</title><link href=static/css/chunk-elementUI.81cf475c.css rel=stylesheet><link href=static/css/chunk-libs.3dfb7769.css rel=stylesheet><link href=static/css/app.4f0872ef.css rel=stylesheet></head><body><noscript><strong>We're sorry but SOP Admin doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script>(function(e){function t(t){for(var r,c,o=t[0],i=t[1],f=t[2],l=0,d=[];l<o.length;l++)c=o[l],u[c]&&d.push(u[c][0]),u[c]=0;for(r in i)Object.prototype.hasOwnProperty.call(i,r)&&(e[r]=i[r]);h&&h(t);while(d.length)d.shift()();return a.push.apply(a,f||[]),n()}function n(){for(var e,t=0;t<a.length;t++){for(var n=a[t],r=!0,c=1;c<n.length;c++){var o=n[c];0!==u[o]&&(r=!1)}r&&(a.splice(t--,1),e=i(i.s=n[0]))}return e}var r={},c={runtime:0},u={runtime:0},a=[];function o(e){return i.p+"static/js/"+({}[e]||e)+"."+{"chunk-009073d4":"28312808","chunk-238a81e9":"5955f13d","chunk-25908fca":"e7e4b6d5","chunk-29e7142c":"994a3ac0","chunk-2d2085ef":"a63a74dc","chunk-2d22c2e3":"4a098244","chunk-37401378":"4e39ec9b","chunk-4a59cbe4":"a6360c68","chunk-6a68a33e":"15951b55","chunk-73b2dcec":"094bb2fa"}[e]+".js"}function i(t){if(r[t])return r[t].exports;var n=r[t]={i:t,l:!1,exports:{}};return e[t].call(n.exports,n,n.exports,i),n.l=!0,n.exports}i.e=function(e){var t=[],n={"chunk-009073d4":1,"chunk-238a81e9":1,"chunk-25908fca":1,"chunk-29e7142c":1,"chunk-37401378":1,"chunk-6a68a33e":1,"chunk-73b2dcec":1};c[e]?t.push(c[e]):0!==c[e]&&n[e]&&t.push(c[e]=new Promise(function(t,n){for(var r="static/css/"+({}[e]||e)+"."+{"chunk-009073d4":"0af16c7e","chunk-238a81e9":"e8e2beee","chunk-25908fca":"89ab33e8","chunk-29e7142c":"d10599db","chunk-2d2085ef":"31d6cfe0","chunk-2d22c2e3":"31d6cfe0","chunk-37401378":"a43114f3","chunk-4a59cbe4":"31d6cfe0","chunk-6a68a33e":"3b12267b","chunk-73b2dcec":"99cf6327"}[e]+".css",u=i.p+r,a=document.getElementsByTagName("link"),o=0;o<a.length;o++){var f=a[o],l=f.getAttribute("data-href")||f.getAttribute("href");if("stylesheet"===f.rel&&(l===r||l===u))return t()}var d=document.getElementsByTagName("style");for(o=0;o<d.length;o++){f=d[o],l=f.getAttribute("data-href");if(l===r||l===u)return t()}var h=document.createElement("link");h.rel="stylesheet",h.type="text/css",h.onload=t,h.onerror=function(t){var r=t&&t.target&&t.target.src||u,a=new Error("Loading CSS chunk "+e+" failed.\n("+r+")");a.code="CSS_CHUNK_LOAD_FAILED",a.request=r,delete c[e],h.parentNode.removeChild(h),n(a)},h.href=u;var s=document.getElementsByTagName("head")[0];s.appendChild(h)}).then(function(){c[e]=0}));var r=u[e];if(0!==r)if(r)t.push(r[2]);else{var a=new Promise(function(t,n){r=u[e]=[t,n]});t.push(r[2]=a);var f,l=document.createElement("script");l.charset="utf-8",l.timeout=120,i.nc&&l.setAttribute("nonce",i.nc),l.src=o(e),f=function(t){l.onerror=l.onload=null,clearTimeout(d);var n=u[e];if(0!==n){if(n){var r=t&&("load"===t.type?"missing":t.type),c=t&&t.target&&t.target.src,a=new Error("Loading chunk "+e+" failed.\n("+r+": "+c+")");a.type=r,a.request=c,n[1](a)}u[e]=void 0}};var d=setTimeout(function(){f({type:"timeout",target:l})},12e4);l.onerror=l.onload=f,document.head.appendChild(l)}return Promise.all(t)},i.m=e,i.c=r,i.d=function(e,t,n){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},i.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(i.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)i.d(n,r,function(t){return e[t]}.bind(null,r));return n},i.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="",i.oe=function(e){throw console.error(e),e};var f=window["webpackJsonp"]=window["webpackJsonp"]||[],l=f.push.bind(f);f.push=t,f=f.slice();for(var d=0;d<f.length;d++)t(f[d]);var h=l;n()})([]);</script><script src=static/js/chunk-elementUI.8ebdfbab.js></script><script src=static/js/chunk-libs.9cf9cc40.js></script><script src=static/js/app.095e8b9c.js></script></body></html>

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

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

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

@ -91,8 +91,8 @@ export const constantRoutes = [
{ {
path: '/isv', path: '/isv',
component: Layout, component: Layout,
name: 'Isv',
meta: { title: 'ISV管理', icon: 'user' }, meta: { title: 'ISV管理', icon: 'user' },
redirect: '/isv/list',
children: [ children: [
{ {
path: 'list', path: 'list',
@ -100,6 +100,12 @@ export const constantRoutes = [
component: () => import('@/views/isv/index'), component: () => import('@/views/isv/index'),
meta: { title: 'ISV列表' } meta: { title: 'ISV列表' }
}, },
{
path: 'role',
name: 'Role',
component: () => import('@/views/isv/role'),
meta: { title: '角色管理' }
},
{ {
path: 'keys', path: 'keys',
name: 'Keys', name: 'Keys',

@ -288,7 +288,7 @@ export default {
}, },
onIsvDialogSave: function() { onIsvDialogSave: function() {
const that = this const that = this
this.$refs['isvForm'].validate((valid) => { this.$refs.isvForm.validate((valid) => {
if (valid) { if (valid) {
const uri = this.isvDialogFormData.id === 0 ? 'isv.info.add' : 'isv.info.update' const uri = this.isvDialogFormData.id === 0 ? 'isv.info.add' : 'isv.info.update'
that.post(uri, that.isvDialogFormData, function() { that.post(uri, that.isvDialogFormData, function() {

@ -0,0 +1,179 @@
<template>
<div class="app-container">
<el-form :inline="true" :model="searchFormData" class="demo-form-inline" size="mini">
<el-form-item label="角色码">
<el-input v-model="searchFormData.roleCode" :clearable="true" placeholder="输入角色码" style="width: 250px;" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="loadTable">查询</el-button>
</el-form-item>
</el-form>
<el-button type="primary" size="mini" icon="el-icon-plus" style="margin-bottom: 10px;" @click="onAdd">新增角色</el-button>
<el-table
:data="pageInfo.rows"
border
highlight-current-row
>
<el-table-column
prop="roleCode"
label="角色码"
width="200"
/>
<el-table-column
prop="description"
label="角色描述"
width="200"
/>
<el-table-column
prop="gmtCreate"
label="添加时间"
width="160"
/>
<el-table-column
prop="gmtModified"
label="修改时间"
width="160"
/>
<el-table-column
label="操作"
width="150"
>
<template slot-scope="scope">
<el-button type="text" size="mini" @click="onTableUpdate(scope.row)">修改</el-button>
<el-button type="text" size="mini" @click="onTableDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
background
style="margin-top: 5px"
:current-page="searchFormData.pageIndex"
:page-size="searchFormData.pageSize"
:page-sizes="[5, 10, 20, 40]"
:total="pageInfo.total"
layout="total, sizes, prev, pager, next"
@size-change="onSizeChange"
@current-change="onPageIndexChange"
/>
<!--dialog-->
<el-dialog
:title="roleDialogTitle"
:visible.sync="roleDialogVisible"
:close-on-click-modal="false"
@close="resetForm('roleForm')"
>
<el-form
ref="roleForm"
:rules="roleDialogFormRules"
:model="roleDialogFormData"
label-width="120px"
size="mini"
>
<el-form-item prop="roleCode" label="角色码">
<el-input v-show="roleDialogFormData.id === 0" v-model="roleDialogFormData.roleCode" />
<span v-show="roleDialogFormData.id > 0">{{ roleDialogFormData.roleCode }}</span>
</el-form-item>
<el-form-item prop="description" label="角色描述">
<el-input v-model="roleDialogFormData.description" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="roleDialogVisible = false"> </el-button>
<el-button type="primary" @click="onRoleDialogSave"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
export default {
data() {
return {
searchFormData: {
roleCode: '',
pageIndex: 1,
pageSize: 10
},
pageInfo: {
rows: [],
total: 0
},
roleDialogVisible: false,
roleDialogTitle: '',
roleDialogFormData: {
id: 0,
roleCode: '',
description: ''
},
roleDialogFormRules: {
roleCode: [
{ required: true, message: '不能为空', trigger: 'blur' },
{ min: 1, max: 64, message: '长度在 1 到 100 个字符', trigger: 'blur' }
],
description: [
{ max: 64, message: '不能超过 64 个字符', trigger: 'blur' }
]
},
rulesIsvForm: {
appKey: [
{ required: true, message: '不能为空', trigger: 'blur' },
{ min: 1, max: 100, message: '长度在 1 到 100 个字符', trigger: 'blur' }
]
}
}
},
created() {
this.loadTable()
},
methods: {
loadTable: function() {
this.post('role.page', this.searchFormData, function(resp) {
this.pageInfo = resp.data
})
},
onTableUpdate: function(row) {
this.roleDialogTitle = '修改角色'
this.roleDialogVisible = true
this.$nextTick(() => {
Object.assign(this.roleDialogFormData, row)
})
},
onTableDelete: function(row) {
this.confirm(`确认要删除角色【${row.roleCode}】吗?`, function(done) {
const data = {
id: row.id
}
this.post('role.del', data, function() {
done()
this.tip('删除成功')
this.loadTable()
})
})
},
onRoleDialogSave: function() {
this.$refs.roleForm.validate((valid) => {
if (valid) {
const uri = this.roleDialogFormData.id ? 'role.update' : 'role.add'
this.post(uri, this.roleDialogFormData, function() {
this.roleDialogVisible = false
this.loadTable()
})
}
})
},
onSizeChange: function(size) {
this.searchFormData.pageSize = size
this.loadTable()
},
onAdd: function() {
this.roleDialogTitle = '新增角色'
this.roleDialogVisible = true
this.roleDialogFormData.id = 0
},
onPageIndexChange: function(pageIndex) {
this.searchFormData.pageIndex = pageIndex
this.loadTable()
}
}
}
</script>

@ -211,7 +211,7 @@ export default {
methods: { methods: {
// //
loadTree: function() { loadTree: function() {
this.post('service.list', {}, function(resp) { this.post('zookeeper.service.list', {}, function(resp) {
const respData = resp.data const respData = resp.data
this.treeData = this.convertToTreeData(respData, 0) this.treeData = this.convertToTreeData(respData, 0)
}) })

@ -337,7 +337,7 @@ export default {
methods: { methods: {
// //
loadTree: function() { loadTree: function() {
this.post('service.list', {}, function(resp) { this.post('zookeeper.service.list', {}, function(resp) {
const respData = resp.data const respData = resp.data
this.treeData = this.convertToTreeData(respData, 0) this.treeData = this.convertToTreeData(respData, 0)
}) })

@ -15,25 +15,19 @@
row-key="id" row-key="id"
> >
<el-table-column <el-table-column
prop="name" prop="serviceId"
label="服务名称(serviceId)" label="服务名称"
width="200" width="200"
/> >
<template slot-scope="scope">
<span v-html="renderServiceName(scope.row)"></span>
</template>
</el-table-column>
<el-table-column <el-table-column
prop="instanceId" prop="ipPort"
label="instanceId" label="IP端口"
width="250" width="250"
/> />
<el-table-column
prop="ipAddr"
label="IP地址"
width="150"
/>
<el-table-column
prop="serverPort"
label="端口号"
width="100"
/>
<el-table-column <el-table-column
prop="status" prop="status"
label="服务状态" label="服务状态"
@ -110,28 +104,30 @@ export default {
this.loadTable() this.loadTable()
}, },
onOffline: function(row) { onOffline: function(row) {
this.confirm('确定要下线【' + row.name + '】吗?', function(done) { this.confirm('确定要下线【' + row.serviceId + '】吗?', function(done) {
const params = { this.post('service.instance.offline', row, function() {
serviceId: row.name,
instanceId: row.instanceId
}
this.post('service.instance.offline', params, function() {
this.tip('下线成功') this.tip('下线成功')
done() done()
}) })
}) })
}, },
onOnline: function(row) { onOnline: function(row) {
this.confirm('确定要上线【' + row.name + '】吗?', function(done) { this.confirm('确定要上线【' + row.serviceId + '】吗?', function(done) {
const params = { this.post('service.instance.online', row, function() {
serviceId: row.name,
instanceId: row.instanceId
}
this.post('service.instance.online', params, function() {
this.tip('上线成功') this.tip('上线成功')
done() done()
}) })
}) })
},
renderServiceName: function(row) {
let instanceCount = ''
if (row.children && row.children.length > 0) {
const onlineCount = row.children.filter(el => {
return el.status === 'UP'
}).length
instanceCount = ` (${onlineCount}/${row.children.length})`
}
return row.serviceId + instanceCount
} }
} }
} }

@ -85,12 +85,12 @@
<el-option <el-option
v-for="item in serviceData" v-for="item in serviceData"
:key="item.id" :key="item.id"
:label="item.name + '(' + item.ipAddr + ':' + item.serverPort + ')'" :label="item.serviceId + '(' + item.ipPort + ')'"
:value="item" :value="item"
:disabled="isOptionDisabled(item)" :disabled="isOptionDisabled(item)"
> >
<span style="float: left">{{ item.name }} <span v-if="isOptionDisabled(item)">(已添加)</span></span> <span style="float: left">{{ item.serviceId }} <span v-if="isOptionDisabled(item)">(已添加)</span></span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.ipAddr + ':' + item.serverPort }}</span> <span style="float: right; color: #8492a6; font-size: 13px">{{ item.ipPort }}</span>
</el-option> </el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
@ -156,7 +156,7 @@ export default {
}) })
}, },
isOptionDisabled: function(item) { isOptionDisabled: function(item) {
const ipPort = item.ipAddr + ':' + item.serverPort const ipPort = item.ipPort
const index = this.addedInstanceList.findIndex((value, index, arr) => { const index = this.addedInstanceList.findIndex((value, index, arr) => {
return value === ipPort return value === ipPort
}) })
@ -218,12 +218,7 @@ export default {
this.$refs['logDialogForm'].validate((valid) => { this.$refs['logDialogForm'].validate((valid) => {
if (valid) { if (valid) {
const instanceData = this.logDialogFormData.instanceData const instanceData = this.logDialogFormData.instanceData
const data = { this.post('monitor.instance.add', instanceData, function(resp) {
serviceId: instanceData.serviceId,
ip: instanceData.ipAddr,
port: instanceData.serverPort
}
this.post('monitor.instance.add', data, function(resp) {
this.logDialogInstanceVisible = false this.logDialogInstanceVisible = false
this.loadTable() this.loadTable()
}) })

@ -343,7 +343,7 @@ export default {
methods: { methods: {
// //
loadTree: function() { loadTree: function() {
this.post('service.list', {}, function(resp) { this.post('zookeeper.service.list', {}, function(resp) {
const respData = resp.data const respData = resp.data
this.treeData = this.convertToTreeData(respData, 0) this.treeData = this.convertToTreeData(respData, 0)
this.$nextTick(() => { this.$nextTick(() => {

@ -5,7 +5,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sop-common</artifactId> <artifactId>sop-common</artifactId>
<version>1.11.0-SNAPSHOT</version> <version>1.12.0-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<properties> <properties>
@ -37,6 +37,7 @@
<modules> <modules>
<module>sop-gateway-common</module> <module>sop-gateway-common</module>
<module>sop-service-common</module> <module>sop-service-common</module>
<module>sop-registry-api</module>
</modules> </modules>
<dependencyManagement> <dependencyManagement>

@ -1,8 +1,11 @@
# sop-common # sop-common
- sop-gateway-common:提供给网关使用 - sop-gateway-common:提供给网关使用
- sop-registry-api:注册中心API
- sop-service-common:提供给微服务端使用,需要打成jar - sop-service-common:提供给微服务端使用,需要打成jar
正式开发请将这些模块上传的maven私服
- 打包成jar:`mvn clean package` - 打包成jar:`mvn clean package`
- 上传到本机仓库:`mvn clean install` - 上传到本机仓库:`mvn clean install`
- 上传到maven私服:`mvn clean deploy` - 上传到maven私服:`mvn clean deploy`

@ -5,11 +5,11 @@
<parent> <parent>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sop-common</artifactId> <artifactId>sop-common</artifactId>
<version>1.11.0-SNAPSHOT</version> <version>1.12.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<artifactId>sop-gateway-common</artifactId> <artifactId>sop-gateway-common</artifactId>
<version>1.11.0-SNAPSHOT</version> <version>1.12.0-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>sop-gateway-common</name> <name>sop-gateway-common</name>

@ -1,3 +1,3 @@
# sop-gateway-common # sop-gateway-common
zuul通用组件,主要封装网关的一些功能。 网关通用组件,主要封装网关的一些功能。

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-registry-api</artifactId>
<version>1.12.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<okhttp.version>3.11.0</okhttp.version>
</properties>
<dependencies>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
</dependency>
<!-- nacos sdk -->
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.0.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.6.RELEASE</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.1.4.RELEASE</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,3 @@
# sop-registry-api
统一各注册中心接口

@ -1,4 +1,4 @@
package com.gitee.sop.adminserver.bean; package com.gitee.sop.registryapi.bean;
import lombok.Data; import lombok.Data;

@ -1,4 +1,4 @@
package com.gitee.sop.adminserver.bean; package com.gitee.sop.registryapi.bean;
import lombok.Data; import lombok.Data;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;

@ -1,11 +1,9 @@
package com.gitee.sop.adminserver.bean; package com.gitee.sop.registryapi.bean;
import okhttp3.MediaType; import okhttp3.MediaType;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.RequestBody; import okhttp3.RequestBody;
import okhttp3.internal.http.HttpMethod; import okhttp3.internal.http.HttpMethod;
import org.apache.commons.lang.ArrayUtils;
import org.springframework.web.bind.annotation.RequestMethod;
/** /**
* https://github.com/Netflix/eureka/wiki/Eureka-REST-operations * https://github.com/Netflix/eureka/wiki/Eureka-REST-operations
@ -17,22 +15,22 @@ public enum EurekaUri {
/** /**
* 查询所有实例 Query for all instances * 查询所有实例 Query for all instances
*/ */
QUERY_APPS(RequestMethod.GET, "/apps"), QUERY_APPS("GET", "/apps"),
/** /**
* 下线 Take instance out of service * 下线 Take instance out of service
*/ */
OFFLINE_SERVICE(RequestMethod.PUT, "/apps/%s/%s/status?value=OUT_OF_SERVICE"), OFFLINE_SERVICE("PUT", "/apps/%s/%s/status?value=OUT_OF_SERVICE"),
/** /**
* 上线 Move instance back into service (remove override) * 上线 Move instance back into service (remove override)
*/ */
ONLINE_SERVICE(RequestMethod.DELETE, "/apps/%s/%s/status?value=UP"), ONLINE_SERVICE("DELETE", "/apps/%s/%s/status?value=UP"),
; ;
public static final String URL_PREFIX = "/"; public static final String URL_PREFIX = "/";
String uri; String uri;
RequestMethod requestMethod; String requestMethod;
EurekaUri(RequestMethod httpMethod, String uri) { EurekaUri(String httpMethod, String uri) {
if (!uri.startsWith(URL_PREFIX)) { if (!uri.startsWith(URL_PREFIX)) {
uri = "/" + uri; uri = "/" + uri;
} }
@ -41,14 +39,20 @@ public enum EurekaUri {
} }
public String getUri(String... args) { public String getUri(String... args) {
if (ArrayUtils.isEmpty(args)) { if (args == null || args.length == 0) {
return uri; return uri;
} }
Object[] param = ArrayUtils.clone(args); Object[] param = new Object[args.length];
for (int i = 0; i < args.length; i++) {
param[i] = args[i];
}
return String.format(uri, param); return String.format(uri, param);
} }
public Request getRequest(String url, String... args) { public Request getRequest(String url, String... args) {
if (url.endsWith("/")) {
url = url.substring(0, url.length() - 1);
}
String requestUrl = url + getUri(args); String requestUrl = url + getUri(args);
Request request = this.getBuilder() Request request = this.getBuilder()
.url(requestUrl) .url(requestUrl)
@ -59,12 +63,12 @@ public enum EurekaUri {
} }
public Request.Builder getBuilder() { public Request.Builder getBuilder() {
String method = requestMethod.name(); String method = requestMethod;
RequestBody requestBody = null; RequestBody requestBody = null;
if (HttpMethod.requiresRequestBody(method)) { if (HttpMethod.requiresRequestBody(method)) {
MediaType contentType = MediaType.parse(org.springframework.http.MediaType.APPLICATION_JSON_VALUE); MediaType contentType = MediaType.parse("application/json");
requestBody = RequestBody.create(contentType, "{}"); requestBody = RequestBody.create(contentType, "{}");
} }
return new Request.Builder().method(requestMethod.name(), requestBody); return new Request.Builder().method(requestMethod, requestBody);
} }
} }

@ -0,0 +1,24 @@
package com.gitee.sop.registryapi.bean;
import lombok.Getter;
import lombok.Setter;
import java.util.Collections;
import java.util.List;
/**
* @author tanghc
*/
@Getter
@Setter
public class ServiceInfo {
/** 服务名称 */
private String serviceId;
/** 实例列表 */
private List<ServiceInstance> instances = Collections.emptyList();
@Override
public String toString() {
return "服务名称: " + serviceId + ", 实例数:" + instances.size();
}
}

@ -0,0 +1,32 @@
package com.gitee.sop.registryapi.bean;
import lombok.Data;
@Data
public class ServiceInstance {
/**
* 实例id
*/
private String instanceId;
/**
* 服务名称
*/
private String serviceId;
/**
* ip
*/
private String ip;
/**
* port
*/
private int port;
/**
* 服务状态UP已上线OUT_OF_SERVICE已下线
*/
private String status;
}

@ -0,0 +1,36 @@
package com.gitee.sop.registryapi.config;
import com.gitee.sop.registryapi.service.RegistryService;
import com.gitee.sop.registryapi.service.impl.RegistryServiceEureka;
import com.gitee.sop.registryapi.service.impl.RegistryServiceNacos;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
/**
* @author tanghc
*/
public class BaseRegistryConfig {
/**
* 当配置了registry.name=eureka生效
*
* @return
*/
@Bean
@ConditionalOnProperty(prefix = "registry", name = "name", havingValue = "eureka")
RegistryService registryServiceEureka() {
return new RegistryServiceEureka();
}
/**
* 当配置了registry.name=nacos生效
*
* @return
*/
@Bean
@ConditionalOnProperty(prefix = "registry", name = "name", havingValue = "nacos")
RegistryService registryServiceNacos() {
return new RegistryServiceNacos();
}
}

@ -0,0 +1,36 @@
package com.gitee.sop.registryapi.service;
import com.gitee.sop.registryapi.bean.ServiceInfo;
import com.gitee.sop.registryapi.bean.ServiceInstance;
import java.util.List;
/**
* @author tanghc
*/
public interface RegistryService {
/**
* 获取所有服务列表
*
* @param pageNo 当前页码
* @param pageSize 分页大小
* @return 返回服务列表
*/
List<ServiceInfo> listAllService(int pageNo, int pageSize) throws Exception;
/**
* 服务上线
*
* @param serviceInstance
*/
void onlineInstance(ServiceInstance serviceInstance) throws Exception;
/**
* 服务下线
*
* @param serviceInstance
*/
void offlineInstance(ServiceInstance serviceInstance) throws Exception;
}

@ -0,0 +1,90 @@
package com.gitee.sop.registryapi.service.impl;
import com.alibaba.fastjson.JSON;
import com.gitee.sop.registryapi.bean.EurekaApplication;
import com.gitee.sop.registryapi.bean.EurekaApps;
import com.gitee.sop.registryapi.bean.EurekaInstance;
import com.gitee.sop.registryapi.bean.EurekaUri;
import com.gitee.sop.registryapi.bean.ServiceInfo;
import com.gitee.sop.registryapi.bean.ServiceInstance;
import com.gitee.sop.registryapi.service.RegistryService;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.CollectionUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* eureka接口实现
* @author tanghc
*/
@Slf4j
public class RegistryServiceEureka implements RegistryService {
OkHttpClient client = new OkHttpClient();
@Value("${registry.eureka-server-addr:}")
private String eurekaUrl;
@Override
public List<ServiceInfo> listAllService(int pageNo, int pageSize) throws Exception {
if (StringUtils.isBlank(eurekaUrl)) {
return Collections.emptyList();
}
String json = this.requestEurekaServer(EurekaUri.QUERY_APPS);
EurekaApps eurekaApps = JSON.parseObject(json, EurekaApps.class);
List<ServiceInfo> serviceInfoList = new ArrayList<>();
List<EurekaApplication> applicationList = eurekaApps.getApplications().getApplication();
for (EurekaApplication eurekaApplication : applicationList) {
ServiceInfo serviceInfo = new ServiceInfo();
serviceInfo.setServiceId(eurekaApplication.getName());
List<EurekaInstance> instanceList = eurekaApplication.getInstance();
if (!CollectionUtils.isEmpty(instanceList)) {
serviceInfo.setInstances(new ArrayList<>(instanceList.size()));
for (EurekaInstance eurekaInstance : instanceList) {
ServiceInstance serviceInstance = new ServiceInstance();
serviceInstance.setInstanceId(eurekaInstance.getInstanceId());
serviceInstance.setServiceId(serviceInfo.getServiceId());
serviceInstance.setIp(eurekaInstance.getIpAddr());
serviceInstance.setPort(Integer.valueOf(eurekaInstance.fetchPort()));
serviceInstance.setStatus(eurekaInstance.getStatus());
serviceInfo.getInstances().add(serviceInstance);
}
}
serviceInfoList.add(serviceInfo);
}
return serviceInfoList;
}
@Override
public void onlineInstance(ServiceInstance serviceInstance) throws Exception {
this.requestEurekaServer(EurekaUri.ONLINE_SERVICE, serviceInstance.getServiceId(), serviceInstance.getInstanceId());
}
@Override
public void offlineInstance(ServiceInstance serviceInstance) throws Exception {
this.requestEurekaServer(EurekaUri.OFFLINE_SERVICE, serviceInstance.getServiceId(), serviceInstance.getInstanceId());
}
private String requestEurekaServer(EurekaUri eurekaUri, String... args) throws IOException {
Request request = eurekaUri.getRequest(this.eurekaUrl, args);
Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
return response.body().string();
} else {
log.error("操作失败,url:{}, msg:{}, code:{}", eurekaUri.getUri(args), response.message(), response.code());
throw new RuntimeException("操作失败");
}
}
}

@ -0,0 +1,106 @@
package com.gitee.sop.registryapi.service.impl;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingFactory;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.naming.pojo.ListView;
import com.gitee.sop.registryapi.bean.ServiceInfo;
import com.gitee.sop.registryapi.bean.ServiceInstance;
import com.gitee.sop.registryapi.service.RegistryService;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.CollectionUtils;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* nacos接口实现
* @author tanghc
*/
public class RegistryServiceNacos implements RegistryService {
OkHttpClient client = new OkHttpClient();
@Value("${registry.nacos-server-addr:}")
private String nacosAddr;
private NamingService namingService;
@PostConstruct
public void after() throws NacosException {
if (StringUtils.isNotBlank(nacosAddr)) {
namingService = NamingFactory.createNamingService(nacosAddr);
}
}
@Override
public List<ServiceInfo> listAllService(int pageNo, int pageSize) throws Exception {
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);
if (!CollectionUtils.isEmpty(instanceList)) {
serviceInfo.setInstances(new ArrayList<>(instanceList.size()));
for (Instance instance : instanceList) {
ServiceInstance serviceInstance = new ServiceInstance();
serviceInstance.setInstanceId(instance.getInstanceId());
serviceInstance.setServiceId(serviceInfo.getServiceId());
serviceInstance.setIp(instance.getIp());
serviceInstance.setPort(instance.getPort());
boolean isOnline = instance.getWeight() > 0;
String status = isOnline ? "UP" : "OUT_OF_SERVICE";
serviceInstance.setStatus(status);
serviceInfo.getInstances().add(serviceInstance);
}
}
serviceInfoList.add(serviceInfo);
}
return serviceInfoList;
}
@Override
public void onlineInstance(ServiceInstance serviceInstance) throws Exception {
Map<String, String> params = new HashMap<>();
// 上线,把权重设置成1
params.put("weight", "1");
this.updateInstance(serviceInstance, params);
}
@Override
public void offlineInstance(ServiceInstance serviceInstance) throws Exception {
Map<String, String> params = new HashMap<>();
// 下线,把权重设置成0
params.put("weight", "0");
this.updateInstance(serviceInstance, params);
}
private Response updateInstance(ServiceInstance serviceInstance, Map<String, String> params) throws IOException {
FormBody.Builder builder = new FormBody.Builder();
for (Map.Entry<String, String> entry : params.entrySet()) {
builder.add(entry.getKey(), String.valueOf(entry.getValue()));
}
builder.add("serviceName", serviceInstance.getServiceId())
.add("ip", serviceInstance.getIp())
.add("port", String.valueOf(serviceInstance.getPort()));
FormBody formBody = builder.build();
final Request request = new Request.Builder()
.url("http://" + nacosAddr + "/nacos/v1/ns/instance")
.put(formBody)
.build();
return client.newCall(request).execute();
}
}

@ -6,11 +6,11 @@
<parent> <parent>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sop-common</artifactId> <artifactId>sop-common</artifactId>
<version>1.11.0-SNAPSHOT</version> <version>1.12.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<artifactId>sop-service-common</artifactId> <artifactId>sop-service-common</artifactId>
<version>1.11.0-SNAPSHOT</version> <version>1.12.0-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>sop-service-common</name> <name>sop-service-common</name>

@ -26,7 +26,7 @@
<dependency> <dependency>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sop-service-common</artifactId> <artifactId>sop-service-common</artifactId>
<version>1.11.0-SNAPSHOT</version> <version>1.12.0-SNAPSHOT</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>

@ -24,7 +24,7 @@
<dependency> <dependency>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sop-service-common</artifactId> <artifactId>sop-service-common</artifactId>
<version>1.11.0-SNAPSHOT</version> <version>1.12.0-SNAPSHOT</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
@ -36,10 +36,29 @@
<artifactId>sop-story-api</artifactId> <artifactId>sop-story-api</artifactId>
<version>1.0-SNAPSHOT</version> <version>1.0-SNAPSHOT</version>
</dependency> </dependency>
<!-- 注册中心【只能用一个,不用的注释掉】 -->
<!-- 使用eureka注册中心 -->
<dependency> <dependency>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency> </dependency>
<!-- 使用nacos注册中心
版本 0.2.x.RELEASE 对应的是 Spring Boot 2.x 版本,版本 0.1.x.RELEASE 对应的是 Spring Boot 1.x 版本。
https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-alibaba-nacos-discovery
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>0.2.2.RELEASE</version>
</dependency>
-->
<!-- 注册中心end -->
<dependency> <dependency>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId> <artifactId>spring-cloud-starter-openfeign</artifactId>

@ -1,6 +1,5 @@
package com.gitee.sop.bookweb.controller; package com.gitee.sop.bookweb.controller;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import lombok.Data; import lombok.Data;
/** /**
@ -48,7 +47,6 @@ import lombok.Data;
* *
* @author tanghc * @author tanghc
*/ */
@XStreamAlias("response")
@Data @Data
public class ApiResult { public class ApiResult {

@ -10,12 +10,16 @@ spring:
# zookeeper配置 # zookeeper配置
connect-string: localhost:2181 connect-string: localhost:2181
# nacos注册中心,和eureka只能用一个
# nacos:
# discovery:
# server-addr: 127.0.0.1:8848
# eureka注册中心,如果使用nacos注册中心,这里要注释掉
eureka: eureka:
port: 1111
host: localhost
client: client:
serviceUrl: serviceUrl:
defaultZone: http://${eureka.host}:${eureka.port}/eureka/ defaultZone: http://localhost:1111/eureka/
# dubbo consumer # dubbo consumer
demo: demo:

@ -29,7 +29,7 @@
<dependency> <dependency>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sop-service-common</artifactId> <artifactId>sop-service-common</artifactId>
<version>1.11.0-SNAPSHOT</version> <version>1.12.0-SNAPSHOT</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>

@ -20,7 +20,7 @@
<dependency> <dependency>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sop-service-common</artifactId> <artifactId>sop-service-common</artifactId>
<version>1.11.0-SNAPSHOT</version> <version>1.12.0-SNAPSHOT</version>
</dependency> </dependency>
<!-- eureka 服务发现 --> <!-- eureka 服务发现 -->
<dependency> <dependency>

@ -24,17 +24,34 @@
<dependency> <dependency>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sop-service-common</artifactId> <artifactId>sop-service-common</artifactId>
<version>1.11.0-SNAPSHOT</version> <version>1.12.0-SNAPSHOT</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sop-story-api</artifactId> <artifactId>sop-story-api</artifactId>
<version>1.0-SNAPSHOT</version> <version>1.0-SNAPSHOT</version>
</dependency> </dependency>
<!-- 注册中心【只能用一个,不用的注释掉】 -->
<!-- 使用eureka注册中心 -->
<dependency> <dependency>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency> </dependency>
<!-- 使用nacos注册中心
版本 0.2.x.RELEASE 对应的是 Spring Boot 2.x 版本,版本 0.1.x.RELEASE 对应的是 Spring Boot 1.x 版本。
https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-alibaba-nacos-discovery
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>0.2.2.RELEASE</version>
</dependency>
-->
<!-- 注册中心end -->
<dependency> <dependency>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId> <artifactId>spring-cloud-starter-openfeign</artifactId>

@ -87,7 +87,7 @@ public class AlipayController {
public Story getStory() { public Story getStory() {
Story story = new Story(); Story story = new Story();
story.setId(1); story.setId(1);
story.setName("海底小纵队(alipay.story.get)"); story.setName("海底小纵队(alipay.story.get),2222");
return story; return story;
} }

@ -10,13 +10,16 @@ spring:
zookeeper: zookeeper:
connect-string: localhost:2181 connect-string: localhost:2181
# 注册中心 # nacos注册中心,和eureka只能用一个
# nacos:
# discovery:
# server-addr: 127.0.0.1:8848
# eureka注册中心,如果使用nacos注册中心,这里要注释掉
eureka: eureka:
port: 1111
host: localhost
client: client:
serviceUrl: serviceUrl:
defaultZone: http://${eureka.host}:${eureka.port}/eureka/ defaultZone: http://localhost:1111/eureka/
# dubbo provider # dubbo provider
dubbo: dubbo:

@ -23,7 +23,7 @@
<dependency> <dependency>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sop-gateway-common</artifactId> <artifactId>sop-gateway-common</artifactId>
<version>1.11.0-SNAPSHOT</version> <version>1.12.0-SNAPSHOT</version>
</dependency> </dependency>
<!-- ↓↓↓ 使用spring cloud zuul ↓↓↓ --> <!-- ↓↓↓ 使用spring cloud zuul ↓↓↓ -->
@ -65,11 +65,26 @@
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<!-- 注册中心【只能用一个,不用的注释掉】 -->
<!-- 使用eureka注册中心 -->
<dependency> <dependency>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency> </dependency>
<!-- 使用nacos注册中心
版本 0.2.x.RELEASE 对应的是 Spring Boot 2.x 版本,版本 0.1.x.RELEASE 对应的是 Spring Boot 1.x 版本。
https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-alibaba-nacos-discovery
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>0.2.2.RELEASE</version>
</dependency>
-->
<!-- 注册中心end -->
<!-- test --> <!-- test -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>

@ -19,12 +19,11 @@ ribbon:
# 请谨慎设置,因为post请求大多都是写入请求,如果要支持重试,确保服务的幂等性 # 请谨慎设置,因为post请求大多都是写入请求,如果要支持重试,确保服务的幂等性
OkToRetryOnAllOperations: false OkToRetryOnAllOperations: false
# 注册中心,根据实际情况修改 # eureka注册中心,如果使用nacos注册中心,这里要注释掉
eureka: eureka:
serverAddr: localhost:1111
client: client:
serviceUrl: serviceUrl:
defaultZone: http://${eureka.serverAddr}/eureka/ defaultZone: http://localhost:1111/eureka/
spring: spring:
application: application:
@ -41,6 +40,10 @@ spring:
lower-case-service-id: true lower-case-service-id: true
enabled: true enabled: true
# nacos注册中心,和eureka只能用一个
# nacos:
# discovery:
# server-addr: 127.0.0.1:8848
# 数据源,根据实际情况修改 # 数据源,根据实际情况修改
datasource: datasource:

@ -0,0 +1,64 @@
package com.gitee.sop;
import com.alibaba.fastjson.JSON;
import com.gitee.sop.alipay.AlipayApiException;
import com.gitee.sop.alipay.AlipaySignature;
import org.junit.Test;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class LoadBalanceTest extends TestBase {
String url = "http://localhost:8081/api"; // zuul
String appId = "2019032617262200001";
// 支付宝私钥
String privateKey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXJv1pQFqWNA/++OYEV7WYXwexZK/J8LY1OWlP9X0T6wHFOvxNKRvMkJ5544SbgsJpVcvRDPrcxmhPbi/sAhdO4x2PiPKIz9Yni2OtYCCeaiE056B+e1O2jXoLeXbfi9fPivJZkxH/tb4xfLkH3bA8ZAQnQsoXA0SguykMRZntF0TndUfvDrLqwhlR8r5iRdZLB6F8o8qXH6UPDfNEnf/K8wX5T4EB1b8x8QJ7Ua4GcIUqeUxGHdQpzNbJdaQvoi06lgccmL+PHzminkFYON7alj1CjDN833j7QMHdPtS9l7B67fOU/p2LAAkPMtoVBfxQt9aFj7B8rEhGCz02iJIBAgMBAAECggEARqOuIpY0v6WtJBfmR3lGIOOokLrhfJrGTLF8CiZMQha+SRJ7/wOLPlsH9SbjPlopyViTXCuYwbzn2tdABigkBHYXxpDV6CJZjzmRZ+FY3S/0POlTFElGojYUJ3CooWiVfyUMhdg5vSuOq0oCny53woFrf32zPHYGiKdvU5Djku1onbDU0Lw8w+5tguuEZ76kZ/lUcccGy5978FFmYpzY/65RHCpvLiLqYyWTtaNT1aQ/9pw4jX9HO9NfdJ9gYFK8r/2f36ZE4hxluAfeOXQfRC/WhPmiw/ReUhxPznG/WgKaa/OaRtAx3inbQ+JuCND7uuKeRe4osP2jLPHPP6AUwQKBgQDUNu3BkLoKaimjGOjCTAwtp71g1oo+k5/uEInAo7lyEwpV0EuUMwLA/HCqUgR4K9pyYV+Oyb8d6f0+Hz0BMD92I2pqlXrD7xV2WzDvyXM3s63NvorRooKcyfd9i6ccMjAyTR2qfLkxv0hlbBbsPHz4BbU63xhTJp3Ghi0/ey/1HQKBgQC2VsgqC6ykfSidZUNLmQZe3J0p/Qf9VLkfrQ+xaHapOs6AzDU2H2osuysqXTLJHsGfrwVaTs00ER2z8ljTJPBUtNtOLrwNRlvgdnzyVAKHfOgDBGwJgiwpeE9voB1oAV/mXqSaUWNnuwlOIhvQEBwekqNyWvhLqC7nCAIhj3yvNQKBgQCqYbeec56LAhWP903Zwcj9VvG7sESqXUhIkUqoOkuIBTWFFIm54QLTA1tJxDQGb98heoCIWf5x/A3xNI98RsqNBX5JON6qNWjb7/dobitti3t99v/ptDp9u8JTMC7penoryLKK0Ty3bkan95Kn9SC42YxaSghzqkt+uvfVQgiNGQKBgGxU6P2aDAt6VNwWosHSe+d2WWXt8IZBhO9d6dn0f7ORvcjmCqNKTNGgrkewMZEuVcliueJquR47IROdY8qmwqcBAN7Vg2K7r7CPlTKAWTRYMJxCT1Hi5gwJb+CZF3+IeYqsJk2NF2s0w5WJTE70k1BSvQsfIzAIDz2yE1oPHvwVAoGAA6e+xQkVH4fMEph55RJIZ5goI4Y76BSvt2N5OKZKd4HtaV+eIhM3SDsVYRLIm9ZquJHMiZQGyUGnsvrKL6AAVNK7eQZCRDk9KQz+0GKOGqku0nOZjUbAu6A2/vtXAaAuFSFx1rUQVVjFulLexkXR3KcztL1Qu2k5pB6Si0K/uwQ=";
// 测试稳定性
@Test
public void testLoadBalance() throws Exception {
int i = 0;
while (i++ < 50) {
doTest();
Thread.sleep(3000);
}
}
private void doTest() throws AlipayApiException {
// 公共请求参数
Map<String, String> params = new HashMap<String, String>();
params.put("app_id", appId);
params.put("method", "alipay.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("----------- 返回结果 -----------");
String responseData = get(url, params);// 发送请求
System.out.println(responseData);
}
}

@ -21,6 +21,13 @@
</properties> </properties>
<dependencies> <dependencies>
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-registry-api</artifactId>
<version>1.12.0-SNAPSHOT</version>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>

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

@ -3,6 +3,7 @@ package com.gitee.sop.websiteserver.controller;
import com.gitee.sop.websiteserver.sign.AlipayApiException; import com.gitee.sop.websiteserver.sign.AlipayApiException;
import com.gitee.sop.websiteserver.sign.AlipaySignature; import com.gitee.sop.websiteserver.sign.AlipaySignature;
import lombok.Data; import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.http.NameValuePair; import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.entity.UrlEncodedFormEntity;
@ -30,6 +31,7 @@ import java.util.stream.Collectors;
* 沙箱环境代理类 * 沙箱环境代理类
* @author tanghc * @author tanghc
*/ */
@Slf4j
@RestController @RestController
@RequestMapping("sandbox") @RequestMapping("sandbox")
public class SandboxController { public class SandboxController {
@ -45,8 +47,8 @@ public class SandboxController {
, @RequestParam String version , @RequestParam String version
, @RequestParam String bizContent) throws AlipayApiException { , @RequestParam String bizContent) throws AlipayApiException {
Assert.isTrue(StringUtils.isNotBlank(appId), "appId不能为空"); Assert.isTrue(StringUtils.isNotBlank(appId), "AppId不能为空");
Assert.isTrue(StringUtils.isNotBlank(privateKey), "privateKey不能为空"); Assert.isTrue(StringUtils.isNotBlank(privateKey), "PrivateKey不能为空");
Assert.isTrue(StringUtils.isNotBlank(method), "method不能为空"); Assert.isTrue(StringUtils.isNotBlank(method), "method不能为空");
// 公共请求参数 // 公共请求参数
@ -69,7 +71,12 @@ public class SandboxController {
String content = AlipaySignature.getSignContent(params); String content = AlipaySignature.getSignContent(params);
result.beforeSign = content; result.beforeSign = content;
String sign = AlipaySignature.rsa256Sign(content, privateKey, "utf-8"); String sign = null;
try {
sign = AlipaySignature.rsa256Sign(content, privateKey, "utf-8");
} catch (AlipayApiException e) {
throw new RuntimeException("构建签名失败");
}
result.sign = sign; result.sign = sign;
params.put("sign", sign); params.put("sign", sign);

@ -3,19 +3,13 @@ package com.gitee.sop.websiteserver.manager;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature; import com.alibaba.fastjson.parser.Feature;
import com.gitee.sop.registryapi.bean.ServiceInfo;
import com.gitee.sop.registryapi.bean.ServiceInstance;
import com.gitee.sop.registryapi.service.RegistryService;
import com.gitee.sop.websiteserver.bean.DocInfo; import com.gitee.sop.websiteserver.bean.DocInfo;
import com.gitee.sop.websiteserver.bean.DocItem; import com.gitee.sop.websiteserver.bean.DocItem;
import com.gitee.sop.websiteserver.bean.EurekaApplication;
import com.gitee.sop.websiteserver.bean.EurekaApps;
import com.gitee.sop.websiteserver.bean.EurekaInstance;
import com.gitee.sop.websiteserver.bean.EurekaUri;
import com.gitee.sop.websiteserver.bean.ZookeeperContext; import com.gitee.sop.websiteserver.bean.ZookeeperContext;
import com.gitee.sop.websiteserver.vo.ServiceInfoVO;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.lang.StringUtils;
import org.apache.curator.framework.recipes.cache.TreeCacheEvent; import org.apache.curator.framework.recipes.cache.TreeCacheEvent;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
@ -26,8 +20,6 @@ import org.springframework.util.DigestUtils;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -53,8 +45,6 @@ public class DocManagerImpl implements DocManager {
Map<String, DocItem> docItemMap = new HashMap<>(); Map<String, DocItem> docItemMap = new HashMap<>();
OkHttpClient client = new OkHttpClient();
RestTemplate restTemplate = new RestTemplate(); RestTemplate restTemplate = new RestTemplate();
DocParser swaggerDocParser = new SwaggerDocParser(); DocParser swaggerDocParser = new SwaggerDocParser();
@ -70,33 +60,35 @@ public class DocManagerImpl implements DocManager {
@Autowired @Autowired
private Environment environment; private Environment environment;
private String eurekaUrl; @Autowired
private RegistryService registryService;
private volatile boolean listenInited; private volatile boolean listenInited;
@Override @Override
public void load() { public void load() {
try { try {
// {"STORY-SERVICE":[{"ipAddr":"10.1.30.54","name":"STORY-SERVICE","serverPort":"2222"}],"API-GATEWAY":[{"ipAddr":"10.1.30.54","name":"API-GATEWAY","serverPort":"8081"}]} List<ServiceInfo> serviceInfoList = registryService.listAllService(1, 9999);
Map<String, List<ServiceInfoVO>> listMap = this.getAllServiceList(); log.info("服务列表:{}", serviceInfoList);
log.info("服务列表:{}", JSON.toJSONString(listMap.keySet()));
listMap.entrySet() serviceInfoList
.stream() .stream()
// 网关没有文档提供,需要排除 // 网关没有文档提供,需要排除
.filter(entry -> !"API-GATEWAY".equalsIgnoreCase(entry.getKey())) .filter(serviceInfo -> !"API-GATEWAY".equalsIgnoreCase(serviceInfo.getServiceId()))
.forEach(entry -> { .filter(serviceInfo -> !serviceInfo.getInstances().isEmpty())
ServiceInfoVO serviceInfoVo = entry.getValue().get(0); .map(serviceInfo -> serviceInfo.getInstances().get(0))
loadDocInfo(serviceInfoVo); .collect(Collectors.toList())
}); .forEach(this::loadDocInfo);
} catch (Exception e) { } catch (Exception e) {
log.error("加载失败", e); log.error("加载失败", e);
} }
} }
protected void loadDocInfo(ServiceInfoVO serviceInfoVo) { protected void loadDocInfo(ServiceInstance serviceInstance) {
String query = this.buildQuery(); String query = this.buildQuery();
String url = "http://" + serviceInfoVo.getIpAddr() + ":" + serviceInfoVo.getServerPort() + "/v2/api-docs" + query; String url = "http://" + serviceInstance.getIpPort() + "/v2/api-docs" + query;
try { try {
log.info("读取swagger文档,url:{}", url);
ResponseEntity<String> entity = restTemplate.getForEntity(url, String.class); ResponseEntity<String> entity = restTemplate.getForEntity(url, String.class);
if (entity.getStatusCode() != HttpStatus.OK) { if (entity.getStatusCode() != HttpStatus.OK) {
throw new IllegalAccessException("无权访问"); throw new IllegalAccessException("无权访问");
@ -144,55 +136,8 @@ public class DocManagerImpl implements DocManager {
return docDefinitionMap.values(); return docDefinitionMap.values();
} }
protected Map<String, List<ServiceInfoVO>> getAllServiceList() throws IOException {
String json = this.requestEurekaServer(EurekaUri.QUERY_APPS);
EurekaApps eurekaApps = JSON.parseObject(json, EurekaApps.class);
List<ServiceInfoVO> serviceInfoVoList = new ArrayList<>();
List<EurekaApplication> applicationList = eurekaApps.getApplications().getApplication();
applicationList.stream()
.forEach(eurekaApplication -> {
List<EurekaInstance> instanceList = eurekaApplication.getInstance();
for (EurekaInstance instance : instanceList) {
if ("UP".equals(instance.getStatus())) {
ServiceInfoVO vo = new ServiceInfoVO();
vo.setName(eurekaApplication.getName());
vo.setIpAddr(instance.getIpAddr());
vo.setServerPort(instance.fetchPort());
serviceInfoVoList.add(vo);
}
}
});
Map<String, List<ServiceInfoVO>> listMap = serviceInfoVoList.stream()
.collect(Collectors.groupingBy(ServiceInfoVO::getName));
return listMap;
}
protected String requestEurekaServer(EurekaUri eurekaUri, String... args) throws IOException {
Request request = eurekaUri.getRequest(this.eurekaUrl, args);
Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
return response.body().string();
} else {
log.error("操作失败,url:{}, msg:{}, code:{}", eurekaUri.getUri(args), response.message(), response.code());
throw new RuntimeException("操作失败");
}
}
@PostConstruct @PostConstruct
protected void after() throws Exception { protected void after() throws Exception {
String eurekaUrls = environment.getProperty("eureka.client.serviceUrl.defaultZone");
if (StringUtils.isBlank(eurekaUrls)) {
throw new IllegalArgumentException("未指定eureka.client.serviceUrl.defaultZone参数");
}
String url = eurekaUrls.split("\\,")[0];
if (url.endsWith("/")) {
url = eurekaUrls.substring(0, eurekaUrls.length() - 1);
}
this.eurekaUrl = url;
this.listenServiceId(); this.listenServiceId();
} }

@ -1,12 +1,13 @@
server: server:
port: 8083 port: 8083
# 注册中心 # 注册中心地址,根据实际情况改,这里只是参数,并不会去注册
eureka: registry:
serverAddr: localhost:1111 eureka-server-addr: http://localhost:1111/eureka/
client: # nacos服务器地址
serviceUrl: nacos-server-addr: 127.0.0.1:8848
defaultZone: http://${eureka.serverAddr}/eureka/ # 使用eureka,填:eureka,使用nacos填:nacos
name: eureka
spring: spring:
application: application:

@ -78,7 +78,7 @@ CREATE TABLE `config_route_limit` (
CREATE TABLE `isv_info` ( CREATE TABLE `isv_info` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`app_key` varchar(100) NOT NULL COMMENT 'appKey', `app_key` varchar(100) NOT NULL COMMENT 'appKey',
`secret` varchar(200) NOT NULL COMMENT 'secret', `secret` varchar(200) COMMENT 'secret',
`pub_key` text COMMENT '公钥', `pub_key` text COMMENT '公钥',
`pri_key` text COMMENT '私钥', `pri_key` text COMMENT '私钥',
`status` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '1启用,2禁用', `status` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '1启用,2禁用',

Loading…
Cancel
Save