mirror of
https://gitee.com/durcframework/SOP.git
synced 2025-08-11 21:57:56 +08:00
文档分组显示,代码优化
This commit is contained in:
@@ -5,15 +5,10 @@ import com.gitee.sop.gatewaycommon.bean.BaseRouteDefinition;
|
||||
import com.gitee.sop.gatewaycommon.bean.BaseServiceRouteInfo;
|
||||
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.curator.framework.CuratorFramework;
|
||||
import org.apache.curator.framework.recipes.cache.ChildData;
|
||||
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
|
||||
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
|
||||
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* 路由管理,采用zookeeper实现,监听路由的增删改,并适时更新到本地。路由的存储格式为:
|
||||
@@ -64,8 +59,10 @@ public abstract class BaseRouteManager<R extends BaseServiceRouteInfo<E>, E exte
|
||||
|
||||
public BaseRouteManager(Environment environment, RouteRepository<T> routeRepository) {
|
||||
this.environment = environment;
|
||||
ZookeeperContext.setEnvironment(environment);
|
||||
this.routeRepository = routeRepository;
|
||||
this.routeRootPath = ZookeeperContext.getRouteRootPath();
|
||||
this.createRouteRootPath(this.routeRootPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -75,52 +72,44 @@ public abstract class BaseRouteManager<R extends BaseServiceRouteInfo<E>, E exte
|
||||
|
||||
protected void refreshRouteInfo() {
|
||||
try {
|
||||
ZookeeperContext.createOrUpdateData(routeRootPath, "");
|
||||
CuratorFramework client = ZookeeperContext.getClient();
|
||||
this.watchServiceChange(client, routeRootPath);
|
||||
this.watchServiceChange(routeRootPath);
|
||||
} catch (Exception e) {
|
||||
log.error("刷新路由配置失败", e);
|
||||
throw new IllegalStateException("刷新路由配置失败");
|
||||
}
|
||||
}
|
||||
|
||||
protected void createRouteRootPath(String routeRootPath) {
|
||||
try {
|
||||
ZookeeperContext.createPath(routeRootPath, "");
|
||||
} catch (Exception e) {
|
||||
log.error("创建路由根节点失败", e);
|
||||
throw new IllegalStateException("创建路由根节点失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听微服务更改
|
||||
*
|
||||
* @param client
|
||||
* @param rootPath
|
||||
* @throws Exception
|
||||
*/
|
||||
protected void watchServiceChange(CuratorFramework client, String rootPath) throws Exception {
|
||||
// 为子节点添加watcher
|
||||
// PathChildrenCache: 监听数据节点的增删改,可以设置触发的事件
|
||||
PathChildrenCache childrenCache = new PathChildrenCache(client, rootPath, true);
|
||||
|
||||
/**
|
||||
* StartMode: 初始化方式
|
||||
* POST_INITIALIZED_EVENT:异步初始化,初始化之后会触发事件
|
||||
* NORMAL:异步初始化
|
||||
* BUILD_INITIAL_CACHE:同步初始化
|
||||
*/
|
||||
childrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
|
||||
|
||||
// 列出子节点数据列表,需要使用BUILD_INITIAL_CACHE同步初始化模式才能获得,异步是获取不到的
|
||||
List<ChildData> childDataList = childrenCache.getCurrentData();
|
||||
protected void watchServiceChange(String rootPath) throws Exception {
|
||||
ZookeeperContext.getChildrenAndListen(rootPath, childDataList -> {
|
||||
log.info("========== 加载路由信息 ==========");
|
||||
log.info("{} # 根节点", rootPath);
|
||||
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);
|
||||
try {
|
||||
this.loadServiceRouteItem(serviceRouteInfo, servicePath);
|
||||
} catch (Exception e) {
|
||||
log.error("加载路由信息失败,servicePath:{}", servicePath, e);
|
||||
}
|
||||
log.info("监听服务节点增删改,rootPath:{}", rootPath);
|
||||
// 监听根节点下面的子节点
|
||||
childrenCache.getListenable().addListener(new PathChildrenCacheListener() {
|
||||
@Override
|
||||
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
|
||||
}
|
||||
}, (client, event) -> {
|
||||
PathChildrenCacheEvent.Type type = event.getType();
|
||||
synchronized (type) {
|
||||
// 通过判断event type的方式来实现不同事件的触发
|
||||
@@ -130,7 +119,7 @@ public abstract class BaseRouteManager<R extends BaseServiceRouteInfo<E>, E exte
|
||||
// 添加子节点时触发
|
||||
String servicePath = event.getData().getPath();
|
||||
log.info("新增serviceId节点:{},节点数据:{}", servicePath, serviceNodeData);
|
||||
loadServiceRouteItem(client, serviceRouteInfo, servicePath);
|
||||
this.watchRouteItems(serviceRouteInfo, servicePath);
|
||||
} else if (PathChildrenCacheEvent.Type.CHILD_UPDATED.equals(type)) {
|
||||
// 修改子节点数据时触发,暂时没有什么操作
|
||||
String nodeData = new String(event.getData().getData());
|
||||
@@ -143,7 +132,6 @@ public abstract class BaseRouteManager<R extends BaseServiceRouteInfo<E>, E exte
|
||||
routeRepository.deleteAll(serviceRouteInfo.getServiceId());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -152,35 +140,30 @@ public abstract class BaseRouteManager<R extends BaseServiceRouteInfo<E>, E exte
|
||||
*
|
||||
* @param servicePath
|
||||
*/
|
||||
protected void loadServiceRouteItem(CuratorFramework client, R serviceRouteInfo, String servicePath) throws Exception {
|
||||
// 获取service节点下所有的路由节点,里面保存的是路由名称,前面没有斜杠"/"
|
||||
List<String> pathNameList = client.getChildren().forPath(servicePath);
|
||||
for (String pathName : pathNameList) {
|
||||
// 完整的路径
|
||||
String routeItemPath = servicePath + "/" + pathName;
|
||||
byte[] routeItemData = client.getData().forPath(routeItemPath);
|
||||
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(client, serviceRouteInfo, servicePath);
|
||||
});
|
||||
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 {
|
||||
ZookeeperContext.listenChildren(servicePath, (client, event) -> {
|
||||
PathChildrenCacheEvent.Type type = event.getType();
|
||||
synchronized (type) {
|
||||
// 通过判断event type的方式来实现不同事件的触发
|
||||
@@ -201,7 +184,6 @@ public abstract class BaseRouteManager<R extends BaseServiceRouteInfo<E>, E exte
|
||||
deleteRouteItem(serviceRouteInfo, routeDataJson);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
@@ -9,19 +9,17 @@ import org.apache.curator.framework.recipes.cache.ChildData;
|
||||
import org.apache.curator.framework.recipes.cache.NodeCache;
|
||||
import org.apache.curator.framework.recipes.cache.NodeCacheListener;
|
||||
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
|
||||
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
|
||||
import org.apache.curator.retry.ExponentialBackoffRetry;
|
||||
import org.apache.zookeeper.data.Stat;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static com.gitee.sop.gatewaycommon.bean.SopConstants.SOP_MSG_CHANNEL_PATH;
|
||||
import static com.gitee.sop.gatewaycommon.bean.SopConstants.SOP_SERVICE_ROUTE_PATH;
|
||||
import static com.gitee.sop.gatewaycommon.bean.SopConstants.SOP_ROUTE_PERMISSION_PATH;
|
||||
import static com.gitee.sop.gatewaycommon.bean.SopConstants.SOP_SERVICE_ROUTE_PATH;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
@@ -86,46 +84,9 @@ 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 data
|
||||
* @return 返回Stat
|
||||
* @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()
|
||||
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
|
||||
.creatingParentContainersIfNeeded()
|
||||
.forPath(path, data.getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* 新建或保存节点
|
||||
*
|
||||
* @param path
|
||||
* @param data
|
||||
* @return 返回path
|
||||
@@ -140,8 +101,35 @@ public class ZookeeperContext {
|
||||
.forPath(path, data.getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建path,如果path存在不报错,静默返回path名称
|
||||
*
|
||||
* @param path
|
||||
* @param data
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public static String createPath(String path, String data) throws Exception {
|
||||
if (isPathExist(path)) {
|
||||
return path;
|
||||
}
|
||||
return getClient().create()
|
||||
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
|
||||
.creatingParentContainersIfNeeded()
|
||||
.forPath(path, data.getBytes());
|
||||
}
|
||||
|
||||
public static boolean isPathExist(String path) {
|
||||
try {
|
||||
return client.checkExists().forPath(path) != null;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听一个节点
|
||||
*
|
||||
* @param path
|
||||
* @param onChange 节点修改后触发
|
||||
* @return 返回path
|
||||
@@ -160,39 +148,82 @@ public class ZookeeperContext {
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static String getData(String path) throws Exception {
|
||||
if (!isPathExist(path)) {
|
||||
return null;
|
||||
}
|
||||
byte[] data = getClient().getData().forPath(path);
|
||||
return new String(data);
|
||||
/**
|
||||
* 获取子节点信息并监听子节点
|
||||
*
|
||||
* @param parentPath 父节点路径
|
||||
* @param listConsumer 子节点数据
|
||||
* @param listener 监听事件
|
||||
* @throws Exception
|
||||
*/
|
||||
public static void getChildrenAndListen(String parentPath, Consumer<List<ChildData>> listConsumer, PathChildrenCacheListener listener) throws Exception {
|
||||
// 为子节点添加watcher
|
||||
// PathChildrenCache: 监听数据节点的增删改,可以设置触发的事件
|
||||
PathChildrenCache childrenCache = new PathChildrenCache(client, parentPath, true);
|
||||
|
||||
/**
|
||||
* StartMode: 初始化方式
|
||||
* POST_INITIALIZED_EVENT:异步初始化,初始化之后会触发事件
|
||||
* NORMAL:异步初始化
|
||||
* BUILD_INITIAL_CACHE:同步初始化
|
||||
*/
|
||||
childrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
|
||||
|
||||
// 列出子节点数据列表,需要使用BUILD_INITIAL_CACHE同步初始化模式才能获得,异步是获取不到的
|
||||
List<ChildData> childDataList = childrenCache.getCurrentData();
|
||||
listConsumer.accept(childDataList);
|
||||
log.info("监听子节点增删改,监听路径:{}", parentPath);
|
||||
// 监听根节点下面的子节点
|
||||
childrenCache.getListenable().addListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取子节点数据
|
||||
* 获取子节点信息
|
||||
*
|
||||
* @param parentPath 父节点
|
||||
* @return 返回子节点数据
|
||||
* @param parentPath 父节点路径
|
||||
* @param listConsumer 子节点数据
|
||||
* @throws Exception
|
||||
*/
|
||||
public static List<ChildData> getChildrenData(String parentPath) throws Exception {
|
||||
PathChildrenCache pathChildrenCache = buildPathChildrenCache(parentPath);
|
||||
if (pathChildrenCache == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return pathChildrenCache.getCurrentData();
|
||||
}
|
||||
|
||||
public static PathChildrenCache buildPathChildrenCache(String path) throws Exception {
|
||||
if (!isPathExist(path)) {
|
||||
return null;
|
||||
}
|
||||
public static void getChildrenData(String parentPath, Consumer<List<ChildData>> listConsumer) 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;
|
||||
|
||||
// 列出子节点数据列表,需要使用BUILD_INITIAL_CACHE同步初始化模式才能获得,异步是获取不到的
|
||||
List<ChildData> childDataList = childrenCache.getCurrentData();
|
||||
listConsumer.accept(childDataList);
|
||||
childrenCache.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听子节点的增删改
|
||||
*
|
||||
* @param parentPath 父节点路径
|
||||
* @param listener
|
||||
* @throws Exception
|
||||
*/
|
||||
public static void listenChildren(String parentPath, PathChildrenCacheListener listener) throws Exception {
|
||||
// 为子节点添加watcher
|
||||
// PathChildrenCache: 监听数据节点的增删改,可以设置触发的事件
|
||||
PathChildrenCache childrenCache = new PathChildrenCache(client, parentPath, true);
|
||||
|
||||
/**
|
||||
* StartMode: 初始化方式
|
||||
* POST_INITIALIZED_EVENT:异步初始化,初始化之后会触发事件
|
||||
* NORMAL:异步初始化
|
||||
* BUILD_INITIAL_CACHE:同步初始化
|
||||
*/
|
||||
childrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
|
||||
// 监听根节点下面的子节点
|
||||
childrenCache.getListenable().addListener(listener);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -1,27 +0,0 @@
|
||||
package com.gitee.sop.servercommon.bean;
|
||||
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
/**
|
||||
* @author thc
|
||||
*/
|
||||
public class EnvironmentContext {
|
||||
|
||||
private static Environment environment;
|
||||
|
||||
public static Environment getEnvironment() {
|
||||
return environment;
|
||||
}
|
||||
|
||||
public static void setEnvironment(Environment environment) {
|
||||
EnvironmentContext.environment = environment;
|
||||
}
|
||||
|
||||
public static String getProfile(Environment env) {
|
||||
return env.getProperty("spring.profiles.active", "default");
|
||||
}
|
||||
|
||||
public static String getProfile() {
|
||||
return getProfile(environment);
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
package com.gitee.sop.servercommon.bean;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public class ServiceConstants {
|
||||
/**
|
||||
* zookeeper存放接口路由信息的根目录
|
||||
*/
|
||||
public static final String SOP_SERVICE_ROUTE_PATH = "/com.gitee.sop.route";
|
||||
}
|
@@ -0,0 +1,99 @@
|
||||
package com.gitee.sop.servercommon.bean;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang.math.NumberUtils;
|
||||
import org.apache.curator.framework.CuratorFramework;
|
||||
import org.apache.curator.framework.CuratorFrameworkFactory;
|
||||
import org.apache.curator.retry.ExponentialBackoffRetry;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@Slf4j
|
||||
@Getter
|
||||
public class ZookeeperTool implements Closeable {
|
||||
|
||||
private CuratorFramework client;
|
||||
private Environment environment;
|
||||
|
||||
public ZookeeperTool(Environment environment) {
|
||||
this.environment = environment;
|
||||
initZookeeperClient(environment);
|
||||
}
|
||||
|
||||
public void initZookeeperClient(Environment environment) {
|
||||
String zookeeperServerAddr = environment.getProperty("spring.cloud.zookeeper.connect-string");
|
||||
if (StringUtils.isBlank(zookeeperServerAddr)) {
|
||||
throw new RuntimeException("未指定spring.cloud.zookeeper.connect-string参数");
|
||||
}
|
||||
String baseSleepTimeMs = environment.getProperty("spring.cloud.zookeeper.baseSleepTimeMs");
|
||||
String maxRetries = environment.getProperty("spring.cloud.zookeeper.maxRetries");
|
||||
log.info("初始化zookeeper客户端,zookeeperServerAddr:{}, baseSleepTimeMs:{}, maxRetries:{}",
|
||||
zookeeperServerAddr, baseSleepTimeMs, maxRetries);
|
||||
CuratorFramework client = CuratorFrameworkFactory.builder()
|
||||
.connectString(zookeeperServerAddr)
|
||||
.retryPolicy(new ExponentialBackoffRetry(NumberUtils.toInt(baseSleepTimeMs, 3000), NumberUtils.toInt(maxRetries, 3)))
|
||||
.build();
|
||||
|
||||
client.start();
|
||||
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public boolean isPathExist(String path) {
|
||||
try {
|
||||
return client.checkExists().forPath(path) != null;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建path,如果path存在不报错,静默返回path名称
|
||||
*
|
||||
* @param path
|
||||
* @param data
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public String createPath(String path, String data) throws Exception {
|
||||
if (isPathExist(path)) {
|
||||
return path;
|
||||
}
|
||||
return getClient().create()
|
||||
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
|
||||
.creatingParentContainersIfNeeded()
|
||||
.forPath(path, data.getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* 新建或保存节点
|
||||
*
|
||||
* @param path
|
||||
* @param data
|
||||
* @return 返回path
|
||||
* @throws Exception
|
||||
*/
|
||||
public String createOrUpdateData(String path, String data) throws Exception {
|
||||
return getClient().create()
|
||||
// 如果节点存在则Curator将会使用给出的数据设置这个节点的值
|
||||
.orSetData()
|
||||
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
|
||||
.creatingParentContainersIfNeeded()
|
||||
.forPath(path, data.getBytes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (this.client != null) {
|
||||
this.client.close();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
package com.gitee.sop.servercommon.configuration;
|
||||
|
||||
import com.gitee.easyopen.doc.ApiDocHolder;
|
||||
import com.gitee.sop.servercommon.swagger.SwaggerValidator;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 文档支持
|
||||
* @author thc
|
||||
*/
|
||||
public abstract class EasyopenDocSupportController {
|
||||
|
||||
private SwaggerValidator swaggerValidator;
|
||||
|
||||
public abstract String getDocTitle();
|
||||
|
||||
public EasyopenDocSupportController() {
|
||||
swaggerValidator = new SwaggerValidator(this.swaggerAccessProtected());
|
||||
}
|
||||
|
||||
@RequestMapping("/v2/api-docs")
|
||||
@ResponseBody
|
||||
public Map<String, Object> getDocInfo(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
if (swaggerValidator.swaggerAccessProtected() && !swaggerValidator.validate(request)) {
|
||||
swaggerValidator.writeForbidden(response);
|
||||
return null;
|
||||
}
|
||||
Map<String, Object> context = this.getContext();
|
||||
context.put("easyopen", "1.16.3");
|
||||
context.put("apiModules", ApiDocHolder.getApiDocBuilder().getApiModules());
|
||||
context.put("title", getDocTitle());
|
||||
return context;
|
||||
}
|
||||
|
||||
public Map<String, Object> getContext() {
|
||||
return new HashMap<>(8);
|
||||
}
|
||||
|
||||
/**
|
||||
* swagger访问是否加密保护
|
||||
* @return
|
||||
*/
|
||||
protected boolean swaggerAccessProtected() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@@ -1,6 +1,5 @@
|
||||
package com.gitee.sop.servercommon.configuration;
|
||||
|
||||
import com.gitee.sop.servercommon.bean.EnvironmentContext;
|
||||
import com.gitee.sop.servercommon.bean.ServiceConfig;
|
||||
import com.gitee.sop.servercommon.manager.ApiMetaManager;
|
||||
import com.gitee.sop.servercommon.manager.DefaultRequestMappingEvent;
|
||||
@@ -55,7 +54,6 @@ public class SpringMvcServiceConfiguration {
|
||||
@PostConstruct
|
||||
public final void after() {
|
||||
log.info("-----spring容器加载完毕-----");
|
||||
EnvironmentContext.setEnvironment(environment);
|
||||
Executors.newSingleThreadExecutor().execute(()->{
|
||||
uploadRouteToZookeeper();
|
||||
});
|
||||
|
@@ -2,15 +2,15 @@ package com.gitee.sop.servercommon.manager;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.gitee.sop.servercommon.bean.ServiceApiInfo;
|
||||
import com.gitee.sop.servercommon.bean.ServiceConstants;
|
||||
import com.gitee.sop.servercommon.bean.ZookeeperTool;
|
||||
import com.gitee.sop.servercommon.route.GatewayPredicateDefinition;
|
||||
import com.gitee.sop.servercommon.route.GatewayRouteDefinition;
|
||||
import com.gitee.sop.servercommon.route.ServiceRouteInfo;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.curator.framework.CuratorFramework;
|
||||
import org.apache.curator.framework.CuratorFrameworkFactory;
|
||||
import org.apache.curator.retry.ExponentialBackoffRetry;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -33,7 +33,7 @@ public class ServiceZookeeperApiMetaManager implements ApiMetaManager {
|
||||
/**
|
||||
* zookeeper存放接口路由信息的根目录
|
||||
*/
|
||||
public static final String SOP_SERVICE_ROUTE_PATH = "/com.gitee.sop.route";
|
||||
public static final String SOP_SERVICE_ROUTE_PATH = ServiceConstants.SOP_SERVICE_ROUTE_PATH;
|
||||
public static final String PATH_START_CHAR = "/";
|
||||
|
||||
/**
|
||||
@@ -44,24 +44,56 @@ public class ServiceZookeeperApiMetaManager implements ApiMetaManager {
|
||||
|
||||
private static ServiceApiInfo.ApiMeta FIRST_API_META = new ServiceApiInfo.ApiMeta("_first.route_", "/", "v_000");
|
||||
|
||||
private final String routeRootPath;
|
||||
private final String routeRootPath = SOP_SERVICE_ROUTE_PATH;
|
||||
private final String zookeeperServerAddr;
|
||||
|
||||
private Environment environment;
|
||||
|
||||
private ZookeeperTool zookeeperTool;
|
||||
|
||||
private String serviceId;
|
||||
|
||||
public ServiceZookeeperApiMetaManager(Environment environment) {
|
||||
this.environment = environment;
|
||||
this.routeRootPath = SOP_SERVICE_ROUTE_PATH;
|
||||
serviceId = environment.getProperty("spring.application.name");
|
||||
if (StringUtils.isEmpty(serviceId)) {
|
||||
throw new IllegalArgumentException("请在application.properties中指定spring.application.name属性");
|
||||
}
|
||||
zookeeperServerAddr = environment.getProperty("spring.cloud.zookeeper.connect-string");
|
||||
if (StringUtils.isEmpty(zookeeperServerAddr)) {
|
||||
throw new IllegalArgumentException("未指定spring.cloud.zookeeper.connect-string参数");
|
||||
}
|
||||
this.zookeeperTool = new ZookeeperTool(environment);
|
||||
|
||||
this.uploadServiceId(environment);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传serviceId目录
|
||||
*
|
||||
* @param environment
|
||||
*/
|
||||
protected void uploadServiceId(Environment environment) {
|
||||
try {
|
||||
ServiceRouteInfo serviceRouteInfo = this.buildServiceRouteInfo();
|
||||
// 保存路径
|
||||
String savePath = routeRootPath + "/" + serviceId;
|
||||
String nodeData = JSON.toJSONString(serviceRouteInfo);
|
||||
log.info("zookeeper创建serviceId路径,path:{}, nodeData:{}", savePath, nodeData);
|
||||
this.zookeeperTool.createPath(savePath, nodeData);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("zookeeper操作失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uploadApi(ServiceApiInfo serviceApiInfo) {
|
||||
try {
|
||||
ServiceRouteInfo serviceRouteInfo = this.buildServiceGatewayInfo(serviceApiInfo);
|
||||
this.uploadServiceRouteInfoToZookeeper(serviceRouteInfo);
|
||||
} finally {
|
||||
IOUtils.closeQuietly(zookeeperTool);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,11 +110,16 @@ public class ServiceZookeeperApiMetaManager implements ApiMetaManager {
|
||||
GatewayRouteDefinition gatewayRouteDefinition = this.buildGatewayRouteDefinition(serviceApiInfo, apiMeta);
|
||||
routeDefinitionList.add(gatewayRouteDefinition);
|
||||
}
|
||||
ServiceRouteInfo serviceRouteInfo = this.buildServiceRouteInfo();
|
||||
serviceRouteInfo.setRouteDefinitionList(routeDefinitionList);
|
||||
return serviceRouteInfo;
|
||||
}
|
||||
|
||||
protected ServiceRouteInfo buildServiceRouteInfo() {
|
||||
ServiceRouteInfo serviceRouteInfo = new ServiceRouteInfo();
|
||||
serviceRouteInfo.setServiceId(serviceApiInfo.getServiceId());
|
||||
serviceRouteInfo.setServiceId(serviceId);
|
||||
String description = environment.getProperty("spring.application.description");
|
||||
serviceRouteInfo.setDescription(description);
|
||||
serviceRouteInfo.setRouteDefinitionList(routeDefinitionList);
|
||||
return serviceRouteInfo;
|
||||
}
|
||||
|
||||
@@ -144,75 +181,51 @@ public class ServiceZookeeperApiMetaManager implements ApiMetaManager {
|
||||
* @param serviceRouteInfo
|
||||
*/
|
||||
protected void uploadServiceRouteInfoToZookeeper(ServiceRouteInfo serviceRouteInfo) {
|
||||
CuratorFramework client = null;
|
||||
try {
|
||||
// 保存路径
|
||||
String savePath = routeRootPath + "/" + serviceRouteInfo.getServiceId();
|
||||
|
||||
client = CuratorFrameworkFactory.builder()
|
||||
.connectString(zookeeperServerAddr)
|
||||
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
|
||||
.build();
|
||||
|
||||
client.start();
|
||||
|
||||
log.info("上传接口信息到zookeeper,path:{}, serviceId:{}, 接口数量:{}",
|
||||
savePath,
|
||||
serviceRouteInfo.getServiceId(),
|
||||
serviceRouteInfo.getRouteDefinitionList().size());
|
||||
|
||||
String parentPath = this.uploadFolder(client, serviceRouteInfo);
|
||||
this.uploadRouteItems(client, serviceRouteInfo, parentPath);
|
||||
String parentPath = this.uploadFolder(serviceRouteInfo);
|
||||
this.uploadRouteItems(serviceRouteInfo, parentPath);
|
||||
} catch (Exception e) {
|
||||
log.error("更新接口信息到zookeeper失败, serviceId:{}", serviceRouteInfo.getServiceId(), e);
|
||||
} finally {
|
||||
if (client != null) {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件夹内容
|
||||
*
|
||||
* @param client
|
||||
* @param serviceRouteInfo
|
||||
* @return 返回文件夹路径
|
||||
*/
|
||||
protected String uploadFolder(CuratorFramework client, ServiceRouteInfo serviceRouteInfo) throws Exception {
|
||||
protected String uploadFolder(ServiceRouteInfo serviceRouteInfo) throws Exception {
|
||||
// 保存路径
|
||||
String savePath = routeRootPath + "/" + serviceRouteInfo.getServiceId();
|
||||
String serviceRouteInfoJson = JSON.toJSONString(serviceRouteInfo);
|
||||
log.info("上传service目录到zookeeper,路径:{},内容:{}", savePath, serviceRouteInfoJson);
|
||||
this.saveNode(client, savePath, serviceRouteInfoJson.getBytes());
|
||||
this.zookeeperTool.createOrUpdateData(savePath, serviceRouteInfoJson);
|
||||
return savePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传路由信息
|
||||
*
|
||||
* @param client
|
||||
* @param serviceRouteInfo
|
||||
* @throws Exception
|
||||
*/
|
||||
protected void uploadRouteItems(CuratorFramework client, ServiceRouteInfo serviceRouteInfo, String parentPath) throws Exception {
|
||||
protected void uploadRouteItems(ServiceRouteInfo serviceRouteInfo, String parentPath) throws Exception {
|
||||
List<GatewayRouteDefinition> routeDefinitionList = serviceRouteInfo.getRouteDefinitionList();
|
||||
for (GatewayRouteDefinition routeDefinition : routeDefinitionList) {
|
||||
// 父目录/子目录
|
||||
String savePath = parentPath + PATH_START_CHAR + routeDefinition.getId();
|
||||
String routeDefinitionJson = JSON.toJSONString(routeDefinition);
|
||||
log.info("上传路由配置到zookeeper,路径:{},路由数据:{}", savePath, routeDefinitionJson);
|
||||
this.saveNode(client, savePath, routeDefinitionJson.getBytes());
|
||||
this.zookeeperTool.createOrUpdateData(savePath, routeDefinitionJson);
|
||||
}
|
||||
}
|
||||
|
||||
protected void saveNode(CuratorFramework client, String path, byte[] data) throws Exception {
|
||||
client.create()
|
||||
// 如果节点存在则Curator将会使用给出的数据设置这个节点的值
|
||||
.orSetData()
|
||||
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
|
||||
.creatingParentContainersIfNeeded()
|
||||
.forPath(path, data);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -10,7 +10,7 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
|
||||
/**
|
||||
* 解析request参数中的业务参数,隐射到方法参数上
|
||||
* 解析request参数中的业务参数,绑定到方法参数上
|
||||
*
|
||||
* @author tanghc
|
||||
*/
|
||||
|
@@ -1,16 +0,0 @@
|
||||
package com.gitee.sop.servercommon.swagger;
|
||||
|
||||
import springfox.documentation.spring.web.plugins.DocumentationPluginsManager;
|
||||
import springfox.documentation.spring.web.scanners.ApiDescriptionReader;
|
||||
import springfox.documentation.spring.web.scanners.ApiListingScanner;
|
||||
import springfox.documentation.spring.web.scanners.ApiModelReader;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public class ApiListingScannerExt extends ApiListingScanner {
|
||||
public ApiListingScannerExt(ApiDescriptionReader apiDescriptionReader, ApiModelReader apiModelReader, DocumentationPluginsManager pluginsManager) {
|
||||
super(apiDescriptionReader, apiModelReader, pluginsManager);
|
||||
}
|
||||
|
||||
}
|
@@ -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<String> 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() {
|
||||
|
@@ -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() {
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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<String, Object> getDocInfo() {
|
||||
Map<String, Object> context = this.getContext();
|
||||
context.put("easyopen", "1.16.3");
|
||||
context.put("apiModules", ApiDocHolder.getApiDocBuilder().getApiModules());
|
||||
context.put("title", getDocTitle());
|
||||
return context;
|
||||
}
|
||||
|
||||
public Map<String, Object> getContext() {
|
||||
return new HashMap<>(8);
|
||||
}
|
||||
|
||||
}
|
@@ -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";
|
||||
|
@@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
17
sop-registry/src/main/resources/application-dev.yml
Normal file
17
sop-registry/src/main/resources/application-dev.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
eureka:
|
||||
client:
|
||||
fetch-registry: false
|
||||
# 不注册自己
|
||||
register-with-eureka: false
|
||||
serviceUrl:
|
||||
defaultZone: http://${eureka.host}:${eureka.port}/eureka/
|
||||
# 注册中心地址
|
||||
host: localhost
|
||||
port: 1111
|
||||
|
||||
server:
|
||||
port: 1111
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: sop-registry
|
@@ -1,12 +0,0 @@
|
||||
spring.application.name=sop-registry
|
||||
server.port=1111
|
||||
|
||||
# ---- eureka注册中心 ----
|
||||
# 不注册自己
|
||||
eureka.client.register-with-eureka=false
|
||||
eureka.client.fetch-registry=false
|
||||
# 注册中心地址
|
||||
eureka.host=localhost
|
||||
eureka.port=1111
|
||||
eureka.client.serviceUrl.defaultZone=http://${eureka.host}:${eureka.port}/eureka/
|
||||
|
3
sop-registry/src/main/resources/application.yml
Normal file
3
sop-registry/src/main/resources/application.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
spring:
|
||||
profiles:
|
||||
active: dev
|
@@ -82,12 +82,6 @@
|
||||
<blockquote class="layui-elem-quote layui-text sop-description">
|
||||
</blockquote>
|
||||
|
||||
|
||||
<div class="site-title">
|
||||
<fieldset class="layui-elem-field layui-field-title site-title">
|
||||
<legend><a name="use">公共参数</a></legend>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="site-text">
|
||||
<h3>请求地址</h3>
|
||||
<table class="layui-table">
|
||||
@@ -106,6 +100,12 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="site-title">
|
||||
<fieldset class="layui-elem-field layui-field-title site-title">
|
||||
<legend><a name="use">公共参数</a></legend>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="site-text">
|
||||
<h3>公共请求参数</h3>
|
||||
<table class="layui-table">
|
||||
|
@@ -0,0 +1,11 @@
|
||||
package com.gitee.sop.websiteserver.bean;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public class WebsiteConstants {
|
||||
/**
|
||||
* zookeeper存放接口路由信息的根目录
|
||||
*/
|
||||
public static final String SOP_SERVICE_ROUTE_PATH = "/com.gitee.sop.route";
|
||||
}
|
@@ -0,0 +1,80 @@
|
||||
package com.gitee.sop.websiteserver.bean;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang.math.NumberUtils;
|
||||
import org.apache.curator.framework.CuratorFramework;
|
||||
import org.apache.curator.framework.CuratorFrameworkFactory;
|
||||
import org.apache.curator.framework.recipes.cache.TreeCache;
|
||||
import org.apache.curator.framework.recipes.cache.TreeCacheListener;
|
||||
import org.apache.curator.retry.ExponentialBackoffRetry;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@Slf4j
|
||||
public class ZookeeperContext {
|
||||
|
||||
private static CuratorFramework client;
|
||||
|
||||
public static void setEnvironment(Environment environment) {
|
||||
Assert.notNull(environment, "environment不能为null");
|
||||
initZookeeperClient(environment);
|
||||
}
|
||||
|
||||
public synchronized static void initZookeeperClient(Environment environment) {
|
||||
if (client != null) {
|
||||
return;
|
||||
}
|
||||
String zookeeperServerAddr = environment.getProperty("spring.cloud.zookeeper.connect-string");
|
||||
if (StringUtils.isBlank(zookeeperServerAddr)) {
|
||||
throw new RuntimeException("未指定spring.cloud.zookeeper.connect-string参数");
|
||||
}
|
||||
String baseSleepTimeMs = environment.getProperty("spring.cloud.zookeeper.baseSleepTimeMs");
|
||||
String maxRetries = environment.getProperty("spring.cloud.zookeeper.maxRetries");
|
||||
log.info("初始化zookeeper客户端,zookeeperServerAddr:{}, baseSleepTimeMs:{}, maxRetries:{}",
|
||||
zookeeperServerAddr, baseSleepTimeMs, maxRetries);
|
||||
CuratorFramework client = CuratorFrameworkFactory.builder()
|
||||
.connectString(zookeeperServerAddr)
|
||||
.retryPolicy(new ExponentialBackoffRetry(NumberUtils.toInt(baseSleepTimeMs, 3000), NumberUtils.toInt(maxRetries, 3)))
|
||||
.build();
|
||||
|
||||
client.start();
|
||||
|
||||
setClient(client);
|
||||
}
|
||||
|
||||
public static String getRouteRootPath() {
|
||||
return WebsiteConstants.SOP_SERVICE_ROUTE_PATH;
|
||||
}
|
||||
|
||||
public static CuratorFramework getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public static void setClient(CuratorFramework client) {
|
||||
ZookeeperContext.client = client;
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听子节点,可以自定义层级
|
||||
* @param parentPath 父节点路径
|
||||
* @param maxDepth 层级,从1开始。比如当前监听节点/t1,目录最深为/t1/t2/t3/t4,则maxDepth=3,说明下面3级子目录全
|
||||
* @param listener
|
||||
* @throws Exception
|
||||
*/
|
||||
public static void listenChildren(String parentPath, int maxDepth, TreeCacheListener listener) throws Exception {
|
||||
final TreeCache treeCache = TreeCache
|
||||
.newBuilder(client, parentPath)
|
||||
.setCacheData(true)
|
||||
.setMaxDepth(maxDepth)
|
||||
.build();
|
||||
|
||||
treeCache.getListenable().addListener(listener);
|
||||
//没有开启模式作为入参的方法
|
||||
treeCache.start();
|
||||
}
|
||||
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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<Msg> 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,12 +149,14 @@ public class DocManagerImpl implements DocManager {
|
||||
.forEach(eurekaApplication -> {
|
||||
List<EurekaInstance> instanceList = eurekaApplication.getInstance();
|
||||
for (EurekaInstance instance : instanceList) {
|
||||
if ("UP".equals(instance.getStatus())) {
|
||||
ServiceInfoVO vo = new ServiceInfoVO();
|
||||
vo.setName(eurekaApplication.getName());
|
||||
vo.setIpAddr(instance.getIpAddr());
|
||||
vo.setServerPort(instance.fetchPort());
|
||||
serviceInfoVoList.add(vo);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Map<String, List<ServiceInfoVO>> listMap = serviceInfoVoList.stream()
|
||||
@@ -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<Msg> queue;
|
||||
private DocManager docManager;
|
||||
|
||||
public Consumer(DelayQueue<Msg> queue, DocManager docManager) {
|
||||
this.queue = queue;
|
||||
this.docManager = docManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (true) {
|
||||
try {
|
||||
queue.take();
|
||||
log.info("延迟队列触发--重新加载文档信息");
|
||||
docManager.load();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user