From 5bd787b7e31254adf269dd507e3add8309f39daf Mon Sep 17 00:00:00 2001 From: tanghc Date: Fri, 19 Apr 2019 14:39:45 +0800 Subject: [PATCH] =?UTF-8?q?=20=E6=96=87=E6=A1=A3=E5=88=86=E7=BB=84?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=EF=BC=8C=E4=BB=A3=E7=A0=81=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../manager/BaseRouteManager.java | 178 ++++++++---------- .../manager/ZookeeperContext.java | 151 +++++++++------ .../configuration/ZuulErrorController.java | 7 +- .../servercommon/bean/EnvironmentContext.java | 27 --- .../servercommon/bean/ServiceConstants.java | 11 ++ .../sop/servercommon/bean/ZookeeperTool.java | 99 ++++++++++ .../EasyopenDocSupportController.java | 54 ++++++ .../SpringMvcServiceConfiguration.java | 2 - .../ServiceZookeeperApiMetaManager.java | 93 +++++---- .../param/ApiArgumentResolver.java | 2 +- .../swagger/ApiListingScannerExt.java | 16 -- .../swagger/SwaggerSecurityFilter.java | 36 ++-- .../servercommon/swagger/SwaggerSupport.java | 10 +- .../swagger/SwaggerValidator.java | 53 ++++++ .../server/config/BaseSopDocController.java | 28 --- .../easyopen/server/config/SopConfig.java | 3 +- .../com/gitee/sop/gateway/CuratorTest.java | 14 +- .../src/main/resources/application-dev.yml | 17 ++ .../src/main/resources/application.properties | 12 -- .../src/main/resources/application.yml | 3 + sop-website/website-front/pages/doc/doc.html | 12 +- .../websiteserver/bean/WebsiteConstants.java | 11 ++ .../websiteserver/bean/ZookeeperContext.java | 80 ++++++++ .../controller/DocController.java | 9 +- .../websiteserver/manager/DocManagerImpl.java | 108 ++++++++++- 25 files changed, 697 insertions(+), 339 deletions(-) delete mode 100644 sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/EnvironmentContext.java create mode 100644 sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/ServiceConstants.java create mode 100644 sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/ZookeeperTool.java create mode 100644 sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/EasyopenDocSupportController.java delete mode 100644 sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/swagger/ApiListingScannerExt.java create mode 100644 sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/swagger/SwaggerValidator.java delete mode 100644 sop-example/sop-easyopen/src/main/java/com/gitee/easyopen/server/config/BaseSopDocController.java create mode 100644 sop-registry/src/main/resources/application-dev.yml delete mode 100644 sop-registry/src/main/resources/application.properties create mode 100644 sop-registry/src/main/resources/application.yml create mode 100644 sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/WebsiteConstants.java create mode 100644 sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/bean/ZookeeperContext.java 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/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 index 1e3c92d9..9c8ffe26 100644 --- 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 @@ -1,8 +1,6 @@ package com.gitee.sop.servercommon.swagger; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.springframework.util.DigestUtils; import javax.servlet.Filter; import javax.servlet.FilterChain; @@ -13,7 +11,6 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -25,9 +22,7 @@ public class SwaggerSecurityFilter implements Filter { protected List urlFilters = new ArrayList<>(); - private String secret = "b749a2ec000f4f29"; - - public SwaggerSecurityFilter() { + { urlFilters.add(".*?/doc\\.html.*"); urlFilters.add(".*?/v2/api-docs.*"); urlFilters.add(".*?/v2/api-docs-ext.*"); @@ -37,6 +32,12 @@ public class SwaggerSecurityFilter implements Filter { 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) { @@ -50,6 +51,7 @@ public class SwaggerSecurityFilter implements Filter { return match; } + @Override public void init(FilterConfig filterConfig) throws ServletException { @@ -57,6 +59,10 @@ public class SwaggerSecurityFilter implements Filter { @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(); @@ -64,29 +70,15 @@ public class SwaggerSecurityFilter implements Filter { if (!match(uri)) { filterChain.doFilter(servletRequest, servletResponse); } else { - if (validate(request)) { + if (swaggerValidator.validate(request)) { filterChain.doFilter(servletRequest, servletResponse); } else { - response.setContentType("text/palin;charset=UTF-8"); - response.setStatus(403); - PrintWriter printWriter = response.getWriter(); - printWriter.write("access forbidden"); - printWriter.flush(); + swaggerValidator.writeForbidden(response); } } } - protected 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); - } @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 46bfc6ed..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 @@ -32,7 +32,15 @@ public abstract class SwaggerSupport { @Bean public SwaggerSecurityFilter swaggerSecurityFilter() { - return new SwaggerSecurityFilter(); + return new SwaggerSecurityFilter(swaggerAccessProtected()); + } + + /** + * swagger访问是否加密保护 + * @return + */ + protected boolean swaggerAccessProtected() { + return true; } protected ApiInfo apiInfo() { 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-easyopen/src/main/java/com/gitee/easyopen/server/config/BaseSopDocController.java b/sop-example/sop-easyopen/src/main/java/com/gitee/easyopen/server/config/BaseSopDocController.java deleted file mode 100644 index 11a4a32e..00000000 --- a/sop-example/sop-easyopen/src/main/java/com/gitee/easyopen/server/config/BaseSopDocController.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.gitee.easyopen.server.config; - -import com.gitee.easyopen.doc.ApiDocHolder; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; - -import java.util.HashMap; -import java.util.Map; - -public abstract class BaseSopDocController { - - public abstract String getDocTitle(); - - @RequestMapping("/v2/api-docs") - @ResponseBody - public Map getDocInfo() { - 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); - } - -} \ No newline at end of file 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 32213655..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,5 +1,6 @@ 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; @@ -11,7 +12,7 @@ import org.springframework.stereotype.Controller; public class SopConfig extends EasyopenServiceConfiguration { @Controller - public static class SopDocController extends BaseSopDocController { + public static class SopDocController extends EasyopenDocSupportController { @Override public String getDocTitle() { return "商品API"; 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 f9503144..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 @@
+
+
+ 公共参数 +
+
+

公共请求参数

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 712d723c..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 @@ -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/DocManagerImpl.java b/sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/manager/DocManagerImpl.java index ee5e9593..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 @@ -8,12 +8,14 @@ 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; @@ -29,6 +31,11 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.DelayQueue; +import java.util.concurrent.Delayed; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** @@ -53,13 +60,19 @@ public class DocManagerImpl implements DocManager { DocParser easyopenDocParser = new EasyopenDocParser(); - private String secret = "b749a2ec000f4f29p"; + 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 { @@ -70,7 +83,7 @@ public class DocManagerImpl implements DocManager { ServiceInfoVO serviceInfoVo = entry.getValue().get(0); loadDocInfo(serviceInfoVo); } - } catch (IOException e) { + } catch (Exception e) { log.error("加载失败", e); } } @@ -136,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); + } } }); @@ -162,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参数"); @@ -172,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(); + } + } + } } }