Merge branch 'develop'

# Conflicts:
#	sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/DocManagerImpl.java
1.x
tanghc 6 years ago
commit 12df3e403f
  1. 6
      changelog.md
  2. 2
      sop-common/pom.xml
  3. 4
      sop-common/sop-gateway-common/pom.xml
  4. 17
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/easyopen/EasyopenResultExecutor.java
  5. 30
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/easyopen/EasyopenZuulConfiguration.java
  6. 178
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/BaseRouteManager.java
  7. 151
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/ZookeeperContext.java
  8. 7
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/configuration/ZuulErrorController.java
  9. 4
      sop-common/sop-service-common/pom.xml
  10. 27
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/EnvironmentContext.java
  11. 11
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/ServiceConstants.java
  12. 99
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/ZookeeperTool.java
  13. 54
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/EasyopenDocSupportController.java
  14. 2
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/SpringMvcServiceConfiguration.java
  15. 93
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/ServiceZookeeperApiMetaManager.java
  16. 2
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ApiArgumentResolver.java
  17. 16
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/swagger/ApiListingScannerExt.java
  18. 87
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/swagger/SwaggerSecurityFilter.java
  19. 13
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/swagger/SwaggerSupport.java
  20. 53
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/swagger/SwaggerValidator.java
  21. 2
      sop-example/sop-book/sop-book-web/pom.xml
  22. 2
      sop-example/sop-easyopen/pom.xml
  23. 31
      sop-example/sop-easyopen/src/main/java/com/gitee/easyopen/server/api/Goods2Api.java
  24. 2
      sop-example/sop-easyopen/src/main/java/com/gitee/easyopen/server/api/GoodsApi.java
  25. 10
      sop-example/sop-easyopen/src/main/java/com/gitee/easyopen/server/config/SopConfig.java
  26. 2
      sop-example/sop-springmvc/pom.xml
  27. 2
      sop-example/sop-story/sop-story-web/pom.xml
  28. 2
      sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/controller/AlipayController.java
  29. 2
      sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/controller/Story2Controller.java
  30. 2
      sop-gateway/pom.xml
  31. 20
      sop-gateway/src/main/java/com/gitee/sop/gateway/config/ZuulConfig.java
  32. 14
      sop-gateway/src/test/java/com/gitee/sop/gateway/CuratorTest.java
  33. 17
      sop-registry/src/main/resources/application-dev.yml
  34. 12
      sop-registry/src/main/resources/application.properties
  35. 3
      sop-registry/src/main/resources/application.yml
  36. 14
      sop-website/website-front/pages/doc/doc.html
  37. 76
      sop-website/website-front/pages/doc/doc.js
  38. 14
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/DocInfo.java
  39. 1
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/DocItem.java
  40. 11
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/WebsiteConstants.java
  41. 80
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/ZookeeperContext.java
  42. 29
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/controller/DocController.java
  43. 6
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/DocManager.java
  44. 230
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/DocManagerImpl.java
  45. 11
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/DocParser.java
  46. 83
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/EasyopenDocParser.java
  47. 115
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/SwaggerDocParser.java
  48. 2
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/vo/DocBaseInfoVO.java
  49. 4
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/vo/DocInfoVO.java

@ -1,5 +1,11 @@
# changelog
## 1.4.0
- 新增文档分组显示
- 支持easyopen文档注解
- BUG修复
## 1.3.0
- 新增接口限流功能 [doc](http://durcframework.gitee.io/sop/#/files/10092_%E6%8E%A5%E5%8F%A3%E9%99%90%E6%B5%81?t=1555378655699)

@ -5,7 +5,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-common</artifactId>
<version>1.3.0-SNAPSHOT</version>
<version>1.4.0-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>

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

@ -1,19 +1,34 @@
package com.gitee.sop.gatewaycommon.easyopen;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.gitee.sop.gatewaycommon.message.Error;
import com.gitee.sop.gatewaycommon.result.ApiResult;
import com.gitee.sop.gatewaycommon.result.ResultExecutor;
import com.gitee.sop.gatewaycommon.zuul.result.ZuulResultExecutor;
import com.netflix.zuul.context.RequestContext;
import java.util.Optional;
/**
* @author tanghc
*/
public class EasyopenResultExecutor implements ResultExecutor<RequestContext, String> {
boolean onlyReturnData;
public EasyopenResultExecutor(boolean onlyReturnData) {
this.onlyReturnData = onlyReturnData;
}
@Override
public String mergeResult(RequestContext request, String serviceResult) {
return serviceResult;
if (onlyReturnData) {
JSONObject jsonObject = JSON.parseObject(serviceResult);
return Optional.ofNullable(jsonObject.getString("data")).orElse("{}");
} else {
return serviceResult;
}
}
@Override

@ -10,15 +10,29 @@ import com.gitee.sop.gatewaycommon.zuul.configuration.BaseZuulConfiguration;
*/
public class EasyopenZuulConfiguration extends BaseZuulConfiguration {
static {
ParamNames.APP_KEY_NAME = "app_key";
ParamNames.API_NAME = "name";
ParamNames.SIGN_TYPE_NAME = "sign_type";
ParamNames.APP_AUTH_TOKEN_NAME = "access_token";
public EasyopenZuulConfiguration() {
ApiConfig apiConfig = ApiContext.getApiConfig();
apiConfig.setSigner(new EasyopenSigner());
apiConfig.setZuulResultExecutor(new EasyopenResultExecutor());
apiConfig.setMergeResult(false);
if (compatibilityModel()) {
ParamNames.APP_KEY_NAME = "app_key";
ParamNames.API_NAME = "name";
ParamNames.SIGN_TYPE_NAME = "sign_type";
ParamNames.APP_AUTH_TOKEN_NAME = "access_token";
apiConfig.setSigner(new EasyopenSigner());
apiConfig.setZuulResultExecutor(new EasyopenResultExecutor(false));
apiConfig.setMergeResult(false);
} else {
apiConfig.setZuulResultExecutor(new EasyopenResultExecutor(true));
}
}
/**
* 是否是兼容模式
* @return 返回true返回true可兼容之前的easyopen接口
*/
public boolean compatibilityModel() {
return true;
}
}

@ -5,15 +5,10 @@ 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.CuratorFramework;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.springframework.core.env.Environment;
import java.util.List;
/**
* 路由管理采用zookeeper实现监听路由的增删改并适时更新到本地路由的存储格式为
@ -64,8 +59,10 @@ public abstract class BaseRouteManager<R extends BaseServiceRouteInfo<E>, E exte
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
@ -75,73 +72,64 @@ public abstract class BaseRouteManager<R extends BaseServiceRouteInfo<E>, E exte
protected void refreshRouteInfo() {
try {
ZookeeperContext.createOrUpdateData(routeRootPath, "");
CuratorFramework client = ZookeeperContext.getClient();
this.watchServiceChange(client, routeRootPath);
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 client
* @param rootPath
* @throws Exception
*/
protected void watchServiceChange(CuratorFramework client, String rootPath) throws Exception {
// 为子节点添加watcher
// PathChildrenCache: 监听数据节点的增删改,可以设置触发的事件
PathChildrenCache childrenCache = new PathChildrenCache(client, rootPath, true);
/**
* StartMode: 初始化方式
* POST_INITIALIZED_EVENT异步初始化初始化之后会触发事件
* NORMAL异步初始化
* BUILD_INITIAL_CACHE同步初始化
*/
childrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
// 列出子节点数据列表,需要使用BUILD_INITIAL_CACHE同步初始化模式才能获得,异步是获取不到的
List<ChildData> childDataList = childrenCache.getCurrentData();
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);
this.loadServiceRouteItem(client, serviceRouteInfo, servicePath);
}
log.info("监听服务节点增删改,rootPath:{}", rootPath);
// 监听根节点下面的子节点
childrenCache.getListenable().addListener(new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
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);
loadServiceRouteItem(client, 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());
}
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());
}
}
});
@ -152,54 +140,48 @@ public abstract class BaseRouteManager<R extends BaseServiceRouteInfo<E>, E exte
*
* @param servicePath
*/
protected void loadServiceRouteItem(CuratorFramework client, R serviceRouteInfo, String servicePath) throws Exception {
// 获取service节点下所有的路由节点,里面保存的是路由名称,前面没有斜杠"/"
List<String> pathNameList = client.getChildren().forPath(servicePath);
for (String pathName : pathNameList) {
// 完整的路径
String routeItemPath = servicePath + "/" + pathName;
byte[] routeItemData = client.getData().forPath(routeItemPath);
String routeDataJson = buildZookeeperData(routeItemData);
log.info("\t\t{} # 路由节点,节点数据:{}", routeItemPath, routeDataJson);
this.saveRouteItem(serviceRouteInfo, routeDataJson);
}
this.watchRouteItems(client, serviceRouteInfo, 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 client
* @param serviceRouteInfo
* @param servicePath serviceId节点
*/
protected void watchRouteItems(CuratorFramework client, R serviceRouteInfo, String servicePath) throws Exception {
log.info("监听路由节点增删改,servicePath:{}", servicePath);
PathChildrenCache childrenCache = new PathChildrenCache(client, servicePath, true);
childrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
protected void watchRouteItems(R serviceRouteInfo, String servicePath) throws Exception {
log.info("监听{}下子节点增删改", servicePath);
// 添加事件监听器
childrenCache.getListenable().addListener(new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
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);
}
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);
}
}
});

