全面使用nacos,舍弃zookeeper

pull/1/head
tanghc 6 years ago
parent 55e5a7e795
commit 219f944162
  1. 46
      sop-admin/sop-admin-server/pom.xml
  2. 18
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/isv/IsvApi.java
  3. 22
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/IPBlacklistApi.java
  4. 55
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/RouteApi.java
  5. 71
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/ServiceApi.java
  6. 29
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/NacosConfigs.java
  7. 344
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/ZookeeperContext.java
  8. 26
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/common/ZookeeperOperationException.java
  9. 10
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/common/ZookeeperPathExistException.java
  10. 10
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/common/ZookeeperPathNotExistException.java
  11. 2
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/config/WebConfig.java
  12. 32
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/ConfigPushService.java
  13. 17
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/RouteConfigService.java
  14. 24
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/RoutePermissionService.java
  15. 31
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/RouteService.java
  16. 17
      sop-admin/sop-admin-server/src/main/resources/application-dev.properties
  17. 45
      sop-admin/sop-admin-server/src/test/java/com/gitee/sop/adminserver/CuratorTest.java
  18. 42
      sop-common/pom.xml
  19. 9
      sop-common/sop-gateway-common/pom.xml
  20. 12
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/AbstractTargetRoute.java
  21. 2
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/GatewayFilterDefinition.java
  22. 42
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/GatewayPredicateDefinition.java
  23. 72
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/GatewayRouteDefinition.java
  24. 29
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/NacosConfigs.java
  25. 38
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ServiceRouteInfo.java
  26. 6
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/TargetRoute.java
  27. 8
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/configuration/BaseGatewayConfiguration.java
  28. 17
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/GatewayPredicateDefinition.java
  29. 32
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/GatewayRouteCache.java
  30. 21
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/GatewayRouteDefinition.java
  31. 1
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/GatewayRouteRepository.java
  32. 9
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/GatewayServiceRouteInfo.java
  33. 9
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/GatewayTargetRoute.java
  34. 31
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/AbstractConfiguration.java
  35. 62
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/BaseRouteCache.java
  36. 220
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/BaseRouteManager.java
  37. 24
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/ManagerContext.java
  38. 12
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/RouteLoader.java
  39. 1
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/RouteManager.java
  40. 20
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/RoutePermissionManager.java
  41. 118
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/ServiceRoutesLoader.java
  42. 252
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/ZookeeperContext.java
  43. 4
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/BaseParamBuilder.java
  44. 14
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/BaseExecutorAdapter.java
  45. 4
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/ApiValidator.java
  46. 15
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/configuration/BaseZuulConfiguration.java
  47. 54
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/PreRoutePermissionFilter.java
  48. 34
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/route/ZuulRouteCache.java
  49. 13
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/route/ZuulRouteDefinition.java
  50. 9
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/route/ZuulServiceRouteInfo.java
  51. 8
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/route/ZuulTargetRoute.java
  52. 40
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/route/ZuulZookeeperRouteManager.java
  53. 2
      sop-common/sop-registry-api/pom.xml
  54. 2
      sop-common/sop-registry-api/src/main/java/com/gitee/sop/registryapi/service/impl/RegistryServiceNacos.java
  55. 5
      sop-common/sop-service-common/pom.xml
  56. 138
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/ZookeeperTool.java
  57. 26
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/BaseServiceConfiguration.java
  58. 6
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/EasyopenServiceConfiguration.java
  59. 21
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/SpringMvcServiceConfiguration.java
  60. 26
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/exception/ZookeeperOperationException.java
  61. 10
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/exception/ZookeeperPathNotExistException.java
  62. 100
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/ApiMetaBuilder.java
  63. 89
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/DefaultRequestMappingEvent.java
  64. 46
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/ServiceRouteController.java
  65. 150
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/ServiceRouteInfoBuilder.java
  66. 271
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/ServiceZookeeperApiMetaManager.java
  67. 12
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/swagger/SwaggerValidator.java
  68. 11
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/util/OpenUtil.java
  69. 16
      sop-example/sop-story/sop-story-web/pom.xml
  70. 20
      sop-gateway/pom.xml
  71. 35
      sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbEnvGrayManager.java
  72. 31
      sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbIPBlacklistManager.java
  73. 30
      sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbIsvManager.java
  74. 40
      sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbIsvRoutePermissionManager.java
  75. 30
      sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbLimitConfigManager.java
  76. 36
      sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbRouteConfigManager.java
  77. 19
      sop-gateway/src/main/java/com/gitee/sop/gateway/manager/ManagerInitializer.java
  78. 2
      sop-gateway/src/main/resources/application-dev.properties
  79. 3
      sop-gateway/src/main/resources/bootstrap.properties
  80. 52
      sop-gateway/src/test/java/com/gitee/sop/gateway/CuratorTest.java
  81. 63
      sop-website/website-server/pom.xml
  82. 381
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/HttpTool.java
  83. 179
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/ZookeeperContext.java
  84. 12
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/config/RegistryConfig.java
  85. 2
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/config/WebsiteConfig.java
  86. 11
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/controller/DocController.java
  87. 9
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/controller/SandboxController.java
  88. 99
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/DocDiscovery.java
  89. 4
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/DocManager.java
  90. 244
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/DocManagerImpl.java
  91. 14
      sop-website/website-server/src/main/resources/application-dev.properties

