diff --git a/changelog.md b/changelog.md index ba975e89..32e84f98 100644 --- a/changelog.md +++ b/changelog.md @@ -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) diff --git a/sop-common/pom.xml b/sop-common/pom.xml index 775140ec..ac939b7c 100644 --- a/sop-common/pom.xml +++ b/sop-common/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.gitee.sop sop-common - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT pom diff --git a/sop-common/sop-gateway-common/pom.xml b/sop-common/sop-gateway-common/pom.xml index 591b2017..2816d97d 100644 --- a/sop-common/sop-gateway-common/pom.xml +++ b/sop-common/sop-gateway-common/pom.xml @@ -5,11 +5,11 @@ com.gitee.sop sop-common - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT ../pom.xml sop-gateway-common - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT jar sop-gateway-common diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/easyopen/EasyopenResultExecutor.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/easyopen/EasyopenResultExecutor.java index 2f1a2eac..d0ab701c 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/easyopen/EasyopenResultExecutor.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/easyopen/EasyopenResultExecutor.java @@ -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 { + + 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 diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/easyopen/EasyopenZuulConfiguration.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/easyopen/EasyopenZuulConfiguration.java index 6475368c..5e531fe4 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/easyopen/EasyopenZuulConfiguration.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/easyopen/EasyopenZuulConfiguration.java @@ -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; + } + + + } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/BaseRouteManager.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/BaseRouteManager.java index fe905877..4b4ea6ec 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/BaseRouteManager.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/BaseRouteManager.java @@ -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, E exte public BaseRouteManager(Environment environment, RouteRepository 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, 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 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, E exte * * @param servicePath */ - protected void loadServiceRouteItem(CuratorFramework client, R serviceRouteInfo, String servicePath) throws Exception { - // 获取service节点下所有的路由节点,里面保存的是路由名称,前面没有斜杠"/" - List 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); } } }); diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/ZookeeperContext.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/ZookeeperContext.java index 53ba87be..539bf001 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/ZookeeperContext.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/ZookeeperContext.java @@ -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> 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 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 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> 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 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); } } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/configuration/ZuulErrorController.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/configuration/ZuulErrorController.java index a2f9c135..a1a82b65 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/configuration/ZuulErrorController.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/configuration/ZuulErrorController.java @@ -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); diff --git a/sop-common/sop-service-common/pom.xml b/sop-common/sop-service-common/pom.xml index ef77ee82..dff31f08 100644 --- a/sop-common/sop-service-common/pom.xml +++ b/sop-common/sop-service-common/pom.xml @@ -6,11 +6,11 @@ com.gitee.sop sop-common - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT ../pom.xml sop-service-common - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT jar sop-service-common diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/EnvironmentContext.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/EnvironmentContext.java deleted file mode 100644 index 1244ea8c..00000000 --- a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/EnvironmentContext.java +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/ServiceConstants.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/ServiceConstants.java new file mode 100644 index 00000000..9b596b12 --- /dev/null +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/ServiceConstants.java @@ -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"; +} diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/ZookeeperTool.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/ZookeeperTool.java new file mode 100644 index 00000000..4d39a3f9 --- /dev/null +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/ZookeeperTool.java @@ -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(); + } + } +} diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/EasyopenDocSupportController.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/EasyopenDocSupportController.java new file mode 100644 index 00000000..69d5f804 --- /dev/null +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/EasyopenDocSupportController.java @@ -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 getDocInfo(HttpServletRequest request, HttpServletResponse response) throws IOException { + if (swaggerValidator.swaggerAccessProtected() && !swaggerValidator.validate(request)) { + swaggerValidator.writeForbidden(response); + return null; + } + Map context = this.getContext(); + context.put("easyopen", "1.16.3"); + context.put("apiModules", ApiDocHolder.getApiDocBuilder().getApiModules()); + context.put("title", getDocTitle()); + return context; + } + + public Map getContext() { + return new HashMap<>(8); + } + + /** + * swagger访问是否加密保护 + * @return + */ + protected boolean swaggerAccessProtected() { + return true; + } + +} \ No newline at end of file diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/SpringMvcServiceConfiguration.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/SpringMvcServiceConfiguration.java index c78307e2..6b76778e 100644 --- a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/SpringMvcServiceConfiguration.java +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/SpringMvcServiceConfiguration.java @@ -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(); }); diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/ServiceZookeeperApiMetaManager.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/ServiceZookeeperApiMetaManager.java index 19120ef6..bd475053 100644 --- a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/ServiceZookeeperApiMetaManager.java +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/ServiceZookeeperApiMetaManager.java @@ -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 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); - } - } diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ApiArgumentResolver.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ApiArgumentResolver.java index ce6e9458..0ffb944c 100644 --- a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ApiArgumentResolver.java +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ApiArgumentResolver.java @@ -10,7 +10,7 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; /** - * 解析request参数中的业务参数,隐射到方法参数上 + * 解析request参数中的业务参数,绑定到方法参数上 * * @author tanghc */ diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/swagger/ApiListingScannerExt.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/swagger/ApiListingScannerExt.java deleted file mode 100644 index fd322af4..00000000 --- a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/swagger/ApiListingScannerExt.java +++ /dev/null @@ -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); - } - -} diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/swagger/SwaggerSecurityFilter.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/swagger/SwaggerSecurityFilter.java new file mode 100644 index 00000000..9c8ffe26 --- /dev/null +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/swagger/SwaggerSecurityFilter.java @@ -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 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() { + + } +} diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/swagger/SwaggerSupport.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/swagger/SwaggerSupport.java index 6097c459..c207eb8d 100644 --- a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/swagger/SwaggerSupport.java +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/swagger/SwaggerSupport.java @@ -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()) diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/swagger/SwaggerValidator.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/swagger/SwaggerValidator.java new file mode 100644 index 00000000..80667950 --- /dev/null +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/swagger/SwaggerValidator.java @@ -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(); + } +} diff --git a/sop-example/sop-book/sop-book-web/pom.xml b/sop-example/sop-book/sop-book-web/pom.xml index 8f08939b..7b10e241 100644 --- a/sop-example/sop-book/sop-book-web/pom.xml +++ b/sop-example/sop-book/sop-book-web/pom.xml @@ -23,7 +23,7 @@ com.gitee.sop sop-service-common - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT com.gitee.sop diff --git a/sop-example/sop-easyopen/pom.xml b/sop-example/sop-easyopen/pom.xml index e10cddad..8d514de1 100644 --- a/sop-example/sop-easyopen/pom.xml +++ b/sop-example/sop-easyopen/pom.xml @@ -29,7 +29,7 @@ com.gitee.sop sop-service-common - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT org.springframework.cloud diff --git a/sop-example/sop-easyopen/src/main/java/com/gitee/easyopen/server/api/Goods2Api.java b/sop-example/sop-easyopen/src/main/java/com/gitee/easyopen/server/api/Goods2Api.java new file mode 100644 index 00000000..b41676cf --- /dev/null +++ b/sop-example/sop-easyopen/src/main/java/com/gitee/easyopen/server/api/Goods2Api.java @@ -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; + } + +} diff --git a/sop-example/sop-easyopen/src/main/java/com/gitee/easyopen/server/api/GoodsApi.java b/sop-example/sop-easyopen/src/main/java/com/gitee/easyopen/server/api/GoodsApi.java index fab74c0c..f26168ed 100644 --- a/sop-example/sop-easyopen/src/main/java/com/gitee/easyopen/server/api/GoodsApi.java +++ b/sop-example/sop-easyopen/src/main/java/com/gitee/easyopen/server/api/GoodsApi.java @@ -15,7 +15,7 @@ import java.math.BigDecimal; * @author tanghc */ @ApiService -@ApiDoc("商品模块") +@ApiDoc("商品接口") public class GoodsApi { @Api(name = "goods.get") diff --git a/sop-example/sop-easyopen/src/main/java/com/gitee/easyopen/server/config/SopConfig.java b/sop-example/sop-easyopen/src/main/java/com/gitee/easyopen/server/config/SopConfig.java index 490e16f2..a7d974f4 100644 --- a/sop-example/sop-easyopen/src/main/java/com/gitee/easyopen/server/config/SopConfig.java +++ b/sop-example/sop-easyopen/src/main/java/com/gitee/easyopen/server/config/SopConfig.java @@ -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"; + } + } } diff --git a/sop-example/sop-springmvc/pom.xml b/sop-example/sop-springmvc/pom.xml index 6d081bdb..3cf728cc 100644 --- a/sop-example/sop-springmvc/pom.xml +++ b/sop-example/sop-springmvc/pom.xml @@ -20,7 +20,7 @@ com.gitee.sop sop-service-common - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT diff --git a/sop-example/sop-story/sop-story-web/pom.xml b/sop-example/sop-story/sop-story-web/pom.xml index dcb1a855..d5c14358 100644 --- a/sop-example/sop-story/sop-story-web/pom.xml +++ b/sop-example/sop-story/sop-story-web/pom.xml @@ -23,7 +23,7 @@ com.gitee.sop sop-service-common - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT com.gitee.sop diff --git a/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/controller/AlipayController.java b/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/controller/AlipayController.java index 92b8a13e..2ff337d4 100644 --- a/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/controller/AlipayController.java +++ b/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/controller/AlipayController.java @@ -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") diff --git a/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/controller/Story2Controller.java b/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/controller/Story2Controller.java index 0c058582..360c3447 100644 --- a/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/controller/Story2Controller.java +++ b/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/bookweb/controller/Story2Controller.java @@ -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") diff --git a/sop-gateway/pom.xml b/sop-gateway/pom.xml index 37193ff7..02fc4cb3 100644 --- a/sop-gateway/pom.xml +++ b/sop-gateway/pom.xml @@ -23,7 +23,7 @@ com.gitee.sop sop-gateway-common - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/config/ZuulConfig.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/config/ZuulConfig.java index 5c481ef6..e03909c8 100644 --- a/sop-gateway/src/main/java/com/gitee/sop/gateway/config/ZuulConfig.java +++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/config/ZuulConfig.java @@ -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 diff --git a/sop-gateway/src/test/java/com/gitee/sop/gateway/CuratorTest.java b/sop-gateway/src/test/java/com/gitee/sop/gateway/CuratorTest.java index 91798ccc..9239d009 100644 --- a/sop-gateway/src/test/java/com/gitee/sop/gateway/CuratorTest.java +++ b/sop-gateway/src/test/java/com/gitee/sop/gateway/CuratorTest.java @@ -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) { - } } } diff --git a/sop-registry/src/main/resources/application-dev.yml b/sop-registry/src/main/resources/application-dev.yml new file mode 100644 index 00000000..41d0ffad --- /dev/null +++ b/sop-registry/src/main/resources/application-dev.yml @@ -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 \ No newline at end of file diff --git a/sop-registry/src/main/resources/application.properties b/sop-registry/src/main/resources/application.properties deleted file mode 100644 index da1d10a4..00000000 --- a/sop-registry/src/main/resources/application.properties +++ /dev/null @@ -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/ - diff --git a/sop-registry/src/main/resources/application.yml b/sop-registry/src/main/resources/application.yml new file mode 100644 index 00000000..caf4dfcd --- /dev/null +++ b/sop-registry/src/main/resources/application.yml @@ -0,0 +1,3 @@ +spring: + profiles: + active: dev \ No newline at end of file diff --git a/sop-website/website-front/pages/doc/doc.html b/sop-website/website-front/pages/doc/doc.html index 3c1e6e55..8d6b0dc0 100644 --- a/sop-website/website-front/pages/doc/doc.html +++ b/sop-website/website-front/pages/doc/doc.html @@ -82,12 +82,6 @@
- -
-
- 公共参数 -
-

请求地址

@@ -106,6 +100,12 @@
+
+
+ 公共参数 +
+
+

公共请求参数

@@ -206,7 +206,7 @@ - + diff --git a/sop-website/website-front/pages/doc/doc.js b/sop-website/website-front/pages/doc/doc.js index 50990652..17c85fd3 100644 --- a/sop-website/website-front/pages/doc/doc.js +++ b/sop-website/website-front/pages/doc/doc.js @@ -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(''); + var title = docInfo.title; + html.push(''); } $('#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 = ['
  • ' + docModule + '

  • ']; - for (var i = 0; i < docItems.length; i++) { - var docItem = docItems[i]; - docItemStore[docItem.nameVersion] = docItem; - /* -
  • - - 统一收单交易退款查询 - -
  • - */ - var selectedClass = i === 0 ? 'layui-this' : ''; - html.push('
  • '); - html.push(''+docItem.summary+'') + 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('
  • ' + module.module + '

  • '); + 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; + /* +
  • + + 统一收单交易退款查询 + +
  • + */ + html.push('
  • '); + html.push(''+docItem.summary+'') + } } + $('#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) { diff --git a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/DocInfo.java b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/DocInfo.java new file mode 100644 index 00000000..b48b93e5 --- /dev/null +++ b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/DocInfo.java @@ -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 docModuleList; +} diff --git a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/DocItem.java b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/DocItem.java index 076fb2f0..431c9482 100644 --- a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/DocItem.java +++ b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/DocItem.java @@ -9,6 +9,7 @@ import java.util.List; */ @Data public class DocItem { + private String module; private String name; private String version; private String summary; diff --git a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/WebsiteConstants.java b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/WebsiteConstants.java new file mode 100644 index 00000000..982ab15c --- /dev/null +++ b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/WebsiteConstants.java @@ -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"; +} diff --git a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/ZookeeperContext.java b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/ZookeeperContext.java new file mode 100644 index 00000000..94ccc2a9 --- /dev/null +++ b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/ZookeeperContext.java @@ -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(); + } + +} diff --git a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/controller/DocController.java b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/controller/DocController.java index e12b6826..82410925 100644 --- a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/controller/DocController.java +++ b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/controller/DocController.java @@ -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 docModuleVOList = docManager.listAll() + List 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); } } diff --git a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/DocManager.java b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/DocManager.java index 08de87b2..b60eb46f 100644 --- a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/DocManager.java +++ b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/DocManager.java @@ -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 listAll(); + Collection listAll(); } diff --git a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/DocManagerImpl.java b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/DocManagerImpl.java index 0dc78a33..c1af2bfe 100644 --- a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/DocManagerImpl.java +++ b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/DocManagerImpl.java @@ -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 docDefinitionMap = new HashMap<>(); + // key:title + Map docDefinitionMap = new HashMap<>(); // key: name+version Map 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 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 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 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 docItems = new ArrayList<>(); - - JSONObject paths = docRoot.getJSONObject("paths"); - Set pathNameSet = paths.keySet(); - for (String pathName : pathNameSet) { - JSONObject pathInfo = paths.getJSONObject(pathName); - Set pathSet = pathInfo.keySet(); - Optional 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 parametersOptional = Optional.ofNullable(docInfo.getJSONArray("parameters")); - JSONArray parameters = parametersOptional.orElse(new JSONArray()); - List docParameterList = parameters.toJavaList(DocParameter.class); - docItem.setRequestParameters(docParameterList); - - List 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 buildResponseParameterList(JSONObject docInfo, JSONObject docRoot) { - String responseRef = getResponseRef(docInfo); - List respParameterList = new ArrayList<>(); - if (StringUtils.isNotBlank(responseRef)) { - JSONObject responseObject = docRoot.getJSONObject("definitions").getJSONObject(responseRef); - JSONObject properties = responseObject.getJSONObject("properties"); - Set 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 listAll() { + public Collection listAll() { return docDefinitionMap.values(); } @@ -186,11 +149,13 @@ public class DocManagerImpl implements DocManager { .forEach(eurekaApplication -> { List 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 queue; + private DocManager docManager; + + public Consumer(DelayQueue 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(); + } + } + } } } diff --git a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/DocParser.java b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/DocParser.java new file mode 100644 index 00000000..70a4c3f7 --- /dev/null +++ b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/DocParser.java @@ -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); +} diff --git a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/EasyopenDocParser.java b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/EasyopenDocParser.java new file mode 100644 index 00000000..7745a603 --- /dev/null +++ b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/EasyopenDocParser.java @@ -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 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 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 docParameterList = this.buildParameterList(docInfo, "paramDefinitions"); + docItem.setRequestParameters(docParameterList); + + List responseParameterList = this.buildParameterList(docInfo, "resultDefinitions"); + docItem.setResponseParameters(responseParameterList); + + return docItem; + } + + protected List buildParameterList(JSONObject docInfo, String key) { + JSONArray params = docInfo.getJSONArray(key); + if (params == null) { + return Collections.emptyList(); + } + List 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; + } + +} diff --git a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/SwaggerDocParser.java b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/SwaggerDocParser.java new file mode 100644 index 00000000..85000a79 --- /dev/null +++ b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/SwaggerDocParser.java @@ -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 docItems = new ArrayList<>(); + + JSONObject paths = docRoot.getJSONObject("paths"); + Set pathNameSet = paths.keySet(); + for (String pathName : pathNameSet) { + JSONObject pathInfo = paths.getJSONObject(pathName); + Set pathSet = pathInfo.keySet(); + Optional 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 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 parametersOptional = Optional.ofNullable(docInfo.getJSONArray("parameters")); + JSONArray parameters = parametersOptional.orElse(new JSONArray()); + List docParameterList = parameters.toJavaList(DocParameter.class); + docItem.setRequestParameters(docParameterList); + + List 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 buildResponseParameterList(JSONObject docInfo, JSONObject docRoot) { + String responseRef = getResponseRef(docInfo); + List respParameterList = new ArrayList<>(); + if (StringUtils.isNotBlank(responseRef)) { + JSONObject responseObject = docRoot.getJSONObject("definitions").getJSONObject(responseRef); + JSONObject properties = responseObject.getJSONObject("properties"); + Set 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; + } + +} diff --git a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/vo/DocBaseInfoVO.java b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/vo/DocBaseInfoVO.java index 299d35de..ac893f7d 100644 --- a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/vo/DocBaseInfoVO.java +++ b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/vo/DocBaseInfoVO.java @@ -11,5 +11,5 @@ import java.util.List; public class DocBaseInfoVO { private String urlTest; private String urlProd; - private List docModuleVOList; + private List docInfoList; } diff --git a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/vo/DocModuleVO.java b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/vo/DocInfoVO.java similarity index 64% rename from sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/vo/DocModuleVO.java rename to sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/vo/DocInfoVO.java index 2cdc6271..307a02b4 100644 --- a/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/vo/DocModuleVO.java +++ b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/vo/DocInfoVO.java @@ -6,6 +6,6 @@ import lombok.Data; * @author tanghc */ @Data -public class DocModuleVO { - private String module; +public class DocInfoVO { + private String title; }
  • String 40详见应用授权概述暂未实现