@ -9,19 +9,17 @@ 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.apache.zookeeper.data.Stat;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import static com.gitee.sop.gatewaycommon.bean.SopConstants.SOP_MSG_CHANNEL_PATH;
import static com.gitee.sop.gatewaycommon.bean.SopConstants.SOP_SERVICE_ROUTE_PATH;
import static com.gitee.sop.gatewaycommon.bean.SopConstants.SOP_ROUTE_PERMISSION_PATH;
import static com.gitee.sop.gatewaycommon.bean.SopConstants.SOP_SERVICE_ROUTE_PATH;
/**
* @author tanghc
@ -86,62 +84,52 @@ public class ZookeeperContext {
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 path
* @param data
* @return 返回Stat
* @return 返回path
* @throws Exception
*/
public static Stat updatePathData(String path, String data) throws Exception {
if (!isPathExist(path)) {
throw new IllegalStateException("path " + path + " 不存在");
}
return getClient().setData().forPath(path, data.getBytes());
}
/**
* 创建新的path并赋值如果path已存在抛异常
* @param path 待创建的path
* @param data
*/
public static String createNewData(String path, String data) throws Exception {
if (isPathExist(path)) {
throw new IllegalStateException("path " + path + " 已存在");
}
return getClient().create()
public static String createOrUpdateData(String path, String data) throws Exception {
return getClient().create()
// 如果节点存在则Curator将会使用给出的数据设置这个节点的值
.orSetData()
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
.creatingParentContainersIfNeeded()
.forPath(path, data.getBytes());
}
/**
* 新建或保存节点
* 创建path如果path存在不报错静默返回path名称
*
* @param path
* @param data
* @return 返回path
* @return
* @throws Exception
*/
public static String createOrUpdateData(String path, String data) throws Exception {
public static String createPath(String path, String data) throws Exception {
if (isPathExist(path)) {
return path;
}
return getClient().create()
// 如果节点存在则Curator将会使用给出的数据设置这个节点的值
.orSetData()
// 如果指定节点的父节点不存在,则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
@ -160,39 +148,82 @@ public class ZookeeperContext {
return ret;
}
public static String getData(String path) throws Exception {
if (!isPathExist(path)) {
return null;
}
byte[] data = getClient().getData().forPath(path);
return new String(data);
/**
* 获取子节点信息并监听子节点
*
* @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 父节点
* @return 返回子节点数据
* @param parentPath 父节点路径
* @param listConsumer 子节点数据
* @throws Exception
*/
public static List<ChildData> getChildrenData(String parentPath) throws Exception {
PathChildrenCache pathChildrenCache = buildPathChildrenCache(parentPath);
if (pathChildrenCache == null) {
return Collections.emptyList();
}
return pathChildrenCache.getCurrentData();
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();
}
public static PathChildrenCache buildPathChildrenCache(String path) throws Exception {
if (!isPathExist(path)) {
return null;
}
/**
* 监听子节点的增删改
*
* @param parentPath 父节点路径
* @param listener
* @throws Exception
*/
public static void listenChildren(String parentPath, PathChildrenCacheListener listener) throws Exception {
// 为子节点添加watcher
// PathChildrenCache: 监听数据节点的增删改,可以设置触发的事件
// 且第三个参数要设置为true,不然ChildData对象中的getData返回null
PathChildrenCache childrenCache = new PathChildrenCache(client, path, true);
// 列出子节点数据列表,需要使用BUILD_INITIAL_CACHE同步初始化模式才能获得,异步是获取不到的
PathChildrenCache childrenCache = new PathChildrenCache(client, parentPath, true);
/**
* StartMode: 初始化方式
* POST_INITIALIZED_EVENT异步初始化初始化之后会触发事件
* NORMAL异步初始化
* BUILD_INITIAL_CACHE同步初始化
*/
childrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
return childrenCache;
// 监听根节点下面的子节点
childrenCache.getListenable().addListener(listener);
}
}

@ -10,6 +10,8 @@ import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletResponse;
/**
* 处理网关自身异常
*
@ -26,8 +28,11 @@ public class ZuulErrorController implements ErrorController {
*/
@RequestMapping(ERROR_PATH)
@ResponseBody
public Object error() {
public Object error(HttpServletResponse response) {
RequestContext ctx = RequestContext.getCurrentContext();
if (ctx.getResponse() == null) {
ctx.setResponse(response);
}
ctx.setResponseStatusCode(HttpStatus.OK.value());
Throwable throwable = ctx.getThrowable();
return this.buildResult(throwable);

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

@ -1,27 +0,0 @@
package com.gitee.sop.servercommon.bean;
import org.springframework.core.env.Environment;
/**
* @author thc
*/
public class EnvironmentContext {
private static Environment environment;
public static Environment getEnvironment() {
return environment;
}
public static void setEnvironment(Environment environment) {
EnvironmentContext.environment = environment;
}
public static String getProfile(Environment env) {
return env.getProperty("spring.profiles.active", "default");
}
public static String getProfile() {
return getProfile(environment);
}
}

@ -0,0 +1,11 @@
package com.gitee.sop.servercommon.bean;
/**
* @author tanghc
*/
public class ServiceConstants {
/**
* zookeeper存放接口路由信息的根目录
*/
public static final String SOP_SERVICE_ROUTE_PATH = "/com.gitee.sop.route";
}

@ -0,0 +1,99 @@
package com.gitee.sop.servercommon.bean;
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.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;
}
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 返回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();
}
}
}

@ -0,0 +1,54 @@
package com.gitee.sop.servercommon.configuration;
import com.gitee.easyopen.doc.ApiDocHolder;
import com.gitee.sop.servercommon.swagger.SwaggerValidator;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* 文档支持
* @author thc
*/
public abstract class EasyopenDocSupportController {
private SwaggerValidator swaggerValidator;
public abstract String getDocTitle();
public EasyopenDocSupportController() {
swaggerValidator = new SwaggerValidator(this.swaggerAccessProtected());
}
@RequestMapping("/v2/api-docs")
@ResponseBody
public Map<String, Object> getDocInfo(HttpServletRequest request, HttpServletResponse response) throws IOException {
if (swaggerValidator.swaggerAccessProtected() && !swaggerValidator.validate(request)) {
swaggerValidator.writeForbidden(response);
return null;
}
Map<String, Object> context = this.getContext();
context.put("easyopen", "1.16.3");
context.put("apiModules", ApiDocHolder.getApiDocBuilder().getApiModules());
context.put("title", getDocTitle());
return context;
}
public Map<String, Object> getContext() {
return new HashMap<>(8);
}
/**
* swagger访问是否加密保护
* @return
*/
protected boolean swaggerAccessProtected() {
return true;
}
}

@ -1,6 +1,5 @@
package com.gitee.sop.servercommon.configuration;
import com.gitee.sop.servercommon.bean.EnvironmentContext;
import com.gitee.sop.servercommon.bean.ServiceConfig;
import com.gitee.sop.servercommon.manager.ApiMetaManager;
import com.gitee.sop.servercommon.manager.DefaultRequestMappingEvent;
@ -55,7 +54,6 @@ public class SpringMvcServiceConfiguration {
@PostConstruct
public final void after() {
log.info("-----spring容器加载完毕-----");
EnvironmentContext.setEnvironment(environment);
Executors.newSingleThreadExecutor().execute(()->{
uploadRouteToZookeeper();
});

@ -2,15 +2,15 @@ package com.gitee.sop.servercommon.manager;
import com.alibaba.fastjson.JSON;
import com.gitee.sop.servercommon.bean.ServiceApiInfo;
import com.gitee.sop.servercommon.bean.ServiceConstants;
import com.gitee.sop.servercommon.bean.ZookeeperTool;
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.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
@ -33,7 +33,7 @@ public class ServiceZookeeperApiMetaManager implements ApiMetaManager {
/**
* zookeeper存放接口路由信息的根目录
*/
public static final String SOP_SERVICE_ROUTE_PATH = "/com.gitee.sop.route";
public static final String SOP_SERVICE_ROUTE_PATH = ServiceConstants.SOP_SERVICE_ROUTE_PATH;
public static final String PATH_START_CHAR = "/";
/**
@ -44,24 +44,56 @@ public class ServiceZookeeperApiMetaManager implements ApiMetaManager {
private static ServiceApiInfo.ApiMeta FIRST_API_META = new ServiceApiInfo.ApiMeta("_first.route_", "/", "v_000");
private final String routeRootPath;
private final String routeRootPath = SOP_SERVICE_ROUTE_PATH;
private final String zookeeperServerAddr;
private Environment environment;
private ZookeeperTool zookeeperTool;
private String serviceId;
public ServiceZookeeperApiMetaManager(Environment environment) {
this.environment = environment;
this.routeRootPath = SOP_SERVICE_ROUTE_PATH;
serviceId = environment.getProperty("spring.application.name");
if (StringUtils.isEmpty(serviceId)) {
throw new IllegalArgumentException("请在application.properties中指定spring.application.name属性");
}
zookeeperServerAddr = environment.getProperty("spring.cloud.zookeeper.connect-string");
if (StringUtils.isEmpty(zookeeperServerAddr)) {
throw new IllegalArgumentException("未指定spring.cloud.zookeeper.connect-string参数");
}
this.zookeeperTool = new ZookeeperTool(environment);
this.uploadServiceId(environment);
}
/**
* 上传serviceId目录
*
* @param environment
*/
protected void uploadServiceId(Environment environment) {
try {
ServiceRouteInfo serviceRouteInfo = this.buildServiceRouteInfo();
// 保存路径
String savePath = routeRootPath + "/" + serviceId;
String nodeData = JSON.toJSONString(serviceRouteInfo);
log.info("zookeeper创建serviceId路径,path:{}, nodeData:{}", savePath, nodeData);
this.zookeeperTool.createPath(savePath, nodeData);
} catch (Exception e) {
throw new IllegalStateException("zookeeper操作失败");
}
}
@Override
public void uploadApi(ServiceApiInfo serviceApiInfo) {
ServiceRouteInfo serviceRouteInfo = this.buildServiceGatewayInfo(serviceApiInfo);
this.uploadServiceRouteInfoToZookeeper(serviceRouteInfo);
try {
ServiceRouteInfo serviceRouteInfo = this.buildServiceGatewayInfo(serviceApiInfo);
this.uploadServiceRouteInfoToZookeeper(serviceRouteInfo);
} finally {
IOUtils.closeQuietly(zookeeperTool);
}
}
/**
@ -78,11 +110,16 @@ public class ServiceZookeeperApiMetaManager implements ApiMetaManager {
GatewayRouteDefinition gatewayRouteDefinition = this.buildGatewayRouteDefinition(serviceApiInfo, apiMeta);
routeDefinitionList.add(gatewayRouteDefinition);
}
ServiceRouteInfo serviceRouteInfo = this.buildServiceRouteInfo();
serviceRouteInfo.setRouteDefinitionList(routeDefinitionList);
return serviceRouteInfo;
}
protected ServiceRouteInfo buildServiceRouteInfo() {
ServiceRouteInfo serviceRouteInfo = new ServiceRouteInfo();
serviceRouteInfo.setServiceId(serviceApiInfo.getServiceId());
serviceRouteInfo.setServiceId(serviceId);
String description = environment.getProperty("spring.application.description");
serviceRouteInfo.setDescription(description);
serviceRouteInfo.setRouteDefinitionList(routeDefinitionList);
return serviceRouteInfo;
}
@ -144,75 +181,51 @@ public class ServiceZookeeperApiMetaManager implements ApiMetaManager {
* @param serviceRouteInfo
*/
protected void uploadServiceRouteInfoToZookeeper(ServiceRouteInfo serviceRouteInfo) {
CuratorFramework client = null;
try {
// 保存路径
String savePath = routeRootPath + "/" + serviceRouteInfo.getServiceId();
client = CuratorFrameworkFactory.builder()
.connectString(zookeeperServerAddr)
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
client.start();
log.info("上传接口信息到zookeeper,path:{}, serviceId:{}, 接口数量:{}",
savePath,
serviceRouteInfo.getServiceId(),
serviceRouteInfo.getRouteDefinitionList().size());
String parentPath = this.uploadFolder(client, serviceRouteInfo);
this.uploadRouteItems(client, serviceRouteInfo, parentPath);
String parentPath = this.uploadFolder(serviceRouteInfo);
this.uploadRouteItems(serviceRouteInfo, parentPath);
} catch (Exception e) {
log.error("更新接口信息到zookeeper失败, serviceId:{}", serviceRouteInfo.getServiceId(), e);
} finally {
if (client != null) {
client.close();
}
}
}
/**
* 上传文件夹内容
*
* @param client
* @param serviceRouteInfo
* @return 返回文件夹路径
*/
protected String uploadFolder(CuratorFramework client, ServiceRouteInfo serviceRouteInfo) throws Exception {
protected String uploadFolder(ServiceRouteInfo serviceRouteInfo) throws Exception {
// 保存路径
String savePath = routeRootPath + "/" + serviceRouteInfo.getServiceId();
String serviceRouteInfoJson = JSON.toJSONString(serviceRouteInfo);
log.info("上传service目录到zookeeper,路径:{},内容:{}", savePath, serviceRouteInfoJson);
this.saveNode(client, savePath, serviceRouteInfoJson.getBytes());
this.zookeeperTool.createOrUpdateData(savePath, serviceRouteInfoJson);
return savePath;
}
/**
* 上传路由信息
*
* @param client
* @param serviceRouteInfo
* @throws Exception
*/
protected void uploadRouteItems(CuratorFramework client, ServiceRouteInfo serviceRouteInfo, String 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_START_CHAR + routeDefinition.getId();
String routeDefinitionJson = JSON.toJSONString(routeDefinition);
log.info("上传路由配置到zookeeper,路径:{},路由数据:{}", savePath, routeDefinitionJson);
this.saveNode(client, savePath, routeDefinitionJson.getBytes());
this.zookeeperTool.createOrUpdateData(savePath, routeDefinitionJson);
}
}
protected void saveNode(CuratorFramework client, String path, byte[] data) throws Exception {
client.create()
// 如果节点存在则Curator将会使用给出的数据设置这个节点的值
.orSetData()
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
.creatingParentContainersIfNeeded()
.forPath(path, data);
}
}

@ -10,7 +10,7 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* 解析request参数中的业务参数隐射到方法参数上
* 解析request参数中的业务参数绑定到方法参数上
*
* @author tanghc
*/

@ -1,16 +0,0 @@
package com.gitee.sop.servercommon.swagger;
import springfox.documentation.spring.web.plugins.DocumentationPluginsManager;
import springfox.documentation.spring.web.scanners.ApiDescriptionReader;
import springfox.documentation.spring.web.scanners.ApiListingScanner;
import springfox.documentation.spring.web.scanners.ApiModelReader;
/**
* @author tanghc
*/
public class ApiListingScannerExt extends ApiListingScanner {
public ApiListingScannerExt(ApiDescriptionReader apiDescriptionReader, ApiModelReader apiModelReader, DocumentationPluginsManager pluginsManager) {
super(apiDescriptionReader, apiModelReader, pluginsManager);
}
}

@ -0,0 +1,87 @@
package com.gitee.sop.servercommon.swagger;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* @author tanghc
*/
@Slf4j
public class SwaggerSecurityFilter implements Filter {
protected List<String> urlFilters = new ArrayList<>();
{
urlFilters.add(".*?/doc\\.html.*");
urlFilters.add(".*?/v2/api-docs.*");
urlFilters.add(".*?/v2/api-docs-ext.*");
urlFilters.add(".*?/swagger-resources.*");
urlFilters.add(".*?/swagger-ui\\.html.*");
urlFilters.add(".*?/swagger-resources/configuration/ui.*");
urlFilters.add(".*?/swagger-resources/configuration/security.*");
}
private SwaggerValidator swaggerValidator;
public SwaggerSecurityFilter(boolean swaggerAccessProtected) {
this.swaggerValidator = new SwaggerValidator(swaggerAccessProtected);
}
protected boolean match(String uri) {
boolean match = false;
if (uri != null) {
for (String regex : urlFilters) {
if (uri.matches(regex)) {
match = true;
break;
}
}
}
return match;
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
if (!swaggerValidator.swaggerAccessProtected()) {
filterChain.doFilter(servletRequest, servletResponse);
return;
}
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String uri = request.getRequestURI();
// 没有匹配到,直接放行
if (!match(uri)) {
filterChain.doFilter(servletRequest, servletResponse);
} else {
if (swaggerValidator.validate(request)) {
filterChain.doFilter(servletRequest, servletResponse);
} else {
swaggerValidator.writeForbidden(response);
}
}
}
@Override
public void destroy() {
}
}

@ -30,6 +30,19 @@ public abstract class SwaggerSupport {
.build();
}
@Bean
public SwaggerSecurityFilter swaggerSecurityFilter() {
return new SwaggerSecurityFilter(swaggerAccessProtected());
}
/**
* swagger访问是否加密保护
* @return
*/
protected boolean swaggerAccessProtected() {
return true;
}
protected ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title(getDocTitle())

@ -0,0 +1,53 @@
package com.gitee.sop.servercommon.swagger;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.DigestUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @author tanghc
*/
public class SwaggerValidator {
private String secret = "b749a2ec000f4f29";
private boolean swaggerAccessProtected = true;
public SwaggerValidator(boolean swaggerAccessProtected) {
this.swaggerAccessProtected = swaggerAccessProtected;
}
public SwaggerValidator() {
}
/**
* swagger访问是否加密保护
* @return
*/
public boolean swaggerAccessProtected() {
return swaggerAccessProtected;
}
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);
}
public void writeForbidden(HttpServletResponse response) throws IOException {
response.setContentType("text/palin;charset=UTF-8");
response.setStatus(403);
PrintWriter printWriter = response.getWriter();
printWriter.write("access forbidden");
printWriter.flush();
}
}

@ -23,7 +23,7 @@
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-service-common</artifactId>
<version>1.3.0-SNAPSHOT</version>
<version>1.4.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.gitee.sop</groupId>

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

@ -0,0 +1,31 @@
package com.gitee.easyopen.server.api;
import com.gitee.easyopen.annotation.Api;
import com.gitee.easyopen.annotation.ApiService;
import com.gitee.easyopen.doc.annotation.ApiDoc;
import com.gitee.easyopen.doc.annotation.ApiDocMethod;
import com.gitee.easyopen.server.api.param.GoodsParam;
import com.gitee.easyopen.server.api.result.Goods;
import java.math.BigDecimal;
/**
* 业务类
*
* @author tanghc
*/
@ApiService
@ApiDoc("库存接口")
public class Goods2Api {
@Api(name = "store.get")
@ApiDocMethod(description = "获取库存")
Goods getGoods(GoodsParam param) {
Goods goods = new Goods();
goods.setId(1L);
goods.setGoods_name("苹果iPhoneX");
goods.setPrice(new BigDecimal(8000));
return goods;
}
}

@ -15,7 +15,7 @@ import java.math.BigDecimal;
* @author tanghc
*/
@ApiService
@ApiDoc("商品模块")
@ApiDoc("商品接口")
public class GoodsApi {
@Api(name = "goods.get")

@ -1,11 +1,21 @@
package com.gitee.easyopen.server.config;
import com.gitee.sop.servercommon.configuration.EasyopenDocSupportController;
import com.gitee.sop.servercommon.configuration.EasyopenServiceConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Controller;
/**
* @author tanghc
*/
@Configuration
public class SopConfig extends EasyopenServiceConfiguration {
@Controller
public static class SopDocController extends EasyopenDocSupportController {
@Override
public String getDocTitle() {
return "商品API";
}
}
}

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

@ -23,7 +23,7 @@
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-service-common</artifactId>
<version>1.3.0-SNAPSHOT</version>
<version>1.4.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.gitee.sop</groupId>

@ -3,6 +3,7 @@ package com.gitee.sop.bookweb.controller;
import com.gitee.sop.bookweb.controller.param.StoryParam;
import com.gitee.sop.servercommon.annotation.ApiMapping;
import com.gitee.sop.story.api.domain.Story;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiOperation;
import lombok.Data;
@ -16,6 +17,7 @@ import java.util.Date;
* @author tanghc
*/
@RestController
@Api(tags = "故事接口")
public class AlipayController {
@ApiMapping(value = "alipay.story.get")

@ -3,6 +3,7 @@ package com.gitee.sop.bookweb.controller;
import com.gitee.sop.servercommon.annotation.ApiAbility;
import com.gitee.sop.servercommon.annotation.ApiMapping;
import com.gitee.sop.story.api.domain.Story;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@ -13,6 +14,7 @@ import org.springframework.web.bind.annotation.RestController;
@ApiAbility // 放在这里,下面所有的接口都具备接口提供能力
@RestController
@RequestMapping("story2")
@Api(tags = "故事接口2")
public class Story2Controller{
@RequestMapping("getStory4")

@ -23,7 +23,7 @@
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-gateway-common</artifactId>
<version>1.3.0-SNAPSHOT</version>
<version>1.4.0-SNAPSHOT</version>
</dependency>
<!-- ↓↓↓ 使用spring cloud zuul ↓↓↓ -->

@ -1,28 +1,20 @@
package com.gitee.sop.gateway.config;
import com.gitee.sop.gateway.entity.IsvInfo;
/**
* 使用Spring Cloud Zuul推荐使用
*
* 注意下面两个只能使用一个
*/
import com.gitee.sop.gateway.manager.ManagerInitializer;
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
import com.gitee.sop.gatewaycommon.bean.ApiContext;
import com.gitee.sop.gatewaycommon.easyopen.EasyopenZuulConfiguration;
import com.gitee.sop.gatewaycommon.manager.IsvRoutePermissionManager;
import com.gitee.sop.gatewaycommon.secret.IsvManager;
import com.gitee.sop.gatewaycommon.zuul.configuration.AlipayZuulConfiguration;
import com.gitee.sop.gatewaycommon.zuul.configuration.TaobaoZuulConfiguration;
import com.gitee.sop.gatewaycommon.zuul.filter.PreLimitFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* 使用Spring Cloud Zuul推荐使用
*
* 注意下面两个只能使用一个
*/
/**
* 开通支付宝开放平台能力
* @author tanghc

@ -11,7 +11,7 @@ import org.apache.curator.retry.ExponentialBackoffRetry;
*/
public class CuratorTest extends TestCase {
private String zookeeperServerAddr = "127.0.0.1:2181";
private String zookeeperServerAddr = "localhost:2181";
/**
* 递归删除节点只能在测试环境用
@ -30,17 +30,5 @@ public class CuratorTest extends TestCase {
client.delete().deletingChildrenIfNeeded().forPath(SopConstants.SOP_SERVICE_ROUTE_PATH);
} catch (Exception e) {
}
try {
client.delete().deletingChildrenIfNeeded().forPath(SopConstants.SOP_SERVICE_ROUTE_PATH + "-default");
} catch (Exception e) {
}
try {
client.delete().deletingChildrenIfNeeded().forPath(SopConstants.SOP_SERVICE_ROUTE_PATH + "-dev");
} catch (Exception e) {
}
try {
client.delete().deletingChildrenIfNeeded().forPath(SopConstants.SOP_SERVICE_ROUTE_PATH + "-test");
} catch (Exception e) {
}
}
}

@ -0,0 +1,17 @@
eureka:
client:
fetch-registry: false
# 不注册自己
register-with-eureka: false
serviceUrl:
defaultZone: http://${eureka.host}:${eureka.port}/eureka/
# 注册中心地址
host: localhost
port: 1111
server:
port: 1111
spring:
application:
name: sop-registry

@ -1,12 +0,0 @@
spring.application.name=sop-registry
server.port=1111
# ---- eureka注册中心 ----
# 不注册自己
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
# 注册中心地址
eureka.host=localhost
eureka.port=1111
eureka.client.serviceUrl.defaultZone=http://${eureka.host}:${eureka.port}/eureka/

@ -0,0 +1,3 @@
spring:
profiles:
active: dev

@ -82,12 +82,6 @@
<blockquote class="layui-elem-quote layui-text sop-description">
</blockquote>
<div class="site-title">
<fieldset class="layui-elem-field layui-field-title site-title">
<legend><a name="use">公共参数</a></legend>
</fieldset>
</div>
<div class="site-text">
<h3>请求地址</h3>
<table class="layui-table">
@ -106,6 +100,12 @@
</table>
</div>
<div class="site-title">
<fieldset class="layui-elem-field layui-field-title site-title">
<legend><a name="use">公共参数</a></legend>
</fieldset>
</div>
<div class="site-text">
<h3>公共请求参数</h3>
<table class="layui-table">
@ -206,7 +206,7 @@
<td>String</td>
<td></td>
<td>40</td>
<td>详见<a href="#" target="_blank">应用授权概述</a></td>
<td>暂未实现</td>
<td></td>
</tr>

@ -7,43 +7,53 @@ layui.use(['element', 'form'], function(){ //加载code模块
function initDocModules() {
$.getJSON(SopConfig.url + '/doc/getDocBaseInfo', function (baseInfo) {
var html = [];
var modules = baseInfo.docModuleVOList;
for (var i = 0; i < modules.length; i++) {
var docDefinition = modules[i];
var module = docDefinition.module;
var docInfoList = baseInfo.docInfoList;
for (var i = 0; i < docInfoList.length; i++) {
var docInfo = docInfoList[i];
var selected = i === 0 ? 'selected="selected"' : '';
html.push('<option value="' + module + '" ' + selected + '>' + module + '</option>');
var title = docInfo.title;
html.push('<option value="' + title + '" ' + selected + '>' + title + '</option>');
}
$('#moduleList').html(html.join(''));
$('.url-prod').text(baseInfo.urlProd);
form.render('select');
if (modules && modules.length > 0) {
selectModule(modules[0].module);
if (docInfoList && docInfoList.length > 0) {
selectDocInfo(docInfoList[0].title);
}
$('.url-prod').text(baseInfo.urlProd);
})
}
function selectModule(docModule) {
$.getJSON(SopConfig.url + '/doc/module/' + docModule, function (module) {
var docItems = module.docItems;
var html = ['<li><h2>' + docModule + '</h2></li>'];
for (var i = 0; i < docItems.length; i++) {
var docItem = docItems[i];
docItemStore[docItem.nameVersion] = docItem;
/*
<li class="site-tree-noicon layui-this">
<a href="/">
<cite>统一收单交易退款查询</cite>
</a>
</li>
*/
var selectedClass = i === 0 ? 'layui-this' : '';
html.push('<li class="site-tree-noicon ' + selectedClass + '">');
html.push('<a href="#" nameversion="'+docItem.nameVersion+'"><cite>'+docItem.summary+'</cite></a>')
function selectDocInfo(title) {
$.getJSON(SopConfig.url + '/doc/docinfo/' + title, function (docInfo) {
var moduleList = docInfo.docModuleList;
var html = [];
var firstItem;
for (var j = 0; j < moduleList.length; j++) {
var module = moduleList[j];
var docItems = module.docItems;
html.push('<li><h2>' + module.module + '</h2></li>');
for (var i = 0; i < docItems.length; i++) {
var docItem = docItems[i];
var first = j == 0 && j == 0;
if (first) {
firstItem = docItem;
}
docItemStore[docItem.nameVersion] = docItem;
/*
<li class="site-tree-noicon layui-this">
<a href="/">
<cite>统一收单交易退款查询</cite>
</a>
</li>
*/
html.push('<li class="site-tree-noicon" nameversion="'+docItem.nameVersion+'">');
html.push('<a href="#"><cite>'+docItem.summary+'</cite></a>')
}
}
$('#docItemTree').html(html.join(''));
if (docItems && docItems.length > 0) {
var firstItem = docItems[0];
if (firstItem) {
selectDocItem(firstItem.nameVersion);
}
})
@ -51,12 +61,11 @@ layui.use(['element', 'form'], function(){ //加载code模块
function initEvent() {
form.on('select(moduleListFilter)', function (data) {
selectModule(data.value);
selectDocInfo(data.value);
})
$('#docItemTree').on('click', 'a', function () {
var $tagA = $(this);
selectDocItem($tagA.attr('nameversion'));
$tagA.parent().addClass('layui-this').siblings().removeClass('layui-this');
$('#docItemTree').on('click', 'li', function () {
var $li = $(this);
selectDocItem($li.attr('nameversion'));
})
}
@ -70,6 +79,9 @@ layui.use(['element', 'form'], function(){ //加载code模块
createRequestParameter(docItem);
createResponseParameter(docItem);
createResponseCode(docItem);
var $li = $('#docItemTree').find('li[nameversion="'+nameVersion+'"]');
$li.addClass('layui-this').siblings().removeClass('layui-this');
}
function createRequestParameter(docItem) {

@ -0,0 +1,14 @@
package com.gitee.sop.websiteserver.bean;
import lombok.Data;
import java.util.List;
/**
* @author tanghc
*/
@Data
public class DocInfo {
private String title;
private List<DocModule> docModuleList;
}

@ -9,6 +9,7 @@ import java.util.List;
*/
@Data
public class DocItem {
private String module;
private String name;
private String version;
private String summary;

@ -0,0 +1,11 @@
package com.gitee.sop.websiteserver.bean;
/**
* @author tanghc
*/
public class WebsiteConstants {
/**
* zookeeper存放接口路由信息的根目录
*/
public static final String SOP_SERVICE_ROUTE_PATH = "/com.gitee.sop.route";
}

@ -0,0 +1,80 @@
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.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;
/**
* @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 CuratorFramework getClient() {
return client;
}
public static void setClient(CuratorFramework client) {
ZookeeperContext.client = client;
}
/**
* 监听子节点可以自定义层级
* @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,10 +1,10 @@
package com.gitee.sop.websiteserver.controller;
import com.gitee.sop.websiteserver.bean.DocInfo;
import com.gitee.sop.websiteserver.bean.DocItem;
import com.gitee.sop.websiteserver.bean.DocModule;
import com.gitee.sop.websiteserver.manager.DocManager;
import com.gitee.sop.websiteserver.vo.DocBaseInfoVO;
import com.gitee.sop.websiteserver.vo.DocModuleVO;
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;
@ -38,11 +38,11 @@ public class DocController {
@GetMapping("/getDocBaseInfo")
public DocBaseInfoVO getDocBaseInfo() {
List<DocModuleVO> docModuleVOList = docManager.listAll()
List<DocInfoVO> docInfoList = docManager.listAll()
.stream()
.map(docModule -> {
DocModuleVO vo = new DocModuleVO();
BeanUtils.copyProperties(docModule, vo);
.map(docInfo -> {
DocInfoVO vo = new DocInfoVO();
BeanUtils.copyProperties(docInfo, vo);
return vo;
})
.collect(Collectors.toList());
@ -50,13 +50,13 @@ public class DocController {
DocBaseInfoVO baseInfoVO = new DocBaseInfoVO();
baseInfoVO.setUrlTest(urlTest);
baseInfoVO.setUrlProd(urlProd);
baseInfoVO.setDocModuleVOList(docModuleVOList);
baseInfoVO.setDocInfoList(docInfoList);
return baseInfoVO;
}
@GetMapping("/module/{module}")
public DocModule getDocModule(@PathVariable("module") String module) {
return docManager.getByTitle(module);
@GetMapping("/docinfo/{title}")
public DocInfo getDocModule(@PathVariable("title") String title) {
return docManager.getByTitle(title);
}
@GetMapping("/item/{method}/{version}/")
@ -65,10 +65,13 @@ public class DocController {
}
@GetMapping("/doc/reload")
public void reload(String pwd) {
if (StringUtils.equals(this.pwd, pwd)) {
// 后门地址,可手动更新文档内容,一般情况下用不到
@GetMapping("/reload")
public String reload(String pwd) {
boolean correct = StringUtils.equals(this.pwd, pwd);
if (correct) {
docManager.load();
}
return String.valueOf(correct);
}
}

@ -1,6 +1,6 @@
package com.gitee.sop.websiteserver.manager;
import com.gitee.sop.websiteserver.bean.DocModule;
import com.gitee.sop.websiteserver.bean.DocInfo;
import com.gitee.sop.websiteserver.bean.DocItem;
import java.util.Collection;
@ -14,7 +14,7 @@ public interface DocManager {
DocItem get(String method, String version);
DocModule getByTitle(String title);
DocInfo getByTitle(String title);
Collection<DocModule> listAll();
Collection<DocInfo> listAll();
}

@ -1,26 +1,27 @@
package com.gitee.sop.websiteserver.manager;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.gitee.sop.websiteserver.bean.DocInfo;
import com.gitee.sop.websiteserver.bean.DocItem;
import com.gitee.sop.websiteserver.bean.DocModule;
import com.gitee.sop.websiteserver.bean.DocParameter;
import com.gitee.sop.websiteserver.bean.EurekaApplication;
import com.gitee.sop.websiteserver.bean.EurekaApps;
import com.gitee.sop.websiteserver.bean.EurekaInstance;
import com.gitee.sop.websiteserver.bean.EurekaUri;
import com.gitee.sop.websiteserver.bean.ZookeeperContext;
import com.gitee.sop.websiteserver.vo.ServiceInfoVO;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.lang.StringUtils;
import org.apache.curator.framework.recipes.cache.TreeCacheEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClientException;
import org.springframework.util.DigestUtils;
import org.springframework.web.client.RestTemplate;
import javax.annotation.PostConstruct;
@ -30,9 +31,11 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
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;
/**
@ -42,8 +45,8 @@ import java.util.stream.Collectors;
@Slf4j
public class DocManagerImpl implements DocManager {
// key:module
Map<String, DocModule> docDefinitionMap = new HashMap<>();
// key:title
Map<String, DocInfo> docDefinitionMap = new HashMap<>();
// key: name+version
Map<String, DocItem> docItemMap = new HashMap<>();
@ -53,11 +56,23 @@ public class DocManagerImpl implements DocManager {
RestTemplate restTemplate = new RestTemplate();
DocParser swaggerDocParser = new SwaggerDocParser();
DocParser easyopenDocParser = new EasyopenDocParser();
ExecutorService executorService = Executors.newSingleThreadExecutor();
DelayQueue<Msg> queue = new DelayQueue<>();
private String secret = "b749a2ec000f4f29";
@Autowired
private Environment environment;
private String eurekaUrl;
private volatile boolean listenInited;
@Override
public void load() {
try {
@ -68,97 +83,45 @@ public class DocManagerImpl implements DocManager {
ServiceInfoVO serviceInfoVo = entry.getValue().get(0);
loadDocInfo(serviceInfoVo);
}
Map<String, DocItem> itemMap = docDefinitionMap.values()
.stream()
.map(DocModule::getDocItems)
.flatMap(docItems -> docItems.stream())
.collect(Collectors.toMap(DocItem::getNameVersion, Function.identity()));
this.docItemMap.putAll(itemMap);
} catch (IOException e) {
} catch (Exception e) {
log.error("加载失败", e);
}
}
private void loadDocInfo(ServiceInfoVO serviceInfoVo) {
String url = "http://" + serviceInfoVo.getIpAddr() + ":" + serviceInfoVo.getServerPort() + "/v2/api-docs";
protected void loadDocInfo(ServiceInfoVO serviceInfoVo) {
String query = this.buildQuery();
String url = "http://" + serviceInfoVo.getIpAddr() + ":" + serviceInfoVo.getServerPort() + "/v2/api-docs" + query;
try {
ResponseEntity<String> entity = restTemplate.getForEntity(url, String.class);
if (entity.getStatusCode() != HttpStatus.OK) {
throw new IllegalAccessException("无权访问");
}
String docInfoJson = entity.getBody();
DocModule docDefinition = this.parseDocJson(docInfoJson);
docDefinitionMap.put(docDefinition.getModule(), docDefinition);
} catch (RestClientException e) {
JSONObject docRoot = JSON.parseObject(docInfoJson);
DocParser docParser = this.buildDocParser(docRoot);
DocInfo docInfo = docParser.parseJson(docRoot);
docDefinitionMap.put(docInfo.getTitle(), docInfo);
} catch (Exception e) {
// 这里报错可能是因为有些微服务没有配置swagger文档,导致404访问不到
// 这里catch跳过即可
log.warn("读取文档失败, url:{}", url, e);
}
}
private DocModule parseDocJson(String docInfoJson) {
JSONObject docRoot = JSON.parseObject(docInfoJson);
String title = docRoot.getJSONObject("info").getString("title");
List<DocItem> docItems = new ArrayList<>();
JSONObject paths = docRoot.getJSONObject("paths");
Set<String> pathNameSet = paths.keySet();
for (String pathName : pathNameSet) {
JSONObject pathInfo = paths.getJSONObject(pathName);
Set<String> pathSet = pathInfo.keySet();
Optional<String> first = pathSet.stream().findFirst();
if (first.isPresent()) {
String path = first.get();
JSONObject docInfo = pathInfo.getJSONObject(path);
DocItem docItem = buildDocItem(docInfo, docRoot);
docItems.add(docItem);
}
log.warn("读取文档失败, url:{}, msg:{}", url, e.getMessage());
}
DocModule docDefinition = new DocModule();
docDefinition.setModule(title);
docDefinition.setDocItems(docItems);
return docDefinition;
}
private DocItem buildDocItem(JSONObject docInfo, JSONObject docRoot) {
DocItem docItem = new DocItem();
docItem.setName(docInfo.getString("sop_name"));
docItem.setVersion(docInfo.getString("sop_version"));
docItem.setSummary(docInfo.getString("summary"));
docItem.setDescription(docInfo.getString("description"));
Optional<JSONArray> parametersOptional = Optional.ofNullable(docInfo.getJSONArray("parameters"));
JSONArray parameters = parametersOptional.orElse(new JSONArray());
List<DocParameter> docParameterList = parameters.toJavaList(DocParameter.class);
docItem.setRequestParameters(docParameterList);
List<DocParameter> responseParameterList = this.buildResponseParameterList(docInfo, docRoot);
docItem.setResponseParameters(responseParameterList);
return docItem;
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;
}
private List<DocParameter> buildResponseParameterList(JSONObject docInfo, JSONObject docRoot) {
String responseRef = getResponseRef(docInfo);
List<DocParameter> respParameterList = new ArrayList<>();
if (StringUtils.isNotBlank(responseRef)) {
JSONObject responseObject = docRoot.getJSONObject("definitions").getJSONObject(responseRef);
JSONObject properties = responseObject.getJSONObject("properties");
Set<String> fieldNames = properties.keySet();
for (String fieldName : fieldNames) {
JSONObject fieldInfo = properties.getJSONObject(fieldName);
DocParameter respParam = fieldInfo.toJavaObject(DocParameter.class);
respParam.setName(fieldName);
respParameterList.add(respParam);
}
protected DocParser buildDocParser(JSONObject rootDoc) {
Object easyopen = rootDoc.get("easyopen");
if (easyopen != null) {
return easyopenDocParser;
} else {
return swaggerDocParser;
}
return respParameterList;
}
private String getResponseRef(JSONObject docInfo) {
String ref = Optional.ofNullable(docInfo.getJSONObject("responses"))
.flatMap(jsonObject -> Optional.ofNullable(jsonObject.getJSONObject("200")))
.flatMap(jsonObject -> Optional.ofNullable(jsonObject.getJSONObject("schema")))
.flatMap(jsonObject -> Optional.ofNullable(jsonObject.getString("originalRef")))
.orElse("");
return ref;
}
@Override
@ -167,12 +130,12 @@ public class DocManagerImpl implements DocManager {
}
@Override
public DocModule getByTitle(String title) {
public DocInfo getByTitle(String title) {
return docDefinitionMap.get(title);
}
@Override
public Collection<DocModule> listAll() {
public Collection<DocInfo> listAll() {
return docDefinitionMap.values();
}
@ -186,11 +149,13 @@ public class DocManagerImpl implements DocManager {
.forEach(eurekaApplication -> {
List<EurekaInstance> instanceList = eurekaApplication.getInstance();
for (EurekaInstance instance : instanceList) {
ServiceInfoVO vo = new ServiceInfoVO();
vo.setName(eurekaApplication.getName());
vo.setIpAddr(instance.getIpAddr());
vo.setServerPort(instance.fetchPort());
serviceInfoVoList.add(vo);
if ("UP".equals(instance.getStatus())) {
ServiceInfoVO vo = new ServiceInfoVO();
vo.setName(eurekaApplication.getName());
vo.setIpAddr(instance.getIpAddr());
vo.setServerPort(instance.fetchPort());
serviceInfoVoList.add(vo);
}
}
});
@ -200,7 +165,7 @@ public class DocManagerImpl implements DocManager {
return listMap;
}
private String requestEurekaServer(EurekaUri eurekaUri, String... args) throws IOException {
protected String requestEurekaServer(EurekaUri eurekaUri, String... args) throws IOException {
Request request = eurekaUri.getRequest(this.eurekaUrl, args);
Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
@ -212,7 +177,7 @@ public class DocManagerImpl implements DocManager {
}
@PostConstruct
protected void after() {
protected void after() throws Exception {
String eurekaUrls = environment.getProperty("eureka.client.serviceUrl.defaultZone");
if (StringUtils.isBlank(eurekaUrls)) {
throw new IllegalArgumentException("未指定eureka.client.serviceUrl.defaultZone参数");
@ -222,5 +187,82 @@ public class DocManagerImpl implements DocManager {
url = eurekaUrls.substring(0, eurekaUrls.length() - 1);
}
this.eurekaUrl = url;
this.listenServiceId();
}
/**
* 监听serviceId更改
*
* @throws Exception
*/
protected void listenServiceId() throws Exception {
executorService.execute(new Consumer(queue, this));
ZookeeperContext.setEnvironment(environment);
String routeRootPath = ZookeeperContext.getRouteRootPath();
// 如果节点内容有变化则自动更新文档
ZookeeperContext.listenChildren(routeRootPath, 1, (client, event) -> {
if (listenInited) {
long id = System.currentTimeMillis();
// 延迟20秒执行
queue.offer(new Msg(id, 1000 * 20));
}
TreeCacheEvent.Type type = event.getType();
if (type == TreeCacheEvent.Type.INITIALIZED) {
listenInited = true;
}
});
}
static class Msg implements Delayed {
private long id;
private long delay;
// 自定义实现比较方法返回 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.valueOf(this.id) > Long.valueOf(msg.id) ? 1
: (Long.valueOf(this.id) < Long.valueOf(msg.id) ? -1 : 0);
}
// 延迟任务是否到时就是按照这个方法判断如果返回的是负数则说明到期否则还没到期
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(this.delay - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
}
@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 {
queue.take();
log.info("延迟队列触发--重新加载文档信息");
docManager.load();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}

@ -0,0 +1,11 @@
package com.gitee.sop.websiteserver.manager;
import com.alibaba.fastjson.JSONObject;
import com.gitee.sop.websiteserver.bean.DocInfo;
/**
* @author tanghc
*/
public interface DocParser {
DocInfo parseJson(JSONObject docRoot);
}

@ -0,0 +1,83 @@
package com.gitee.sop.websiteserver.manager;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.gitee.sop.websiteserver.bean.DocInfo;
import com.gitee.sop.websiteserver.bean.DocItem;
import com.gitee.sop.websiteserver.bean.DocModule;
import com.gitee.sop.websiteserver.bean.DocParameter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author tanghc
*/
public class EasyopenDocParser implements DocParser {
@Override
public DocInfo parseJson(JSONObject docRoot) {
String title = docRoot.getString("title");
List<DocItem> docItems = new ArrayList<>();
JSONArray apiModules = docRoot.getJSONArray("apiModules");
for (int i = 0; i < apiModules.size(); i++) {
JSONObject module = apiModules.getJSONObject(i);
JSONArray moduleItems = module.getJSONArray("moduleItems");
for (int k = 0; k < moduleItems.size(); k++) {
JSONObject docInfo = moduleItems.getJSONObject(k);
DocItem docItem = buildDocItem(docInfo);
docItem.setModule(module.getString("name"));
docItems.add(docItem);
}
}
List<DocModule> docModuleList = docItems.stream()
.collect(Collectors.groupingBy(DocItem::getModule))
.entrySet()
.stream()
.map(entry -> {
DocModule docModule = new DocModule();
docModule.setModule(entry.getKey());
docModule.setDocItems(entry.getValue());
return docModule;
})
.collect(Collectors.toList());
DocInfo docInfo = new DocInfo();
docInfo.setTitle(title);
docInfo.setDocModuleList(docModuleList);
return docInfo;
}
protected DocItem buildDocItem(JSONObject docInfo) {
DocItem docItem = new DocItem();
docItem.setName(docInfo.getString("name"));
docItem.setVersion(docInfo.getString("version"));
docItem.setSummary(docInfo.getString("description"));
docItem.setDescription(docInfo.getString("description"));
List<DocParameter> docParameterList = this.buildParameterList(docInfo, "paramDefinitions");
docItem.setRequestParameters(docParameterList);
List<DocParameter> responseParameterList = this.buildParameterList(docInfo, "resultDefinitions");
docItem.setResponseParameters(responseParameterList);
return docItem;
}
protected List<DocParameter> buildParameterList(JSONObject docInfo, String key) {
JSONArray params = docInfo.getJSONArray(key);
if (params == null) {
return Collections.emptyList();
}
List<DocParameter> docParameterList = new ArrayList<>();
for (int i = 0; i < params.size(); i++) {
JSONObject jsonObject = params.getJSONObject(i);
DocParameter docParameter = jsonObject.toJavaObject(DocParameter.class);
docParameter.setType(jsonObject.getString("dataType"));
docParameterList.add(docParameter);
}
return docParameterList;
}
}

@ -0,0 +1,115 @@
package com.gitee.sop.websiteserver.manager;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.gitee.sop.websiteserver.bean.DocInfo;
import com.gitee.sop.websiteserver.bean.DocItem;
import com.gitee.sop.websiteserver.bean.DocModule;
import com.gitee.sop.websiteserver.bean.DocParameter;
import org.apache.commons.lang.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 解析swagger的json内容
*
* @author tanghc
*/
public class SwaggerDocParser implements DocParser {
@Override
public DocInfo parseJson(JSONObject docRoot) {
String title = docRoot.getJSONObject("info").getString("title");
List<DocItem> docItems = new ArrayList<>();
JSONObject paths = docRoot.getJSONObject("paths");
Set<String> pathNameSet = paths.keySet();
for (String pathName : pathNameSet) {
JSONObject pathInfo = paths.getJSONObject(pathName);
Set<String> pathSet = pathInfo.keySet();
Optional<String> first = pathSet.stream().findFirst();
if (first.isPresent()) {
String path = first.get();
JSONObject docInfo = pathInfo.getJSONObject(path);
DocItem docItem = buildDocItem(docInfo, docRoot);
docItems.add(docItem);
}
}
List<DocModule> docModuleList = docItems.stream()
.collect(Collectors.groupingBy(DocItem::getModule))
.entrySet()
.stream()
.map(entry -> {
DocModule docModule = new DocModule();
docModule.setModule(entry.getKey());
docModule.setDocItems(entry.getValue());
return docModule;
})
.collect(Collectors.toList());
DocInfo docInfo = new DocInfo();
docInfo.setTitle(title);
docInfo.setDocModuleList(docModuleList);
return docInfo;
}
protected DocItem buildDocItem(JSONObject docInfo, JSONObject docRoot) {
DocItem docItem = new DocItem();
docItem.setName(docInfo.getString("sop_name"));
docItem.setVersion(docInfo.getString("sop_version"));
docItem.setSummary(docInfo.getString("summary"));
docItem.setDescription(docInfo.getString("description"));
String moduleName = this.buildModuleName(docInfo, docRoot);
docItem.setModule(moduleName);
Optional<JSONArray> parametersOptional = Optional.ofNullable(docInfo.getJSONArray("parameters"));
JSONArray parameters = parametersOptional.orElse(new JSONArray());
List<DocParameter> docParameterList = parameters.toJavaList(DocParameter.class);
docItem.setRequestParameters(docParameterList);
List<DocParameter> responseParameterList = this.buildResponseParameterList(docInfo, docRoot);
docItem.setResponseParameters(responseParameterList);
return docItem;
}
protected String buildModuleName(JSONObject docInfo, JSONObject docRoot) {
String title = docRoot.getJSONObject("info").getString("title");
JSONArray tags = docInfo.getJSONArray("tags");
if (tags != null && tags.size() > 0) {
return tags.getString(0);
}
return title;
}
protected List<DocParameter> buildResponseParameterList(JSONObject docInfo, JSONObject docRoot) {
String responseRef = getResponseRef(docInfo);
List<DocParameter> respParameterList = new ArrayList<>();
if (StringUtils.isNotBlank(responseRef)) {
JSONObject responseObject = docRoot.getJSONObject("definitions").getJSONObject(responseRef);
JSONObject properties = responseObject.getJSONObject("properties");
Set<String> fieldNames = properties.keySet();
for (String fieldName : fieldNames) {
JSONObject fieldInfo = properties.getJSONObject(fieldName);
DocParameter respParam = fieldInfo.toJavaObject(DocParameter.class);
respParam.setName(fieldName);
respParameterList.add(respParam);
}
}
return respParameterList;
}
protected String getResponseRef(JSONObject docInfo) {
String ref = Optional.ofNullable(docInfo.getJSONObject("responses"))
.flatMap(jsonObject -> Optional.ofNullable(jsonObject.getJSONObject("200")))
.flatMap(jsonObject -> Optional.ofNullable(jsonObject.getJSONObject("schema")))
.flatMap(jsonObject -> Optional.ofNullable(jsonObject.getString("originalRef")))
.orElse("");
return ref;
}
}

@ -11,5 +11,5 @@ import java.util.List;
public class DocBaseInfoVO {
private String urlTest;
private String urlProd;
private List<DocModuleVO> docModuleVOList;
private List<DocInfoVO> docInfoList;
}

@ -6,6 +6,6 @@ import lombok.Data;
* @author tanghc
*/
@Data
public class DocModuleVO {
private String module;
public class DocInfoVO {
private String title;
}
Loading…
Cancel
Save