@ -27,6 +27,17 @@
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>nacos-config-spring-boot-starter</artifactId>
<version>0.2.2</version>
</dependency>
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>nacos-discovery-spring-boot-starter</artifactId>
<version>0.2.2</version>
</dependency>
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-registry-api</artifactId>
@ -45,39 +56,13 @@
<artifactId>fastmybatis-spring-boot-starter</artifactId>
<version>1.8.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- zookeeper客户端针对zookeeper-3.4.x
如果zookeeper使用3.5.x,可以直接使用curator-recipes最高版本
详情:http://curator.apache.org/zk-compatibility.html
-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>${curator-recipes.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>${zookeeper.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
@ -122,5 +107,12 @@
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
</project>

@ -1,6 +1,5 @@
package com.gitee.sop.adminserver.api.isv;
import com.alibaba.fastjson.JSON;
import com.gitee.easyopen.annotation.Api;
import com.gitee.easyopen.annotation.ApiService;
import com.gitee.easyopen.doc.DataType;
@ -24,12 +23,11 @@ import com.gitee.sop.adminserver.api.isv.result.IsvKeysGenVO;
import com.gitee.sop.adminserver.api.isv.result.IsvKeysVO;
import com.gitee.sop.adminserver.api.isv.result.RoleVO;
import com.gitee.sop.adminserver.bean.ChannelMsg;
import com.gitee.sop.adminserver.bean.ZookeeperContext;
import com.gitee.sop.adminserver.bean.NacosConfigs;
import com.gitee.sop.adminserver.common.BizException;
import com.gitee.sop.adminserver.common.ChannelOperation;
import com.gitee.sop.adminserver.common.IdGen;
import com.gitee.sop.adminserver.common.RSATool;
import com.gitee.sop.adminserver.common.ZookeeperPathNotExistException;
import com.gitee.sop.adminserver.entity.IsvInfo;
import com.gitee.sop.adminserver.entity.IsvKeys;
import com.gitee.sop.adminserver.entity.PermIsvRole;
@ -38,6 +36,7 @@ import com.gitee.sop.adminserver.mapper.IsvInfoMapper;
import com.gitee.sop.adminserver.mapper.IsvKeysMapper;
import com.gitee.sop.adminserver.mapper.PermIsvRoleMapper;
import com.gitee.sop.adminserver.mapper.PermRoleMapper;
import com.gitee.sop.adminserver.service.ConfigPushService;
import com.gitee.sop.adminserver.service.RoutePermissionService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
@ -86,6 +85,9 @@ public class IsvApi {
@Autowired
RoutePermissionService routePermissionService;
@Autowired
private ConfigPushService configPushService;
@Value("${sop.sign-type}")
private String sopSignType;
@ -231,15 +233,7 @@ public class IsvApi {
return;
}
ChannelMsg channelMsg = new ChannelMsg(ChannelOperation.ISV_INFO_UPDATE, isvDetail);
String path = ZookeeperContext.getIsvInfoChannelPath();
String data = JSON.toJSONString(channelMsg);
try {
log.info("消息推送--ISV信息(update), path:{}, data:{}", path, data);
ZookeeperContext.updatePathData(path, data);
} catch (ZookeeperPathNotExistException e) {
log.error("发送isvChannelMsg失败, path:{}, msg:{}", path, data, e);
throw new BizException("路径不存在");
}
configPushService.publishConfig(NacosConfigs.DATA_ID_ISV, NacosConfigs.GROUP_CHANNEL, channelMsg);
}
private IsvKeysGenVO createIsvKeys() throws Exception {

@ -1,6 +1,5 @@
package com.gitee.sop.adminserver.api.service;
import com.alibaba.fastjson.JSON;
import com.gitee.easyopen.annotation.Api;
import com.gitee.easyopen.annotation.ApiService;
import com.gitee.easyopen.doc.annotation.ApiDoc;
@ -14,11 +13,12 @@ import com.gitee.sop.adminserver.api.service.param.ConfigIpBlackForm;
import com.gitee.sop.adminserver.api.service.param.ConfigIpBlacklistPageParam;
import com.gitee.sop.adminserver.api.service.result.ConfigIpBlacklistVO;
import com.gitee.sop.adminserver.bean.ChannelMsg;
import com.gitee.sop.adminserver.bean.ZookeeperContext;
import com.gitee.sop.adminserver.bean.NacosConfigs;
import com.gitee.sop.adminserver.common.BizException;
import com.gitee.sop.adminserver.common.ChannelOperation;
import com.gitee.sop.adminserver.entity.ConfigIpBlacklist;
import com.gitee.sop.adminserver.mapper.ConfigIpBlacklistMapper;
import com.gitee.sop.adminserver.service.ConfigPushService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@ -33,6 +33,9 @@ public class IPBlacklistApi {
@Autowired
ConfigIpBlacklistMapper configIpBlacklistMapper;
@Autowired
private ConfigPushService configPushService;
@ApiDocMethod(description = "获取IP黑名单,分页")
@Api(name = "ip.blacklist.page")
PageEasyui<ConfigIpBlacklistVO> page(ConfigIpBlacklistPageParam form) {
@ -85,20 +88,7 @@ public class IPBlacklistApi {
public void sendIpBlacklistMsg(ConfigIpBlacklist configIpBlacklist, ChannelOperation channelOperation) throws Exception {
ChannelMsg channelMsg = new ChannelMsg(channelOperation, configIpBlacklist);
String jsonData = JSON.toJSONString(channelMsg);
String path = ZookeeperContext.getIpBlacklistChannelPath();
log.info("消息推送--IP黑名单设置({}), path:{}, data:{}",channelOperation.getOperation(), path, jsonData);
ZookeeperContext.createOrUpdateData(path, jsonData);
configPushService.publishConfig(NacosConfigs.DATA_ID_IP_BLACKLIST, NacosConfigs.GROUP_CHANNEL, channelMsg);
}
enum BlacklistMsgType {
/**
* 黑名单消息类型添加
*/
ADD,
/**
* 黑名单消息类型删除
*/
DELETE
}
}

@ -1,6 +1,5 @@
package com.gitee.sop.adminserver.api.service;
import com.alibaba.fastjson.JSON;
import com.gitee.easyopen.annotation.Api;
import com.gitee.easyopen.annotation.ApiService;
import com.gitee.easyopen.doc.annotation.ApiDoc;
@ -15,10 +14,7 @@ import com.gitee.sop.adminserver.api.service.param.RouteUpdateParam;
import com.gitee.sop.adminserver.api.service.result.RouteVO;
import com.gitee.sop.adminserver.bean.GatewayRouteDefinition;
import com.gitee.sop.adminserver.bean.RouteConfigDto;
import com.gitee.sop.adminserver.bean.ZookeeperContext;
import com.gitee.sop.adminserver.common.BizException;
import com.gitee.sop.adminserver.common.ZookeeperPathExistException;
import com.gitee.sop.adminserver.common.ZookeeperPathNotExistException;
import com.gitee.sop.adminserver.entity.ConfigRouteBase;
import com.gitee.sop.adminserver.entity.PermRole;
import com.gitee.sop.adminserver.mapper.ConfigRouteBaseMapper;
@ -28,7 +24,6 @@ import com.gitee.sop.adminserver.service.RouteConfigService;
import com.gitee.sop.adminserver.service.RoutePermissionService;
import com.gitee.sop.adminserver.service.RouteService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
@ -71,6 +66,10 @@ public class RouteApi {
RouteVO vo = new RouteVO();
BeanUtils.copyProperties(gatewayRouteDefinition, vo);
vo.setRoles(this.getRouteRole(gatewayRouteDefinition.getId()));
ConfigRouteBase configRouteBase = configRouteBaseMapper.getByColumn("route_id", gatewayRouteDefinition.getId());
if (configRouteBase != null) {
vo.setStatus(configRouteBase.getStatus());
}
return vo;
})
.collect(Collectors.toList());
@ -96,54 +95,48 @@ public class RouteApi {
@Api(name = "route.add")
@ApiDocMethod(description = "新增路由")
void addRoute(RouteAddParam param) {
// TODO:新增路由
String id = param.getName() + param.getVersion();
String routePath = ZookeeperContext.buildRoutePath(param.getServiceId(), id);
// String routePath = ZookeeperContext.buildRoutePath(param.getServiceId(), id);
GatewayRouteDefinition routeDefinition = new GatewayRouteDefinition();
CopyUtil.copyPropertiesIgnoreNull(param, routeDefinition);
routeDefinition.setId(id);
routeDefinition.setCustom(1);
try {
ZookeeperContext.addPath(routePath, JSON.toJSONString(routeDefinition));
} catch (ZookeeperPathExistException e) {
throw new BizException("路由已存在");
}
// try {
// ZookeeperContext.addPath(routePath, JSON.toJSONString(routeDefinition));
// } catch (ZookeeperPathExistException e) {
// throw new BizException("路由已存在");
// }
this.updateRouteConfig(routeDefinition);
}
@Api(name = "route.update")
@ApiDocMethod(description = "修改路由")
void updateRoute(RouteUpdateParam param) {
String routePath = ZookeeperContext.buildRoutePath(param.getServiceId(), param.getId());
GatewayRouteDefinition routeDefinition = this.getGatewayRouteDefinition(routePath);
CopyUtil.copyPropertiesIgnoreNull(param, routeDefinition);
try {
ZookeeperContext.updatePathData(routePath, JSON.toJSONString(routeDefinition));
} catch (ZookeeperPathNotExistException e) {
throw new BizException("路由不存在");
}
this.updateRouteConfig(routeDefinition);
// TODO:修改路由
// String routePath = ZookeeperContext.buildRoutePath(param.getServiceId(), param.getId());
// GatewayRouteDefinition routeDefinition = this.getGatewayRouteDefinition(routePath);
// CopyUtil.copyPropertiesIgnoreNull(param, routeDefinition);
// try {
// ZookeeperContext.updatePathData(routePath, JSON.toJSONString(routeDefinition));
// } catch (ZookeeperPathNotExistException e) {
// throw new BizException("路由不存在");
// }
// this.updateRouteConfig(routeDefinition);
}
@Api(name = "route.del")
@ApiDocMethod(description = "删除路由")
void delRoute(RouteDeleteParam param) {
String routePath = ZookeeperContext.buildRoutePath(param.getServiceId(), param.getId());
// TODO:删除路由
/*String routePath = ZookeeperContext.buildRoutePath(param.getServiceId(), param.getId());
GatewayRouteDefinition routeDefinition = this.getGatewayRouteDefinition(routePath);
if (!BooleanUtils.toBoolean(routeDefinition.getCustom())) {
throw new BizException("非自定义路由,无法删除");
}
ZookeeperContext.deletePathDeep(routePath);
ZookeeperContext.deletePathDeep(routePath);*/
}
private GatewayRouteDefinition getGatewayRouteDefinition(String zookeeperRoutePath) {
String data = null;
try {
data = ZookeeperContext.getData(zookeeperRoutePath);
} catch (ZookeeperPathNotExistException e) {
throw new BizException("路由不存在");
}
return JSON.parseObject(data, GatewayRouteDefinition.class);
}
private void updateRouteConfig(GatewayRouteDefinition routeDefinition) {
try {

@ -1,6 +1,8 @@
package com.gitee.sop.adminserver.api.service;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.annotation.NacosInjected;
import com.alibaba.nacos.api.naming.NamingService;
import com.gitee.easyopen.annotation.Api;
import com.gitee.easyopen.annotation.ApiService;
import com.gitee.easyopen.doc.annotation.ApiDoc;
@ -15,27 +17,26 @@ import com.gitee.sop.adminserver.api.service.result.ServiceInfoVo;
import com.gitee.sop.adminserver.api.service.result.ServiceInstanceVO;
import com.gitee.sop.adminserver.bean.ChannelMsg;
import com.gitee.sop.adminserver.bean.MetadataEnum;
import com.gitee.sop.adminserver.bean.NacosConfigs;
import com.gitee.sop.adminserver.bean.ServiceGrayDefinition;
import com.gitee.sop.adminserver.bean.ServiceRouteInfo;
import com.gitee.sop.adminserver.bean.ZookeeperContext;
import com.gitee.sop.adminserver.common.BizException;
import com.gitee.sop.adminserver.common.ChannelOperation;
import com.gitee.sop.adminserver.common.StatusEnum;
import com.gitee.sop.adminserver.common.ZookeeperPathExistException;
import com.gitee.sop.adminserver.common.ZookeeperPathNotExistException;
import com.gitee.sop.adminserver.entity.ConfigGray;
import com.gitee.sop.adminserver.entity.ConfigGrayInstance;
import com.gitee.sop.adminserver.mapper.ConfigGrayInstanceMapper;
import com.gitee.sop.adminserver.mapper.ConfigGrayMapper;
import com.gitee.sop.adminserver.service.ConfigPushService;
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 org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import java.io.IOException;
import java.util.ArrayList;
@ -63,35 +64,54 @@ public class ServiceApi {
@Autowired
private ConfigGrayInstanceMapper configGrayInstanceMapper;
@Autowired
private ConfigPushService configPushService;
@NacosInjected
private NamingService namingService;
@Api(name = "zookeeper.service.list")
@ApiDocMethod(description = "zk中的服务列表", elementClass = RouteServiceInfo.class)
List<RouteServiceInfo> listServiceInfo(ServiceSearchParam param) {
String routeRootPath = ZookeeperContext.getSopRouteRootPath();
List<ChildData> childDataList = ZookeeperContext.getChildrenData(routeRootPath);
List<RouteServiceInfo> serviceInfoList = childDataList.stream()
.filter(childData -> childData.getData() != null && childData.getData().length > 0)
.map(childData -> {
String serviceNodeData = new String(childData.getData());
RouteServiceInfo serviceInfo = JSON.parseObject(serviceNodeData, RouteServiceInfo.class);
return serviceInfo;
})
List<ServiceInfo> servicesOfServer = null;
try {
servicesOfServer = registryService.listAllService(1, Integer.MAX_VALUE);
} catch (Exception e) {
log.error("nacos获取服务列表失败", e);
throw new BizException("获取服务列表失败");
}
return servicesOfServer
.stream()
.filter(serviceInfo -> {
String serviceId = serviceInfo.getServiceId();
if ("api-gateway".equalsIgnoreCase(serviceId)) {
return false;
}
// 隐藏空服务
if (CollectionUtils.isEmpty(serviceInfo.getInstances())) {
return false;
}
if (StringUtils.isBlank(param.getServiceId())) {
return true;
} else {
return serviceInfo.getServiceId().contains(param.getServiceId());
return serviceId.contains(param.getServiceId());
}
})
.map(serviceInfo -> {
RouteServiceInfo routeServiceInfo = new RouteServiceInfo();
routeServiceInfo.setServiceId(serviceInfo.getServiceId());
return routeServiceInfo;
})
.collect(Collectors.toList());
return serviceInfoList;
}
@Api(name = "service.custom.add")
@ApiDocMethod(description = "添加服务")
void addService(ServiceAddParam param) {
// TODO:添加服务
String serviceId = param.getServiceId();
String servicePath = ZookeeperContext.buildServiceIdPath(serviceId);
// String servicePath = ZookeeperContext.buildServiceIdPath(serviceId);
ServiceRouteInfo serviceRouteInfo = new ServiceRouteInfo();
Date now = new Date();
serviceRouteInfo.setServiceId(serviceId);
@ -100,17 +120,19 @@ public class ServiceApi {
serviceRouteInfo.setUpdateTime(now);
serviceRouteInfo.setCustom(BooleanUtils.toInteger(true));
String serviceData = JSON.toJSONString(serviceRouteInfo);
try {
/* try {
ZookeeperContext.addPath(servicePath, serviceData);
} catch (ZookeeperPathExistException e) {
throw new BizException("服务已存在");
}
}*/
}
@Api(name = "service.custom.del")
@ApiDocMethod(description = "删除自定义服务")
void delService(ServiceSearchParam param) {
String serviceId = param.getServiceId();
// TODO:删除自定义服务
/*String serviceId = param.getServiceId();
String servicePath = ZookeeperContext.buildServiceIdPath(serviceId);
String data = null;
try {
@ -126,7 +148,7 @@ public class ServiceApi {
if (!BooleanUtils.toBoolean(custom)) {
throw new BizException("非自定义服务,无法删除");
}
ZookeeperContext.deletePathDeep(servicePath);
ZookeeperContext.deletePathDeep(servicePath);*/
}
@Api(name = "service.instance.list")
@ -134,7 +156,7 @@ public class ServiceApi {
List<ServiceInstanceVO> listService(ServiceSearchParam param) {
List<ServiceInfo> serviceInfos;
try {
serviceInfos = registryService.listAllService(1, 99999);
serviceInfos = registryService.listAllService(1, Integer.MAX_VALUE);
} catch (Exception e) {
log.error("获取服务实例失败", e);
return Collections.emptyList();
@ -293,10 +315,7 @@ public class ServiceApi {
serviceGrayDefinition.setInstanceId(instanceId);
serviceGrayDefinition.setServiceId(serviceId);
ChannelMsg channelMsg = new ChannelMsg(channelOperation, serviceGrayDefinition);
String jsonData = JSON.toJSONString(channelMsg);
String path = ZookeeperContext.getServiceGrayChannelPath();
log.info("消息推送--灰度发布({}), path:{}, data:{}", channelOperation.getOperation(), path, jsonData);
ZookeeperContext.createOrUpdateData(path, jsonData);
configPushService.publishConfig(NacosConfigs.DATA_ID_GRAY, NacosConfigs.GROUP_CHANNEL, channelMsg);
}
private ConfigGray getConfigGray(String serviceId) {

@ -0,0 +1,29 @@
package com.gitee.sop.adminserver.bean;
/**
* @author tanghc
*/
public class NacosConfigs {
public static final String GROUP_CHANNEL = "sop:channel";
public static final String GROUP_ROUTE = "sop:route";
public static final String DATA_ID_GRAY = "com.gitee.sop.channel.gray";
public static final String DATA_ID_IP_BLACKLIST = "com.gitee.sop.channel.ipblacklist";
public static final String DATA_ID_ISV = "com.gitee.sop.channel.isv";
public static final String DATA_ID_ROUTE_PERMISSION = "com.gitee.sop.channel.routepermission";
public static final String DATA_ID_LIMIT_CONFIG = "com.gitee.sop.channel.limitconfig";
public static final String DATA_ID_ROUTE_CONFIG = "com.gitee.sop.channel.routeconfig";
private static final String DATA_ID_TPL = "com.gitee.sop.route.%s";
public static String getRouteDataId(String serviceId) {
return String.format(DATA_ID_TPL, serviceId);
}
}

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

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

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

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

@ -8,7 +8,6 @@ import com.gitee.easyopen.ApiParamParser;
import com.gitee.easyopen.ParamNames;
import com.gitee.easyopen.interceptor.ApiInterceptor;
import com.gitee.easyopen.session.ApiSessionManager;
import com.gitee.sop.adminserver.bean.ZookeeperContext;
import com.gitee.sop.adminserver.interceptor.LoginInterceptor;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
@ -68,7 +67,6 @@ public class WebConfig {
@PostConstruct
public void after() {
ZookeeperContext.setEnvironment(environment);
}
}

@ -0,0 +1,32 @@
package com.gitee.sop.adminserver.service;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.annotation.NacosInjected;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.exception.NacosException;
import com.gitee.sop.adminserver.bean.ChannelMsg;
import com.gitee.sop.adminserver.common.BizException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* @author tanghc
*/
@Slf4j
@Service
public class ConfigPushService {
@NacosInjected
private ConfigService configService;
public void publishConfig(String dataId, String groupId, ChannelMsg channelMsg) {
try {
log.info("nacos配置, dataId:{}, groupId:{}, operation:{}", dataId, groupId, channelMsg.getOperation());
configService.publishConfig(dataId, groupId, JSON.toJSONString(channelMsg));
} catch (NacosException e) {
log.error("nacos配置失败, dataId:{}, groupId:{}, operation:{}", dataId, groupId, channelMsg.getOperation(), e);
throw new BizException("nacos配置失败");
}
}
}

@ -1,12 +1,12 @@
package com.gitee.sop.adminserver.service;
import com.alibaba.fastjson.JSON;
import com.gitee.sop.adminserver.bean.ChannelMsg;
import com.gitee.sop.adminserver.bean.ConfigLimitDto;
import com.gitee.sop.adminserver.bean.NacosConfigs;
import com.gitee.sop.adminserver.bean.RouteConfigDto;
import com.gitee.sop.adminserver.bean.ZookeeperContext;
import com.gitee.sop.adminserver.common.ChannelOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
@ -16,6 +16,9 @@ import org.springframework.stereotype.Service;
@Slf4j
public class RouteConfigService {
@Autowired
private ConfigPushService configPushService;
/**
* 发送路由配置消息
* @param routeConfigDto
@ -23,10 +26,7 @@ public class RouteConfigService {
*/
public void sendRouteConfigMsg(RouteConfigDto routeConfigDto) {
ChannelMsg channelMsg = new ChannelMsg(ChannelOperation.ROUTE_CONFIG_UPDATE, routeConfigDto);
String jsonData = JSON.toJSONString(channelMsg);
String path = ZookeeperContext.getRouteConfigChannelPath();
log.info("消息推送--路由配置(update), path:{}, data:{}", path, jsonData);
ZookeeperContext.createOrUpdateData(path, jsonData);
configPushService.publishConfig(NacosConfigs.DATA_ID_ROUTE_CONFIG, NacosConfigs.GROUP_CHANNEL, channelMsg);
}
/**
@ -36,9 +36,6 @@ public class RouteConfigService {
*/
public void sendLimitConfigMsg(ConfigLimitDto routeConfigDto) throws Exception {
ChannelMsg channelMsg = new ChannelMsg(ChannelOperation.LIMIT_CONFIG_UPDATE, routeConfigDto);
String jsonData = JSON.toJSONString(channelMsg);
String path = ZookeeperContext.getLimitConfigChannelPath();
log.info("消息推送--限流配置(update), path:{}, data:{}", path, jsonData);
ZookeeperContext.createOrUpdateData(path, jsonData);
configPushService.publishConfig(NacosConfigs.DATA_ID_LIMIT_CONFIG, NacosConfigs.GROUP_CHANNEL, channelMsg);
}
}

@ -5,8 +5,7 @@ import com.gitee.fastmybatis.core.query.Query;
import com.gitee.sop.adminserver.api.service.param.RoutePermissionParam;
import com.gitee.sop.adminserver.bean.ChannelMsg;
import com.gitee.sop.adminserver.bean.IsvRoutePermission;
import com.gitee.sop.adminserver.bean.SopAdminConstants;
import com.gitee.sop.adminserver.bean.ZookeeperContext;
import com.gitee.sop.adminserver.bean.NacosConfigs;
import com.gitee.sop.adminserver.common.ChannelOperation;
import com.gitee.sop.adminserver.entity.PermIsvRole;
import com.gitee.sop.adminserver.entity.PermRolePermission;
@ -42,6 +41,9 @@ public class RoutePermissionService {
@Autowired
PermRolePermissionMapper permRolePermissionMapper;
@Autowired
private ConfigPushService configPushService;
/**
* 获取客户端角色码列表
*
@ -70,10 +72,8 @@ public class RoutePermissionService {
isvRoutePermission.setRouteIdList(routeIdList);
isvRoutePermission.setRouteIdListMd5(roleCodeListMd5);
ChannelMsg channelMsg = new ChannelMsg(ChannelOperation.ROUTE_PERMISSION_UPDATE, isvRoutePermission);
String jsonData = JSON.toJSONString(channelMsg);
String path = ZookeeperContext.getIsvRoutePermissionChannelPath();
log.info("消息推送--路由权限(update), path:{}, data:{}", path, jsonData);
ZookeeperContext.createOrUpdateData(path, jsonData);
configPushService.publishConfig(NacosConfigs.DATA_ID_ROUTE_PERMISSION, NacosConfigs.GROUP_CHANNEL, channelMsg);
}
/**
@ -98,19 +98,9 @@ public class RoutePermissionService {
* 推送所有路由权限到zookeeper
*/
public void sendRoutePermissionReloadMsg(RoutePermissionParam oldRoutePermission) throws Exception {
String listenPath = SopAdminConstants.RELOAD_ROUTE_PERMISSION_PATH + "/" + System.currentTimeMillis();
ZookeeperContext.listenTempPath(listenPath, errorMsg -> {
log.error("推送所有路由权限到zookeeper失败,进行回滚,errorMsg: {},oldRoutePermission:{}", errorMsg, JSON.toJSONString(oldRoutePermission));
// 回滚
updateRoutePermission(oldRoutePermission);
});
IsvRoutePermission isvRoutePermission = new IsvRoutePermission();
isvRoutePermission.setListenPath(listenPath);
ChannelMsg channelMsg = new ChannelMsg(ChannelOperation.ROUTE_PERMISSION_RELOAD, isvRoutePermission);
String jsonData = JSON.toJSONString(channelMsg);
String path = ZookeeperContext.getIsvRoutePermissionChannelPath();
log.info("消息推送--路由权限(reload), path:{}, data:{}", path, jsonData);
ZookeeperContext.createOrUpdateData(path, jsonData);
configPushService.publishConfig(NacosConfigs.DATA_ID_ROUTE_PERMISSION, NacosConfigs.GROUP_CHANNEL, channelMsg);
}
/**

@ -1,11 +1,13 @@
package com.gitee.sop.adminserver.service;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.annotation.NacosInjected;
import com.alibaba.nacos.api.config.ConfigService;
import com.gitee.sop.adminserver.api.service.param.RouteSearchParam;
import com.gitee.sop.adminserver.bean.GatewayRouteDefinition;
import com.gitee.sop.adminserver.bean.ZookeeperContext;
import com.gitee.sop.adminserver.bean.NacosConfigs;
import com.gitee.sop.adminserver.bean.ServiceRouteInfo;
import org.apache.commons.lang.StringUtils;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.springframework.stereotype.Service;
import java.util.Collections;
@ -19,21 +21,22 @@ import static java.util.stream.Collectors.toList;
@Service
public class RouteService {
@NacosInjected
private ConfigService configService;
public List<GatewayRouteDefinition> getRouteDefinitionList(RouteSearchParam param) throws Exception {
if (StringUtils.isBlank(param.getServiceId())) {
String serviceId = param.getServiceId();
if (StringUtils.isBlank(serviceId)) {
return Collections.emptyList();
}
String configData = configService.getConfig(NacosConfigs.getRouteDataId(serviceId), NacosConfigs.GROUP_ROUTE, 3000);
if (StringUtils.isBlank(configData)) {
return Collections.emptyList();
}
ServiceRouteInfo serviceRouteInfo = JSON.parseObject(configData, ServiceRouteInfo.class);
String searchPath = ZookeeperContext.getSopRouteRootPath() + "/" + param.getServiceId();
List<ChildData> childDataList = ZookeeperContext.getChildrenData(searchPath);
List<GatewayRouteDefinition> routeDefinitionStream = childDataList.stream()
.map(childData -> {
String serviceNodeData = new String(childData.getData());
GatewayRouteDefinition routeDefinition = JSON.parseObject(serviceNodeData, GatewayRouteDefinition.class);
return routeDefinition;
})
return serviceRouteInfo.getRouteDefinitionList()
.stream()
.filter(gatewayRouteDefinition -> {
boolean isRoute = gatewayRouteDefinition.getOrder() != Integer.MIN_VALUE;
String id = param.getId();
@ -44,8 +47,6 @@ public class RouteService {
}
})
.collect(toList());
return routeDefinitionStream;
}
}

@ -6,12 +6,9 @@ spring.application.name=sop-admin
mysql.username=root
mysql.password=root
# eureka注册中心地址
eureka.url=http://localhost:1111/eureka/
# nacos注册中心地址
nacos.url=127.0.0.1:8848
# zookeeper地址
zookeeper.url=localhost:2181
# zipkin服务监控地址,没有开启不用改
zipkin.url=http://127.0.0.1:9411/
# ------- 需要改的配置end -------
@ -21,10 +18,9 @@ admin.access-token.timeout-minutes=30
# 签名方式,rsa:支付宝开放平台签名方式,md5:淘宝开放平台签名方式
sop.sign-type=rsa
# zookeeper配置
spring.cloud.zookeeper.connect-string=${zookeeper.url}
spring.cloud.zookeeper.baseSleepTimeMs=3000
spring.cloud.zookeeper.maxRetries=3
# nacos配置
nacos.config.server-addr=${nacos.url}
nacos.discovery.server-addr=${nacos.url}
# 数据库配置
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
@ -36,10 +32,7 @@ spring.datasource.password=${mysql.password}
easyopen.show-doc=false
easyopen.ignore-validate=true
# 注册中心地址,根据实际情况改,这里只是参数,并不会去注册
registry.eureka-server-addr=${eureka.url}
registry.nacos-server-addr=${nacos.url}
# 使用eureka,填:eureka,使用nacos填:nacos
# 固定不用改
registry.name=nacos
logging.level.com.gitee=debug

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

@ -28,8 +28,6 @@
<commons-collection.version>3.2.2</commons-collection.version>
<commons-lang3.version>3.8.1</commons-lang3.version>
<commons-codec.version>1.11</commons-codec.version>
<zookeeper.version>3.4.12</zookeeper.version>
<curator-recipes.version>4.0.1</curator-recipes.version>
<validation-api.version>2.0.1.Final</validation-api.version>
<hibernate-validator.version>6.0.13.Final</hibernate-validator.version>
</properties>
@ -57,36 +55,24 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
<version>0.9.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>0.9.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- zookeeper客户端针对zookeeper-3.4.x
如果zookeeper使用3.5.x,可以直接使用curator-recipes最高版本
详情:http://curator.apache.org/zk-compatibility.html
-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>${curator-recipes.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>${zookeeper.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>

@ -20,6 +20,14 @@
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
@ -60,7 +68,6 @@
<artifactId>spring-boot-starter-webflux</artifactId>
<optional>true</optional>
</dependency>
</dependencies>

@ -3,25 +3,25 @@ package com.gitee.sop.gatewaycommon.bean;
/**
* @author tanghc
*/
public abstract class AbstractTargetRoute<R extends BaseServiceRouteInfo, E extends BaseRouteDefinition, T> implements TargetRoute<R,E, T> {
public abstract class AbstractTargetRoute<T> implements TargetRoute<T> {
private R serviceRouteInfo;
private E routeDefinition;
private ServiceRouteInfo serviceRouteInfo;
private GatewayRouteDefinition routeDefinition;
private T targetRoute;
public AbstractTargetRoute(R serviceRouteInfo, E routeDefinition, T targetRoute) {
public AbstractTargetRoute(ServiceRouteInfo serviceRouteInfo, GatewayRouteDefinition routeDefinition, T targetRoute) {
this.serviceRouteInfo = serviceRouteInfo;
this.routeDefinition = routeDefinition;
this.targetRoute = targetRoute;
}
@Override
public R getServiceRouteInfo() {
public ServiceRouteInfo getServiceRouteInfo() {
return serviceRouteInfo;
}
@Override
public E getRouteDefinition() {
public GatewayRouteDefinition getRouteDefinition() {
return routeDefinition;
}

@ -0,0 +1,42 @@
package com.gitee.sop.gatewaycommon.bean;
import lombok.Data;
import org.springframework.util.StringUtils;
import javax.validation.ValidationException;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author tanghc
*/
@Data
public class GatewayPredicateDefinition {
public static final String GEN_KEY = "_genkey_";
/** 断言对应的Name */
private String name;
/** 配置的断言规则 */
private Map<String, String> args = new LinkedHashMap<>();
public GatewayPredicateDefinition() {
}
public GatewayPredicateDefinition(String text) {
int eqIdx = text.indexOf(61);
if (eqIdx <= 0) {
throw new ValidationException("Unable to parse GatewayPredicateDefinition text '" + text + "', must be of the form name=value");
} else {
this.setName(text.substring(0, eqIdx));
String[] params = StringUtils.tokenizeToStringArray(text.substring(eqIdx + 1), ",");
for(int i = 0; i < params.length; ++i) {
this.args.put(generateName(i), params[i]);
}
}
}
public static String generateName(int i) {
return GEN_KEY + i;
}
}

@ -0,0 +1,72 @@
package com.gitee.sop.gatewaycommon.bean;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* @author tanghc
*/
@Data
public class GatewayRouteDefinition {
/**
* 路由的Id接口名+版本号确保此id全局唯一
*/
private String id;
/**
* 接口名
*/
private String name;
/**
* 版本号
*/
private String version;
/**
* 路由断言集合配置
*/
private List<GatewayPredicateDefinition> predicates = new ArrayList<>();
/**
* 路由过滤器集合配置
*/
private List<GatewayFilterDefinition> filters = new ArrayList<>();
/**
* 路由规则转发的目标uri
*/
private String uri;
/**
* uri后面跟的path
*/
private String path;
/**
* 路由执行的顺序
*/
private int order = 0;
/**
* 是否忽略验证业务参数验证除外
*/
private int ignoreValidate;
/**
* 状态0待审核1启用2禁用
*/
private int status = 1;
/**
* 是否合并结果
*/
private int mergeResult;
/**
* 是否需要授权才能访问
*/
private int permission;
}

@ -0,0 +1,29 @@
package com.gitee.sop.gatewaycommon.bean;
/**
* @author tanghc
*/
public class NacosConfigs {
public static final String GROUP_CHANNEL = "sop:channel";
public static final String GROUP_ROUTE = "sop:route";
public static final String DATA_ID_GRAY = "com.gitee.sop.channel.gray";
public static final String DATA_ID_IP_BLACKLIST = "com.gitee.sop.channel.ipblacklist";
public static final String DATA_ID_ISV = "com.gitee.sop.channel.isv";
public static final String DATA_ID_ROUTE_PERMISSION = "com.gitee.sop.channel.routepermission";
public static final String DATA_ID_LIMIT_CONFIG = "com.gitee.sop.channel.limitconfig";
public static final String DATA_ID_ROUTE_CONFIG = "com.gitee.sop.channel.routeconfig";
private static final String DATA_ID_TPL = "com.gitee.sop.route.%s";
public static String getRouteDataId(String serviceId) {
return String.format(DATA_ID_TPL, serviceId);
}
}

@ -0,0 +1,38 @@
package com.gitee.sop.gatewaycommon.bean;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import java.util.Date;
import java.util.List;
/**
* @author tanghc
*/
@Data
public class ServiceRouteInfo {
/**
* 服务名称对应spring.application.name
*/
private String serviceId;
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date createTime = new Date();
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date updateTime = new Date();
private String description;
/**
* 路由信息md5md5(sort(routeIdList))
*/
private String md5;
private List<GatewayRouteDefinition> routeDefinitionList;
public String fetchServiceIdLowerCase() {
return serviceId.toLowerCase();
}
}

@ -3,21 +3,21 @@ package com.gitee.sop.gatewaycommon.bean;
/**
* @author tanghc
*/
public interface TargetRoute<R extends BaseServiceRouteInfo, E extends BaseRouteDefinition, T> {
public interface TargetRoute<T> {
/**
* 返回服务信息
*
* @return 返回服务信息
*/
R getServiceRouteInfo();
ServiceRouteInfo getServiceRouteInfo();
/**
* 返回微服务路由对象
*
* @return 返回微服务路由对象
*/
E getRouteDefinition();
GatewayRouteDefinition getRouteDefinition();
/**
* 返回网关路由对象

@ -6,12 +6,11 @@ import com.gitee.sop.gatewaycommon.gateway.filter.LoadBalancerClientExtFilter;
import com.gitee.sop.gatewaycommon.gateway.filter.ParameterFormatterFilter;
import com.gitee.sop.gatewaycommon.gateway.filter.ValidateFilter;
import com.gitee.sop.gatewaycommon.gateway.handler.GatewayExceptionHandler;
import com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteCache;
import com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteRepository;
import com.gitee.sop.gatewaycommon.gateway.route.GatewayZookeeperRouteManager;
import com.gitee.sop.gatewaycommon.gateway.route.NameVersionRoutePredicateFactory;
import com.gitee.sop.gatewaycommon.gateway.route.ReadBodyRoutePredicateFactory;
import com.gitee.sop.gatewaycommon.manager.AbstractConfiguration;
import com.gitee.sop.gatewaycommon.manager.RouteManager;
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
import com.gitee.sop.gatewaycommon.param.ParamBuilder;
import org.springframework.beans.factory.ObjectProvider;
@ -21,7 +20,6 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
@ -96,8 +94,8 @@ public class BaseGatewayConfiguration extends AbstractConfiguration {
}
@Bean
RouteManager gatewayZookeeperRouteManager(Environment environment, GatewayRouteRepository gatewayRouteManager) {
return new GatewayZookeeperRouteManager(environment, gatewayRouteManager);
GatewayRouteCache gatewayRouteLoader(GatewayRouteRepository gatewayRouteManager) {
return new GatewayRouteCache(gatewayRouteManager);
}
@Bean

@ -1,17 +0,0 @@
package com.gitee.sop.gatewaycommon.gateway.route;
import lombok.Data;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author tanghc
*/
@Data
public class GatewayPredicateDefinition {
/** 断言对应的Name */
private String name;
/** 配置的断言规则 */
private Map<String, String> args = new LinkedHashMap<>();
}

@ -1,14 +1,15 @@
package com.gitee.sop.gatewaycommon.gateway.route;
import com.gitee.sop.gatewaycommon.manager.BaseRouteManager;
import com.gitee.sop.gatewaycommon.bean.GatewayFilterDefinition;
import com.gitee.sop.gatewaycommon.bean.GatewayPredicateDefinition;
import com.gitee.sop.gatewaycommon.bean.GatewayRouteDefinition;
import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo;
import com.gitee.sop.gatewaycommon.manager.BaseRouteCache;
import com.gitee.sop.gatewaycommon.manager.RouteRepository;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.core.env.Environment;
import java.net.URI;
import java.util.ArrayList;
@ -16,30 +17,16 @@ import java.util.LinkedList;
import java.util.List;
/**
* 从zookeeper监听route信息
*
* @author tanghc
*/
@Getter
@Slf4j
public class GatewayZookeeperRouteManager extends BaseRouteManager<GatewayServiceRouteInfo, GatewayRouteDefinition, GatewayTargetRoute> {
public class GatewayRouteCache extends BaseRouteCache<GatewayTargetRoute> {
public GatewayZookeeperRouteManager(Environment environment, RouteRepository<GatewayTargetRoute> routeRepository) {
super(environment, routeRepository);
public GatewayRouteCache(RouteRepository<GatewayTargetRoute> routeRepository) {
super(routeRepository);
}
@Override
protected Class<GatewayServiceRouteInfo> getServiceRouteInfoClass() {
return GatewayServiceRouteInfo.class;
}
@Override
protected Class<GatewayRouteDefinition> getRouteDefinitionClass() {
return GatewayRouteDefinition.class;
}
@Override
protected GatewayTargetRoute buildRouteDefinition(GatewayServiceRouteInfo serviceRouteInfo, GatewayRouteDefinition gatewayRouteDefinition) {
protected GatewayTargetRoute buildRouteDefinition(ServiceRouteInfo serviceRouteInfo, GatewayRouteDefinition gatewayRouteDefinition) {
RouteDefinition routeDefinition = new RouteDefinition();
routeDefinition.setId(gatewayRouteDefinition.getId());
routeDefinition.setUri(URI.create(gatewayRouteDefinition.getUri() + "#" + gatewayRouteDefinition.getPath()));
@ -80,5 +67,4 @@ public class GatewayZookeeperRouteManager extends BaseRouteManager<GatewayServic
}
predicateDefinitionList.addFirst(new PredicateDefinition(name + "=" + args));
}
}

@ -1,21 +0,0 @@
package com.gitee.sop.gatewaycommon.gateway.route;
import com.gitee.sop.gatewaycommon.bean.BaseRouteDefinition;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
/**
* @author tanghc
*/
@Getter
@Setter
public class GatewayRouteDefinition extends BaseRouteDefinition {
/** 路由断言集合配置 */
private List<GatewayPredicateDefinition> predicates = new ArrayList<>();
/** 路由过滤器集合配置 */
private List<GatewayFilterDefinition> filters = new ArrayList<>();
}

@ -1,5 +1,6 @@
package com.gitee.sop.gatewaycommon.gateway.route;
import com.gitee.sop.gatewaycommon.bean.GatewayRouteDefinition;
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
import com.gitee.sop.gatewaycommon.manager.RouteRepository;
import lombok.extern.slf4j.Slf4j;

@ -1,9 +0,0 @@
package com.gitee.sop.gatewaycommon.gateway.route;
import com.gitee.sop.gatewaycommon.bean.BaseServiceRouteInfo;
/**
* @author tanghc
*/
public class GatewayServiceRouteInfo extends BaseServiceRouteInfo<GatewayRouteDefinition> {
}

@ -1,13 +1,16 @@
package com.gitee.sop.gatewaycommon.gateway.route;
import com.gitee.sop.gatewaycommon.bean.AbstractTargetRoute;
import com.gitee.sop.gatewaycommon.bean.GatewayRouteDefinition;
import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo;
import org.springframework.cloud.gateway.route.RouteDefinition;
/**
* @author tanghc
*/
public class GatewayTargetRoute extends AbstractTargetRoute<GatewayServiceRouteInfo, GatewayRouteDefinition, RouteDefinition> {
public GatewayTargetRoute(GatewayServiceRouteInfo baseServiceRouteInfo, GatewayRouteDefinition baseRouteDefinition, RouteDefinition targetRoute) {
super(baseServiceRouteInfo, baseRouteDefinition, targetRoute);
public class GatewayTargetRoute extends AbstractTargetRoute<RouteDefinition> {
public GatewayTargetRoute(ServiceRouteInfo serviceRouteInfo, GatewayRouteDefinition routeDefinition, RouteDefinition targetRoute) {
super(serviceRouteInfo, routeDefinition, targetRoute);
}
}

@ -13,8 +13,10 @@ import com.gitee.sop.gatewaycommon.validate.Validator;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
import org.springframework.web.cors.CorsConfiguration;
@ -26,12 +28,12 @@ import javax.annotation.PostConstruct;
/**
* @author tanghc
*/
public class AbstractConfiguration implements ApplicationContextAware {
public class AbstractConfiguration implements ApplicationContextAware, ApplicationListener<HeartbeatEvent> {
@Autowired
protected Environment environment;
@Autowired
protected RouteManager apiMetaManager;
private ServiceRoutesLoader serviceRoutesLoader;
protected ApplicationContext applicationContext;
@ -40,6 +42,25 @@ public class AbstractConfiguration implements ApplicationContextAware {
applicationContext = ctx;
}
/**
* nacos事件监听
* @see org.springframework.cloud.alibaba.nacos.discovery.NacosWatch NacosWatch
* @param heartbeatEvent
*/
@Override
public void onApplicationEvent(HeartbeatEvent heartbeatEvent) {
serviceRoutesLoader.load(heartbeatEvent);
}
/**
* 微服务路由加载
*/
@Bean
@ConditionalOnMissingBean
ServiceRoutesLoader serviceRoutesLoader() {
return new ServiceRoutesLoader();
}
@Bean
@ConditionalOnMissingBean
Validator validator() {
@ -96,15 +117,13 @@ public class AbstractConfiguration implements ApplicationContextAware {
@Bean
@ConditionalOnMissingBean
ParameterFormatter sopParameterFormatter(){
ParameterFormatter sopParameterFormatter() {
return ApiConfig.getInstance().getParameterFormatter();
}
/**
* 跨域过滤器
*
* @return
*/
@Bean
public CorsFilter corsFilter() {
@ -136,11 +155,9 @@ public class AbstractConfiguration implements ApplicationContextAware {
throw new IllegalArgumentException("RouteRepositoryContext.setRouteRepository()方法未使用");
}
EnvironmentContext.setEnvironment(environment);
ZookeeperContext.setEnvironment(environment);
initMessage();
initBeanInitializer();
apiMetaManager.refresh();
doAfter();
}

@ -0,0 +1,62 @@
package com.gitee.sop.gatewaycommon.manager;
import com.gitee.sop.gatewaycommon.bean.GatewayRouteDefinition;
import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo;
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* @author tanghc
*/
@Slf4j
public abstract class BaseRouteCache<T extends TargetRoute> implements RouteLoader {
/**
* KEY:serviceId, value: md5
*/
private Map<String, String> serviceIdMd5Map = new HashMap<>();
private RouteRepository<T> routeRepository;
/**
* 构建目标路由对象zuul和gateway定义的路由对象
*
* @param serviceRouteInfo 路由服务对象
* @param gatewayRouteDefinition 路由对象
* @return 返回目标路由对象
*/
protected abstract T buildRouteDefinition(ServiceRouteInfo serviceRouteInfo, GatewayRouteDefinition gatewayRouteDefinition);
public BaseRouteCache(RouteRepository<T> routeRepository) {
this.routeRepository = routeRepository;
}
@Override
public void load(ServiceRouteInfo serviceRouteInfo) {
try {
String newMd5 = serviceRouteInfo.getMd5();
String md5 = serviceIdMd5Map.putIfAbsent(serviceRouteInfo.getServiceId(), newMd5);
if (Objects.equals(newMd5, md5)) {
return;
}
List<GatewayRouteDefinition> routeDefinitionList = serviceRouteInfo.getRouteDefinitionList();
for (GatewayRouteDefinition gatewayRouteDefinition : routeDefinitionList) {
T routeDefinition = this.buildRouteDefinition(serviceRouteInfo, gatewayRouteDefinition);
routeRepository.add(routeDefinition);
}
} catch (Exception e) {
log.error("加载路由信息失败,serviceRouteInfo:{}", serviceRouteInfo, e);
}
}
@Override
public void remove(String serviceId) {
serviceIdMd5Map.remove(serviceId);
routeRepository.deleteAll(serviceId);
}
}

@ -1,220 +0,0 @@
package com.gitee.sop.gatewaycommon.manager;
import com.alibaba.fastjson.JSON;
import com.gitee.sop.gatewaycommon.bean.BaseRouteDefinition;
import com.gitee.sop.gatewaycommon.bean.BaseServiceRouteInfo;
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.springframework.core.env.Environment;
/**
* 路由管理采用zookeeper实现监听路由的增删改并适时更新到本地路由的存储格式为
* <pre>
* /com.gitee.sop.route 根节点
* /serviceId 服务节点名字为服务名
* /route1 路由节点名字为name+version存放路由信息
* /route2
* /...
* </pre>
*
* @param <R> 路由根对象可以理解为最外面的大json{....,routeDefinitionList:[]}
* @param <E> 路由Item对象对应大json里面的具体路由信息routeDefinitionList:[]
* @param <T> 目标路由对象
* @author tanghc
*/
@Slf4j
public abstract class BaseRouteManager<R extends BaseServiceRouteInfo<E>, E extends BaseRouteDefinition, T extends TargetRoute> implements RouteManager {
protected Environment environment;
protected RouteRepository<T> routeRepository;
protected String routeRootPath;
/**
* 返回路由根对象class
*
* @return 返回R.class
*/
protected abstract Class<R> getServiceRouteInfoClass();
/**
* 返回路由Item对象class
*
* @return 返回E.class
*/
protected abstract Class<E> getRouteDefinitionClass();
/**
* 构建目标路由对象zuul和gateway定义的路由对象
*
* @param serviceRouteInfo
* @param routeDefinition
* @return 返回目标路由对象
*/
protected abstract T buildRouteDefinition(R serviceRouteInfo, E routeDefinition);
public BaseRouteManager(Environment environment, RouteRepository<T> routeRepository) {
this.environment = environment;
ZookeeperContext.setEnvironment(environment);
this.routeRepository = routeRepository;
this.routeRootPath = ZookeeperContext.getRouteRootPath();
this.createRouteRootPath(this.routeRootPath);
}
@Override
public void refresh() {
this.refreshRouteInfo();
}
protected void refreshRouteInfo() {
try {
this.watchServiceChange(routeRootPath);
} catch (Exception e) {
log.error("刷新路由配置失败", e);
throw new IllegalStateException("刷新路由配置失败");
}
}
protected void createRouteRootPath(String routeRootPath) {
try {
ZookeeperContext.createPath(routeRootPath, "");
} catch (Exception e) {
log.error("创建路由根节点失败", e);
throw new IllegalStateException("创建路由根节点失败");
}
}
/**
* 监听微服务更改
*
* @param rootPath
* @throws Exception
*/
protected void watchServiceChange(String rootPath) throws Exception {
ZookeeperContext.getChildrenAndListen(rootPath, childDataList -> {
log.info("========== 加载路由信息 ==========");
log.info("{} # 路由根节点", rootPath);
for (ChildData childData : childDataList) {
String serviceNodeData = new String(childData.getData());
R serviceRouteInfo = JSON.parseObject(serviceNodeData, getServiceRouteInfoClass());
String servicePath = childData.getPath();
log.info("\t{} # service节点,节点数据:{}", servicePath, serviceNodeData);
try {
this.loadServiceRouteItem(serviceRouteInfo, servicePath);
} catch (Exception e) {
log.error("加载路由信息失败,servicePath:{}", servicePath, e);
}
}
}, (client, event) -> {
PathChildrenCacheEvent.Type type = event.getType();
synchronized (type) {
// 通过判断event type的方式来实现不同事件的触发
if (PathChildrenCacheEvent.Type.CHILD_ADDED.equals(type)) {
String serviceNodeData = new String(event.getData().getData());
R serviceRouteInfo = JSON.parseObject(serviceNodeData, getServiceRouteInfoClass());
// 添加子节点时触发
String servicePath = event.getData().getPath();
log.info("新增serviceId节点:{},节点数据:{}", servicePath, serviceNodeData);
this.watchRouteItems(serviceRouteInfo, servicePath);
} else if (PathChildrenCacheEvent.Type.CHILD_UPDATED.equals(type)) {
// 修改子节点数据时触发,暂时没有什么操作
String nodeData = new String(event.getData().getData());
log.info("修改serviceId节点:{},节点数据:{}", event.getData().getPath(), nodeData);
} else if (PathChildrenCacheEvent.Type.CHILD_REMOVED.equals(type)) {
// 删除service节点
String nodeData = new String(event.getData().getData());
log.info("删除serviceId节点:{},节点数据:{}", event.getData().getPath(), nodeData);
R serviceRouteInfo = JSON.parseObject(nodeData, getServiceRouteInfoClass());
routeRepository.deleteAll(serviceRouteInfo.getServiceId());
}
}
});
}
/**
* 加载service下的路由信息
*
* @param servicePath
*/
protected void loadServiceRouteItem(R serviceRouteInfo, String servicePath) throws Exception {
ZookeeperContext.getChildrenData(servicePath, childDataList -> {
for (ChildData childData : childDataList) {
String routeItemPath = childData.getPath();
byte[] routeItemData = childData.getData();
String routeDataJson = buildZookeeperData(routeItemData);
log.info("\t\t{} # 路由节点,节点数据:{}", routeItemPath, routeDataJson);
this.saveRouteItem(serviceRouteInfo, routeDataJson);
}
});
this.watchRouteItems(serviceRouteInfo, servicePath);
}
/**
* 监听serviceId目录下面的子节点
*
* @param serviceRouteInfo
* @param servicePath serviceId节点
*/
protected void watchRouteItems(R serviceRouteInfo, String servicePath) throws Exception {
log.info("监听{}下子节点增删改", servicePath);
// 添加事件监听器
ZookeeperContext.listenChildren(servicePath, (client, event) -> {
PathChildrenCacheEvent.Type type = event.getType();
synchronized (type) {
// 通过判断event type的方式来实现不同事件的触发
if (PathChildrenCacheEvent.Type.CHILD_ADDED.equals(type)) {
// 新增单个路由
String routeDataJson = buildZookeeperData(event.getData().getData());
log.info("新增单个路由,serviceId:{}, 路由数据:{}", serviceRouteInfo.getServiceId(), routeDataJson);
saveRouteItem(serviceRouteInfo, routeDataJson);
} else if (PathChildrenCacheEvent.Type.CHILD_UPDATED.equals(type)) {
// 修改单个路由
String routeDataJson = buildZookeeperData(event.getData().getData());
log.info("修改单个路由,serviceId:{}, 路由数据:{}", serviceRouteInfo.getServiceId(), routeDataJson);
updateRouteItem(serviceRouteInfo, routeDataJson);
} else if (PathChildrenCacheEvent.Type.CHILD_REMOVED.equals(type)) {
// 删除单个路由
String routeDataJson = buildZookeeperData(event.getData().getData());
log.info("删除单个路由,serviceId:{}, 路由数据:{}", serviceRouteInfo.getServiceId(), routeDataJson);
deleteRouteItem(serviceRouteInfo, routeDataJson);
}
}
});
}
protected void saveRouteItem(R serviceRouteInfo, String nodeDataJson) {
T routeDefinition = getRouteDefinition(serviceRouteInfo, nodeDataJson);
routeRepository.add(routeDefinition);
}
protected void updateRouteItem(R serviceRouteInfo, String nodeDataJson) {
T routeDefinition = getRouteDefinition(serviceRouteInfo, nodeDataJson);
routeRepository.update(routeDefinition);
}
protected void deleteRouteItem(R serviceRouteInfo, String nodeDataJson) {
E routeDefinitionItem = getRouteDefinitionItem(nodeDataJson);
routeRepository.delete(routeDefinitionItem.getId());
}
protected T getRouteDefinition(R serviceRouteInfo, String nodeDataJson) {
E routeDefinitionItem = getRouteDefinitionItem(nodeDataJson);
T routeDefinition = buildRouteDefinition(serviceRouteInfo, routeDefinitionItem);
return routeDefinition;
}
protected E getRouteDefinitionItem(String nodeDataJson) {
return JSON.parseObject(nodeDataJson, getRouteDefinitionClass());
}
protected String buildZookeeperData(byte[] data) {
return new String(data);
}
}

@ -1,24 +0,0 @@
package com.gitee.sop.gatewaycommon.manager;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
/**
* @author tanghc
*/
public class ManagerContext {
private static ApplicationContext ctx;
public static void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ctx = applicationContext;
}
public static <T> T getManager(Class<T> clazz) {
return ctx.getBean(clazz);
}
public static ApplicationContext getApplicationContext() {
return ctx;
}
}

@ -0,0 +1,12 @@
package com.gitee.sop.gatewaycommon.manager;
import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo;
/**
* @author tanghc
*/
public interface RouteLoader {
void load(ServiceRouteInfo serviceRouteInfo);
void remove(String serviceId);
}

@ -4,6 +4,7 @@ package com.gitee.sop.gatewaycommon.manager;
* 管理各服务路由信息
* @author tanghc
*/
@Deprecated
public interface RouteManager {
/**

@ -1,20 +0,0 @@
package com.gitee.sop.gatewaycommon.manager;
import com.gitee.sop.gatewaycommon.bean.IsvRoutePermission;
/**
* @author tanghc
*/
public interface RoutePermissionManager {
/**
* 加载路由授权
*/
void load();
/**
* 更新路由授权信息
*
* @param isvRoutePermission 授权信息
*/
void update(IsvRoutePermission isvRoutePermission);
}

@ -0,0 +1,118 @@
package com.gitee.sop.gatewaycommon.manager;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.naming.pojo.ServiceInfo;
import com.gitee.sop.gatewaycommon.bean.NacosConfigs;
import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo;
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.alibaba.nacos.NacosConfigProperties;
import org.springframework.cloud.alibaba.nacos.NacosDiscoveryProperties;
import org.springframework.context.ApplicationEvent;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DigestUtils;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.Objects;
/**
* 发现新服务更新路由信息
*
* @author tanghc
*/
@Slf4j
public class ServiceRoutesLoader<T extends TargetRoute> {
private static final String SECRET = "a3d9sf!1@odl90zd>fkASwq";
private static final int FIVE_SECONDS = 1000 * 5;
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Autowired
private NacosConfigProperties nacosConfigProperties;
@Autowired
private BaseRouteCache<T> baseRouteCache;
private RestTemplate restTemplate = new RestTemplate();
private volatile long lastUpdateTime;
public synchronized void load(ApplicationEvent event) {
long now = System.currentTimeMillis();
// 5秒内只能执行一次,解决重启应用连续加载4次问题
if (now - lastUpdateTime < FIVE_SECONDS) {
return;
}
lastUpdateTime = now;
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
List<ServiceInfo> subscribes = null;
try {
subscribes = namingService.getSubscribeServices();
} catch (NacosException e) {
log.error("namingService.getSubscribeServices()错误", e);
}
if (CollectionUtils.isEmpty(subscribes)) {
return;
}
// subscribe
String thisServiceId = nacosDiscoveryProperties.getService();
ConfigService configService = nacosConfigProperties.configServiceInstance();
for (ServiceInfo serviceInfo : subscribes) {
String serviceName = serviceInfo.getName();
// 如果是本机服务,跳过
if (Objects.equals(thisServiceId, serviceName)) {
continue;
}
try {
String dataId = NacosConfigs.getRouteDataId(serviceName);
String groupId = NacosConfigs.GROUP_ROUTE;
List<Instance> allInstances = namingService.getAllInstances(serviceName);
if (CollectionUtils.isEmpty(allInstances)) {
log.info("{}服务下线,删除路由信息", serviceName);
// 如果没有服务列表,则删除所有路由信息
baseRouteCache.remove(serviceName);
configService.removeConfig(dataId, groupId);
} else {
for (Instance instance : allInstances) {
log.info("加载服务路由,instance:{}", instance);
String url = getRouteRequestUrl(instance);
ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
if (responseEntity.getStatusCode() == HttpStatus.OK) {
String body = responseEntity.getBody();
log.debug("加载{}路由,路由信息:{}", serviceName, body);
ServiceRouteInfo serviceRouteInfo = JSON.parseObject(body, ServiceRouteInfo.class);
baseRouteCache.load(serviceRouteInfo);
configService.publishConfig(dataId, groupId, body);
}
}
}
} catch (NacosException e) {
log.error("选择服务实例失败,serviceName:{}", serviceName, e);
}
}
}
private static String getRouteRequestUrl(Instance instance) {
String query = buildQuery(SECRET);
return "http://" + instance.getIp() + ":" + instance.getPort() + "/sop/routes" + query;
}
private static String buildQuery(String secret) {
String time = String.valueOf(System.currentTimeMillis());
String source = secret + time + secret;
String sign = DigestUtils.md5DigestAsHex(source.getBytes());
return "?time=" + time + "&sign=" + sign;
}
}

@ -1,252 +0,0 @@
package com.gitee.sop.gatewaycommon.manager;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.curator.framework.recipes.cache.NodeCacheListener;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
import java.util.List;
import java.util.function.Consumer;
import static com.gitee.sop.gatewaycommon.bean.SopConstants.SOP_MSG_CHANNEL_PATH;
import static com.gitee.sop.gatewaycommon.bean.SopConstants.SOP_SERVICE_ROUTE_PATH;
/**
* @author tanghc
*/
@Slf4j
public class ZookeeperContext {
private static CuratorFramework client;
private static Environment environment;
public static void setEnvironment(Environment environment) {
Assert.notNull(environment, "environment不能为null");
ZookeeperContext.environment = environment;
initZookeeperClient();
}
public synchronized static void initZookeeperClient() {
if (client != null) {
return;
}
setClient(createClient());
}
public static CuratorFramework createClient() {
String zookeeperServerAddr = environment.getProperty("spring.cloud.zookeeper.connect-string");
if (StringUtils.isBlank(zookeeperServerAddr)) {
throw new RuntimeException("未指定spring.cloud.zookeeper.connect-string参数");
}
String baseSleepTimeMs = environment.getProperty("spring.cloud.zookeeper.baseSleepTimeMs");
String maxRetries = environment.getProperty("spring.cloud.zookeeper.maxRetries");
log.info("初始化zookeeper客户端,zookeeperServerAddr:{}, baseSleepTimeMs:{}, maxRetries:{}",
zookeeperServerAddr, baseSleepTimeMs, maxRetries);
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString(zookeeperServerAddr)
.retryPolicy(new ExponentialBackoffRetry(NumberUtils.toInt(baseSleepTimeMs, 3000), NumberUtils.toInt(maxRetries, 3)))
.build();
client.start();
return client;
}
public static String getRouteRootPath() {
return SOP_SERVICE_ROUTE_PATH;
}
public static String getIsvInfoChannelPath() {
return SOP_MSG_CHANNEL_PATH + "/isvinfo";
}
public static String getServiceGrayChannelPath() {
return SOP_MSG_CHANNEL_PATH + "/gray";
}
public static String getIsvRoutePermissionChannelPath() {
return SOP_MSG_CHANNEL_PATH + "/isv-route-permission";
}
public static String getRouteConfigChannelPath() {
return SOP_MSG_CHANNEL_PATH + "/route-conf";
}
public static String getLimitConfigChannelPath() {
return SOP_MSG_CHANNEL_PATH + "/limit-conf";
}
public static String getIpBlacklistChannelPath() {
return SOP_MSG_CHANNEL_PATH + "/ipblacklist-conf";
}
public static CuratorFramework getClient() {
return client;
}
public static void setClient(CuratorFramework client) {
ZookeeperContext.client = client;
}
/**
* 新建或保存节点
*
* @param path
* @param data
* @return 返回path
* @throws Exception
*/
public static String createOrUpdateData(String path, String data) throws Exception {
return getClient().create()
// 如果节点存在则Curator将会使用给出的数据设置这个节点的值
.orSetData()
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
.creatingParentContainersIfNeeded()
.forPath(path, data.getBytes());
}
/**
* 更新节点
* @param path
* @param data
* @throws Exception
*/
public static void updatePath(String path, String data) throws Exception {
getClient().setData().forPath(path, data.getBytes());
}
/**
* 创建path如果path存在不报错静默返回path名称
*
* @param path
* @param data
* @return
* @throws Exception
*/
public static String createPath(String path, String data) throws Exception {
if (isPathExist(path)) {
return path;
}
return getClient().create()
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
.creatingParentContainersIfNeeded()
.forPath(path, data.getBytes());
}
public static boolean isPathExist(String path) {
try {
return client.checkExists().forPath(path) != null;
} catch (Exception e) {
return false;
}
}
/**
* 监听一个节点
*
* @param path
* @param onChange 节点修改后触发
* @return 返回path
* @throws Exception
*/
public static String listenPath(String path, Consumer<NodeCache> onChange) throws Exception {
String ret = createOrUpdateData(path, "{}");
final NodeCache cache = new NodeCache(client, path, false);
cache.getListenable().addListener(new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
onChange.accept(cache);
}
});
cache.start();
return ret;
}
/**
* 获取子节点信息并监听子节点
*
* @param parentPath 父节点路径
* @param listConsumer 子节点数据
* @param listener 监听事件
* @throws Exception
*/
public static void getChildrenAndListen(String parentPath, Consumer<List<ChildData>> listConsumer, PathChildrenCacheListener listener) throws Exception {
// 为子节点添加watcher
// PathChildrenCache: 监听数据节点的增删改,可以设置触发的事件
PathChildrenCache childrenCache = new PathChildrenCache(client, parentPath, true);
/**
* StartMode: 初始化方式
* POST_INITIALIZED_EVENT异步初始化初始化之后会触发事件
* NORMAL异步初始化
* BUILD_INITIAL_CACHE同步初始化
*/
childrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
// 列出子节点数据列表,需要使用BUILD_INITIAL_CACHE同步初始化模式才能获得,异步是获取不到的
List<ChildData> childDataList = childrenCache.getCurrentData();
listConsumer.accept(childDataList);
log.info("监听子节点增删改,监听路径:{}", parentPath);
// 监听根节点下面的子节点
childrenCache.getListenable().addListener(listener);
}
/**
* 获取子节点信息
*
* @param parentPath 父节点路径
* @param listConsumer 子节点数据
* @throws Exception
*/
public static void getChildrenData(String parentPath, Consumer<List<ChildData>> listConsumer) throws Exception {
// 为子节点添加watcher
// PathChildrenCache: 监听数据节点的增删改,可以设置触发的事件
PathChildrenCache childrenCache = new PathChildrenCache(client, parentPath, true);
/**
* StartMode: 初始化方式
* POST_INITIALIZED_EVENT异步初始化初始化之后会触发事件
* NORMAL异步初始化
* BUILD_INITIAL_CACHE同步初始化
*/
childrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
// 列出子节点数据列表,需要使用BUILD_INITIAL_CACHE同步初始化模式才能获得,异步是获取不到的
List<ChildData> childDataList = childrenCache.getCurrentData();
listConsumer.accept(childDataList);
childrenCache.close();
}
/**
* 监听子节点的增删改
*
* @param parentPath 父节点路径
* @param listener
* @throws Exception
*/
public static void listenChildren(String parentPath, PathChildrenCacheListener listener) throws Exception {
// 为子节点添加watcher
// PathChildrenCache: 监听数据节点的增删改,可以设置触发的事件
PathChildrenCache childrenCache = new PathChildrenCache(client, parentPath, true);
/**
* StartMode: 初始化方式
* POST_INITIALIZED_EVENT异步初始化初始化之后会触发事件
* NORMAL异步初始化
* BUILD_INITIAL_CACHE同步初始化
*/
childrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
// 监听根节点下面的子节点
childrenCache.getListenable().addListener(listener);
}
}

@ -1,6 +1,6 @@
package com.gitee.sop.gatewaycommon.param;
import com.gitee.sop.gatewaycommon.bean.BaseRouteDefinition;
import com.gitee.sop.gatewaycommon.bean.GatewayRouteDefinition;
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
import com.gitee.sop.gatewaycommon.manager.RouteRepository;
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
@ -65,7 +65,7 @@ public abstract class BaseParamBuilder<T> implements ParamBuilder<T> {
TargetRoute targetRoute = routeRepository.get(nameVersion);
Integer ignoreValidate = Optional.ofNullable(targetRoute)
.map(t -> t.getRouteDefinition())
.map(BaseRouteDefinition::getIgnoreValidate)
.map(GatewayRouteDefinition::getIgnoreValidate)
// 默认不忽略
.orElse(BooleanUtils.toInteger(false));
apiParam.setIgnoreValidate(BooleanUtils.toBoolean(ignoreValidate));

@ -4,10 +4,10 @@ import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
import com.gitee.sop.gatewaycommon.bean.ApiContext;
import com.gitee.sop.gatewaycommon.bean.BaseRouteDefinition;
import com.gitee.sop.gatewaycommon.bean.BaseServiceRouteInfo;
import com.gitee.sop.gatewaycommon.bean.ErrorDefinition;
import com.gitee.sop.gatewaycommon.bean.GatewayRouteDefinition;
import com.gitee.sop.gatewaycommon.bean.Isv;
import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo;
import com.gitee.sop.gatewaycommon.bean.SopConstants;
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
@ -139,7 +139,7 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
return defaultSetting;
}
ApiInfo apiInfo = this.getApiInfo(request);
BaseRouteDefinition baseRouteDefinition = apiInfo.baseRouteDefinition;
GatewayRouteDefinition baseRouteDefinition = apiInfo.gatewayRouteDefinition;
return Optional.ofNullable(baseRouteDefinition)
.map(routeDefinition -> {
int mergeResult = baseRouteDefinition.getMergeResult();
@ -157,10 +157,10 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
String serviceId = Optional.ofNullable(targetRoute)
.flatMap(route -> Optional.ofNullable(route.getServiceRouteInfo()))
.map(BaseServiceRouteInfo::getServiceId)
.map(ServiceRouteInfo::getServiceId)
.orElse(SopConstants.UNKNOWN_SERVICE);
BaseRouteDefinition baseRouteDefinition = Optional.ofNullable(targetRoute)
GatewayRouteDefinition baseRouteDefinition = Optional.ofNullable(targetRoute)
.map(route -> route.getRouteDefinition())
.orElse(null);
@ -168,7 +168,7 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
apiInfo.name = name;
apiInfo.version = version;
apiInfo.serviceId = serviceId;
apiInfo.baseRouteDefinition = baseRouteDefinition;
apiInfo.gatewayRouteDefinition = baseRouteDefinition;
return apiInfo;
}
@ -276,7 +276,7 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
private String name;
private String version;
private String serviceId;
private BaseRouteDefinition baseRouteDefinition;
private GatewayRouteDefinition gatewayRouteDefinition;
}
enum ErrorType {

@ -2,7 +2,7 @@ package com.gitee.sop.gatewaycommon.validate;
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
import com.gitee.sop.gatewaycommon.bean.ApiContext;
import com.gitee.sop.gatewaycommon.bean.BaseRouteDefinition;
import com.gitee.sop.gatewaycommon.bean.GatewayRouteDefinition;
import com.gitee.sop.gatewaycommon.bean.Isv;
import com.gitee.sop.gatewaycommon.bean.RouteConfig;
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
@ -216,7 +216,7 @@ public class ApiValidator implements Validator {
protected void checkPermission(ApiParam apiParam) {
String routeId = apiParam.fetchNameVersion();
TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(routeId);
BaseRouteDefinition routeDefinition = targetRoute.getRouteDefinition();
GatewayRouteDefinition routeDefinition = targetRoute.getRouteDefinition();
boolean needCheckPermission = BooleanUtils.toBoolean(routeDefinition.getPermission());
if (needCheckPermission) {
String appKey = apiParam.fetchAppKey();

@ -15,8 +15,8 @@ import com.gitee.sop.gatewaycommon.zuul.filter.PreValidateFilter;
import com.gitee.sop.gatewaycommon.zuul.filter.PreVersionDecisionFilter;
import com.gitee.sop.gatewaycommon.zuul.filter.Servlet30WrapperFilterExt;
import com.gitee.sop.gatewaycommon.zuul.route.SopRouteLocator;
import com.gitee.sop.gatewaycommon.zuul.route.ZuulRouteCache;
import com.gitee.sop.gatewaycommon.zuul.route.ZuulRouteRepository;
import com.gitee.sop.gatewaycommon.zuul.route.ZuulZookeeperRouteManager;
import com.netflix.zuul.context.RequestContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@ -26,7 +26,6 @@ import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
/**
* @author tanghc
@ -46,8 +45,7 @@ public class BaseZuulConfiguration extends AbstractConfiguration {
}
/**
* 路由存储
* @return
* 路由仓库
*/
@Bean
ZuulRouteRepository zuulRouteRepository() {
@ -89,12 +87,11 @@ public class BaseZuulConfiguration extends AbstractConfiguration {
/**
* 路由管理
* @param environment
* @param zuulRouteRepository
* @param zuulRouteRepository 路由仓库
*/
@Bean
ZuulZookeeperRouteManager zuulZookeeperRouteManager(Environment environment, ZuulRouteRepository zuulRouteRepository) {
return new ZuulZookeeperRouteManager(environment, zuulRouteRepository);
ZuulRouteCache zuulRouteLoader(ZuulRouteRepository zuulRouteRepository) {
return new ZuulRouteCache(zuulRouteRepository);
}
/**
@ -126,8 +123,6 @@ public class BaseZuulConfiguration extends AbstractConfiguration {
return new PreVersionDecisionFilter();
}
/**
* 错误处理扩展
*/

@ -1,54 +0,0 @@
package com.gitee.sop.gatewaycommon.zuul.filter;
import com.gitee.sop.gatewaycommon.bean.BaseRouteDefinition;
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
import com.gitee.sop.gatewaycommon.manager.IsvRoutePermissionManager;
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
import com.gitee.sop.gatewaycommon.message.ErrorEnum;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.beans.factory.annotation.Autowired;
/**
* 路由权限校验有些接口需要配置权限才能访问
* @author tanghc
* @deprecated 已经整合到ApiValidator中见ApiValidator.checkPermission()
*/
@Deprecated
public class PreRoutePermissionFilter extends BaseZuulFilter {
@Autowired
private IsvRoutePermissionManager isvRoutePermissionManager;
@Override
protected FilterType getFilterType() {
return FilterType.PRE;
}
@Override
protected int getFilterOrder() {
// 放在签名验证后面
return PRE_ROUTE_PERMISSION_FILTER_ORDER;
}
@Override
protected Object doRun(RequestContext requestContext) throws ZuulException {
ApiParam apiParam = ZuulContext.getApiParam();
String routeId = apiParam.fetchNameVersion();
TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(routeId);
BaseRouteDefinition routeDefinition = targetRoute.getRouteDefinition();
boolean needCheckPermission = BooleanUtils.toBoolean(routeDefinition.getPermission());
if (needCheckPermission) {
String appKey = apiParam.fetchAppKey();
boolean hasPermission = isvRoutePermissionManager.hasPermission(appKey, routeId);
if (!hasPermission) {
throw ErrorEnum.ISV_ROUTE_NO_PERMISSIONS.getErrorMeta().getException();
}
}
return null;
}
}

@ -0,0 +1,34 @@
package com.gitee.sop.gatewaycommon.zuul.route;
import com.gitee.sop.gatewaycommon.bean.GatewayRouteDefinition;
import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo;
import com.gitee.sop.gatewaycommon.manager.BaseRouteCache;
import com.gitee.sop.gatewaycommon.manager.RouteRepository;
import com.gitee.sop.gatewaycommon.util.RouteUtil;
import org.springframework.cloud.netflix.zuul.filters.Route;
/**
* @author tanghc
*/
public class ZuulRouteCache extends BaseRouteCache<ZuulTargetRoute> {
/** 路由重试 */
public static final boolean RETRYABLE = true;
public ZuulRouteCache(RouteRepository<ZuulTargetRoute> routeRepository) {
super(routeRepository);
}
@Override
protected ZuulTargetRoute buildRouteDefinition(ServiceRouteInfo serviceRouteInfo, GatewayRouteDefinition gatewayRouteDefinition) {
Route route = new Route(
gatewayRouteDefinition.getId()
, gatewayRouteDefinition.getPath()
, RouteUtil.getZuulLocation(gatewayRouteDefinition.getUri())
, ""
, RETRYABLE
, null
);
return new ZuulTargetRoute(serviceRouteInfo, gatewayRouteDefinition, route);
}
}

@ -1,13 +0,0 @@
package com.gitee.sop.gatewaycommon.zuul.route;
import com.gitee.sop.gatewaycommon.bean.BaseRouteDefinition;
import lombok.Getter;
import lombok.Setter;
/**
* @author tanghc
*/
@Getter
@Setter
public class ZuulRouteDefinition extends BaseRouteDefinition {
}

@ -1,9 +0,0 @@
package com.gitee.sop.gatewaycommon.zuul.route;
import com.gitee.sop.gatewaycommon.bean.BaseServiceRouteInfo;
/**
* @author tanghc
*/
public class ZuulServiceRouteInfo extends BaseServiceRouteInfo<ZuulRouteDefinition> {
}

@ -1,6 +1,8 @@
package com.gitee.sop.gatewaycommon.zuul.route;
import com.gitee.sop.gatewaycommon.bean.AbstractTargetRoute;
import com.gitee.sop.gatewaycommon.bean.GatewayRouteDefinition;
import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo;
import lombok.Getter;
import org.springframework.cloud.netflix.zuul.filters.Route;
@ -8,9 +10,9 @@ import org.springframework.cloud.netflix.zuul.filters.Route;
* @author tanghc
*/
@Getter
public class ZuulTargetRoute extends AbstractTargetRoute<ZuulServiceRouteInfo, ZuulRouteDefinition, Route> {
public class ZuulTargetRoute extends AbstractTargetRoute<Route> {
public ZuulTargetRoute(ZuulServiceRouteInfo baseServiceRouteInfo, ZuulRouteDefinition baseRouteDefinition, Route targetRoute) {
super(baseServiceRouteInfo, baseRouteDefinition, targetRoute);
public ZuulTargetRoute(ServiceRouteInfo serviceRouteInfo, GatewayRouteDefinition routeDefinition, Route targetRoute) {
super(serviceRouteInfo, routeDefinition, targetRoute);
}
}

@ -1,40 +0,0 @@
package com.gitee.sop.gatewaycommon.zuul.route;
import com.gitee.sop.gatewaycommon.manager.BaseRouteManager;
import com.gitee.sop.gatewaycommon.manager.RouteRepository;
import com.gitee.sop.gatewaycommon.util.RouteUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.core.env.Environment;
/**
* 路由内容管理新增活修改路由
*
* @author tanghc
*/
@Slf4j
public class ZuulZookeeperRouteManager extends BaseRouteManager<ZuulServiceRouteInfo, ZuulRouteDefinition, ZuulTargetRoute> {
/** 路由重试 */
public static final boolean RETRYABLE = true;
public ZuulZookeeperRouteManager(Environment environment, RouteRepository<ZuulTargetRoute> routeRepository) {
super(environment, routeRepository);
}
@Override
protected Class<ZuulServiceRouteInfo> getServiceRouteInfoClass() {
return ZuulServiceRouteInfo.class;
}
@Override
protected Class<ZuulRouteDefinition> getRouteDefinitionClass() {
return ZuulRouteDefinition.class;
}
@Override
protected ZuulTargetRoute buildRouteDefinition(ZuulServiceRouteInfo serviceRouteInfo, ZuulRouteDefinition routeDefinition) {
Route route = new Route(routeDefinition.getId(), routeDefinition.getPath(), RouteUtil.getZuulLocation(routeDefinition.getUri()), "", RETRYABLE, null);
return new ZuulTargetRoute(serviceRouteInfo, routeDefinition, route);
}
}

@ -25,7 +25,7 @@
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.0.1</version>
<version>1.1.3</version>
</dependency>
<dependency>

@ -33,7 +33,7 @@ public class RegistryServiceNacos implements RegistryService {
static HttpTool httpTool = new HttpTool();
@Value("${registry.nacos-server-addr:}")
@Value("${nacos.discovery.server-addr:}")
private String nacosAddr;
private NamingService namingService;

@ -21,6 +21,11 @@
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>

@ -1,138 +0,0 @@
package com.gitee.sop.servercommon.bean;
import com.gitee.sop.servercommon.exception.ZookeeperOperationException;
import com.gitee.sop.servercommon.exception.ZookeeperPathNotExistException;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.springframework.core.env.Environment;
import java.io.Closeable;
import java.io.IOException;
/**
* @author tanghc
*/
@Slf4j
@Getter
public class ZookeeperTool implements Closeable {
private CuratorFramework client;
private Environment environment;
public ZookeeperTool(Environment environment) {
this.environment = environment;
initZookeeperClient(environment);
}
public void initZookeeperClient(Environment environment) {
String zookeeperServerAddr = environment.getProperty("spring.cloud.zookeeper.connect-string");
if (StringUtils.isBlank(zookeeperServerAddr)) {
throw new RuntimeException("未指定spring.cloud.zookeeper.connect-string参数");
}
String baseSleepTimeMs = environment.getProperty("spring.cloud.zookeeper.baseSleepTimeMs");
String maxRetries = environment.getProperty("spring.cloud.zookeeper.maxRetries");
log.info("初始化zookeeper客户端,zookeeperServerAddr:{}, baseSleepTimeMs:{}, maxRetries:{}",
zookeeperServerAddr, baseSleepTimeMs, maxRetries);
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString(zookeeperServerAddr)
.retryPolicy(new ExponentialBackoffRetry(NumberUtils.toInt(baseSleepTimeMs, 3000), NumberUtils.toInt(maxRetries, 3)))
.build();
client.start();
this.client = client;
}
/**
* 获取节点内容
*
* @param path
* @return 返回节点内容
* @throws ZookeeperPathNotExistException 节点不存在抛出异常
*/
public String getData(String path) throws ZookeeperPathNotExistException {
if (!isPathExist(path)) {
throw new ZookeeperPathNotExistException("path 不存在, path=" + path);
}
try {
byte[] data = getClient().getData().forPath(path);
return new String(data);
} catch (Exception e) {
throw new ZookeeperOperationException("getData error path=" + path, e);
}
}
public boolean isPathExist(String path) {
try {
return client.checkExists().forPath(path) != null;
} catch (Exception e) {
return false;
}
}
/**
* 创建path如果path存在不报错静默返回path名称
*
* @param path
* @param data
* @return
* @throws Exception
*/
public String createPath(String path, String data) throws Exception {
if (isPathExist(path)) {
return path;
}
return getClient().create()
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
.creatingParentContainersIfNeeded()
.forPath(path, data.getBytes());
}
/**
* 创建临时序列节点
* @param path 节点路径
* @param data 数据
* @return 返回节点路径
* @throws Exception
*/
public String createOrUpdateEphemeralSequentialPath(String path, String data) throws Exception {
return getClient().create()
// 如果节点存在则Curator将会使用给出的数据设置这个节点的值
.orSetData()
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
.creatingParentContainersIfNeeded()
.withMode(CreateMode.EPHEMERAL_SEQUENTIAL)
.forPath(path, data.getBytes());
}
/**
* 新建或保存节点
*
* @param path
* @param data
* @return 返回path
* @throws Exception
*/
public String createOrUpdateData(String path, String data) throws Exception {
return getClient().create()
// 如果节点存在则Curator将会使用给出的数据设置这个节点的值
.orSetData()
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
.creatingParentContainersIfNeeded()
.forPath(path, data.getBytes());
}
@Override
public void close() throws IOException {
if (this.client != null) {
this.client.close();
}
}
}

@ -2,18 +2,13 @@ package com.gitee.sop.servercommon.configuration;
import com.gitee.sop.servercommon.bean.ServiceConfig;
import com.gitee.sop.servercommon.interceptor.ServiceContextInterceptor;
import com.gitee.sop.servercommon.manager.ApiMetaManager;
import com.gitee.sop.servercommon.manager.DefaultRequestMappingEvent;
import com.gitee.sop.servercommon.manager.RequestMappingEvent;
import com.gitee.sop.servercommon.manager.ServiceZookeeperApiMetaManager;
import com.gitee.sop.servercommon.manager.ServiceRouteController;
import com.gitee.sop.servercommon.mapping.ApiMappingHandlerMapping;
import com.gitee.sop.servercommon.message.ServiceErrorFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
@ -36,9 +31,6 @@ public class BaseServiceConfiguration extends WebMvcConfigurationSupport
ServiceConfig.getInstance().getI18nModules().add("i18n/isp/bizerror");
}
@Autowired
private Environment environment;
private ApiMappingHandlerMapping apiMappingHandlerMapping = new ApiMappingHandlerMapping();
@Override
@ -77,19 +69,16 @@ public class BaseServiceConfiguration extends WebMvcConfigurationSupport
return apiMappingHandlerMapping;
}
protected RequestMappingEvent getRequestMappingEvent(ApiMetaManager apiMetaManager, Environment environment) {
return new DefaultRequestMappingEvent(apiMetaManager, environment);
}
protected ApiMetaManager getApiMetaManager(Environment environment) {
return new ServiceZookeeperApiMetaManager(environment);
}
@Bean
GlobalExceptionHandler globalExceptionHandler() {
return ServiceConfig.getInstance().getGlobalExceptionHandler();
}
@Bean
ServiceRouteController serviceRouteController() {
return new ServiceRouteController();
}
@PostConstruct
public final void after() {
log.info("-----spring容器加载完毕-----");
@ -105,9 +94,6 @@ public class BaseServiceConfiguration extends WebMvcConfigurationSupport
@Override
public void run(ApplicationArguments args) throws Exception {
log.info("-----服务器启动完毕-----");
ApiMetaManager apiMetaManager = getApiMetaManager(environment);
RequestMappingEvent requestMappingEvent = getRequestMappingEvent(apiMetaManager, environment);
requestMappingEvent.onRegisterSuccess(apiMappingHandlerMapping);
this.onStartup(args);
}

@ -6,7 +6,6 @@ import com.gitee.easyopen.util.ReflectionUtil;
import com.gitee.sop.servercommon.bean.ServiceApiInfo;
import com.gitee.sop.servercommon.manager.ApiMetaManager;
import com.gitee.sop.servercommon.manager.DefaultRequestMappingEvent;
import com.gitee.sop.servercommon.manager.RequestMappingEvent;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
@ -30,11 +29,6 @@ public class EasyopenServiceConfiguration extends BaseServiceConfiguration {
ApiContext.getApiConfig().setIgnoreValidate(true);
}
@Override
protected RequestMappingEvent getRequestMappingEvent(ApiMetaManager apiMetaManager, Environment environment) {
return new EasyopenRequestMappingEvent(apiMetaManager, environment);
}
class EasyopenRequestMappingEvent extends DefaultRequestMappingEvent {
String prefixPath;

@ -1,21 +1,14 @@
package com.gitee.sop.servercommon.configuration;
import com.gitee.sop.servercommon.bean.ServiceConfig;
import com.gitee.sop.servercommon.manager.ApiMetaManager;
import com.gitee.sop.servercommon.manager.DefaultRequestMappingEvent;
import com.gitee.sop.servercommon.manager.RequestMappingEvent;
import com.gitee.sop.servercommon.manager.ServiceZookeeperApiMetaManager;
import com.gitee.sop.servercommon.mapping.ApiMappingHandlerMapping;
import com.gitee.sop.servercommon.message.ServiceErrorFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.annotation.PostConstruct;
import java.util.concurrent.Executors;
/**
* 提供给springmvc工程
@ -30,10 +23,6 @@ public class SpringMvcServiceConfiguration {
private ApiMappingHandlerMapping apiMappingHandlerMapping = new ApiMappingHandlerMapping();
@Autowired
private Environment environment;
/**
* 自定义Mapping详见@ApiMapping
*
@ -54,20 +43,10 @@ public class SpringMvcServiceConfiguration {
@PostConstruct
public final void after() {
log.info("-----spring容器加载完毕-----");
Executors.newSingleThreadExecutor().execute(()->{
uploadRouteToZookeeper();
});
initMessage();
doAfter();
}
private void uploadRouteToZookeeper() {
ApiMetaManager apiMetaManager = new ServiceZookeeperApiMetaManager(environment);
RequestMappingEvent requestMappingEvent = new DefaultRequestMappingEvent(apiMetaManager, environment);
requestMappingEvent.onRegisterSuccess(apiMappingHandlerMapping);
}
/**
* spring容器加载完毕后执行
*/

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

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

@ -0,0 +1,100 @@
package com.gitee.sop.servercommon.manager;
import com.gitee.sop.servercommon.bean.ServiceApiInfo;
import com.gitee.sop.servercommon.bean.ServiceConfig;
import com.gitee.sop.servercommon.mapping.ApiMappingInfo;
import com.gitee.sop.servercommon.mapping.ApiMappingRequestCondition;
import com.gitee.sop.servercommon.mapping.RouteUtil;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author tanghc
*/
public class ApiMetaBuilder {
/** 接口名规则:允许字母、数字、点、下划线 */
private static final String REGEX_API_NAME = "^[a-zA-Z0-9\\.\\_\\-]+$";
public ServiceApiInfo getServiceApiInfo(String serviceId, RequestMappingHandlerMapping requestMappingHandlerMapping) {
if (serviceId == null) {
throw new IllegalArgumentException("请在application.properties中指定spring.application.name属性");
}
List<ServiceApiInfo.ApiMeta> apis = this.buildApiMetaList(requestMappingHandlerMapping);
// 排序
apis.sort(Comparator.comparing(ServiceApiInfo.ApiMeta::fetchNameVersion));
ServiceApiInfo serviceApiInfo = new ServiceApiInfo();
serviceApiInfo.setServiceId(serviceId);
serviceApiInfo.setApis(apis);
return serviceApiInfo;
}
protected List<ServiceApiInfo.ApiMeta> buildApiMetaList(RequestMappingHandlerMapping requestMappingHandlerMapping) {
Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
Set<RequestMappingInfo> requestMappingInfos = handlerMethods.keySet();
List<String> store = new ArrayList<>();
List<ServiceApiInfo.ApiMeta> apis = new ArrayList<>(requestMappingInfos.size());
for (Map.Entry<RequestMappingInfo, HandlerMethod> handlerMethodEntry : handlerMethods.entrySet()) {
ServiceApiInfo.ApiMeta apiMeta = this.buildApiMeta(handlerMethodEntry);
if (apiMeta == null) {
continue;
}
String key = apiMeta.fetchNameVersion();
if (store.contains(key)) {
throw new IllegalArgumentException("重复申明接口,请检查path和version,path:" + apiMeta.getPath() + ", version:" + apiMeta.getVersion());
} else {
store.add(key);
}
apis.add(apiMeta);
}
return apis;
}
protected ServiceApiInfo.ApiMeta buildApiMeta(Map.Entry<RequestMappingInfo, HandlerMethod> handlerMethodEntry) {
RequestMappingInfo requestMappingInfo = handlerMethodEntry.getKey();
Set<String> patterns = requestMappingInfo.getPatternsCondition().getPatterns();
RequestCondition<?> customCondition = requestMappingInfo.getCustomCondition();
if (customCondition instanceof ApiMappingRequestCondition) {
ApiMappingRequestCondition condition = (ApiMappingRequestCondition) customCondition;
ApiMappingInfo apiMappingInfo = condition.getApiMappingInfo();
String name = apiMappingInfo.getName();
String version = apiMappingInfo.getVersion();
// 方法完整的path,如: /goods/listGoods,/users/user/get
String path = patterns.iterator().next();
// 不是ApiMapping注解的接口,name属性是null
if (name == null || ServiceConfig.getInstance().isWebappMode()) {
name = buildName(path);
}
this.checkApiName(name);
ServiceApiInfo.ApiMeta apiMeta = new ServiceApiInfo.ApiMeta(name, path, version);
apiMeta.setIgnoreValidate(BooleanUtils.toInteger(apiMappingInfo.isIgnoreValidate()));
apiMeta.setMergeResult(BooleanUtils.toInteger(apiMappingInfo.isMergeResult()));
apiMeta.setPermission(BooleanUtils.toInteger(apiMappingInfo.isPermission()));
return apiMeta;
}
return null;
}
protected void checkApiName(String name) {
if (!name.matches(REGEX_API_NAME)) {
throw new IllegalArgumentException("接口名称只允许【字母、数字、点(.)、下划线(_)、减号(-)】,错误接口:" + name);
}
}
protected String buildName(String path) {
return RouteUtil.buildApiName(path);
}
}

@ -1,32 +1,15 @@
package com.gitee.sop.servercommon.manager;
import com.gitee.sop.servercommon.bean.ServiceApiInfo;
import com.gitee.sop.servercommon.bean.ServiceConfig;
import com.gitee.sop.servercommon.mapping.ApiMappingInfo;
import com.gitee.sop.servercommon.mapping.ApiMappingRequestCondition;
import com.gitee.sop.servercommon.mapping.RouteUtil;
import lombok.Getter;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.core.env.Environment;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author tanghc
*/
@Getter
public class DefaultRequestMappingEvent implements RequestMappingEvent {
/** 接口名规则:允许字母、数字、点、下划线 */
private static final String REGEX_API_NAME = "^[a-zA-Z0-9\\.\\_\\-]+$";
public class DefaultRequestMappingEvent extends ApiMetaBuilder implements RequestMappingEvent {
private ApiMetaManager apiMetaManager;
private Environment environment;
@ -39,76 +22,8 @@ public class DefaultRequestMappingEvent implements RequestMappingEvent {
@Override
public void onRegisterSuccess(RequestMappingHandlerMapping requestMappingHandlerMapping) {
String serviceId = environment.getProperty("spring.application.name");
if (serviceId == null) {
throw new IllegalArgumentException("请在application.properties中指定spring.application.name属性");
}
List<ServiceApiInfo.ApiMeta> apis = this.buildApiMetaList(requestMappingHandlerMapping);
// 排序
apis.sort(Comparator.comparing(ServiceApiInfo.ApiMeta::fetchNameVersion));
ServiceApiInfo serviceApiInfo = new ServiceApiInfo();
serviceApiInfo.setServiceId(serviceId);
serviceApiInfo.setApis(apis);
ServiceApiInfo serviceApiInfo = getServiceApiInfo(serviceId, requestMappingHandlerMapping);
apiMetaManager.uploadApi(serviceApiInfo);
}
protected List<ServiceApiInfo.ApiMeta> buildApiMetaList(RequestMappingHandlerMapping requestMappingHandlerMapping) {
Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
Set<RequestMappingInfo> requestMappingInfos = handlerMethods.keySet();
List<String> store = new ArrayList<>();
List<ServiceApiInfo.ApiMeta> apis = new ArrayList<>(requestMappingInfos.size());
for (Map.Entry<RequestMappingInfo, HandlerMethod> handlerMethodEntry : handlerMethods.entrySet()) {
ServiceApiInfo.ApiMeta apiMeta = this.buildApiMeta(handlerMethodEntry);
if (apiMeta == null) {
continue;
}
String key = apiMeta.fetchNameVersion();
if (store.contains(key)) {
throw new IllegalArgumentException("重复申明接口,请检查path和version,path:" + apiMeta.getPath() + ", version:" + apiMeta.getVersion());
} else {
store.add(key);
}
apis.add(apiMeta);
}
return apis;
}
protected ServiceApiInfo.ApiMeta buildApiMeta(Map.Entry<RequestMappingInfo, HandlerMethod> handlerMethodEntry) {
RequestMappingInfo requestMappingInfo = handlerMethodEntry.getKey();
Set<String> patterns = requestMappingInfo.getPatternsCondition().getPatterns();
RequestCondition<?> customCondition = requestMappingInfo.getCustomCondition();
if (customCondition instanceof ApiMappingRequestCondition) {
ApiMappingRequestCondition condition = (ApiMappingRequestCondition) customCondition;
ApiMappingInfo apiMappingInfo = condition.getApiMappingInfo();
String name = apiMappingInfo.getName();
String version = apiMappingInfo.getVersion();
// 方法完整的path,如: /goods/listGoods,/users/user/get
String path = patterns.iterator().next();
// 不是ApiMapping注解的接口,name属性是null
if (name == null || ServiceConfig.getInstance().isWebappMode()) {
name = buildName(path);
}
this.checkApiName(name);
ServiceApiInfo.ApiMeta apiMeta = new ServiceApiInfo.ApiMeta(name, path, version);
apiMeta.setIgnoreValidate(BooleanUtils.toInteger(apiMappingInfo.isIgnoreValidate()));
apiMeta.setMergeResult(BooleanUtils.toInteger(apiMappingInfo.isMergeResult()));
apiMeta.setPermission(BooleanUtils.toInteger(apiMappingInfo.isPermission()));
return apiMeta;
}
return null;
}
protected void checkApiName(String name) {
if (!name.matches(REGEX_API_NAME)) {
throw new IllegalArgumentException("接口名称只允许【字母、数字、点(.)、下划线(_)、减号(-)】,错误接口:" + name);
}
}
protected String buildName(String path) {
return RouteUtil.buildApiName(path);
}
}

@ -0,0 +1,46 @@
package com.gitee.sop.servercommon.manager;
import com.gitee.sop.servercommon.bean.ServiceApiInfo;
import com.gitee.sop.servercommon.route.ServiceRouteInfo;
import com.gitee.sop.servercommon.util.OpenUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author tanghc
*/
@Slf4j
@RestController
public class ServiceRouteController {
private static final String SECRET = "a3d9sf!1@odl90zd>fkASwq";
@Autowired
private RequestMappingHandlerMapping requestMappingHandlerMapping;
@Autowired
private Environment environment;
@RequestMapping("/sop/routes")
public ServiceRouteInfo listRoutes(HttpServletRequest request, HttpServletResponse response) {
if (!OpenUtil.validateSimpleSign(request, SECRET)) {
log.error("签名验证失败, params:{}", request.getQueryString());
return null;
}
String serviceId = environment.getProperty("spring.application.name");
ApiMetaBuilder apiMetaBuilder = new ApiMetaBuilder();
ServiceApiInfo serviceApiInfo = apiMetaBuilder.getServiceApiInfo(serviceId, requestMappingHandlerMapping);
ServiceRouteInfoBuilder serviceRouteInfoBuilder = new ServiceRouteInfoBuilder(environment);
return serviceRouteInfoBuilder.build(serviceApiInfo);
}
}

@ -0,0 +1,150 @@
package com.gitee.sop.servercommon.manager;
import com.alibaba.fastjson.JSON;
import com.gitee.sop.servercommon.bean.ServiceApiInfo;
import com.gitee.sop.servercommon.route.GatewayPredicateDefinition;
import com.gitee.sop.servercommon.route.GatewayRouteDefinition;
import com.gitee.sop.servercommon.route.ServiceRouteInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.core.env.Environment;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author tanghc
*/
@Slf4j
public class ServiceRouteInfoBuilder {
/**
* 网关对应的LoadBalance协议
*/
private static final String PROTOCOL_LOAD_BALANCE = "lb://";
private static final String PATH_SPLIT = "/";
private static final String DEFAULT_CONTEXT_PATH = "/";
/**
* NameVersion=alipay.story.get1.0
* see com.gitee.sop.gatewaycommon.routeDefinition.NameVersionRoutePredicateFactory
*/
private static String QUERY_PREDICATE_DEFINITION_TPL = "NameVersion=%s";
private static ServiceApiInfo.ApiMeta FIRST_API_META = new ServiceApiInfo.ApiMeta("_first.route_", "/", "v_000");
private Environment environment;
public ServiceRouteInfoBuilder(Environment environment) {
this.environment = environment;
}
public ServiceRouteInfo build(ServiceApiInfo serviceApiInfo) {
return this.buildServiceGatewayInfo(serviceApiInfo);
}
/**
* 构建接口信息符合spring cloud gateway的格式
*
* @param serviceApiInfo 服务接口信息
* @return 返回服务路由信息
*/
protected ServiceRouteInfo buildServiceGatewayInfo(ServiceApiInfo serviceApiInfo) {
List<ServiceApiInfo.ApiMeta> apis = serviceApiInfo.getApis();
List<GatewayRouteDefinition> routeDefinitionList = new ArrayList<>(apis.size());
routeDefinitionList.add(this.buildReadBodyRouteDefinition(serviceApiInfo));
for (ServiceApiInfo.ApiMeta apiMeta : apis) {
GatewayRouteDefinition gatewayRouteDefinition = this.buildGatewayRouteDefinition(serviceApiInfo, apiMeta);
routeDefinitionList.add(gatewayRouteDefinition);
}
ServiceRouteInfo serviceRouteInfo = new ServiceRouteInfo();
serviceRouteInfo.setServiceId(serviceApiInfo.getServiceId());
serviceRouteInfo.setRouteDefinitionList(routeDefinitionList);
String md5 = buildMd5(routeDefinitionList);
serviceRouteInfo.setMd5(md5);
return serviceRouteInfo;
}
/**
* 构建路由id MD5
*
* @param routeDefinitionList 路由列表
* @return 返回MD5
*/
protected String buildMd5(List<GatewayRouteDefinition> routeDefinitionList) {
List<String> routeIdList = routeDefinitionList.stream()
.map(JSON::toJSONString)
.sorted()
.collect(Collectors.toList());
String md5Source = org.apache.commons.lang3.StringUtils.join(routeIdList, "");
return DigestUtils.md5DigestAsHex(md5Source.getBytes(StandardCharsets.UTF_8));
}
protected GatewayRouteDefinition buildGatewayRouteDefinition(ServiceApiInfo serviceApiInfo, ServiceApiInfo.ApiMeta apiMeta) {
GatewayRouteDefinition gatewayRouteDefinition = new GatewayRouteDefinition();
// 唯一id规则:接口名 + 版本号
String routeId = apiMeta.fetchNameVersion();
this.checkPath(routeId, "接口定义(" + routeId + ")不能有斜杠字符'/'");
BeanUtils.copyProperties(apiMeta, gatewayRouteDefinition);
gatewayRouteDefinition.setId(routeId);
gatewayRouteDefinition.setFilters(Collections.emptyList());
gatewayRouteDefinition.setPredicates(this.buildPredicates(apiMeta));
String uri = this.buildUri(serviceApiInfo, apiMeta);
String path = this.buildServletPath(serviceApiInfo, apiMeta);
gatewayRouteDefinition.setUri(uri);
gatewayRouteDefinition.setPath(path);
return gatewayRouteDefinition;
}
protected List<GatewayPredicateDefinition> buildPredicates(ServiceApiInfo.ApiMeta apiMeta) {
GatewayPredicateDefinition gatewayPredicateDefinition = new GatewayPredicateDefinition();
gatewayPredicateDefinition.setName("ReadBody");
return Arrays.asList(gatewayPredicateDefinition, this.buildNameVersionPredicateDefinition(apiMeta));
}
protected GatewayPredicateDefinition buildNameVersionPredicateDefinition(ServiceApiInfo.ApiMeta apiMeta) {
return new GatewayPredicateDefinition(String.format(QUERY_PREDICATE_DEFINITION_TPL, apiMeta.fetchNameVersion()));
}
/**
* 添加com.gitee.sop.gatewaycommon.routeDefinition.ReadBodyRoutePredicateFactory,解决form表单获取不到问题
*
* @return 返回路由定义
*/
protected GatewayRouteDefinition buildReadBodyRouteDefinition(ServiceApiInfo serviceApiInfo) {
GatewayRouteDefinition readBodyRouteDefinition = this.buildGatewayRouteDefinition(serviceApiInfo, FIRST_API_META);
readBodyRouteDefinition.setOrder(Integer.MIN_VALUE);
readBodyRouteDefinition.setPredicates(this.buildPredicates(FIRST_API_META));
return readBodyRouteDefinition;
}
protected String buildUri(ServiceApiInfo serviceApiInfo, ServiceApiInfo.ApiMeta apiMeta) {
return PROTOCOL_LOAD_BALANCE + serviceApiInfo.getServiceId();
}
protected String buildServletPath(ServiceApiInfo serviceApiInfo, ServiceApiInfo.ApiMeta apiMeta) {
String contextPath = environment.getProperty("server.servlet.context-path", DEFAULT_CONTEXT_PATH);
String servletPath = apiMeta.getPath();
if (servletPath == null) {
servletPath = "";
}
servletPath = StringUtils.trimLeadingCharacter(servletPath, '/');
return contextPath + servletPath;
}
private void checkPath(String path, String errorMsg) {
if (path.contains(PATH_SPLIT)) {
throw new IllegalArgumentException(errorMsg);
}
}
}

@ -1,271 +0,0 @@
package com.gitee.sop.servercommon.manager;
import com.alibaba.fastjson.JSON;
import com.gitee.sop.servercommon.bean.ServiceApiInfo;
import com.gitee.sop.servercommon.bean.ZookeeperTool;
import com.gitee.sop.servercommon.exception.ZookeeperPathNotExistException;
import com.gitee.sop.servercommon.route.GatewayPredicateDefinition;
import com.gitee.sop.servercommon.route.GatewayRouteDefinition;
import com.gitee.sop.servercommon.route.ServiceRouteInfo;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.core.env.Environment;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 上传路由到zookeeper
*
* @author tanghc
*/
@Slf4j
@Getter
@Setter
public class ServiceZookeeperApiMetaManager implements ApiMetaManager {
/** 网关对应的LoadBalance协议 */
private static final String PROTOCOL_LOAD_BALANCE = "lb://";
private static final String PATH_SPLIT = "/";
private static final String DEFAULT_CONTEXT_PATH = "/";
/**
* NameVersion=alipay.story.get1.0
* see com.gitee.sop.gatewaycommon.routeDefinition.NameVersionRoutePredicateFactory
*/
private static String QUERY_PREDICATE_DEFINITION_TPL = "NameVersion=%s";
private static ServiceApiInfo.ApiMeta FIRST_API_META = new ServiceApiInfo.ApiMeta("_first.route_", "/", "v_000");
private Environment environment;
private ZookeeperTool zookeeperTool;
private String serviceId;
public ServiceZookeeperApiMetaManager(Environment environment) {
this.environment = environment;
serviceId = environment.getProperty("spring.application.name");
if (StringUtils.isEmpty(serviceId)) {
throw new IllegalArgumentException("请在application.properties中指定spring.application.name属性");
}
this.zookeeperTool = new ZookeeperTool(environment);
this.uploadServiceId(environment);
}
/**
* 上传serviceId目录
*
* @param environment environment
*/
protected void uploadServiceId(Environment environment) {
try {
this.checkZookeeperNode(serviceId, "serviceId(" + serviceId + ")不能有斜杠字符'/'");
ServiceRouteInfo serviceRouteInfo = this.buildServiceRouteInfo();
// 保存路径
String savePath = serviceRouteInfo.getZookeeperPath();
String nodeData = JSON.toJSONString(serviceRouteInfo);
log.info("serviceId:{}, zookeeper保存路径:{}", serviceId, savePath);
this.zookeeperTool.createPath(savePath, nodeData);
this.zookeeperTool.createPath(serviceRouteInfo.getZookeeperTempServiceIdPath(), "");
} catch (Exception e) {
throw new IllegalStateException("zookeeper操作失败");
}
}
@Override
public void uploadApi(ServiceApiInfo serviceApiInfo) {
try {
ServiceRouteInfo serviceRouteInfo = this.buildServiceGatewayInfo(serviceApiInfo);
this.uploadServiceRouteInfoToZookeeper(serviceRouteInfo);
// 同时上传一个临时节点
String tempPath = serviceRouteInfo.getZookeeperTempServiceIdChildPath();
this.zookeeperTool.createOrUpdateEphemeralSequentialPath(tempPath, JSON.toJSONString(serviceRouteInfo));
} catch (Exception e) {
log.error("上传一个临时节点失败, serviceApiInfo:{}", serviceApiInfo, e);
}
}
/**
* 构建接口信息符合spring cloud gateway的格式
*
* @param serviceApiInfo 服务接口信息
* @return 返回服务路由信息
*/
protected ServiceRouteInfo buildServiceGatewayInfo(ServiceApiInfo serviceApiInfo) {
List<ServiceApiInfo.ApiMeta> apis = serviceApiInfo.getApis();
List<GatewayRouteDefinition> routeDefinitionList = new ArrayList<>(apis.size());
routeDefinitionList.add(this.buildReadBodyRouteDefinition(serviceApiInfo));
for (ServiceApiInfo.ApiMeta apiMeta : apis) {
GatewayRouteDefinition gatewayRouteDefinition = this.buildGatewayRouteDefinition(serviceApiInfo, apiMeta);
routeDefinitionList.add(gatewayRouteDefinition);
}
ServiceRouteInfo serviceRouteInfo = this.buildServiceRouteInfo();
serviceRouteInfo.setRouteDefinitionList(routeDefinitionList);
String md5 = buildMd5(routeDefinitionList);
serviceRouteInfo.setMd5(md5);
return serviceRouteInfo;
}
/**
* 构建路由id MD5
*
* @param routeDefinitionList 路由列表
* @return 返回MD5
*/
protected String buildMd5(List<GatewayRouteDefinition> routeDefinitionList) {
List<String> routeIdList = routeDefinitionList.stream()
.map(JSON::toJSONString)
.sorted()
.collect(Collectors.toList());
String md5Source = org.apache.commons.lang3.StringUtils.join(routeIdList, "");
return DigestUtils.md5DigestAsHex(md5Source.getBytes(StandardCharsets.UTF_8));
}
protected ServiceRouteInfo buildServiceRouteInfo() {
ServiceRouteInfo serviceRouteInfo = new ServiceRouteInfo();
serviceRouteInfo.setServiceId(serviceId);
String description = environment.getProperty("spring.application.description");
serviceRouteInfo.setDescription(description);
return serviceRouteInfo;
}
protected GatewayRouteDefinition buildGatewayRouteDefinition(ServiceApiInfo serviceApiInfo, ServiceApiInfo.ApiMeta apiMeta) {
GatewayRouteDefinition gatewayRouteDefinition = new GatewayRouteDefinition();
// 唯一id规则:接口名 + 版本号
String routeId = apiMeta.fetchNameVersion();
this.checkZookeeperNode(routeId, "接口定义(" + routeId + ")不能有斜杠字符'/'");
BeanUtils.copyProperties(apiMeta, gatewayRouteDefinition);
gatewayRouteDefinition.setId(routeId);
gatewayRouteDefinition.setFilters(Collections.emptyList());
gatewayRouteDefinition.setPredicates(this.buildPredicates(apiMeta));
String uri = this.buildUri(serviceApiInfo, apiMeta);
String path = this.buildServletPath(serviceApiInfo, apiMeta);
gatewayRouteDefinition.setUri(uri);
gatewayRouteDefinition.setPath(path);
return gatewayRouteDefinition;
}
protected List<GatewayPredicateDefinition> buildPredicates(ServiceApiInfo.ApiMeta apiMeta) {
GatewayPredicateDefinition gatewayPredicateDefinition = new GatewayPredicateDefinition();
gatewayPredicateDefinition.setName("ReadBody");
return Arrays.asList(gatewayPredicateDefinition, this.buildNameVersionPredicateDefinition(apiMeta));
}
protected GatewayPredicateDefinition buildNameVersionPredicateDefinition(ServiceApiInfo.ApiMeta apiMeta) {
return new GatewayPredicateDefinition(String.format(QUERY_PREDICATE_DEFINITION_TPL, apiMeta.fetchNameVersion()));
}
/**
* 添加com.gitee.sop.gatewaycommon.routeDefinition.ReadBodyRoutePredicateFactory,解决form表单获取不到问题
*
* @return 返回路由定义
*/
protected GatewayRouteDefinition buildReadBodyRouteDefinition(ServiceApiInfo serviceApiInfo) {
GatewayRouteDefinition readBodyRouteDefinition = this.buildGatewayRouteDefinition(serviceApiInfo, FIRST_API_META);
readBodyRouteDefinition.setOrder(Integer.MIN_VALUE);
readBodyRouteDefinition.setPredicates(this.buildPredicates(FIRST_API_META));
return readBodyRouteDefinition;
}
protected String buildUri(ServiceApiInfo serviceApiInfo, ServiceApiInfo.ApiMeta apiMeta) {
return PROTOCOL_LOAD_BALANCE + serviceApiInfo.getServiceId();
}
protected String buildServletPath(ServiceApiInfo serviceApiInfo, ServiceApiInfo.ApiMeta apiMeta) {
String contextPath = environment.getProperty("server.servlet.context-path", DEFAULT_CONTEXT_PATH);
String servletPath = apiMeta.getPath();
if (servletPath == null) {
servletPath = "";
}
servletPath = StringUtils.trimLeadingCharacter(servletPath, '/');
return contextPath + servletPath;
}
/**
* 上传接口信息到zookeeper
*
* @param serviceRouteInfo 路由服务信息
*/
protected void uploadServiceRouteInfoToZookeeper(ServiceRouteInfo serviceRouteInfo) {
String savePath = serviceRouteInfo.getZookeeperPath();
try {
String existServiceRouteInfoData = zookeeperTool.getData(savePath);
ServiceRouteInfo serviceRouteInfoExist = JSON.parseObject(existServiceRouteInfoData, ServiceRouteInfo.class);
String oldMD5 = serviceRouteInfoExist.getMd5();
String newMD5 = serviceRouteInfo.getMd5();
if (Objects.equals(oldMD5, newMD5)) {
log.info("接口没有改变,无需上传路由信息");
return;
}
} catch (ZookeeperPathNotExistException e) {
log.warn("服务路径不存在,path:{}", savePath);
}
try {
log.info("上传接口信息到zookeeper,path:{}, serviceId:{}, 接口数量:{}",
savePath,
serviceRouteInfo.getServiceId(),
serviceRouteInfo.getRouteDefinitionList().size());
String parentPath = this.uploadFolder(serviceRouteInfo);
this.uploadRouteItems(serviceRouteInfo, parentPath);
} catch (Exception e) {
log.error("更新接口信息到zookeeper失败, serviceId:{}", serviceRouteInfo.getServiceId(), e);
}
}
/**
* 上传文件夹内容
*
* @param serviceRouteInfo 路由服务信息
* @return 返回文件夹路径
*/
protected String uploadFolder(ServiceRouteInfo serviceRouteInfo) throws Exception {
// 保存路径
String savePath = serviceRouteInfo.getZookeeperPath();
String serviceRouteInfoJson = JSON.toJSONString(serviceRouteInfo);
log.info("上传service目录到zookeeper,路径:{},内容:{}", savePath, serviceRouteInfoJson);
this.zookeeperTool.createOrUpdateData(savePath, serviceRouteInfoJson);
return savePath;
}
/**
* 上传路由信息
*
* @param serviceRouteInfo 路由服务
* @param parentPath 父节点
* @throws Exception
*/
protected void uploadRouteItems(ServiceRouteInfo serviceRouteInfo, String parentPath) throws Exception {
List<GatewayRouteDefinition> routeDefinitionList = serviceRouteInfo.getRouteDefinitionList();
for (GatewayRouteDefinition routeDefinition : routeDefinitionList) {
// 父目录/子目录
String savePath = parentPath + PATH_SPLIT + routeDefinition.getId();
String routeDefinitionJson = JSON.toJSONString(routeDefinition);
log.info("上传路由配置到zookeeper,路径:{},路由数据:{}", savePath, routeDefinitionJson);
this.zookeeperTool.createOrUpdateData(savePath, routeDefinitionJson);
}
}
private void checkZookeeperNode(String path, String errorMsg) {
if (path.contains(PATH_SPLIT)) {
throw new IllegalArgumentException(errorMsg);
}
}
}

@ -1,7 +1,6 @@
package com.gitee.sop.servercommon.swagger;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.DigestUtils;
import com.gitee.sop.servercommon.util.OpenUtil;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -33,14 +32,7 @@ public class SwaggerValidator {
}
public boolean validate(HttpServletRequest request) {
String time = request.getParameter("time");
String sign = request.getParameter("sign");
if (StringUtils.isAnyBlank(time, sign)) {
return false;
}
String source = secret + time + secret;
String serverSign = DigestUtils.md5DigestAsHex(source.getBytes());
return serverSign.equals(sign);
return OpenUtil.validateSimpleSign(request, secret);
}
public void writeForbidden(HttpServletResponse response) throws IOException {

@ -6,6 +6,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;
import org.springframework.util.DigestUtils;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
@ -75,4 +76,14 @@ public class OpenUtil {
return retMap;
}
public static boolean validateSimpleSign(HttpServletRequest request, String secret) {
String time = request.getParameter("time");
String sign = request.getParameter("sign");
if (StringUtils.isAnyBlank(time, sign)) {
return false;
}
String source = secret + time + secret;
String serverSign = DigestUtils.md5DigestAsHex(source.getBytes());
return serverSign.equals(sign);
}
}

@ -48,6 +48,17 @@
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>0.2.2.RELEASE</version>
<exclusions>
<exclusion>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.1.0</version>
</dependency>
<!-- 注册中心end -->
@ -73,6 +84,11 @@
<artifactId>netty-all</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>25.1-jre</version>
</dependency>
<!-- swagger2 -->
<dependency>

@ -65,15 +65,6 @@
<scope>runtime</scope>
</dependency>
<!-- 注册中心【只能用一个,不用的注释掉】 -->
<!-- 使用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
@ -82,6 +73,17 @@
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>0.2.2.RELEASE</version>
<exclusions>
<exclusion>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.1.0</version>
</dependency>
<!-- 注册中心end -->

@ -1,20 +1,23 @@
package com.gitee.sop.gateway.manager;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.AbstractListener;
import com.gitee.fastmybatis.core.query.Query;
import com.gitee.sop.gateway.entity.ConfigGray;
import com.gitee.sop.gateway.entity.ConfigGrayInstance;
import com.gitee.sop.gateway.mapper.ConfigGrayInstanceMapper;
import com.gitee.sop.gateway.mapper.ConfigGrayMapper;
import com.gitee.sop.gatewaycommon.bean.ChannelMsg;
import com.gitee.sop.gatewaycommon.bean.NacosConfigs;
import com.gitee.sop.gatewaycommon.bean.ServiceGrayDefinition;
import com.gitee.sop.gatewaycommon.manager.DefaultEnvGrayManager;
import com.gitee.sop.gatewaycommon.manager.ZookeeperContext;
import com.gitee.sop.gatewaycommon.zuul.loadbalancer.ServiceGrayConfig;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.alibaba.nacos.NacosConfigProperties;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
@ -50,6 +53,9 @@ public class DbEnvGrayManager extends DefaultEnvGrayManager {
@Autowired
private ConfigGrayInstanceMapper configGrayInstanceMapper;
@Autowired
private NacosConfigProperties nacosConfigProperties;
@Override
public void load() {
@ -98,7 +104,7 @@ public class DbEnvGrayManager extends DefaultEnvGrayManager {
@PostConstruct
protected void after() throws Exception {
ZookeeperContext.setEnvironment(environment);
/*ZookeeperContext.setEnvironment(environment);
String isvChannelPath = ZookeeperContext.getServiceGrayChannelPath();
ZookeeperContext.listenPath(isvChannelPath, nodeCache -> {
String nodeData = new String(nodeCache.getCurrentData().getData());
@ -120,6 +126,31 @@ public class DbEnvGrayManager extends DefaultEnvGrayManager {
default:
}
});*/
// nacos
ConfigService configService = nacosConfigProperties.configServiceInstance();
configService.addListener(NacosConfigs.DATA_ID_GRAY, NacosConfigs.GROUP_CHANNEL, new AbstractListener() {
@Override
public void receiveConfigInfo(String configInfo) {
ChannelMsg channelMsg = JSON.parseObject(configInfo, ChannelMsg.class);
String data = channelMsg.getData();
ServiceGrayDefinition userKeyDefinition = JSON.parseObject(data, ServiceGrayDefinition.class);
String serviceId = userKeyDefinition.getServiceId();
switch (channelMsg.getOperation()) {
case "set":
ConfigGray configGray = configGrayMapper.getByColumn("service_id", serviceId);
setServiceGrayConfig(configGray);
break;
case "open":
openGray(userKeyDefinition.getInstanceId(), serviceId);
break;
case "close":
closeGray(userKeyDefinition.getInstanceId());
break;
default:
}
}
});
}

@ -1,13 +1,16 @@
package com.gitee.sop.gateway.manager;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.AbstractListener;
import com.gitee.sop.gateway.mapper.IPBlacklistMapper;
import com.gitee.sop.gatewaycommon.bean.ChannelMsg;
import com.gitee.sop.gatewaycommon.bean.NacosConfigs;
import com.gitee.sop.gatewaycommon.manager.DefaultIPBlacklistManager;
import com.gitee.sop.gatewaycommon.manager.ZookeeperContext;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.alibaba.nacos.NacosConfigProperties;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
@ -29,6 +32,9 @@ public class DbIPBlacklistManager extends DefaultIPBlacklistManager {
@Autowired
Environment environment;
@Autowired
private NacosConfigProperties nacosConfigProperties;
@Override
public void load() {
List<String> ipList = ipBlacklistMapper.listAllIP();
@ -39,7 +45,7 @@ public class DbIPBlacklistManager extends DefaultIPBlacklistManager {
@PostConstruct
protected void after() throws Exception {
ZookeeperContext.setEnvironment(environment);
/*ZookeeperContext.setEnvironment(environment);
String path = ZookeeperContext.getIpBlacklistChannelPath();
ZookeeperContext.listenPath(path, nodeCache -> {
String nodeData = new String(nodeCache.getCurrentData().getData());
@ -57,6 +63,27 @@ public class DbIPBlacklistManager extends DefaultIPBlacklistManager {
break;
default:
}
});*/
// nacos
ConfigService configService = nacosConfigProperties.configServiceInstance();
configService.addListener(NacosConfigs.DATA_ID_IP_BLACKLIST, NacosConfigs.GROUP_CHANNEL, new AbstractListener() {
@Override
public void receiveConfigInfo(String configInfo) {
ChannelMsg channelMsg = JSON.parseObject(configInfo, ChannelMsg.class);
final IPDto ipDto = JSON.parseObject(channelMsg.getData(), IPDto.class);
String ip = ipDto.getIp();
switch (channelMsg.getOperation()) {
case "add":
log.info("添加IP黑名单,ip:{}", ip);
add(ip);
break;
case "delete":
log.info("移除IP黑名单,ip:{}", ip);
remove(ip);
break;
default:
}
}
});
}

@ -1,16 +1,19 @@
package com.gitee.sop.gateway.manager;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.AbstractListener;
import com.gitee.sop.gateway.entity.IsvDetailDTO;
import com.gitee.sop.gateway.mapper.IsvInfoMapper;
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
import com.gitee.sop.gatewaycommon.bean.ChannelMsg;
import com.gitee.sop.gatewaycommon.bean.IsvDefinition;
import com.gitee.sop.gatewaycommon.manager.ZookeeperContext;
import com.gitee.sop.gatewaycommon.bean.NacosConfigs;
import com.gitee.sop.gatewaycommon.secret.CacheIsvManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.alibaba.nacos.NacosConfigProperties;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
@ -30,6 +33,8 @@ public class DbIsvManager extends CacheIsvManager {
@Autowired
Environment environment;
@Autowired
private NacosConfigProperties nacosConfigProperties;
@Override
public void load() {
@ -45,7 +50,7 @@ public class DbIsvManager extends CacheIsvManager {
@PostConstruct
protected void after() throws Exception {
ApiConfig.getInstance().setIsvManager(this);
ZookeeperContext.setEnvironment(environment);
/*ZookeeperContext.setEnvironment(environment);
String isvChannelPath = ZookeeperContext.getIsvInfoChannelPath();
ZookeeperContext.listenPath(isvChannelPath, nodeCache -> {
String nodeData = new String(nodeCache.getCurrentData().getData());
@ -63,6 +68,27 @@ public class DbIsvManager extends CacheIsvManager {
default:
}
});*/
ConfigService configService = nacosConfigProperties.configServiceInstance();
configService.addListener(NacosConfigs.DATA_ID_ISV, NacosConfigs.GROUP_CHANNEL, new AbstractListener() {
@Override
public void receiveConfigInfo(String configInfo) {
ChannelMsg channelMsg = JSON.parseObject(configInfo, ChannelMsg.class);
final IsvDefinition isvDefinition = JSON.parseObject(channelMsg.getData(), IsvDefinition.class);
switch (channelMsg.getOperation()) {
case "update":
log.info("更新ISV信息,isvDefinition:{}", isvDefinition);
update(isvDefinition);
break;
case "remove":
log.info("删除ISV,isvDefinition:{}", isvDefinition);
remove(isvDefinition.getAppKey());
break;
default:
}
}
});
}

@ -1,6 +1,8 @@
package com.gitee.sop.gateway.manager;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.AbstractListener;
import com.gitee.fastmybatis.core.query.Query;
import com.gitee.sop.gateway.entity.IsvInfo;
import com.gitee.sop.gateway.entity.PermIsvRole;
@ -10,12 +12,13 @@ import com.gitee.sop.gateway.mapper.PermIsvRoleMapper;
import com.gitee.sop.gateway.mapper.PermRolePermissionMapper;
import com.gitee.sop.gatewaycommon.bean.ChannelMsg;
import com.gitee.sop.gatewaycommon.bean.IsvRoutePermission;
import com.gitee.sop.gatewaycommon.bean.NacosConfigs;
import com.gitee.sop.gatewaycommon.manager.DefaultIsvRoutePermissionManager;
import com.gitee.sop.gatewaycommon.manager.ZookeeperContext;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.alibaba.nacos.NacosConfigProperties;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
@ -51,6 +54,9 @@ public class DbIsvRoutePermissionManager extends DefaultIsvRoutePermissionManage
@Autowired
IsvInfoMapper isvInfoMapper;
@Autowired
private NacosConfigProperties nacosConfigProperties;
@Override
public void load() {
// key: appKey, value: roleCodeList
@ -121,7 +127,7 @@ public class DbIsvRoutePermissionManager extends DefaultIsvRoutePermissionManage
@PostConstruct
protected void after() throws Exception {
ZookeeperContext.setEnvironment(environment);
/*ZookeeperContext.setEnvironment(environment);
String isvChannelPath = ZookeeperContext.getIsvRoutePermissionChannelPath();
ZookeeperContext.listenPath(isvChannelPath, nodeCache -> {
String nodeData = new String(nodeCache.getCurrentData().getData());
@ -155,6 +161,36 @@ public class DbIsvRoutePermissionManager extends DefaultIsvRoutePermissionManage
default:
}
});*/
ConfigService configService = nacosConfigProperties.configServiceInstance();
configService.addListener(NacosConfigs.DATA_ID_ROUTE_PERMISSION, NacosConfigs.GROUP_CHANNEL, new AbstractListener() {
@Override
public void receiveConfigInfo(String configInfo) {
ChannelMsg channelMsg = JSON.parseObject(configInfo, ChannelMsg.class);
final IsvRoutePermission isvRoutePermission = JSON.parseObject(channelMsg.getData(), IsvRoutePermission.class);
switch (channelMsg.getOperation()) {
case "reload":
log.info("重新加载路由权限信息,isvRoutePermission:{}", isvRoutePermission);
String listenPath = isvRoutePermission.getListenPath();
try {
load();
} catch (Exception e) {
log.error("重新加载路由权限失败, channelMsg:{}", channelMsg, e);
}
break;
case "update":
log.info("更新ISV路由权限信息,isvRoutePermission:{}", isvRoutePermission);
update(isvRoutePermission);
break;
case "remove":
log.info("删除ISV路由权限信息,isvRoutePermission:{}", isvRoutePermission);
remove(isvRoutePermission.getAppKey());
break;
default:
}
}
});
}
}

@ -1,15 +1,18 @@
package com.gitee.sop.gateway.manager;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.AbstractListener;
import com.gitee.fastmybatis.core.query.Query;
import com.gitee.sop.gateway.mapper.ConfigLimitMapper;
import com.gitee.sop.gatewaycommon.bean.ChannelMsg;
import com.gitee.sop.gatewaycommon.bean.ConfigLimitDto;
import com.gitee.sop.gatewaycommon.bean.NacosConfigs;
import com.gitee.sop.gatewaycommon.manager.DefaultLimitConfigManager;
import com.gitee.sop.gatewaycommon.manager.ZookeeperContext;
import com.gitee.sop.gatewaycommon.util.MyBeanUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.alibaba.nacos.NacosConfigProperties;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
@ -29,6 +32,9 @@ public class DbLimitConfigManager extends DefaultLimitConfigManager {
@Autowired
Environment environment;
@Autowired
private NacosConfigProperties nacosConfigProperties;
@Override
public void load() {
Query query = new Query();
@ -47,7 +53,7 @@ public class DbLimitConfigManager extends DefaultLimitConfigManager {
@PostConstruct
protected void after() throws Exception {
ZookeeperContext.setEnvironment(environment);
/*ZookeeperContext.setEnvironment(environment);
String path = ZookeeperContext.getLimitConfigChannelPath();
ZookeeperContext.listenPath(path, nodeCache -> {
String nodeData = new String(nodeCache.getCurrentData().getData());
@ -64,6 +70,26 @@ public class DbLimitConfigManager extends DefaultLimitConfigManager {
break;
default:
}
});*/
ConfigService configService = nacosConfigProperties.configServiceInstance();
configService.addListener(NacosConfigs.DATA_ID_LIMIT_CONFIG, NacosConfigs.GROUP_CHANNEL, new AbstractListener() {
@Override
public void receiveConfigInfo(String configInfo) {
ChannelMsg channelMsg = JSON.parseObject(configInfo, ChannelMsg.class);
final ConfigLimitDto configLimitDto = JSON.parseObject(channelMsg.getData(), ConfigLimitDto.class);
switch (channelMsg.getOperation()) {
case "reload":
log.info("重新加载限流配置信息,configLimitDto:{}", configLimitDto);
load();
break;
case "update":
log.info("更新限流配置信息,configLimitDto:{}", configLimitDto);
update(configLimitDto);
break;
default:
}
}
});
}

@ -1,18 +1,21 @@
package com.gitee.sop.gateway.manager;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.AbstractListener;
import com.gitee.fastmybatis.core.query.Query;
import com.gitee.sop.gateway.mapper.ConfigRouteBaseMapper;
import com.gitee.sop.gateway.mapper.ConfigRouteLimitMapper;
import com.gitee.sop.gatewaycommon.bean.BaseRouteDefinition;
import com.gitee.sop.gatewaycommon.bean.ChannelMsg;
import com.gitee.sop.gatewaycommon.bean.GatewayRouteDefinition;
import com.gitee.sop.gatewaycommon.bean.NacosConfigs;
import com.gitee.sop.gatewaycommon.bean.RouteConfig;
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
import com.gitee.sop.gatewaycommon.manager.DefaultRouteConfigManager;
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
import com.gitee.sop.gatewaycommon.manager.ZookeeperContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.alibaba.nacos.NacosConfigProperties;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
@ -35,6 +38,9 @@ public class DbRouteConfigManager extends DefaultRouteConfigManager {
@Autowired
Environment environment;
@Autowired
private NacosConfigProperties nacosConfigProperties;
@Override
public void load() {
loadAllRoute();
@ -52,12 +58,12 @@ public class DbRouteConfigManager extends DefaultRouteConfigManager {
Collection<? extends TargetRoute> targetRoutes = RouteRepositoryContext.getRouteRepository().getAll();
targetRoutes.stream()
.forEach(targetRoute -> {
BaseRouteDefinition routeDefinition = targetRoute.getRouteDefinition();
GatewayRouteDefinition routeDefinition = targetRoute.getRouteDefinition();
initRouteConfig(routeDefinition);
});
}
protected void initRouteConfig(BaseRouteDefinition routeDefinition) {
protected void initRouteConfig(GatewayRouteDefinition routeDefinition) {
String routeId = routeDefinition.getId();
RouteConfig routeConfig = newRouteConfig();
routeConfig.setRouteId(routeId);
@ -71,7 +77,7 @@ public class DbRouteConfigManager extends DefaultRouteConfigManager {
@PostConstruct
protected void after() throws Exception {
ZookeeperContext.setEnvironment(environment);
/*ZookeeperContext.setEnvironment(environment);
String path = ZookeeperContext.getRouteConfigChannelPath();
ZookeeperContext.listenPath(path, nodeCache -> {
String nodeData = new String(nodeCache.getCurrentData().getData());
@ -88,6 +94,26 @@ public class DbRouteConfigManager extends DefaultRouteConfigManager {
break;
default:
}
});*/
ConfigService configService = nacosConfigProperties.configServiceInstance();
configService.addListener(NacosConfigs.DATA_ID_ROUTE_CONFIG, NacosConfigs.GROUP_CHANNEL, new AbstractListener() {
@Override
public void receiveConfigInfo(String configInfo) {
ChannelMsg channelMsg = JSON.parseObject(configInfo, ChannelMsg.class);
final RouteConfig routeConfig = JSON.parseObject(channelMsg.getData(), RouteConfig.class);
switch (channelMsg.getOperation()) {
case "reload":
log.info("重新加载路由配置信息,routeConfigDto:{}", routeConfig);
load();
break;
case "update":
log.info("更新路由配置信息,routeConfigDto:{}", routeConfig);
update(routeConfig);
break;
default:
}
}
});
}
}

@ -1,19 +0,0 @@
package com.gitee.sop.gateway.manager;
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
/**
* @author tanghc
*/
public class ManagerInitializer {
public ManagerInitializer() {
ApiConfig apiConfig = ApiConfig.getInstance();
apiConfig.setIsvManager(new DbIsvManager());
apiConfig.setIsvRoutePermissionManager(new DbIsvRoutePermissionManager());
apiConfig.setRouteConfigManager(new DbRouteConfigManager());
apiConfig.setLimitConfigManager(new DbLimitConfigManager());
apiConfig.setIpBlacklistManager(new DbIPBlacklistManager());
apiConfig.setUserKeyManager(new DbEnvGrayManager());
}
}

@ -37,7 +37,7 @@ ribbon.ReadTimeout=2000
# 请谨慎设置,因为post请求大多都是写入请求,如果要支持重试,确保服务的幂等性
ribbon.OkToRetryOnAllOperations=false
# nacos注册中心配置
# nacos cloud配置
spring.cloud.nacos.discovery.server-addr=${nacos.url}
# zookeeper配置

@ -0,0 +1,3 @@
spring.application.name=api-gateway
# nacos的config需要放在bootstrap.properties中
spring.cloud.nacos.config.server-addr=127.0.0.1:8848

@ -1,52 +0,0 @@
package com.gitee.sop.gateway;
import com.gitee.sop.gatewaycommon.bean.SopConstants;
import junit.framework.TestCase;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
/**
* @author tanghc
*/
public class CuratorTest extends TestCase {
private String zookeeperServerAddr = "localhost:2181";
/**
* 递归删除节点只能在测试环境用
*
* @throws Exception
*/
public void testDel() throws Exception {
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString(zookeeperServerAddr)
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
client.start();
try {
client.delete().deletingChildrenIfNeeded().forPath(SopConstants.SOP_SERVICE_ROUTE_PATH);
} catch (Exception e) {
}
}
/**
* 删除临时节点
*/
public void testDelTemp() {
String tempRoot = "/com.gitee.sop.service.tmp";
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString(zookeeperServerAddr)
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
client.start();
try {
client.delete().deletingChildrenIfNeeded().forPath(tempRoot);
} catch (Exception e) {
}
}
}

@ -12,20 +12,17 @@
<artifactId>website-server</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>website-server</name>
<description>Demo project for Spring Boot</description>
<description>website-server</description>
<properties>
<java.version>1.8</java.version>
<zookeeper.version>3.4.12</zookeeper.version>
<curator-recipes.version>4.0.1</curator-recipes.version>
</properties>
<dependencies>
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-registry-api</artifactId>
<version>1.15.2-SNAPSHOT</version>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
<version>0.9.0.RELEASE</version>
</dependency>
<dependency>
@ -33,38 +30,6 @@
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- zookeeper客户端针对zookeeper-3.4.x
如果zookeeper使用3.5.x,可以直接使用curator-recipes最高版本
详情:http://curator.apache.org/zk-compatibility.html
-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>${curator-recipes.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>${zookeeper.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
@ -77,12 +42,7 @@
<version>3.14.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
@ -94,6 +54,19 @@
<artifactId>httpclient</artifactId>
<version>4.5.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>

@ -0,0 +1,381 @@
package com.gitee.sop.websiteserver.bean;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.FormBody;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.apache.commons.codec.digest.DigestUtils;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* http请求工具基于OKHTTP3
* @author tanghc
*/
public class HttpTool {
private static final MediaType MEDIA_TYPE_JSON = MediaType.parse("application/json; charset=utf-8");
private Map<String, List<Cookie>> cookieStore = new HashMap<String, List<Cookie>>();
private OkHttpClient httpClient;
public HttpTool() {
this(new HttpToolConfig());
}
public HttpTool(HttpToolConfig httpToolConfig) {
this.initHttpClient(httpToolConfig);
}
protected void initHttpClient(HttpToolConfig httpToolConfig) {
httpClient = new OkHttpClient.Builder()
.connectTimeout(httpToolConfig.connectTimeoutSeconds, TimeUnit.SECONDS) // 设置链接超时时间,默认10秒
.readTimeout(httpToolConfig.readTimeoutSeconds, TimeUnit.SECONDS)
.writeTimeout(httpToolConfig.writeTimeoutSeconds, TimeUnit.SECONDS)
.cookieJar(new CookieJar() {
public void saveFromResponse(HttpUrl httpUrl, List<Cookie> list) {
cookieStore.put(httpUrl.host(), list);
}
public List<Cookie> loadForRequest(HttpUrl httpUrl) {
List<Cookie> cookies = cookieStore.get(httpUrl.host());
return cookies != null ? cookies : new ArrayList<Cookie>();
}
}).build();
}
@Data
public static class HttpToolConfig {
/**
* 请求超时时间
*/
private int connectTimeoutSeconds = 10;
/**
* http读取超时时间
*/
private int readTimeoutSeconds = 10;
/**
* http写超时时间
*/
private int writeTimeoutSeconds = 10;
}
/**
* get请求
*
* @param url
* @param header
* @return
* @throws IOException
*/
public String get(String url, Map<String, String> header) throws IOException {
Request.Builder builder = new Request.Builder().url(url).get();
// 添加header
addHeader(builder, header);
Request request = builder.build();
Response response = httpClient.newCall(request).execute();
return response.body().string();
}
/**
* 提交表单
*
* @param url url
* @param form 参数
* @param header header
* @param method 请求方式postget等
* @return
* @throws IOException
*/
public String request(String url, Map<String, ?> form, Map<String, String> header, HTTPMethod method) throws IOException {
Request.Builder requestBuilder = buildRequestBuilder(url, form, method);
// 添加header
addHeader(requestBuilder, header);
Request request = requestBuilder.build();
Response response = httpClient
.newCall(request)
.execute();
try {
return response.body().string();
} finally {
response.close();
}
}
/**
* 请求json数据contentType=application/json
* @param url 请求路径
* @param json json数据
* @param header header
* @return 返回响应结果
* @throws IOException
*/
public String requestJson(String url, String json, Map<String, String> header) throws IOException {
RequestBody body = RequestBody.create(MEDIA_TYPE_JSON, json);
Request.Builder requestBuilder = new Request.Builder()
.url(url)
.post(body);
// 添加header
addHeader(requestBuilder, header);
Request request = requestBuilder.build();
Response response = httpClient
.newCall(request)
.execute();
try {
return response.body().string();
} finally {
response.close();
}
}
public static Request.Builder buildRequestBuilder(String url, Map<String, ?> form, HTTPMethod method) {
switch (method) {
case GET:
return new Request.Builder()
.url(buildHttpUrl(url, form))
.get();
case HEAD:
return new Request.Builder()
.url(buildHttpUrl(url, form))
.head();
case PUT:
return new Request.Builder()
.url(url)
.put(buildFormBody(form));
case DELETE:
return new Request.Builder()
.url(url)
.delete(buildFormBody(form));
default:
return new Request.Builder()
.url(url)
.post(buildFormBody(form));
}
}
public static HttpUrl buildHttpUrl(String url, Map<String, ?> form) {
HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
for (Map.Entry<String, ?> entry : form.entrySet()) {
urlBuilder.addQueryParameter(entry.getKey(), String.valueOf(entry.getValue()));
}
return urlBuilder.build();
}
public static FormBody buildFormBody(Map<String, ?> form) {
FormBody.Builder paramBuilder = new FormBody.Builder(StandardCharsets.UTF_8);
for (Map.Entry<String, ?> entry : form.entrySet()) {
paramBuilder.add(entry.getKey(), String.valueOf(entry.getValue()));
}
return paramBuilder.build();
}
/**
* 提交表单并且上传文件
*
* @param url
* @param form
* @param header
* @param files
* @return
* @throws IOException
*/
public String requestFile(String url, Map<String, String> form, Map<String, String> header, List<UploadFile> files)
throws IOException {
// 创建MultipartBody.Builder,用于添加请求的数据
MultipartBody.Builder bodyBuilder = new MultipartBody.Builder();
bodyBuilder.setType(MultipartBody.FORM);
for (UploadFile uploadFile : files) {
bodyBuilder.addFormDataPart(uploadFile.getName(), // 请求的名字
uploadFile.getFileName(), // 文件的文字,服务器端用来解析的
RequestBody.create(null, uploadFile.getFileData()) // 创建RequestBody,把上传的文件放入
);
}
Set<Map.Entry<String, String>> entrySet = form.entrySet();
for (Map.Entry<String, String> entry : entrySet) {
bodyBuilder.addFormDataPart(entry.getKey(), entry.getValue());
}
RequestBody requestBody = bodyBuilder.build();
Request.Builder builder = new Request.Builder().url(url).post(requestBody);
// 添加header
addHeader(builder, header);
Request request = builder.build();
Response response = httpClient.newCall(request).execute();
try {
return response.body().string();
} finally {
response.close();
}
}
private void addHeader(Request.Builder builder, Map<String, String> header) {
if (header != null) {
Set<Map.Entry<String, String>> entrySet = header.entrySet();
for (Map.Entry<String, String> entry : entrySet) {
builder.addHeader(entry.getKey(), String.valueOf(entry.getValue()));
}
}
}
public void setCookieStore(Map<String, List<Cookie>> cookieStore) {
this.cookieStore = cookieStore;
}
public void setHttpClient(OkHttpClient httpClient) {
this.httpClient = httpClient;
}
public enum HTTPMethod {
GET,
POST,
PUT,
HEAD,
DELETE;
private HTTPMethod() {
}
public String value() {
return this.name();
}
public static HTTPMethod fromValue(String v) {
return valueOf(v.toUpperCase());
}
}
/**
* 文件上传类
* @author tanghc
*/
@Getter
@Setter
public static class UploadFile implements Serializable {
private static final long serialVersionUID = -1100614660944996398L;
/**
* @param name 表单名称不能重复
* @param file 文件
* @throws IOException
*/
public UploadFile(String name, File file) throws IOException {
this(name, file.getName(), FileUtil.toBytes(file));
}
/**
* @param name 表单名称不能重复
* @param fileName 文件名
* @param input 文件流
* @throws IOException
*/
public UploadFile(String name, String fileName, InputStream input) throws IOException {
this(name, fileName, FileUtil.toBytes(input));
}
/**
* @param name 表单名称不能重复
* @param fileName 文件名
* @param fileData 文件数据
*/
public UploadFile(String name, String fileName, byte[] fileData) {
super();
this.name = name;
this.fileName = fileName;
this.fileData = fileData;
this.md5 = DigestUtils.md5Hex(fileData);
}
private String name;
private String fileName;
private byte[] fileData;
private String md5;
}
public static class FileUtil {
/**
* The default buffer size to use.
*/
private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
private static final int EOF = -1;
/**
* 将文件流转换成byte[]
* @param input
* @return
* @throws IOException
*/
public static byte[] toBytes(InputStream input) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
int n = 0;
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
while (EOF != (n = input.read(buffer))) {
output.write(buffer, 0, n);
}
return output.toByteArray();
}
/**
* 将文件转换成数据流
* @param file 文件
* @return 返回数据流
* @throws IOException
*/
public static byte[] toBytes(File file) throws IOException {
if (file.exists()) {
if (file.isDirectory()) {
throw new IOException("File '" + file + "' exists but is a directory");
}
if (file.canRead() == false) {
throw new IOException("File '" + file + "' cannot be read");
}
} else {
throw new FileNotFoundException("File '" + file + "' does not exist");
}
InputStream input = null;
try {
input = new FileInputStream(file);
return toBytes(input);
} finally {
try {
if (input != null) {
input.close();
}
} catch (IOException ioe) {
}
}
}
}
}

@ -1,179 +0,0 @@
package com.gitee.sop.websiteserver.bean;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.framework.recipes.cache.TreeCache;
import org.apache.curator.framework.recipes.cache.TreeCacheListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.function.Consumer;
/**
* @author tanghc
*/
@Slf4j
public class ZookeeperContext {
private static CuratorFramework client;
public static void setEnvironment(Environment environment) {
Assert.notNull(environment, "environment不能为null");
initZookeeperClient(environment);
}
public synchronized static void initZookeeperClient(Environment environment) {
if (client != null) {
return;
}
String zookeeperServerAddr = environment.getProperty("spring.cloud.zookeeper.connect-string");
if (StringUtils.isBlank(zookeeperServerAddr)) {
throw new RuntimeException("未指定spring.cloud.zookeeper.connect-string参数");
}
String baseSleepTimeMs = environment.getProperty("spring.cloud.zookeeper.baseSleepTimeMs");
String maxRetries = environment.getProperty("spring.cloud.zookeeper.maxRetries");
log.info("初始化zookeeper客户端,zookeeperServerAddr:{}, baseSleepTimeMs:{}, maxRetries:{}",
zookeeperServerAddr, baseSleepTimeMs, maxRetries);
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString(zookeeperServerAddr)
.retryPolicy(new ExponentialBackoffRetry(NumberUtils.toInt(baseSleepTimeMs, 3000), NumberUtils.toInt(maxRetries, 3)))
.build();
client.start();
setClient(client);
}
public static String getRouteRootPath() {
return WebsiteConstants.SOP_SERVICE_ROUTE_PATH;
}
public static String getServiceTempRootPath() {
return WebsiteConstants.SOP_SERVICE_TEMP_PATH;
}
public static CuratorFramework getClient() {
return client;
}
public static void setClient(CuratorFramework client) {
ZookeeperContext.client = client;
}
public static boolean isPathExist(String path) {
try {
return client.checkExists().forPath(path) != null;
} catch (Exception e) {
return false;
}
}
/**
* 是否有子节点
* @param parentPath
* @return
* @throws Exception
*/
public static boolean hasChildren(String parentPath) throws Exception {
List<String> children = client.getChildren().forPath(parentPath);
return !CollectionUtils.isEmpty(children);
}
/**
* 创建path如果path存在不报错静默返回path名称
*
* @param path
* @param data
* @return
* @throws Exception
*/
public static String createPath(String path, String data) throws Exception {
if (isPathExist(path)) {
return path;
}
return getClient().create()
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
.creatingParentContainersIfNeeded()
.forPath(path, data.getBytes());
}
/**
* 获取子节点信息并监听子节点
*
* @param parentPath 父节点路径
* @param listConsumer 子节点数据
* @param listener 监听事件
* @throws Exception
*/
public static void getChildrenAndListen(String parentPath, Consumer<List<ChildData>> listConsumer, PathChildrenCacheListener listener) throws Exception {
// 为子节点添加watcher
// PathChildrenCache: 监听数据节点的增删改,可以设置触发的事件
PathChildrenCache childrenCache = new PathChildrenCache(client, parentPath, true);
/**
* StartMode: 初始化方式
* POST_INITIALIZED_EVENT异步初始化初始化之后会触发事件
* NORMAL异步初始化
* BUILD_INITIAL_CACHE同步初始化
*/
childrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
// 列出子节点数据列表,需要使用BUILD_INITIAL_CACHE同步初始化模式才能获得,异步是获取不到的
List<ChildData> childDataList = childrenCache.getCurrentData();
listConsumer.accept(childDataList);
log.info("监听子节点增删改,监听路径:{}", parentPath);
// 监听根节点下面的子节点
childrenCache.getListenable().addListener(listener);
}
/**
* 监听子节点的增删改
*
* @param parentPath 父节点路径
* @param listener
* @throws Exception
*/
public static void listenChildren(String parentPath, PathChildrenCacheListener listener) throws Exception {
// 为子节点添加watcher
// PathChildrenCache: 监听数据节点的增删改,可以设置触发的事件
PathChildrenCache childrenCache = new PathChildrenCache(client, parentPath, true);
/**
* StartMode: 初始化方式
* POST_INITIALIZED_EVENT异步初始化初始化之后会触发事件
* NORMAL异步初始化
* BUILD_INITIAL_CACHE同步初始化
*/
childrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
// 监听根节点下面的子节点
childrenCache.getListenable().addListener(listener);
}
/**
* 监听子节点可以自定义层级
* @param parentPath 父节点路径
* @param maxDepth 层级从1开始比如当前监听节点/t1目录最深为/t1/t2/t3/t4,则maxDepth=3,说明下面3级子目录全部监听
* @param listener
* @throws Exception
*/
public static void listenChildren(String parentPath, int maxDepth, TreeCacheListener listener) throws Exception {
final TreeCache treeCache = TreeCache
.newBuilder(client, parentPath)
.setCacheData(true)
.setMaxDepth(maxDepth)
.build();
treeCache.getListenable().addListener(listener);
treeCache.start();
}
}

@ -1,12 +0,0 @@
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 {
}

@ -40,6 +40,6 @@ public class WebsiteConfig implements ApplicationRunner {
*/
@Override
public void run(ApplicationArguments args) throws Exception {
docManager.load(null);
// docManager.load(null);
}
}

@ -4,7 +4,6 @@ import com.gitee.sop.websiteserver.bean.DocInfo;
import com.gitee.sop.websiteserver.manager.DocManager;
import com.gitee.sop.websiteserver.vo.DocBaseInfoVO;
import com.gitee.sop.websiteserver.vo.DocInfoVO;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@ -58,14 +57,4 @@ public class DocController {
return docManager.getByTitle(title);
}
// 后门地址,可手动更新文档内容,一般情况下用不到
@GetMapping("/reload")
public String reload(String pwd) {
boolean correct = StringUtils.equals(this.pwd, pwd);
if (correct) {
docManager.load(null);
}
return String.valueOf(correct);
}
}

@ -1,7 +1,6 @@
package com.gitee.sop.websiteserver.controller;
import com.gitee.sop.registryapi.bean.HttpTool;
import com.gitee.sop.registryapi.bean.HttpTool.*;
import com.gitee.sop.websiteserver.bean.HttpTool;
import com.gitee.sop.websiteserver.sign.AlipayApiException;
import com.gitee.sop.websiteserver.sign.AlipaySignature;
import com.gitee.sop.websiteserver.util.UploadUtil;
@ -97,10 +96,10 @@ public class SandboxController {
params.put("sign", sign);
Collection<MultipartFile> uploadFiles = UploadUtil.getUploadFiles(request);
List<UploadFile> files = uploadFiles.stream()
List<HttpTool.UploadFile> files = uploadFiles.stream()
.map(multipartFile -> {
try {
return new UploadFile(multipartFile.getName(), multipartFile.getOriginalFilename(), multipartFile.getBytes());
return new HttpTool.UploadFile(multipartFile.getName(), multipartFile.getOriginalFilename(), multipartFile.getBytes());
} catch (IOException e) {
log.error("封装文件失败", e);
return null;
@ -114,7 +113,7 @@ public class SandboxController {
if (!CollectionUtils.isEmpty(files)) {
responseData = httpTool.requestFile(url, params, Collections.emptyMap(), files);
} else {
responseData = httpTool.request(url, params, Collections.emptyMap(), HTTPMethod.fromValue(httpMethod));
responseData = httpTool.request(url, params, Collections.emptyMap(), HttpTool.HTTPMethod.fromValue(httpMethod));
}
result.apiResult = responseData;
return result;

@ -0,0 +1,99 @@
package com.gitee.sop.websiteserver.manager;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.naming.pojo.ServiceInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.alibaba.nacos.NacosDiscoveryProperties;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DigestUtils;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.Objects;
/**
* @author tanghc
*/
@Slf4j
@Service
public class DocDiscovery {
private static final String SECRET = "b749a2ec000f4f29";
private static final int FIVE_SECONDS = 1000 * 5;
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
private RestTemplate restTemplate = new RestTemplate();
private volatile long lastUpdateTime;
public synchronized void refresh(DocManager docManager) {
long now = System.currentTimeMillis();
// 5秒内只能执行一次,解决重启应用连续加载4次问题
if (now - lastUpdateTime < FIVE_SECONDS) {
return;
}
lastUpdateTime = now;
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
List<ServiceInfo> subscribes = null;
try {
subscribes = namingService.getSubscribeServices();
} catch (NacosException e) {
log.error("namingService.getSubscribeServices()错误", e);
}
if (CollectionUtils.isEmpty(subscribes)) {
return;
}
// subscribe
String thisServiceId = nacosDiscoveryProperties.getService();
for (ServiceInfo serviceInfo : subscribes) {
String serviceName = serviceInfo.getName();
// 如果是本机服务,跳过
if (Objects.equals(thisServiceId, serviceName) || "api-gateway".equalsIgnoreCase(serviceName)) {
continue;
}
try {
List<Instance> allInstances = namingService.getAllInstances(serviceName);
if (CollectionUtils.isEmpty(allInstances)) {
log.info("{}服务下线,删除文档信息", serviceName);
// 如果没有服务列表,则删除所有路由信息
docManager.remove(serviceName);
} else {
for (Instance instance : allInstances) {
log.info("加载服务文档,instance:{}", instance);
String url = getRouteRequestUrl(instance);
ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
if (responseEntity.getStatusCode() == HttpStatus.OK) {
String body = responseEntity.getBody();
log.debug("加载{}文档,文档信息:{}", serviceName, body);
docManager.addDocInfo(serviceName, body);
}
}
}
} catch (NacosException e) {
log.error("选择服务实例失败,serviceName:{}", serviceName, e);
}
}
}
private static String getRouteRequestUrl(Instance instance) {
String query = buildQuery(SECRET);
return "http://" + instance.getIp() + ":" + instance.getPort() + "/v2/api-docs" + query;
}
private static String buildQuery(String secret) {
String time = String.valueOf(System.currentTimeMillis());
String source = secret + time + secret;
String sign = DigestUtils.md5DigestAsHex(source.getBytes());
return "?time=" + time + "&sign=" + sign;
}
}

@ -9,9 +9,11 @@ import java.util.Collection;
*/
public interface DocManager {
void load(String serviceId);
void addDocInfo(String serviceId, String docJson);
DocInfo getByTitle(String title);
Collection<DocInfo> listAll();
void remove(String serviceId);
}

@ -3,119 +3,54 @@ package com.gitee.sop.websiteserver.manager;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
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.ZookeeperContext;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.Objects;
/**
* @author tanghc
*/
@Service
@Slf4j
public class DocManagerImpl implements DocManager {
public class DocManagerImpl implements DocManager, ApplicationListener<HeartbeatEvent> {
// key:title
private Map<String, DocInfo> docDefinitionMap = new HashMap<>();
private RestTemplate restTemplate = new RestTemplate();
/**
* KEY:serviceId, value: md5
*/
private Map<String, String> serviceIdMd5Map = new HashMap<>();
private DocParser swaggerDocParser = new SwaggerDocParser();
private DocParser easyopenDocParser = new EasyopenDocParser();
private ExecutorService executorService = Executors.newSingleThreadExecutor();
private DelayQueue<Msg> queue = new DelayQueue<>();
private String secret = "b749a2ec000f4f29";
@Autowired
private Environment environment;
@Autowired
private RegistryService registryService;
@Value("${doc.refresh-seconds:60}")
private String refreshSeconds;
private DocDiscovery docDiscovery;
@Override
public void load(String serviceId) {
try {
List<ServiceInfo> serviceInfoList = registryService.listAllService(1, 9999);
serviceInfoList
.stream()
// 网关没有文档提供,需要排除
.filter(serviceInfo -> !"API-GATEWAY".equalsIgnoreCase(serviceInfo.getServiceId()))
.filter(serviceInfo -> !serviceInfo.getInstances().isEmpty())
.filter(serviceInfo -> {
if (StringUtils.isEmpty(serviceId)) {
return true;
}
return serviceId.equalsIgnoreCase(serviceInfo.getServiceId());
})
.map(serviceInfo -> serviceInfo.getInstances().get(0))
.collect(Collectors.toList())
.forEach(this::loadDocInfo);
} catch (Exception e) {
log.error("加载失败", e);
}
}
protected void loadDocInfo(ServiceInstance serviceInstance) {
String query = this.buildQuery();
String url = "http://" + serviceInstance.getIp() + ":" + serviceInstance.getPort() + "/v2/api-docs" + query;
try {
log.info("读取swagger文档,serviceId:{}, url:{}", serviceInstance.getServiceId(), url);
ResponseEntity<String> entity = restTemplate.getForEntity(url, String.class);
if (entity.getStatusCode() != HttpStatus.OK) {
throw new IllegalAccessException("无权访问");
}
String docInfoJson = entity.getBody();
JSONObject docRoot = JSON.parseObject(docInfoJson, Feature.OrderedField, Feature.DisableCircularReferenceDetect);
DocParser docParser = this.buildDocParser(docRoot);
DocInfo docInfo = docParser.parseJson(docRoot);
docInfo.setServiceId(serviceInstance.getServiceId());
docDefinitionMap.put(docInfo.getTitle(), docInfo);
} catch (Exception e) {
// 这里报错可能是因为有些微服务没有配置swagger文档,导致404访问不到
// 这里catch跳过即可
log.warn("读取文档失败, url:{}, msg:{}", url, e.getMessage());
public void addDocInfo(String serviceId, String docInfoJson) {
String newMd5 = DigestUtils.md5DigestAsHex(docInfoJson.getBytes(StandardCharsets.UTF_8));
String md5 = serviceIdMd5Map.putIfAbsent(serviceId, newMd5);
if (Objects.equals(newMd5, md5)) {
return;
}
}
protected String buildQuery() {
String time = String.valueOf(System.currentTimeMillis());
String source = secret + time + secret;
String sign = DigestUtils.md5DigestAsHex(source.getBytes());
return "?time=" + time + "&sign=" + sign;
JSONObject docRoot = JSON.parseObject(docInfoJson, Feature.OrderedField, Feature.DisableCircularReferenceDetect);
DocParser docParser = this.buildDocParser(docRoot);
DocInfo docInfo = docParser.parseJson(docRoot);
docInfo.setServiceId(serviceId);
docDefinitionMap.put(docInfo.getTitle(), docInfo);
}
protected DocParser buildDocParser(JSONObject rootDoc) {
@ -137,143 +72,14 @@ public class DocManagerImpl implements DocManager {
return docDefinitionMap.values();
}
@PostConstruct
protected void after() throws Exception {
this.listenServiceId();
}
/**
* 监听serviceId更改
*
* @throws Exception
*/
protected void listenServiceId() throws Exception {
executorService.execute(new Consumer(queue, this));
ZookeeperContext.setEnvironment(environment);
String serviceTempRootPath = ZookeeperContext.getServiceTempRootPath();
ZookeeperContext.createPath(serviceTempRootPath, "{}");
// 如果节点内容有变化则自动更新文档
ZookeeperContext.getChildrenAndListen(serviceTempRootPath, childDataList -> {
for (ChildData childData : childDataList) {
String serviceIdPath = childData.getPath();
try {
boolean hasChildren = ZookeeperContext.hasChildren(serviceIdPath);
if (hasChildren) {
log.info("加载文档服务器,path:{}", serviceIdPath);
listenServiceIdPath(serviceIdPath);
}
} catch (Exception e) {
log.error("监听路径失败,serviceIdPath:{}", serviceIdPath);
}
}
}, (client, event) -> {
PathChildrenCacheEvent.Type type = event.getType();
if (type == PathChildrenCacheEvent.Type.CHILD_ADDED) {
String serviceIdPath = event.getData().getPath();
log.info("新增文档服务器,path:{}", serviceIdPath);
listenServiceIdPath(serviceIdPath);
}
});
}
private void listenServiceIdPath(String serviceIdPath) throws Exception {
ZookeeperContext.listenChildren(serviceIdPath, (client, event) -> {
String path = event.getData().getPath();
PathChildrenCacheEvent.Type type = event.getType();
log.info("服务节点变更,path:{}, eventType:{}", path, event.getType().name());
if (type == PathChildrenCacheEvent.Type.CHILD_ADDED
|| type == PathChildrenCacheEvent.Type.CHILD_UPDATED) {
byte[] data = event.getData().getData();
String serviceInfoJson = new String(data);
if (StringUtils.isEmpty(serviceInfoJson)) {
return;
}
ZKServiceInfo serviceInfo = JSON.parseObject(serviceInfoJson, ZKServiceInfo.class);
String serviceId = serviceInfo.getServiceId();
int delaySeconds = NumberUtils.toInt(refreshSeconds, 60);
log.info("微服务[{}]推送更新,{}秒后加载文档内容", serviceId, delaySeconds);
long id = System.currentTimeMillis();
Msg msg = new Msg(id, delaySeconds * 1000);
msg.serviceId = serviceId;
// 延迟20秒执行
queue.offer(msg);
} else if (event.getType() == PathChildrenCacheEvent.Type.CHILD_REMOVED) {
byte[] data = event.getData().getData();
String serviceInfoJson = new String(data);
ZKServiceInfo serviceInfo = JSON.parseObject(serviceInfoJson, ZKServiceInfo.class);
String serviceId = serviceInfo.getServiceId();
boolean hasChildren = ZookeeperContext.hasChildren(serviceIdPath);
// 如果没有子节点就删除
if (!hasChildren) {
log.info("服务节点已删除,删除对应文档信息,path:{}", event.getData().getPath());
removeDoc(serviceId);
}
}
});
}
public void removeDoc(String serviceId) {
@Override
public void remove(String serviceId) {
docDefinitionMap.entrySet().removeIf(entry -> serviceId.equalsIgnoreCase(entry.getValue().getServiceId()));
}
static class Msg implements Delayed {
private long id;
private long delay;
private String serviceId;
// 自定义实现比较方法返回 1 0 -1三个参数
public Msg(long id, long delay) {
this.id = id;
this.delay = delay + System.currentTimeMillis();
}
@Override
public int compareTo(Delayed delayed) {
Msg msg = (Msg) delayed;
return Long.compare(this.id, msg.id);
}
// 延迟任务是否到时就是按照这个方法判断如果返回的是负数则说明到期否则还没到期
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(this.delay - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
}
@Data
static class ZKServiceInfo {
/** 服务名称,对应spring.application.name */
private String serviceId;
private String description;
@Override
public void onApplicationEvent(HeartbeatEvent heartbeatEvent) {
docDiscovery.refresh(this);
}
@Slf4j
static class Consumer implements Runnable {
private DelayQueue<Msg> queue;
private DocManager docManager;
public Consumer(DelayQueue<Msg> queue, DocManager docManager) {
this.queue = queue;
this.docManager = docManager;
}
@Override
public void run() {
while (true) {
try {
Msg msg = queue.take();
log.info("延迟队列触发--重新加载文档信息");
docManager.load(msg.serviceId);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}

@ -2,9 +2,6 @@ server.port=8083
spring.application.name=website-server
# ------- 需要改的配置 -------
# eureka注册中心地址
eureka.url=http://localhost:1111/eureka/
# zookeeper地址
zookeeper.url=localhost:2181
# nacos地址
@ -12,15 +9,8 @@ nacos.url=127.0.0.1:8848
# ------- 需要改的配置end -------
# eureka注册中心地址,这里只是参数,并不会去注册
registry.eureka-server-addr=${eureka.url}
# nacos服务器地址
registry.nacos-server-addr=${nacos.url}
# 使用eureka,填:eureka,使用nacos填:nacos
registry.name=nacos
# zookeeper配置
spring.cloud.zookeeper.connect-string=${zookeeper.url}
# nacos cloud配置
spring.cloud.nacos.discovery.server-addr=${nacos.url}
# 测试环境
api.url-test=http://api-test.yourdomain.com/api

Loading…
Cancel
Save