parent
55e5a7e795
commit
219f944162
@ -0,0 +1,29 @@ |
|||||||
|
package com.gitee.sop.adminserver.bean; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
public class NacosConfigs { |
||||||
|
|
||||||
|
public static final String GROUP_CHANNEL = "sop:channel"; |
||||||
|
|
||||||
|
public static final String GROUP_ROUTE = "sop:route"; |
||||||
|
|
||||||
|
public static final String DATA_ID_GRAY = "com.gitee.sop.channel.gray"; |
||||||
|
|
||||||
|
public static final String DATA_ID_IP_BLACKLIST = "com.gitee.sop.channel.ipblacklist"; |
||||||
|
|
||||||
|
public static final String DATA_ID_ISV = "com.gitee.sop.channel.isv"; |
||||||
|
|
||||||
|
public static final String DATA_ID_ROUTE_PERMISSION = "com.gitee.sop.channel.routepermission"; |
||||||
|
|
||||||
|
public static final String DATA_ID_LIMIT_CONFIG = "com.gitee.sop.channel.limitconfig"; |
||||||
|
|
||||||
|
public static final String DATA_ID_ROUTE_CONFIG = "com.gitee.sop.channel.routeconfig"; |
||||||
|
|
||||||
|
private static final String DATA_ID_TPL = "com.gitee.sop.route.%s"; |
||||||
|
|
||||||
|
public static String getRouteDataId(String serviceId) { |
||||||
|
return String.format(DATA_ID_TPL, serviceId); |
||||||
|
} |
||||||
|
} |
@ -1,344 +0,0 @@ |
|||||||
package com.gitee.sop.adminserver.bean; |
|
||||||
|
|
||||||
import com.gitee.sop.adminserver.common.ZookeeperOperationException; |
|
||||||
import com.gitee.sop.adminserver.common.ZookeeperPathExistException; |
|
||||||
import com.gitee.sop.adminserver.common.ZookeeperPathNotExistException; |
|
||||||
import lombok.extern.slf4j.Slf4j; |
|
||||||
import org.apache.commons.io.IOUtils; |
|
||||||
import org.apache.commons.lang.StringUtils; |
|
||||||
import org.apache.commons.lang.math.NumberUtils; |
|
||||||
import org.apache.curator.framework.CuratorFramework; |
|
||||||
import org.apache.curator.framework.CuratorFrameworkFactory; |
|
||||||
import org.apache.curator.framework.recipes.cache.ChildData; |
|
||||||
import org.apache.curator.framework.recipes.cache.NodeCache; |
|
||||||
import org.apache.curator.framework.recipes.cache.NodeCacheListener; |
|
||||||
import org.apache.curator.framework.recipes.cache.PathChildrenCache; |
|
||||||
import org.apache.curator.retry.ExponentialBackoffRetry; |
|
||||||
import org.apache.zookeeper.CreateMode; |
|
||||||
import org.apache.zookeeper.data.Stat; |
|
||||||
import org.springframework.core.env.Environment; |
|
||||||
import org.springframework.util.Assert; |
|
||||||
|
|
||||||
import java.io.Closeable; |
|
||||||
import java.util.Collections; |
|
||||||
import java.util.List; |
|
||||||
import java.util.concurrent.Executors; |
|
||||||
|
|
||||||
import static com.gitee.sop.adminserver.bean.SopAdminConstants.SOP_MSG_CHANNEL_PATH; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author tanghc |
|
||||||
*/ |
|
||||||
@Slf4j |
|
||||||
public class ZookeeperContext { |
|
||||||
|
|
||||||
private static CuratorFramework client; |
|
||||||
|
|
||||||
private static Environment environment; |
|
||||||
|
|
||||||
public static void setEnvironment(Environment environment) { |
|
||||||
Assert.notNull(environment, "environment不能为null"); |
|
||||||
ZookeeperContext.environment = environment; |
|
||||||
initZookeeperClient(); |
|
||||||
} |
|
||||||
|
|
||||||
public synchronized static void initZookeeperClient() { |
|
||||||
if (client != null) { |
|
||||||
return; |
|
||||||
} |
|
||||||
setClient(createClient()); |
|
||||||
} |
|
||||||
|
|
||||||
public static CuratorFramework createClient() { |
|
||||||
String zookeeperServerAddr = environment.getProperty("spring.cloud.zookeeper.connect-string"); |
|
||||||
if (StringUtils.isBlank(zookeeperServerAddr)) { |
|
||||||
throw new RuntimeException("未指定spring.cloud.zookeeper.connect-string参数"); |
|
||||||
} |
|
||||||
String baseSleepTimeMs = environment.getProperty("spring.cloud.zookeeper.baseSleepTimeMs"); |
|
||||||
String maxRetries = environment.getProperty("spring.cloud.zookeeper.maxRetries"); |
|
||||||
log.info("初始化zookeeper客户端,zookeeperServerAddr:{}, baseSleepTimeMs:{}, maxRetries:{}", |
|
||||||
zookeeperServerAddr, baseSleepTimeMs, maxRetries); |
|
||||||
CuratorFramework client = CuratorFrameworkFactory.builder() |
|
||||||
.connectString(zookeeperServerAddr) |
|
||||||
.retryPolicy(new ExponentialBackoffRetry(NumberUtils.toInt(baseSleepTimeMs, 3000), NumberUtils.toInt(maxRetries, 3))) |
|
||||||
.build(); |
|
||||||
|
|
||||||
client.start(); |
|
||||||
return client; |
|
||||||
} |
|
||||||
|
|
||||||
public static String getSopRouteRootPath() { |
|
||||||
return SopAdminConstants.SOP_SERVICE_ROUTE_PATH; |
|
||||||
} |
|
||||||
|
|
||||||
public static String buildServiceIdPath(String serviceId) { |
|
||||||
if (StringUtils.isBlank(serviceId)) { |
|
||||||
throw new NullPointerException("serviceId不能为空"); |
|
||||||
} |
|
||||||
return getSopRouteRootPath() + "/" + serviceId; |
|
||||||
} |
|
||||||
|
|
||||||
public static String buildRoutePath(String serviceId, String routeId) { |
|
||||||
if (StringUtils.isBlank(serviceId)) { |
|
||||||
throw new NullPointerException("serviceId不能为空"); |
|
||||||
} |
|
||||||
if (StringUtils.isBlank(routeId)) { |
|
||||||
throw new NullPointerException("routeId不能为空"); |
|
||||||
} |
|
||||||
String serviceIdPath = getSopRouteRootPath() + "/" + serviceId; |
|
||||||
return serviceIdPath + "/" + routeId; |
|
||||||
} |
|
||||||
|
|
||||||
public static String getServiceGrayChannelPath() { |
|
||||||
return SOP_MSG_CHANNEL_PATH + "/gray"; |
|
||||||
} |
|
||||||
|
|
||||||
public static String getIsvInfoChannelPath() { |
|
||||||
return SOP_MSG_CHANNEL_PATH + "/isvinfo"; |
|
||||||
} |
|
||||||
|
|
||||||
public static String getIsvRoutePermissionChannelPath() { |
|
||||||
return SOP_MSG_CHANNEL_PATH + "/isv-route-permission"; |
|
||||||
} |
|
||||||
|
|
||||||
public static String getRouteConfigChannelPath() { |
|
||||||
return SOP_MSG_CHANNEL_PATH + "/route-conf"; |
|
||||||
} |
|
||||||
|
|
||||||
public static String getLimitConfigChannelPath() { |
|
||||||
return SOP_MSG_CHANNEL_PATH + "/limit-conf"; |
|
||||||
} |
|
||||||
|
|
||||||
public static String getIpBlacklistChannelPath() { |
|
||||||
return SOP_MSG_CHANNEL_PATH + "/ipblacklist-conf"; |
|
||||||
} |
|
||||||
|
|
||||||
public static CuratorFramework getClient() { |
|
||||||
return client; |
|
||||||
} |
|
||||||
|
|
||||||
public static void setClient(CuratorFramework client) { |
|
||||||
ZookeeperContext.client = client; |
|
||||||
} |
|
||||||
|
|
||||||
public static boolean isPathExist(String path) { |
|
||||||
try { |
|
||||||
return client.checkExists().forPath(path) != null; |
|
||||||
} catch (Exception e) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 对已存在的path赋值。如果path不存在抛异常 |
|
||||||
* |
|
||||||
* @param path 已存在的 |
|
||||||
* @param data 数据 |
|
||||||
* @return |
|
||||||
* @throws ZookeeperPathNotExistException |
|
||||||
*/ |
|
||||||
public static Stat updatePathData(String path, String data) throws ZookeeperPathNotExistException { |
|
||||||
if (!isPathExist(path)) { |
|
||||||
throw new ZookeeperPathNotExistException("path " + path + " 不存在"); |
|
||||||
} |
|
||||||
try { |
|
||||||
return getClient().setData().forPath(path, data.getBytes()); |
|
||||||
} catch (Exception e) { |
|
||||||
throw new ZookeeperOperationException("updatePathData error, path=" + path, e); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 创建新的path,并赋值。如果path已存在抛异常 |
|
||||||
* |
|
||||||
* @param path 待创建的path |
|
||||||
* @param data 值 |
|
||||||
* @throws ZookeeperPathExistException |
|
||||||
*/ |
|
||||||
public static String addPath(String path, String data) throws ZookeeperPathExistException { |
|
||||||
if (isPathExist(path)) { |
|
||||||
throw new ZookeeperPathExistException("path " + path + " 已存在"); |
|
||||||
} |
|
||||||
try { |
|
||||||
return addPath(path, CreateMode.PERSISTENT, data); |
|
||||||
} catch (Exception e) { |
|
||||||
throw new ZookeeperOperationException("addPath error path=" + path, e); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 添加节点 |
|
||||||
* |
|
||||||
* @param path 待创建的path |
|
||||||
* @param createMode 节点类型 |
|
||||||
* @param data 节点数据 |
|
||||||
* @return |
|
||||||
*/ |
|
||||||
public static String addPath(String path, CreateMode createMode, String data) { |
|
||||||
try { |
|
||||||
return getClient().create() |
|
||||||
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
|
|
||||||
.creatingParentContainersIfNeeded() |
|
||||||
.withMode(createMode) |
|
||||||
.forPath(path, data.getBytes()); |
|
||||||
} catch (Exception e) { |
|
||||||
throw new ZookeeperOperationException("addPath error path=" + path, e); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 删除节点及子节点 |
|
||||||
* |
|
||||||
* @param path |
|
||||||
*/ |
|
||||||
public static void deletePathDeep(String path) { |
|
||||||
try { |
|
||||||
getClient().delete() |
|
||||||
.deletingChildrenIfNeeded() |
|
||||||
.forPath(path); |
|
||||||
} catch (Exception e) { |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* 创建新的path,并赋值。如果path已存在则不创建 |
|
||||||
* |
|
||||||
* @param path 待创建的path |
|
||||||
* @param data 值 |
|
||||||
*/ |
|
||||||
public static String addPathQuietly(String path, String data) { |
|
||||||
if (isPathExist(path)) { |
|
||||||
return path; |
|
||||||
} |
|
||||||
try { |
|
||||||
return addPath(path, data); |
|
||||||
} catch (Exception e) { |
|
||||||
throw new ZookeeperOperationException("addPathQuietly error path=" + path, e); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 新建或保存节点 |
|
||||||
* |
|
||||||
* @param path |
|
||||||
* @param data |
|
||||||
* @return |
|
||||||
*/ |
|
||||||
public static String createOrUpdateData(String path, String data) { |
|
||||||
try { |
|
||||||
return getClient().create() |
|
||||||
// 如果节点存在则Curator将会使用给出的数据设置这个节点的值
|
|
||||||
.orSetData() |
|
||||||
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
|
|
||||||
.creatingParentContainersIfNeeded() |
|
||||||
.forPath(path, data.getBytes()); |
|
||||||
} catch (Exception e) { |
|
||||||
throw new ZookeeperOperationException("createOrUpdateData error path=" + path, e); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 获取节点内容 |
|
||||||
* |
|
||||||
* @param path |
|
||||||
* @return |
|
||||||
* @throws ZookeeperPathNotExistException |
|
||||||
*/ |
|
||||||
public static String getData(String path) throws ZookeeperPathNotExistException { |
|
||||||
if (!isPathExist(path)) { |
|
||||||
throw new ZookeeperPathNotExistException("path 不存在, path=" + path); |
|
||||||
} |
|
||||||
try { |
|
||||||
byte[] data = getClient().getData().forPath(path); |
|
||||||
return new String(data); |
|
||||||
} catch (Exception e) { |
|
||||||
throw new ZookeeperOperationException("getData error path=" + path, e); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 获取子节点数据 |
|
||||||
* |
|
||||||
* @param parentPath 父节点 |
|
||||||
* @return |
|
||||||
*/ |
|
||||||
public static List<ChildData> getChildrenData(String parentPath) { |
|
||||||
PathChildrenCache pathChildrenCache = buildPathChildrenCache(parentPath); |
|
||||||
if (pathChildrenCache == null) { |
|
||||||
return Collections.emptyList(); |
|
||||||
} |
|
||||||
return pathChildrenCache.getCurrentData(); |
|
||||||
} |
|
||||||
|
|
||||||
public static PathChildrenCache buildPathChildrenCache(String path) { |
|
||||||
if (!isPathExist(path)) { |
|
||||||
return null; |
|
||||||
} |
|
||||||
// PathChildrenCache: 监听数据节点的增删改,可以设置触发的事件
|
|
||||||
// 且第三个参数要设置为true,不然ChildData对象中的getData返回null
|
|
||||||
PathChildrenCache childrenCache = new PathChildrenCache(client, path, true); |
|
||||||
// 列出子节点数据列表,需要使用BUILD_INITIAL_CACHE同步初始化模式才能获得,异步是获取不到的
|
|
||||||
try { |
|
||||||
childrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE); |
|
||||||
} catch (Exception e) { |
|
||||||
throw new ZookeeperOperationException("buildPathChildrenCache error path=" + path, e); |
|
||||||
} |
|
||||||
return childrenCache; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 监听一个临时节点 |
|
||||||
* |
|
||||||
* @param path |
|
||||||
* @param listenCallback 回调 |
|
||||||
* @return 返回path |
|
||||||
* @throws Exception |
|
||||||
*/ |
|
||||||
public static void listenTempPath(String path, ListenCallback listenCallback) throws Exception { |
|
||||||
String initData = "{}"; |
|
||||||
CuratorFramework client = createClient(); |
|
||||||
client.create() |
|
||||||
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
|
|
||||||
.creatingParentContainersIfNeeded() |
|
||||||
.withMode(CreateMode.EPHEMERAL) |
|
||||||
.forPath(path, initData.getBytes()); |
|
||||||
|
|
||||||
final NodeCache cache = new NodeCache(client, path, false); |
|
||||||
cache.getListenable().addListener(new NodeCacheListener() { |
|
||||||
@Override |
|
||||||
public void nodeChanged() throws Exception { |
|
||||||
byte[] nodeData = cache.getCurrentData().getData(); |
|
||||||
String data = new String(nodeData); |
|
||||||
if (StringUtils.isNotBlank(data) && !initData.equals(data)) { |
|
||||||
listenCallback.onError(data); |
|
||||||
Executors.newSingleThreadExecutor().execute(() -> new ZKClose(cache, client)); |
|
||||||
} |
|
||||||
} |
|
||||||
}); |
|
||||||
cache.start(); |
|
||||||
} |
|
||||||
|
|
||||||
public interface ListenCallback { |
|
||||||
void onError(String errorMsg); |
|
||||||
} |
|
||||||
|
|
||||||
static class ZKClose implements Runnable { |
|
||||||
Closeable[] closes; |
|
||||||
|
|
||||||
public ZKClose(Closeable ...closes) { |
|
||||||
this.closes = closes; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void run() { |
|
||||||
try { |
|
||||||
Thread.sleep(2000); |
|
||||||
IOUtils.closeQuietly(closes); |
|
||||||
} catch (InterruptedException e) { |
|
||||||
e.printStackTrace(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,26 +0,0 @@ |
|||||||
package com.gitee.sop.adminserver.common; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author tanghc |
|
||||||
*/ |
|
||||||
public class ZookeeperOperationException extends RuntimeException { |
|
||||||
|
|
||||||
public ZookeeperOperationException() { |
|
||||||
} |
|
||||||
|
|
||||||
public ZookeeperOperationException(String message) { |
|
||||||
super(message); |
|
||||||
} |
|
||||||
|
|
||||||
public ZookeeperOperationException(String message, Throwable cause) { |
|
||||||
super(message, cause); |
|
||||||
} |
|
||||||
|
|
||||||
public ZookeeperOperationException(Throwable cause) { |
|
||||||
super(cause); |
|
||||||
} |
|
||||||
|
|
||||||
public ZookeeperOperationException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { |
|
||||||
super(message, cause, enableSuppression, writableStackTrace); |
|
||||||
} |
|
||||||
} |
|
@ -1,10 +0,0 @@ |
|||||||
package com.gitee.sop.adminserver.common; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author tanghc |
|
||||||
*/ |
|
||||||
public class ZookeeperPathExistException extends Exception { |
|
||||||
public ZookeeperPathExistException(String message) { |
|
||||||
super(message); |
|
||||||
} |
|
||||||
} |
|
@ -1,10 +0,0 @@ |
|||||||
package com.gitee.sop.adminserver.common; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author tanghc |
|
||||||
*/ |
|
||||||
public class ZookeeperPathNotExistException extends Exception { |
|
||||||
public ZookeeperPathNotExistException(String message) { |
|
||||||
super(message); |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,32 @@ |
|||||||
|
package com.gitee.sop.adminserver.service; |
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON; |
||||||
|
import com.alibaba.nacos.api.annotation.NacosInjected; |
||||||
|
import com.alibaba.nacos.api.config.ConfigService; |
||||||
|
import com.alibaba.nacos.api.exception.NacosException; |
||||||
|
import com.gitee.sop.adminserver.bean.ChannelMsg; |
||||||
|
import com.gitee.sop.adminserver.common.BizException; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.stereotype.Service; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Slf4j |
||||||
|
@Service |
||||||
|
public class ConfigPushService { |
||||||
|
|
||||||
|
@NacosInjected |
||||||
|
private ConfigService configService; |
||||||
|
|
||||||
|
public void publishConfig(String dataId, String groupId, ChannelMsg channelMsg) { |
||||||
|
try { |
||||||
|
log.info("nacos配置, dataId:{}, groupId:{}, operation:{}", dataId, groupId, channelMsg.getOperation()); |
||||||
|
configService.publishConfig(dataId, groupId, JSON.toJSONString(channelMsg)); |
||||||
|
} catch (NacosException e) { |
||||||
|
log.error("nacos配置失败, dataId:{}, groupId:{}, operation:{}", dataId, groupId, channelMsg.getOperation(), e); |
||||||
|
throw new BizException("nacos配置失败"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -1,45 +0,0 @@ |
|||||||
package com.gitee.sop.adminserver; |
|
||||||
|
|
||||||
import com.gitee.sop.adminserver.bean.SopAdminConstants; |
|
||||||
import junit.framework.TestCase; |
|
||||||
import org.apache.curator.framework.CuratorFramework; |
|
||||||
import org.apache.curator.framework.CuratorFrameworkFactory; |
|
||||||
import org.apache.curator.retry.ExponentialBackoffRetry; |
|
||||||
import org.apache.zookeeper.data.Stat; |
|
||||||
|
|
||||||
public class CuratorTest extends TestCase { |
|
||||||
|
|
||||||
private static String zookeeperServerAddr = "localhost:2181"; |
|
||||||
|
|
||||||
static CuratorFramework client; |
|
||||||
|
|
||||||
public CuratorTest() { |
|
||||||
client = CuratorFrameworkFactory.builder() |
|
||||||
.connectString(zookeeperServerAddr) |
|
||||||
.retryPolicy(new ExponentialBackoffRetry(1000, 3)) |
|
||||||
.build(); |
|
||||||
|
|
||||||
client.start(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 递归删除节点,只能在测试环境用。 |
|
||||||
* |
|
||||||
* @throws Exception |
|
||||||
*/ |
|
||||||
public void testDel() { |
|
||||||
try { |
|
||||||
client.delete() |
|
||||||
.deletingChildrenIfNeeded() |
|
||||||
.forPath(SopAdminConstants.RELOAD_ROUTE_PERMISSION_PATH); |
|
||||||
} catch (Exception e) { |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public void testCheck() throws Exception { |
|
||||||
String path = SopAdminConstants.RELOAD_ROUTE_PERMISSION_PATH + "/1562231019332"; |
|
||||||
Stat stat = client.checkExists().forPath(path); |
|
||||||
System.out.println(path + (stat == null ? "不存在" : "存在")); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,4 +1,4 @@ |
|||||||
package com.gitee.sop.gatewaycommon.gateway.route; |
package com.gitee.sop.gatewaycommon.bean; |
||||||
|
|
||||||
import lombok.Data; |
import lombok.Data; |
||||||
|
|
@ -0,0 +1,42 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.bean; |
||||||
|
|
||||||
|
import lombok.Data; |
||||||
|
import org.springframework.util.StringUtils; |
||||||
|
|
||||||
|
import javax.validation.ValidationException; |
||||||
|
import java.util.LinkedHashMap; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Data |
||||||
|
public class GatewayPredicateDefinition { |
||||||
|
public static final String GEN_KEY = "_genkey_"; |
||||||
|
/** 断言对应的Name */ |
||||||
|
private String name; |
||||||
|
/** 配置的断言规则 */ |
||||||
|
private Map<String, String> args = new LinkedHashMap<>(); |
||||||
|
|
||||||
|
public GatewayPredicateDefinition() { |
||||||
|
} |
||||||
|
|
||||||
|
public GatewayPredicateDefinition(String text) { |
||||||
|
int eqIdx = text.indexOf(61); |
||||||
|
if (eqIdx <= 0) { |
||||||
|
throw new ValidationException("Unable to parse GatewayPredicateDefinition text '" + text + "', must be of the form name=value"); |
||||||
|
} else { |
||||||
|
this.setName(text.substring(0, eqIdx)); |
||||||
|
String[] params = StringUtils.tokenizeToStringArray(text.substring(eqIdx + 1), ","); |
||||||
|
|
||||||
|
for(int i = 0; i < params.length; ++i) { |
||||||
|
this.args.put(generateName(i), params[i]); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static String generateName(int i) { |
||||||
|
return GEN_KEY + i; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,72 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.bean; |
||||||
|
|
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Data |
||||||
|
public class GatewayRouteDefinition { |
||||||
|
/** |
||||||
|
* 路由的Id(接口名+版本号),确保此id全局唯一 |
||||||
|
*/ |
||||||
|
private String id; |
||||||
|
|
||||||
|
/** |
||||||
|
* 接口名 |
||||||
|
*/ |
||||||
|
private String name; |
||||||
|
|
||||||
|
/** |
||||||
|
* 版本号 |
||||||
|
*/ |
||||||
|
private String version; |
||||||
|
|
||||||
|
/** |
||||||
|
* 路由断言集合配置 |
||||||
|
*/ |
||||||
|
private List<GatewayPredicateDefinition> predicates = new ArrayList<>(); |
||||||
|
|
||||||
|
/** |
||||||
|
* 路由过滤器集合配置 |
||||||
|
*/ |
||||||
|
private List<GatewayFilterDefinition> filters = new ArrayList<>(); |
||||||
|
|
||||||
|
/** |
||||||
|
* 路由规则转发的目标uri |
||||||
|
*/ |
||||||
|
private String uri; |
||||||
|
|
||||||
|
/** |
||||||
|
* uri后面跟的path |
||||||
|
*/ |
||||||
|
private String path; |
||||||
|
|
||||||
|
/** |
||||||
|
* 路由执行的顺序 |
||||||
|
*/ |
||||||
|
private int order = 0; |
||||||
|
|
||||||
|
/** |
||||||
|
* 是否忽略验证,业务参数验证除外 |
||||||
|
*/ |
||||||
|
private int ignoreValidate; |
||||||
|
|
||||||
|
/** |
||||||
|
* 状态,0:待审核,1:启用,2:禁用 |
||||||
|
*/ |
||||||
|
private int status = 1; |
||||||
|
|
||||||
|
/** |
||||||
|
* 是否合并结果 |
||||||
|
*/ |
||||||
|
private int mergeResult; |
||||||
|
|
||||||
|
/** |
||||||
|
* 是否需要授权才能访问 |
||||||
|
*/ |
||||||
|
private int permission; |
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.bean; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
public class NacosConfigs { |
||||||
|
|
||||||
|
public static final String GROUP_CHANNEL = "sop:channel"; |
||||||
|
|
||||||
|
public static final String GROUP_ROUTE = "sop:route"; |
||||||
|
|
||||||
|
public static final String DATA_ID_GRAY = "com.gitee.sop.channel.gray"; |
||||||
|
|
||||||
|
public static final String DATA_ID_IP_BLACKLIST = "com.gitee.sop.channel.ipblacklist"; |
||||||
|
|
||||||
|
public static final String DATA_ID_ISV = "com.gitee.sop.channel.isv"; |
||||||
|
|
||||||
|
public static final String DATA_ID_ROUTE_PERMISSION = "com.gitee.sop.channel.routepermission"; |
||||||
|
|
||||||
|
public static final String DATA_ID_LIMIT_CONFIG = "com.gitee.sop.channel.limitconfig"; |
||||||
|
|
||||||
|
public static final String DATA_ID_ROUTE_CONFIG = "com.gitee.sop.channel.routeconfig"; |
||||||
|
|
||||||
|
private static final String DATA_ID_TPL = "com.gitee.sop.route.%s"; |
||||||
|
|
||||||
|
public static String getRouteDataId(String serviceId) { |
||||||
|
return String.format(DATA_ID_TPL, serviceId); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,38 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.bean; |
||||||
|
|
||||||
|
import com.alibaba.fastjson.annotation.JSONField; |
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
import java.util.Date; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Data |
||||||
|
public class ServiceRouteInfo { |
||||||
|
|
||||||
|
/** |
||||||
|
* 服务名称,对应spring.application.name |
||||||
|
*/ |
||||||
|
private String serviceId; |
||||||
|
|
||||||
|
@JSONField(format = "yyyy-MM-dd HH:mm:ss") |
||||||
|
private Date createTime = new Date(); |
||||||
|
|
||||||
|
@JSONField(format = "yyyy-MM-dd HH:mm:ss") |
||||||
|
private Date updateTime = new Date(); |
||||||
|
|
||||||
|
private String description; |
||||||
|
|
||||||
|
/** |
||||||
|
* 路由信息md5,md5(sort(routeIdList)) |
||||||
|
*/ |
||||||
|
private String md5; |
||||||
|
|
||||||
|
private List<GatewayRouteDefinition> routeDefinitionList; |
||||||
|
|
||||||
|
public String fetchServiceIdLowerCase() { |
||||||
|
return serviceId.toLowerCase(); |
||||||
|
} |
||||||
|
} |
@ -1,17 +0,0 @@ |
|||||||
package com.gitee.sop.gatewaycommon.gateway.route; |
|
||||||
|
|
||||||
import lombok.Data; |
|
||||||
|
|
||||||
import java.util.LinkedHashMap; |
|
||||||
import java.util.Map; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author tanghc |
|
||||||
*/ |
|
||||||
@Data |
|
||||||
public class GatewayPredicateDefinition { |
|
||||||
/** 断言对应的Name */ |
|
||||||
private String name; |
|
||||||
/** 配置的断言规则 */ |
|
||||||
private Map<String, String> args = new LinkedHashMap<>(); |
|
||||||
} |
|
@ -1,21 +0,0 @@ |
|||||||
package com.gitee.sop.gatewaycommon.gateway.route; |
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.BaseRouteDefinition; |
|
||||||
import lombok.Data; |
|
||||||
import lombok.Getter; |
|
||||||
import lombok.Setter; |
|
||||||
|
|
||||||
import java.util.ArrayList; |
|
||||||
import java.util.List; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author tanghc |
|
||||||
*/ |
|
||||||
@Getter |
|
||||||
@Setter |
|
||||||
public class GatewayRouteDefinition extends BaseRouteDefinition { |
|
||||||
/** 路由断言集合配置 */ |
|
||||||
private List<GatewayPredicateDefinition> predicates = new ArrayList<>(); |
|
||||||
/** 路由过滤器集合配置 */ |
|
||||||
private List<GatewayFilterDefinition> filters = new ArrayList<>(); |
|
||||||
} |
|
@ -1,9 +0,0 @@ |
|||||||
package com.gitee.sop.gatewaycommon.gateway.route; |
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.BaseServiceRouteInfo; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author tanghc |
|
||||||
*/ |
|
||||||
public class GatewayServiceRouteInfo extends BaseServiceRouteInfo<GatewayRouteDefinition> { |
|
||||||
} |
|
@ -1,13 +1,16 @@ |
|||||||
package com.gitee.sop.gatewaycommon.gateway.route; |
package com.gitee.sop.gatewaycommon.gateway.route; |
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.AbstractTargetRoute; |
import com.gitee.sop.gatewaycommon.bean.AbstractTargetRoute; |
||||||
|
import com.gitee.sop.gatewaycommon.bean.GatewayRouteDefinition; |
||||||
|
import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo; |
||||||
import org.springframework.cloud.gateway.route.RouteDefinition; |
import org.springframework.cloud.gateway.route.RouteDefinition; |
||||||
|
|
||||||
/** |
/** |
||||||
* @author tanghc |
* @author tanghc |
||||||
*/ |
*/ |
||||||
public class GatewayTargetRoute extends AbstractTargetRoute<GatewayServiceRouteInfo, GatewayRouteDefinition, RouteDefinition> { |
public class GatewayTargetRoute extends AbstractTargetRoute<RouteDefinition> { |
||||||
public GatewayTargetRoute(GatewayServiceRouteInfo baseServiceRouteInfo, GatewayRouteDefinition baseRouteDefinition, RouteDefinition targetRoute) { |
|
||||||
super(baseServiceRouteInfo, baseRouteDefinition, targetRoute); |
public GatewayTargetRoute(ServiceRouteInfo serviceRouteInfo, GatewayRouteDefinition routeDefinition, RouteDefinition targetRoute) { |
||||||
|
super(serviceRouteInfo, routeDefinition, targetRoute); |
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -0,0 +1,62 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.manager; |
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.GatewayRouteDefinition; |
||||||
|
import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo; |
||||||
|
import com.gitee.sop.gatewaycommon.bean.TargetRoute; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
|
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Objects; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Slf4j |
||||||
|
public abstract class BaseRouteCache<T extends TargetRoute> implements RouteLoader { |
||||||
|
|
||||||
|
/** |
||||||
|
* KEY:serviceId, value: md5 |
||||||
|
*/ |
||||||
|
private Map<String, String> serviceIdMd5Map = new HashMap<>(); |
||||||
|
|
||||||
|
private RouteRepository<T> routeRepository; |
||||||
|
|
||||||
|
/** |
||||||
|
* 构建目标路由对象,zuul和gateway定义的路由对象 |
||||||
|
* |
||||||
|
* @param serviceRouteInfo 路由服务对象 |
||||||
|
* @param gatewayRouteDefinition 路由对象 |
||||||
|
* @return 返回目标路由对象 |
||||||
|
*/ |
||||||
|
protected abstract T buildRouteDefinition(ServiceRouteInfo serviceRouteInfo, GatewayRouteDefinition gatewayRouteDefinition); |
||||||
|
|
||||||
|
public BaseRouteCache(RouteRepository<T> routeRepository) { |
||||||
|
this.routeRepository = routeRepository; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void load(ServiceRouteInfo serviceRouteInfo) { |
||||||
|
try { |
||||||
|
String newMd5 = serviceRouteInfo.getMd5(); |
||||||
|
String md5 = serviceIdMd5Map.putIfAbsent(serviceRouteInfo.getServiceId(), newMd5); |
||||||
|
if (Objects.equals(newMd5, md5)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
List<GatewayRouteDefinition> routeDefinitionList = serviceRouteInfo.getRouteDefinitionList(); |
||||||
|
for (GatewayRouteDefinition gatewayRouteDefinition : routeDefinitionList) { |
||||||
|
T routeDefinition = this.buildRouteDefinition(serviceRouteInfo, gatewayRouteDefinition); |
||||||
|
routeRepository.add(routeDefinition); |
||||||
|
} |
||||||
|
} catch (Exception e) { |
||||||
|
log.error("加载路由信息失败,serviceRouteInfo:{}", serviceRouteInfo, e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void remove(String serviceId) { |
||||||
|
serviceIdMd5Map.remove(serviceId); |
||||||
|
routeRepository.deleteAll(serviceId); |
||||||
|
} |
||||||
|
} |
@ -1,220 +0,0 @@ |
|||||||
package com.gitee.sop.gatewaycommon.manager; |
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSON; |
|
||||||
import com.gitee.sop.gatewaycommon.bean.BaseRouteDefinition; |
|
||||||
import com.gitee.sop.gatewaycommon.bean.BaseServiceRouteInfo; |
|
||||||
import com.gitee.sop.gatewaycommon.bean.TargetRoute; |
|
||||||
import lombok.extern.slf4j.Slf4j; |
|
||||||
import org.apache.curator.framework.recipes.cache.ChildData; |
|
||||||
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent; |
|
||||||
import org.springframework.core.env.Environment; |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* 路由管理,采用zookeeper实现,监听路由的增删改,并适时更新到本地。路由的存储格式为: |
|
||||||
* <pre> |
|
||||||
* /com.gitee.sop.route 根节点 |
|
||||||
* /serviceId 服务节点,名字为服务名 |
|
||||||
* /route1 路由节点,名字为:name+version,存放路由信息 |
|
||||||
* /route2 |
|
||||||
* /... |
|
||||||
* </pre> |
|
||||||
* |
|
||||||
* @param <R> 路由根对象,可以理解为最外面的大json:{....,routeDefinitionList:[]} |
|
||||||
* @param <E> 路由Item对象,对应大json里面的具体路由信息,routeDefinitionList:[] |
|
||||||
* @param <T> 目标路由对象 |
|
||||||
* @author tanghc |
|
||||||
*/ |
|
||||||
@Slf4j |
|
||||||
public abstract class BaseRouteManager<R extends BaseServiceRouteInfo<E>, E extends BaseRouteDefinition, T extends TargetRoute> implements RouteManager { |
|
||||||
|
|
||||||
protected Environment environment; |
|
||||||
|
|
||||||
protected RouteRepository<T> routeRepository; |
|
||||||
|
|
||||||
protected String routeRootPath; |
|
||||||
|
|
||||||
/** |
|
||||||
* 返回路由根对象class |
|
||||||
* |
|
||||||
* @return 返回R.class |
|
||||||
*/ |
|
||||||
protected abstract Class<R> getServiceRouteInfoClass(); |
|
||||||
|
|
||||||
/** |
|
||||||
* 返回路由Item对象class |
|
||||||
* |
|
||||||
* @return 返回E.class |
|
||||||
*/ |
|
||||||
protected abstract Class<E> getRouteDefinitionClass(); |
|
||||||
|
|
||||||
/** |
|
||||||
* 构建目标路由对象,zuul和gateway定义的路由对象 |
|
||||||
* |
|
||||||
* @param serviceRouteInfo |
|
||||||
* @param routeDefinition |
|
||||||
* @return 返回目标路由对象 |
|
||||||
*/ |
|
||||||
protected abstract T buildRouteDefinition(R serviceRouteInfo, E routeDefinition); |
|
||||||
|
|
||||||
public BaseRouteManager(Environment environment, RouteRepository<T> routeRepository) { |
|
||||||
this.environment = environment; |
|
||||||
ZookeeperContext.setEnvironment(environment); |
|
||||||
this.routeRepository = routeRepository; |
|
||||||
this.routeRootPath = ZookeeperContext.getRouteRootPath(); |
|
||||||
this.createRouteRootPath(this.routeRootPath); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void refresh() { |
|
||||||
this.refreshRouteInfo(); |
|
||||||
} |
|
||||||
|
|
||||||
protected void refreshRouteInfo() { |
|
||||||
try { |
|
||||||
this.watchServiceChange(routeRootPath); |
|
||||||
} catch (Exception e) { |
|
||||||
log.error("刷新路由配置失败", e); |
|
||||||
throw new IllegalStateException("刷新路由配置失败"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
protected void createRouteRootPath(String routeRootPath) { |
|
||||||
try { |
|
||||||
ZookeeperContext.createPath(routeRootPath, ""); |
|
||||||
} catch (Exception e) { |
|
||||||
log.error("创建路由根节点失败", e); |
|
||||||
throw new IllegalStateException("创建路由根节点失败"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 监听微服务更改 |
|
||||||
* |
|
||||||
* @param rootPath |
|
||||||
* @throws Exception |
|
||||||
*/ |
|
||||||
protected void watchServiceChange(String rootPath) throws Exception { |
|
||||||
ZookeeperContext.getChildrenAndListen(rootPath, childDataList -> { |
|
||||||
log.info("========== 加载路由信息 =========="); |
|
||||||
log.info("{} # 路由根节点", rootPath); |
|
||||||
for (ChildData childData : childDataList) { |
|
||||||
String serviceNodeData = new String(childData.getData()); |
|
||||||
R serviceRouteInfo = JSON.parseObject(serviceNodeData, getServiceRouteInfoClass()); |
|
||||||
String servicePath = childData.getPath(); |
|
||||||
log.info("\t{} # service节点,节点数据:{}", servicePath, serviceNodeData); |
|
||||||
try { |
|
||||||
this.loadServiceRouteItem(serviceRouteInfo, servicePath); |
|
||||||
} catch (Exception e) { |
|
||||||
log.error("加载路由信息失败,servicePath:{}", servicePath, e); |
|
||||||
} |
|
||||||
} |
|
||||||
}, (client, event) -> { |
|
||||||
PathChildrenCacheEvent.Type type = event.getType(); |
|
||||||
synchronized (type) { |
|
||||||
// 通过判断event type的方式来实现不同事件的触发
|
|
||||||
if (PathChildrenCacheEvent.Type.CHILD_ADDED.equals(type)) { |
|
||||||
String serviceNodeData = new String(event.getData().getData()); |
|
||||||
R serviceRouteInfo = JSON.parseObject(serviceNodeData, getServiceRouteInfoClass()); |
|
||||||
// 添加子节点时触发
|
|
||||||
String servicePath = event.getData().getPath(); |
|
||||||
log.info("新增serviceId节点:{},节点数据:{}", servicePath, serviceNodeData); |
|
||||||
this.watchRouteItems(serviceRouteInfo, servicePath); |
|
||||||
} else if (PathChildrenCacheEvent.Type.CHILD_UPDATED.equals(type)) { |
|
||||||
// 修改子节点数据时触发,暂时没有什么操作
|
|
||||||
String nodeData = new String(event.getData().getData()); |
|
||||||
log.info("修改serviceId节点:{},节点数据:{}", event.getData().getPath(), nodeData); |
|
||||||
} else if (PathChildrenCacheEvent.Type.CHILD_REMOVED.equals(type)) { |
|
||||||
// 删除service节点
|
|
||||||
String nodeData = new String(event.getData().getData()); |
|
||||||
log.info("删除serviceId节点:{},节点数据:{}", event.getData().getPath(), nodeData); |
|
||||||
R serviceRouteInfo = JSON.parseObject(nodeData, getServiceRouteInfoClass()); |
|
||||||
routeRepository.deleteAll(serviceRouteInfo.getServiceId()); |
|
||||||
} |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 加载service下的路由信息 |
|
||||||
* |
|
||||||
* @param servicePath |
|
||||||
*/ |
|
||||||
protected void loadServiceRouteItem(R serviceRouteInfo, String servicePath) throws Exception { |
|
||||||
ZookeeperContext.getChildrenData(servicePath, childDataList -> { |
|
||||||
for (ChildData childData : childDataList) { |
|
||||||
String routeItemPath = childData.getPath(); |
|
||||||
byte[] routeItemData = childData.getData(); |
|
||||||
String routeDataJson = buildZookeeperData(routeItemData); |
|
||||||
log.info("\t\t{} # 路由节点,节点数据:{}", routeItemPath, routeDataJson); |
|
||||||
this.saveRouteItem(serviceRouteInfo, routeDataJson); |
|
||||||
} |
|
||||||
}); |
|
||||||
this.watchRouteItems(serviceRouteInfo, servicePath); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* 监听serviceId目录下面的子节点 |
|
||||||
* |
|
||||||
* @param serviceRouteInfo |
|
||||||
* @param servicePath serviceId节点 |
|
||||||
*/ |
|
||||||
protected void watchRouteItems(R serviceRouteInfo, String servicePath) throws Exception { |
|
||||||
log.info("监听{}下子节点增删改", servicePath); |
|
||||||
// 添加事件监听器
|
|
||||||
ZookeeperContext.listenChildren(servicePath, (client, event) -> { |
|
||||||
PathChildrenCacheEvent.Type type = event.getType(); |
|
||||||
synchronized (type) { |
|
||||||
// 通过判断event type的方式来实现不同事件的触发
|
|
||||||
if (PathChildrenCacheEvent.Type.CHILD_ADDED.equals(type)) { |
|
||||||
// 新增单个路由
|
|
||||||
String routeDataJson = buildZookeeperData(event.getData().getData()); |
|
||||||
log.info("新增单个路由,serviceId:{}, 路由数据:{}", serviceRouteInfo.getServiceId(), routeDataJson); |
|
||||||
saveRouteItem(serviceRouteInfo, routeDataJson); |
|
||||||
} else if (PathChildrenCacheEvent.Type.CHILD_UPDATED.equals(type)) { |
|
||||||
// 修改单个路由
|
|
||||||
String routeDataJson = buildZookeeperData(event.getData().getData()); |
|
||||||
log.info("修改单个路由,serviceId:{}, 路由数据:{}", serviceRouteInfo.getServiceId(), routeDataJson); |
|
||||||
updateRouteItem(serviceRouteInfo, routeDataJson); |
|
||||||
} else if (PathChildrenCacheEvent.Type.CHILD_REMOVED.equals(type)) { |
|
||||||
// 删除单个路由
|
|
||||||
String routeDataJson = buildZookeeperData(event.getData().getData()); |
|
||||||
log.info("删除单个路由,serviceId:{}, 路由数据:{}", serviceRouteInfo.getServiceId(), routeDataJson); |
|
||||||
deleteRouteItem(serviceRouteInfo, routeDataJson); |
|
||||||
} |
|
||||||
} |
|
||||||
}); |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
protected void saveRouteItem(R serviceRouteInfo, String nodeDataJson) { |
|
||||||
T routeDefinition = getRouteDefinition(serviceRouteInfo, nodeDataJson); |
|
||||||
routeRepository.add(routeDefinition); |
|
||||||
} |
|
||||||
|
|
||||||
protected void updateRouteItem(R serviceRouteInfo, String nodeDataJson) { |
|
||||||
T routeDefinition = getRouteDefinition(serviceRouteInfo, nodeDataJson); |
|
||||||
routeRepository.update(routeDefinition); |
|
||||||
} |
|
||||||
|
|
||||||
protected void deleteRouteItem(R serviceRouteInfo, String nodeDataJson) { |
|
||||||
E routeDefinitionItem = getRouteDefinitionItem(nodeDataJson); |
|
||||||
routeRepository.delete(routeDefinitionItem.getId()); |
|
||||||
} |
|
||||||
|
|
||||||
protected T getRouteDefinition(R serviceRouteInfo, String nodeDataJson) { |
|
||||||
E routeDefinitionItem = getRouteDefinitionItem(nodeDataJson); |
|
||||||
T routeDefinition = buildRouteDefinition(serviceRouteInfo, routeDefinitionItem); |
|
||||||
return routeDefinition; |
|
||||||
} |
|
||||||
|
|
||||||
protected E getRouteDefinitionItem(String nodeDataJson) { |
|
||||||
return JSON.parseObject(nodeDataJson, getRouteDefinitionClass()); |
|
||||||
} |
|
||||||
|
|
||||||
protected String buildZookeeperData(byte[] data) { |
|
||||||
return new String(data); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,24 +0,0 @@ |
|||||||
package com.gitee.sop.gatewaycommon.manager; |
|
||||||
|
|
||||||
import org.springframework.beans.BeansException; |
|
||||||
import org.springframework.context.ApplicationContext; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author tanghc |
|
||||||
*/ |
|
||||||
public class ManagerContext { |
|
||||||
|
|
||||||
private static ApplicationContext ctx; |
|
||||||
|
|
||||||
public static void setApplicationContext(ApplicationContext applicationContext) throws BeansException { |
|
||||||
ctx = applicationContext; |
|
||||||
} |
|
||||||
|
|
||||||
public static <T> T getManager(Class<T> clazz) { |
|
||||||
return ctx.getBean(clazz); |
|
||||||
} |
|
||||||
|
|
||||||
public static ApplicationContext getApplicationContext() { |
|
||||||
return ctx; |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,12 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.manager; |
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
public interface RouteLoader { |
||||||
|
void load(ServiceRouteInfo serviceRouteInfo); |
||||||
|
|
||||||
|
void remove(String serviceId); |
||||||
|
} |
@ -1,20 +0,0 @@ |
|||||||
package com.gitee.sop.gatewaycommon.manager; |
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.IsvRoutePermission; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author tanghc |
|
||||||
*/ |
|
||||||
public interface RoutePermissionManager { |
|
||||||
/** |
|
||||||
* 加载路由授权 |
|
||||||
*/ |
|
||||||
void load(); |
|
||||||
|
|
||||||
/** |
|
||||||
* 更新路由授权信息 |
|
||||||
* |
|
||||||
* @param isvRoutePermission 授权信息 |
|
||||||
*/ |
|
||||||
void update(IsvRoutePermission isvRoutePermission); |
|
||||||
} |
|
@ -0,0 +1,118 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.manager; |
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON; |
||||||
|
import com.alibaba.nacos.api.config.ConfigService; |
||||||
|
import com.alibaba.nacos.api.exception.NacosException; |
||||||
|
import com.alibaba.nacos.api.naming.NamingService; |
||||||
|
import com.alibaba.nacos.api.naming.pojo.Instance; |
||||||
|
import com.alibaba.nacos.api.naming.pojo.ServiceInfo; |
||||||
|
import com.gitee.sop.gatewaycommon.bean.NacosConfigs; |
||||||
|
import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo; |
||||||
|
import com.gitee.sop.gatewaycommon.bean.TargetRoute; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.cloud.alibaba.nacos.NacosConfigProperties; |
||||||
|
import org.springframework.cloud.alibaba.nacos.NacosDiscoveryProperties; |
||||||
|
import org.springframework.context.ApplicationEvent; |
||||||
|
import org.springframework.http.HttpStatus; |
||||||
|
import org.springframework.http.ResponseEntity; |
||||||
|
import org.springframework.util.CollectionUtils; |
||||||
|
import org.springframework.util.DigestUtils; |
||||||
|
import org.springframework.web.client.RestTemplate; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
import java.util.Objects; |
||||||
|
|
||||||
|
/** |
||||||
|
* 发现新服务,更新路由信息 |
||||||
|
* |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Slf4j |
||||||
|
public class ServiceRoutesLoader<T extends TargetRoute> { |
||||||
|
|
||||||
|
private static final String SECRET = "a3d9sf!1@odl90zd>fkASwq"; |
||||||
|
|
||||||
|
private static final int FIVE_SECONDS = 1000 * 5; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private NacosDiscoveryProperties nacosDiscoveryProperties; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private NacosConfigProperties nacosConfigProperties; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private BaseRouteCache<T> baseRouteCache; |
||||||
|
|
||||||
|
private RestTemplate restTemplate = new RestTemplate(); |
||||||
|
|
||||||
|
private volatile long lastUpdateTime; |
||||||
|
|
||||||
|
public synchronized void load(ApplicationEvent event) { |
||||||
|
long now = System.currentTimeMillis(); |
||||||
|
// 5秒内只能执行一次,解决重启应用连续加载4次问题
|
||||||
|
if (now - lastUpdateTime < FIVE_SECONDS) { |
||||||
|
return; |
||||||
|
} |
||||||
|
lastUpdateTime = now; |
||||||
|
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance(); |
||||||
|
List<ServiceInfo> subscribes = null; |
||||||
|
try { |
||||||
|
subscribes = namingService.getSubscribeServices(); |
||||||
|
} catch (NacosException e) { |
||||||
|
log.error("namingService.getSubscribeServices()错误", e); |
||||||
|
} |
||||||
|
if (CollectionUtils.isEmpty(subscribes)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
// subscribe
|
||||||
|
String thisServiceId = nacosDiscoveryProperties.getService(); |
||||||
|
ConfigService configService = nacosConfigProperties.configServiceInstance(); |
||||||
|
for (ServiceInfo serviceInfo : subscribes) { |
||||||
|
String serviceName = serviceInfo.getName(); |
||||||
|
// 如果是本机服务,跳过
|
||||||
|
if (Objects.equals(thisServiceId, serviceName)) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
try { |
||||||
|
String dataId = NacosConfigs.getRouteDataId(serviceName); |
||||||
|
String groupId = NacosConfigs.GROUP_ROUTE; |
||||||
|
List<Instance> allInstances = namingService.getAllInstances(serviceName); |
||||||
|
if (CollectionUtils.isEmpty(allInstances)) { |
||||||
|
log.info("{}服务下线,删除路由信息", serviceName); |
||||||
|
// 如果没有服务列表,则删除所有路由信息
|
||||||
|
baseRouteCache.remove(serviceName); |
||||||
|
configService.removeConfig(dataId, groupId); |
||||||
|
} else { |
||||||
|
for (Instance instance : allInstances) { |
||||||
|
log.info("加载服务路由,instance:{}", instance); |
||||||
|
String url = getRouteRequestUrl(instance); |
||||||
|
ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class); |
||||||
|
if (responseEntity.getStatusCode() == HttpStatus.OK) { |
||||||
|
String body = responseEntity.getBody(); |
||||||
|
log.debug("加载{}路由,路由信息:{}", serviceName, body); |
||||||
|
ServiceRouteInfo serviceRouteInfo = JSON.parseObject(body, ServiceRouteInfo.class); |
||||||
|
baseRouteCache.load(serviceRouteInfo); |
||||||
|
configService.publishConfig(dataId, groupId, body); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} catch (NacosException e) { |
||||||
|
log.error("选择服务实例失败,serviceName:{}", serviceName, e); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static String getRouteRequestUrl(Instance instance) { |
||||||
|
String query = buildQuery(SECRET); |
||||||
|
return "http://" + instance.getIp() + ":" + instance.getPort() + "/sop/routes" + query; |
||||||
|
} |
||||||
|
|
||||||
|
private static String buildQuery(String secret) { |
||||||
|
String time = String.valueOf(System.currentTimeMillis()); |
||||||
|
String source = secret + time + secret; |
||||||
|
String sign = DigestUtils.md5DigestAsHex(source.getBytes()); |
||||||
|
return "?time=" + time + "&sign=" + sign; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -1,252 +0,0 @@ |
|||||||
package com.gitee.sop.gatewaycommon.manager; |
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j; |
|
||||||
import org.apache.commons.lang.StringUtils; |
|
||||||
import org.apache.commons.lang.math.NumberUtils; |
|
||||||
import org.apache.curator.framework.CuratorFramework; |
|
||||||
import org.apache.curator.framework.CuratorFrameworkFactory; |
|
||||||
import org.apache.curator.framework.recipes.cache.ChildData; |
|
||||||
import org.apache.curator.framework.recipes.cache.NodeCache; |
|
||||||
import org.apache.curator.framework.recipes.cache.NodeCacheListener; |
|
||||||
import org.apache.curator.framework.recipes.cache.PathChildrenCache; |
|
||||||
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener; |
|
||||||
import org.apache.curator.retry.ExponentialBackoffRetry; |
|
||||||
import org.springframework.core.env.Environment; |
|
||||||
import org.springframework.util.Assert; |
|
||||||
|
|
||||||
import java.util.List; |
|
||||||
import java.util.function.Consumer; |
|
||||||
|
|
||||||
import static com.gitee.sop.gatewaycommon.bean.SopConstants.SOP_MSG_CHANNEL_PATH; |
|
||||||
import static com.gitee.sop.gatewaycommon.bean.SopConstants.SOP_SERVICE_ROUTE_PATH; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author tanghc |
|
||||||
*/ |
|
||||||
@Slf4j |
|
||||||
public class ZookeeperContext { |
|
||||||
|
|
||||||
private static CuratorFramework client; |
|
||||||
|
|
||||||
private static Environment environment; |
|
||||||
|
|
||||||
public static void setEnvironment(Environment environment) { |
|
||||||
Assert.notNull(environment, "environment不能为null"); |
|
||||||
ZookeeperContext.environment = environment; |
|
||||||
initZookeeperClient(); |
|
||||||
} |
|
||||||
|
|
||||||
public synchronized static void initZookeeperClient() { |
|
||||||
if (client != null) { |
|
||||||
return; |
|
||||||
} |
|
||||||
setClient(createClient()); |
|
||||||
} |
|
||||||
|
|
||||||
public static CuratorFramework createClient() { |
|
||||||
String zookeeperServerAddr = environment.getProperty("spring.cloud.zookeeper.connect-string"); |
|
||||||
if (StringUtils.isBlank(zookeeperServerAddr)) { |
|
||||||
throw new RuntimeException("未指定spring.cloud.zookeeper.connect-string参数"); |
|
||||||
} |
|
||||||
String baseSleepTimeMs = environment.getProperty("spring.cloud.zookeeper.baseSleepTimeMs"); |
|
||||||
String maxRetries = environment.getProperty("spring.cloud.zookeeper.maxRetries"); |
|
||||||
log.info("初始化zookeeper客户端,zookeeperServerAddr:{}, baseSleepTimeMs:{}, maxRetries:{}", |
|
||||||
zookeeperServerAddr, baseSleepTimeMs, maxRetries); |
|
||||||
CuratorFramework client = CuratorFrameworkFactory.builder() |
|
||||||
.connectString(zookeeperServerAddr) |
|
||||||
.retryPolicy(new ExponentialBackoffRetry(NumberUtils.toInt(baseSleepTimeMs, 3000), NumberUtils.toInt(maxRetries, 3))) |
|
||||||
.build(); |
|
||||||
|
|
||||||
client.start(); |
|
||||||
return client; |
|
||||||
} |
|
||||||
|
|
||||||
public static String getRouteRootPath() { |
|
||||||
return SOP_SERVICE_ROUTE_PATH; |
|
||||||
} |
|
||||||
|
|
||||||
public static String getIsvInfoChannelPath() { |
|
||||||
return SOP_MSG_CHANNEL_PATH + "/isvinfo"; |
|
||||||
} |
|
||||||
|
|
||||||
public static String getServiceGrayChannelPath() { |
|
||||||
return SOP_MSG_CHANNEL_PATH + "/gray"; |
|
||||||
} |
|
||||||
|
|
||||||
public static String getIsvRoutePermissionChannelPath() { |
|
||||||
return SOP_MSG_CHANNEL_PATH + "/isv-route-permission"; |
|
||||||
} |
|
||||||
|
|
||||||
public static String getRouteConfigChannelPath() { |
|
||||||
return SOP_MSG_CHANNEL_PATH + "/route-conf"; |
|
||||||
} |
|
||||||
|
|
||||||
public static String getLimitConfigChannelPath() { |
|
||||||
return SOP_MSG_CHANNEL_PATH + "/limit-conf"; |
|
||||||
} |
|
||||||
|
|
||||||
public static String getIpBlacklistChannelPath() { |
|
||||||
return SOP_MSG_CHANNEL_PATH + "/ipblacklist-conf"; |
|
||||||
} |
|
||||||
|
|
||||||
public static CuratorFramework getClient() { |
|
||||||
return client; |
|
||||||
} |
|
||||||
|
|
||||||
public static void setClient(CuratorFramework client) { |
|
||||||
ZookeeperContext.client = client; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 新建或保存节点 |
|
||||||
* |
|
||||||
* @param path |
|
||||||
* @param data |
|
||||||
* @return 返回path |
|
||||||
* @throws Exception |
|
||||||
*/ |
|
||||||
public static String createOrUpdateData(String path, String data) throws Exception { |
|
||||||
return getClient().create() |
|
||||||
// 如果节点存在则Curator将会使用给出的数据设置这个节点的值
|
|
||||||
.orSetData() |
|
||||||
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
|
|
||||||
.creatingParentContainersIfNeeded() |
|
||||||
.forPath(path, data.getBytes()); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 更新节点 |
|
||||||
* @param path |
|
||||||
* @param data |
|
||||||
* @throws Exception |
|
||||||
*/ |
|
||||||
public static void updatePath(String path, String data) throws Exception { |
|
||||||
getClient().setData().forPath(path, data.getBytes()); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 创建path,如果path存在不报错,静默返回path名称 |
|
||||||
* |
|
||||||
* @param path |
|
||||||
* @param data |
|
||||||
* @return |
|
||||||
* @throws Exception |
|
||||||
*/ |
|
||||||
public static String createPath(String path, String data) throws Exception { |
|
||||||
if (isPathExist(path)) { |
|
||||||
return path; |
|
||||||
} |
|
||||||
return getClient().create() |
|
||||||
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
|
|
||||||
.creatingParentContainersIfNeeded() |
|
||||||
.forPath(path, data.getBytes()); |
|
||||||
} |
|
||||||
|
|
||||||
public static boolean isPathExist(String path) { |
|
||||||
try { |
|
||||||
return client.checkExists().forPath(path) != null; |
|
||||||
} catch (Exception e) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 监听一个节点 |
|
||||||
* |
|
||||||
* @param path |
|
||||||
* @param onChange 节点修改后触发 |
|
||||||
* @return 返回path |
|
||||||
* @throws Exception |
|
||||||
*/ |
|
||||||
public static String listenPath(String path, Consumer<NodeCache> onChange) throws Exception { |
|
||||||
String ret = createOrUpdateData(path, "{}"); |
|
||||||
final NodeCache cache = new NodeCache(client, path, false); |
|
||||||
cache.getListenable().addListener(new NodeCacheListener() { |
|
||||||
@Override |
|
||||||
public void nodeChanged() throws Exception { |
|
||||||
onChange.accept(cache); |
|
||||||
} |
|
||||||
}); |
|
||||||
cache.start(); |
|
||||||
return ret; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 获取子节点信息并监听子节点 |
|
||||||
* |
|
||||||
* @param parentPath 父节点路径 |
|
||||||
* @param listConsumer 子节点数据 |
|
||||||
* @param listener 监听事件 |
|
||||||
* @throws Exception |
|
||||||
*/ |
|
||||||
public static void getChildrenAndListen(String parentPath, Consumer<List<ChildData>> listConsumer, PathChildrenCacheListener listener) throws Exception { |
|
||||||
// 为子节点添加watcher
|
|
||||||
// PathChildrenCache: 监听数据节点的增删改,可以设置触发的事件
|
|
||||||
PathChildrenCache childrenCache = new PathChildrenCache(client, parentPath, true); |
|
||||||
|
|
||||||
/** |
|
||||||
* StartMode: 初始化方式 |
|
||||||
* POST_INITIALIZED_EVENT:异步初始化,初始化之后会触发事件 |
|
||||||
* NORMAL:异步初始化 |
|
||||||
* BUILD_INITIAL_CACHE:同步初始化 |
|
||||||
*/ |
|
||||||
childrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE); |
|
||||||
|
|
||||||
// 列出子节点数据列表,需要使用BUILD_INITIAL_CACHE同步初始化模式才能获得,异步是获取不到的
|
|
||||||
List<ChildData> childDataList = childrenCache.getCurrentData(); |
|
||||||
listConsumer.accept(childDataList); |
|
||||||
log.info("监听子节点增删改,监听路径:{}", parentPath); |
|
||||||
// 监听根节点下面的子节点
|
|
||||||
childrenCache.getListenable().addListener(listener); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 获取子节点信息 |
|
||||||
* |
|
||||||
* @param parentPath 父节点路径 |
|
||||||
* @param listConsumer 子节点数据 |
|
||||||
* @throws Exception |
|
||||||
*/ |
|
||||||
public static void getChildrenData(String parentPath, Consumer<List<ChildData>> listConsumer) throws Exception { |
|
||||||
// 为子节点添加watcher
|
|
||||||
// PathChildrenCache: 监听数据节点的增删改,可以设置触发的事件
|
|
||||||
PathChildrenCache childrenCache = new PathChildrenCache(client, parentPath, true); |
|
||||||
|
|
||||||
/** |
|
||||||
* StartMode: 初始化方式 |
|
||||||
* POST_INITIALIZED_EVENT:异步初始化,初始化之后会触发事件 |
|
||||||
* NORMAL:异步初始化 |
|
||||||
* BUILD_INITIAL_CACHE:同步初始化 |
|
||||||
*/ |
|
||||||
childrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE); |
|
||||||
|
|
||||||
// 列出子节点数据列表,需要使用BUILD_INITIAL_CACHE同步初始化模式才能获得,异步是获取不到的
|
|
||||||
List<ChildData> childDataList = childrenCache.getCurrentData(); |
|
||||||
listConsumer.accept(childDataList); |
|
||||||
childrenCache.close(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 监听子节点的增删改 |
|
||||||
* |
|
||||||
* @param parentPath 父节点路径 |
|
||||||
* @param listener |
|
||||||
* @throws Exception |
|
||||||
*/ |
|
||||||
public static void listenChildren(String parentPath, PathChildrenCacheListener listener) throws Exception { |
|
||||||
// 为子节点添加watcher
|
|
||||||
// PathChildrenCache: 监听数据节点的增删改,可以设置触发的事件
|
|
||||||
PathChildrenCache childrenCache = new PathChildrenCache(client, parentPath, true); |
|
||||||
|
|
||||||
/** |
|
||||||
* StartMode: 初始化方式 |
|
||||||
* POST_INITIALIZED_EVENT:异步初始化,初始化之后会触发事件 |
|
||||||
* NORMAL:异步初始化 |
|
||||||
* BUILD_INITIAL_CACHE:同步初始化 |
|
||||||
*/ |
|
||||||
childrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE); |
|
||||||
// 监听根节点下面的子节点
|
|
||||||
childrenCache.getListenable().addListener(listener); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,54 +0,0 @@ |
|||||||
package com.gitee.sop.gatewaycommon.zuul.filter; |
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.BaseRouteDefinition; |
|
||||||
import com.gitee.sop.gatewaycommon.bean.TargetRoute; |
|
||||||
import com.gitee.sop.gatewaycommon.manager.IsvRoutePermissionManager; |
|
||||||
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext; |
|
||||||
import com.gitee.sop.gatewaycommon.message.ErrorEnum; |
|
||||||
import com.gitee.sop.gatewaycommon.param.ApiParam; |
|
||||||
import com.gitee.sop.gatewaycommon.zuul.ZuulContext; |
|
||||||
import com.netflix.zuul.context.RequestContext; |
|
||||||
import com.netflix.zuul.exception.ZuulException; |
|
||||||
import org.apache.commons.lang3.BooleanUtils; |
|
||||||
import org.springframework.beans.factory.annotation.Autowired; |
|
||||||
|
|
||||||
/** |
|
||||||
* 路由权限校验,有些接口需要配置权限才能访问。 |
|
||||||
* @author tanghc |
|
||||||
* @deprecated 已经整合到ApiValidator中,见ApiValidator.checkPermission() |
|
||||||
*/ |
|
||||||
@Deprecated |
|
||||||
public class PreRoutePermissionFilter extends BaseZuulFilter { |
|
||||||
|
|
||||||
@Autowired |
|
||||||
private IsvRoutePermissionManager isvRoutePermissionManager; |
|
||||||
|
|
||||||
@Override |
|
||||||
protected FilterType getFilterType() { |
|
||||||
return FilterType.PRE; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
protected int getFilterOrder() { |
|
||||||
// 放在签名验证后面
|
|
||||||
return PRE_ROUTE_PERMISSION_FILTER_ORDER; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
protected Object doRun(RequestContext requestContext) throws ZuulException { |
|
||||||
ApiParam apiParam = ZuulContext.getApiParam(); |
|
||||||
String routeId = apiParam.fetchNameVersion(); |
|
||||||
TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(routeId); |
|
||||||
BaseRouteDefinition routeDefinition = targetRoute.getRouteDefinition(); |
|
||||||
boolean needCheckPermission = BooleanUtils.toBoolean(routeDefinition.getPermission()); |
|
||||||
if (needCheckPermission) { |
|
||||||
String appKey = apiParam.fetchAppKey(); |
|
||||||
boolean hasPermission = isvRoutePermissionManager.hasPermission(appKey, routeId); |
|
||||||
if (!hasPermission) { |
|
||||||
throw ErrorEnum.ISV_ROUTE_NO_PERMISSIONS.getErrorMeta().getException(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return null; |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,34 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.zuul.route; |
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.GatewayRouteDefinition; |
||||||
|
import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo; |
||||||
|
import com.gitee.sop.gatewaycommon.manager.BaseRouteCache; |
||||||
|
import com.gitee.sop.gatewaycommon.manager.RouteRepository; |
||||||
|
import com.gitee.sop.gatewaycommon.util.RouteUtil; |
||||||
|
import org.springframework.cloud.netflix.zuul.filters.Route; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
public class ZuulRouteCache extends BaseRouteCache<ZuulTargetRoute> { |
||||||
|
|
||||||
|
/** 路由重试 */ |
||||||
|
public static final boolean RETRYABLE = true; |
||||||
|
|
||||||
|
public ZuulRouteCache(RouteRepository<ZuulTargetRoute> routeRepository) { |
||||||
|
super(routeRepository); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected ZuulTargetRoute buildRouteDefinition(ServiceRouteInfo serviceRouteInfo, GatewayRouteDefinition gatewayRouteDefinition) { |
||||||
|
Route route = new Route( |
||||||
|
gatewayRouteDefinition.getId() |
||||||
|
, gatewayRouteDefinition.getPath() |
||||||
|
, RouteUtil.getZuulLocation(gatewayRouteDefinition.getUri()) |
||||||
|
, "" |
||||||
|
, RETRYABLE |
||||||
|
, null |
||||||
|
); |
||||||
|
return new ZuulTargetRoute(serviceRouteInfo, gatewayRouteDefinition, route); |
||||||
|
} |
||||||
|
} |
@ -1,13 +0,0 @@ |
|||||||
package com.gitee.sop.gatewaycommon.zuul.route; |
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.BaseRouteDefinition; |
|
||||||
import lombok.Getter; |
|
||||||
import lombok.Setter; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author tanghc |
|
||||||
*/ |
|
||||||
@Getter |
|
||||||
@Setter |
|
||||||
public class ZuulRouteDefinition extends BaseRouteDefinition { |
|
||||||
} |
|
@ -1,9 +0,0 @@ |
|||||||
package com.gitee.sop.gatewaycommon.zuul.route; |
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.BaseServiceRouteInfo; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author tanghc |
|
||||||
*/ |
|
||||||
public class ZuulServiceRouteInfo extends BaseServiceRouteInfo<ZuulRouteDefinition> { |
|
||||||
} |
|
@ -1,40 +0,0 @@ |
|||||||
package com.gitee.sop.gatewaycommon.zuul.route; |
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.BaseRouteManager; |
|
||||||
import com.gitee.sop.gatewaycommon.manager.RouteRepository; |
|
||||||
import com.gitee.sop.gatewaycommon.util.RouteUtil; |
|
||||||
import lombok.extern.slf4j.Slf4j; |
|
||||||
import org.springframework.cloud.netflix.zuul.filters.Route; |
|
||||||
import org.springframework.core.env.Environment; |
|
||||||
|
|
||||||
/** |
|
||||||
* 路由内容管理,新增活修改路由 |
|
||||||
* |
|
||||||
* @author tanghc |
|
||||||
*/ |
|
||||||
@Slf4j |
|
||||||
public class ZuulZookeeperRouteManager extends BaseRouteManager<ZuulServiceRouteInfo, ZuulRouteDefinition, ZuulTargetRoute> { |
|
||||||
|
|
||||||
/** 路由重试 */ |
|
||||||
public static final boolean RETRYABLE = true; |
|
||||||
|
|
||||||
public ZuulZookeeperRouteManager(Environment environment, RouteRepository<ZuulTargetRoute> routeRepository) { |
|
||||||
super(environment, routeRepository); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
protected Class<ZuulServiceRouteInfo> getServiceRouteInfoClass() { |
|
||||||
return ZuulServiceRouteInfo.class; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
protected Class<ZuulRouteDefinition> getRouteDefinitionClass() { |
|
||||||
return ZuulRouteDefinition.class; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
protected ZuulTargetRoute buildRouteDefinition(ZuulServiceRouteInfo serviceRouteInfo, ZuulRouteDefinition routeDefinition) { |
|
||||||
Route route = new Route(routeDefinition.getId(), routeDefinition.getPath(), RouteUtil.getZuulLocation(routeDefinition.getUri()), "", RETRYABLE, null); |
|
||||||
return new ZuulTargetRoute(serviceRouteInfo, routeDefinition, route); |
|
||||||
} |
|
||||||
} |
|
@ -1,138 +0,0 @@ |
|||||||
package com.gitee.sop.servercommon.bean; |
|
||||||
|
|
||||||
import com.gitee.sop.servercommon.exception.ZookeeperOperationException; |
|
||||||
import com.gitee.sop.servercommon.exception.ZookeeperPathNotExistException; |
|
||||||
import lombok.Getter; |
|
||||||
import lombok.extern.slf4j.Slf4j; |
|
||||||
import org.apache.commons.lang.StringUtils; |
|
||||||
import org.apache.commons.lang.math.NumberUtils; |
|
||||||
import org.apache.curator.framework.CuratorFramework; |
|
||||||
import org.apache.curator.framework.CuratorFrameworkFactory; |
|
||||||
import org.apache.curator.retry.ExponentialBackoffRetry; |
|
||||||
import org.apache.zookeeper.CreateMode; |
|
||||||
import org.springframework.core.env.Environment; |
|
||||||
|
|
||||||
import java.io.Closeable; |
|
||||||
import java.io.IOException; |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* @author tanghc |
|
||||||
*/ |
|
||||||
@Slf4j |
|
||||||
@Getter |
|
||||||
public class ZookeeperTool implements Closeable { |
|
||||||
|
|
||||||
private CuratorFramework client; |
|
||||||
private Environment environment; |
|
||||||
|
|
||||||
public ZookeeperTool(Environment environment) { |
|
||||||
this.environment = environment; |
|
||||||
initZookeeperClient(environment); |
|
||||||
} |
|
||||||
|
|
||||||
public void initZookeeperClient(Environment environment) { |
|
||||||
String zookeeperServerAddr = environment.getProperty("spring.cloud.zookeeper.connect-string"); |
|
||||||
if (StringUtils.isBlank(zookeeperServerAddr)) { |
|
||||||
throw new RuntimeException("未指定spring.cloud.zookeeper.connect-string参数"); |
|
||||||
} |
|
||||||
String baseSleepTimeMs = environment.getProperty("spring.cloud.zookeeper.baseSleepTimeMs"); |
|
||||||
String maxRetries = environment.getProperty("spring.cloud.zookeeper.maxRetries"); |
|
||||||
log.info("初始化zookeeper客户端,zookeeperServerAddr:{}, baseSleepTimeMs:{}, maxRetries:{}", |
|
||||||
zookeeperServerAddr, baseSleepTimeMs, maxRetries); |
|
||||||
CuratorFramework client = CuratorFrameworkFactory.builder() |
|
||||||
.connectString(zookeeperServerAddr) |
|
||||||
.retryPolicy(new ExponentialBackoffRetry(NumberUtils.toInt(baseSleepTimeMs, 3000), NumberUtils.toInt(maxRetries, 3))) |
|
||||||
.build(); |
|
||||||
|
|
||||||
client.start(); |
|
||||||
|
|
||||||
this.client = client; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 获取节点内容 |
|
||||||
* |
|
||||||
* @param path |
|
||||||
* @return 返回节点内容 |
|
||||||
* @throws ZookeeperPathNotExistException 节点不存在,抛出异常 |
|
||||||
*/ |
|
||||||
public String getData(String path) throws ZookeeperPathNotExistException { |
|
||||||
if (!isPathExist(path)) { |
|
||||||
throw new ZookeeperPathNotExistException("path 不存在, path=" + path); |
|
||||||
} |
|
||||||
try { |
|
||||||
byte[] data = getClient().getData().forPath(path); |
|
||||||
return new String(data); |
|
||||||
} catch (Exception e) { |
|
||||||
throw new ZookeeperOperationException("getData error path=" + path, e); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public boolean isPathExist(String path) { |
|
||||||
try { |
|
||||||
return client.checkExists().forPath(path) != null; |
|
||||||
} catch (Exception e) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 创建path,如果path存在不报错,静默返回path名称 |
|
||||||
* |
|
||||||
* @param path |
|
||||||
* @param data |
|
||||||
* @return |
|
||||||
* @throws Exception |
|
||||||
*/ |
|
||||||
public String createPath(String path, String data) throws Exception { |
|
||||||
if (isPathExist(path)) { |
|
||||||
return path; |
|
||||||
} |
|
||||||
return getClient().create() |
|
||||||
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
|
|
||||||
.creatingParentContainersIfNeeded() |
|
||||||
.forPath(path, data.getBytes()); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 创建临时序列节点 |
|
||||||
* @param path 节点路径 |
|
||||||
* @param data 数据 |
|
||||||
* @return 返回节点路径 |
|
||||||
* @throws Exception |
|
||||||
*/ |
|
||||||
public String createOrUpdateEphemeralSequentialPath(String path, String data) throws Exception { |
|
||||||
return getClient().create() |
|
||||||
// 如果节点存在则Curator将会使用给出的数据设置这个节点的值
|
|
||||||
.orSetData() |
|
||||||
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
|
|
||||||
.creatingParentContainersIfNeeded() |
|
||||||
.withMode(CreateMode.EPHEMERAL_SEQUENTIAL) |
|
||||||
.forPath(path, data.getBytes()); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 新建或保存节点 |
|
||||||
* |
|
||||||
* @param path |
|
||||||
* @param data |
|
||||||
* @return 返回path |
|
||||||
* @throws Exception |
|
||||||
*/ |
|
||||||
public String createOrUpdateData(String path, String data) throws Exception { |
|
||||||
return getClient().create() |
|
||||||
// 如果节点存在则Curator将会使用给出的数据设置这个节点的值
|
|
||||||
.orSetData() |
|
||||||
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
|
|
||||||
.creatingParentContainersIfNeeded() |
|
||||||
.forPath(path, data.getBytes()); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void close() throws IOException { |
|
||||||
if (this.client != null) { |
|
||||||
this.client.close(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,26 +0,0 @@ |
|||||||
package com.gitee.sop.servercommon.exception; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author tanghc |
|
||||||
*/ |
|
||||||
public class ZookeeperOperationException extends RuntimeException { |
|
||||||
|
|
||||||
public ZookeeperOperationException() { |
|
||||||
} |
|
||||||
|
|
||||||
public ZookeeperOperationException(String message) { |
|
||||||
super(message); |
|
||||||
} |
|
||||||
|
|
||||||
public ZookeeperOperationException(String message, Throwable cause) { |
|
||||||
super(message, cause); |
|
||||||
} |
|
||||||
|
|
||||||
public ZookeeperOperationException(Throwable cause) { |
|
||||||
super(cause); |
|
||||||
} |
|
||||||
|
|
||||||
public ZookeeperOperationException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { |
|
||||||
super(message, cause, enableSuppression, writableStackTrace); |
|
||||||
} |
|
||||||
} |
|
@ -1,10 +0,0 @@ |
|||||||
package com.gitee.sop.servercommon.exception; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author tanghc |
|
||||||
*/ |
|
||||||
public class ZookeeperPathNotExistException extends Exception { |
|
||||||
public ZookeeperPathNotExistException(String message) { |
|
||||||
super(message); |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,100 @@ |
|||||||
|
package com.gitee.sop.servercommon.manager; |
||||||
|
|
||||||
|
import com.gitee.sop.servercommon.bean.ServiceApiInfo; |
||||||
|
import com.gitee.sop.servercommon.bean.ServiceConfig; |
||||||
|
import com.gitee.sop.servercommon.mapping.ApiMappingInfo; |
||||||
|
import com.gitee.sop.servercommon.mapping.ApiMappingRequestCondition; |
||||||
|
import com.gitee.sop.servercommon.mapping.RouteUtil; |
||||||
|
import org.apache.commons.lang3.BooleanUtils; |
||||||
|
import org.springframework.web.method.HandlerMethod; |
||||||
|
import org.springframework.web.servlet.mvc.condition.RequestCondition; |
||||||
|
import org.springframework.web.servlet.mvc.method.RequestMappingInfo; |
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Comparator; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Set; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
public class ApiMetaBuilder { |
||||||
|
|
||||||
|
/** 接口名规则:允许字母、数字、点、下划线 */ |
||||||
|
private static final String REGEX_API_NAME = "^[a-zA-Z0-9\\.\\_\\-]+$"; |
||||||
|
|
||||||
|
public ServiceApiInfo getServiceApiInfo(String serviceId, RequestMappingHandlerMapping requestMappingHandlerMapping) { |
||||||
|
if (serviceId == null) { |
||||||
|
throw new IllegalArgumentException("请在application.properties中指定spring.application.name属性"); |
||||||
|
} |
||||||
|
List<ServiceApiInfo.ApiMeta> apis = this.buildApiMetaList(requestMappingHandlerMapping); |
||||||
|
// 排序
|
||||||
|
apis.sort(Comparator.comparing(ServiceApiInfo.ApiMeta::fetchNameVersion)); |
||||||
|
|
||||||
|
ServiceApiInfo serviceApiInfo = new ServiceApiInfo(); |
||||||
|
serviceApiInfo.setServiceId(serviceId); |
||||||
|
serviceApiInfo.setApis(apis); |
||||||
|
|
||||||
|
return serviceApiInfo; |
||||||
|
} |
||||||
|
|
||||||
|
protected List<ServiceApiInfo.ApiMeta> buildApiMetaList(RequestMappingHandlerMapping requestMappingHandlerMapping) { |
||||||
|
Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods(); |
||||||
|
Set<RequestMappingInfo> requestMappingInfos = handlerMethods.keySet(); |
||||||
|
List<String> store = new ArrayList<>(); |
||||||
|
List<ServiceApiInfo.ApiMeta> apis = new ArrayList<>(requestMappingInfos.size()); |
||||||
|
|
||||||
|
for (Map.Entry<RequestMappingInfo, HandlerMethod> handlerMethodEntry : handlerMethods.entrySet()) { |
||||||
|
ServiceApiInfo.ApiMeta apiMeta = this.buildApiMeta(handlerMethodEntry); |
||||||
|
if (apiMeta == null) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
String key = apiMeta.fetchNameVersion(); |
||||||
|
if (store.contains(key)) { |
||||||
|
throw new IllegalArgumentException("重复申明接口,请检查path和version,path:" + apiMeta.getPath() + ", version:" + apiMeta.getVersion()); |
||||||
|
} else { |
||||||
|
store.add(key); |
||||||
|
} |
||||||
|
apis.add(apiMeta); |
||||||
|
} |
||||||
|
return apis; |
||||||
|
} |
||||||
|
|
||||||
|
protected ServiceApiInfo.ApiMeta buildApiMeta(Map.Entry<RequestMappingInfo, HandlerMethod> handlerMethodEntry) { |
||||||
|
RequestMappingInfo requestMappingInfo = handlerMethodEntry.getKey(); |
||||||
|
Set<String> patterns = requestMappingInfo.getPatternsCondition().getPatterns(); |
||||||
|
RequestCondition<?> customCondition = requestMappingInfo.getCustomCondition(); |
||||||
|
if (customCondition instanceof ApiMappingRequestCondition) { |
||||||
|
ApiMappingRequestCondition condition = (ApiMappingRequestCondition) customCondition; |
||||||
|
ApiMappingInfo apiMappingInfo = condition.getApiMappingInfo(); |
||||||
|
String name = apiMappingInfo.getName(); |
||||||
|
String version = apiMappingInfo.getVersion(); |
||||||
|
// 方法完整的path,如: /goods/listGoods,/users/user/get
|
||||||
|
String path = patterns.iterator().next(); |
||||||
|
// 不是ApiMapping注解的接口,name属性是null
|
||||||
|
if (name == null || ServiceConfig.getInstance().isWebappMode()) { |
||||||
|
name = buildName(path); |
||||||
|
} |
||||||
|
this.checkApiName(name); |
||||||
|
ServiceApiInfo.ApiMeta apiMeta = new ServiceApiInfo.ApiMeta(name, path, version); |
||||||
|
apiMeta.setIgnoreValidate(BooleanUtils.toInteger(apiMappingInfo.isIgnoreValidate())); |
||||||
|
apiMeta.setMergeResult(BooleanUtils.toInteger(apiMappingInfo.isMergeResult())); |
||||||
|
apiMeta.setPermission(BooleanUtils.toInteger(apiMappingInfo.isPermission())); |
||||||
|
return apiMeta; |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
protected void checkApiName(String name) { |
||||||
|
if (!name.matches(REGEX_API_NAME)) { |
||||||
|
throw new IllegalArgumentException("接口名称只允许【字母、数字、点(.)、下划线(_)、减号(-)】,错误接口:" + name); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
protected String buildName(String path) { |
||||||
|
return RouteUtil.buildApiName(path); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,46 @@ |
|||||||
|
package com.gitee.sop.servercommon.manager; |
||||||
|
|
||||||
|
import com.gitee.sop.servercommon.bean.ServiceApiInfo; |
||||||
|
import com.gitee.sop.servercommon.route.ServiceRouteInfo; |
||||||
|
import com.gitee.sop.servercommon.util.OpenUtil; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.core.env.Environment; |
||||||
|
import org.springframework.web.bind.annotation.RequestMapping; |
||||||
|
import org.springframework.web.bind.annotation.RestController; |
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; |
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest; |
||||||
|
import javax.servlet.http.HttpServletResponse; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Slf4j |
||||||
|
@RestController |
||||||
|
public class ServiceRouteController { |
||||||
|
|
||||||
|
private static final String SECRET = "a3d9sf!1@odl90zd>fkASwq"; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private RequestMappingHandlerMapping requestMappingHandlerMapping; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private Environment environment; |
||||||
|
|
||||||
|
|
||||||
|
@RequestMapping("/sop/routes") |
||||||
|
public ServiceRouteInfo listRoutes(HttpServletRequest request, HttpServletResponse response) { |
||||||
|
if (!OpenUtil.validateSimpleSign(request, SECRET)) { |
||||||
|
log.error("签名验证失败, params:{}", request.getQueryString()); |
||||||
|
return null; |
||||||
|
} |
||||||
|
String serviceId = environment.getProperty("spring.application.name"); |
||||||
|
ApiMetaBuilder apiMetaBuilder = new ApiMetaBuilder(); |
||||||
|
ServiceApiInfo serviceApiInfo = apiMetaBuilder.getServiceApiInfo(serviceId, requestMappingHandlerMapping); |
||||||
|
ServiceRouteInfoBuilder serviceRouteInfoBuilder = new ServiceRouteInfoBuilder(environment); |
||||||
|
return serviceRouteInfoBuilder.build(serviceApiInfo); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
@ -0,0 +1,150 @@ |
|||||||
|
package com.gitee.sop.servercommon.manager; |
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON; |
||||||
|
import com.gitee.sop.servercommon.bean.ServiceApiInfo; |
||||||
|
import com.gitee.sop.servercommon.route.GatewayPredicateDefinition; |
||||||
|
import com.gitee.sop.servercommon.route.GatewayRouteDefinition; |
||||||
|
import com.gitee.sop.servercommon.route.ServiceRouteInfo; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.beans.BeanUtils; |
||||||
|
import org.springframework.core.env.Environment; |
||||||
|
import org.springframework.util.DigestUtils; |
||||||
|
import org.springframework.util.StringUtils; |
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.List; |
||||||
|
import java.util.stream.Collectors; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Slf4j |
||||||
|
public class ServiceRouteInfoBuilder { |
||||||
|
|
||||||
|
/** |
||||||
|
* 网关对应的LoadBalance协议 |
||||||
|
*/ |
||||||
|
private static final String PROTOCOL_LOAD_BALANCE = "lb://"; |
||||||
|
|
||||||
|
private static final String PATH_SPLIT = "/"; |
||||||
|
|
||||||
|
private static final String DEFAULT_CONTEXT_PATH = "/"; |
||||||
|
|
||||||
|
/** |
||||||
|
* NameVersion=alipay.story.get1.0 |
||||||
|
* see com.gitee.sop.gatewaycommon.routeDefinition.NameVersionRoutePredicateFactory |
||||||
|
*/ |
||||||
|
private static String QUERY_PREDICATE_DEFINITION_TPL = "NameVersion=%s"; |
||||||
|
|
||||||
|
private static ServiceApiInfo.ApiMeta FIRST_API_META = new ServiceApiInfo.ApiMeta("_first.route_", "/", "v_000"); |
||||||
|
|
||||||
|
private Environment environment; |
||||||
|
|
||||||
|
public ServiceRouteInfoBuilder(Environment environment) { |
||||||
|
this.environment = environment; |
||||||
|
} |
||||||
|
|
||||||
|
public ServiceRouteInfo build(ServiceApiInfo serviceApiInfo) { |
||||||
|
return this.buildServiceGatewayInfo(serviceApiInfo); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 构建接口信息,符合spring cloud gateway的格式 |
||||||
|
* |
||||||
|
* @param serviceApiInfo 服务接口信息 |
||||||
|
* @return 返回服务路由信息 |
||||||
|
*/ |
||||||
|
protected ServiceRouteInfo buildServiceGatewayInfo(ServiceApiInfo serviceApiInfo) { |
||||||
|
List<ServiceApiInfo.ApiMeta> apis = serviceApiInfo.getApis(); |
||||||
|
List<GatewayRouteDefinition> routeDefinitionList = new ArrayList<>(apis.size()); |
||||||
|
routeDefinitionList.add(this.buildReadBodyRouteDefinition(serviceApiInfo)); |
||||||
|
for (ServiceApiInfo.ApiMeta apiMeta : apis) { |
||||||
|
GatewayRouteDefinition gatewayRouteDefinition = this.buildGatewayRouteDefinition(serviceApiInfo, apiMeta); |
||||||
|
routeDefinitionList.add(gatewayRouteDefinition); |
||||||
|
} |
||||||
|
ServiceRouteInfo serviceRouteInfo = new ServiceRouteInfo(); |
||||||
|
serviceRouteInfo.setServiceId(serviceApiInfo.getServiceId()); |
||||||
|
serviceRouteInfo.setRouteDefinitionList(routeDefinitionList); |
||||||
|
String md5 = buildMd5(routeDefinitionList); |
||||||
|
serviceRouteInfo.setMd5(md5); |
||||||
|
return serviceRouteInfo; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 构建路由id MD5 |
||||||
|
* |
||||||
|
* @param routeDefinitionList 路由列表 |
||||||
|
* @return 返回MD5 |
||||||
|
*/ |
||||||
|
protected String buildMd5(List<GatewayRouteDefinition> routeDefinitionList) { |
||||||
|
List<String> routeIdList = routeDefinitionList.stream() |
||||||
|
.map(JSON::toJSONString) |
||||||
|
.sorted() |
||||||
|
.collect(Collectors.toList()); |
||||||
|
String md5Source = org.apache.commons.lang3.StringUtils.join(routeIdList, ""); |
||||||
|
return DigestUtils.md5DigestAsHex(md5Source.getBytes(StandardCharsets.UTF_8)); |
||||||
|
} |
||||||
|
|
||||||
|
protected GatewayRouteDefinition buildGatewayRouteDefinition(ServiceApiInfo serviceApiInfo, ServiceApiInfo.ApiMeta apiMeta) { |
||||||
|
GatewayRouteDefinition gatewayRouteDefinition = new GatewayRouteDefinition(); |
||||||
|
// 唯一id规则:接口名 + 版本号
|
||||||
|
String routeId = apiMeta.fetchNameVersion(); |
||||||
|
this.checkPath(routeId, "接口定义(" + routeId + ")不能有斜杠字符'/'"); |
||||||
|
BeanUtils.copyProperties(apiMeta, gatewayRouteDefinition); |
||||||
|
gatewayRouteDefinition.setId(routeId); |
||||||
|
gatewayRouteDefinition.setFilters(Collections.emptyList()); |
||||||
|
gatewayRouteDefinition.setPredicates(this.buildPredicates(apiMeta)); |
||||||
|
String uri = this.buildUri(serviceApiInfo, apiMeta); |
||||||
|
String path = this.buildServletPath(serviceApiInfo, apiMeta); |
||||||
|
gatewayRouteDefinition.setUri(uri); |
||||||
|
gatewayRouteDefinition.setPath(path); |
||||||
|
return gatewayRouteDefinition; |
||||||
|
} |
||||||
|
|
||||||
|
protected List<GatewayPredicateDefinition> buildPredicates(ServiceApiInfo.ApiMeta apiMeta) { |
||||||
|
GatewayPredicateDefinition gatewayPredicateDefinition = new GatewayPredicateDefinition(); |
||||||
|
gatewayPredicateDefinition.setName("ReadBody"); |
||||||
|
return Arrays.asList(gatewayPredicateDefinition, this.buildNameVersionPredicateDefinition(apiMeta)); |
||||||
|
} |
||||||
|
|
||||||
|
protected GatewayPredicateDefinition buildNameVersionPredicateDefinition(ServiceApiInfo.ApiMeta apiMeta) { |
||||||
|
return new GatewayPredicateDefinition(String.format(QUERY_PREDICATE_DEFINITION_TPL, apiMeta.fetchNameVersion())); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 添加com.gitee.sop.gatewaycommon.routeDefinition.ReadBodyRoutePredicateFactory,解决form表单获取不到问题 |
||||||
|
* |
||||||
|
* @return 返回路由定义 |
||||||
|
*/ |
||||||
|
protected GatewayRouteDefinition buildReadBodyRouteDefinition(ServiceApiInfo serviceApiInfo) { |
||||||
|
GatewayRouteDefinition readBodyRouteDefinition = this.buildGatewayRouteDefinition(serviceApiInfo, FIRST_API_META); |
||||||
|
readBodyRouteDefinition.setOrder(Integer.MIN_VALUE); |
||||||
|
|
||||||
|
readBodyRouteDefinition.setPredicates(this.buildPredicates(FIRST_API_META)); |
||||||
|
|
||||||
|
return readBodyRouteDefinition; |
||||||
|
} |
||||||
|
|
||||||
|
protected String buildUri(ServiceApiInfo serviceApiInfo, ServiceApiInfo.ApiMeta apiMeta) { |
||||||
|
return PROTOCOL_LOAD_BALANCE + serviceApiInfo.getServiceId(); |
||||||
|
} |
||||||
|
|
||||||
|
protected String buildServletPath(ServiceApiInfo serviceApiInfo, ServiceApiInfo.ApiMeta apiMeta) { |
||||||
|
String contextPath = environment.getProperty("server.servlet.context-path", DEFAULT_CONTEXT_PATH); |
||||||
|
String servletPath = apiMeta.getPath(); |
||||||
|
if (servletPath == null) { |
||||||
|
servletPath = ""; |
||||||
|
} |
||||||
|
servletPath = StringUtils.trimLeadingCharacter(servletPath, '/'); |
||||||
|
return contextPath + servletPath; |
||||||
|
} |
||||||
|
|
||||||
|
private void checkPath(String path, String errorMsg) { |
||||||
|
if (path.contains(PATH_SPLIT)) { |
||||||
|
throw new IllegalArgumentException(errorMsg); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -1,271 +0,0 @@ |
|||||||
package com.gitee.sop.servercommon.manager; |
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSON; |
|
||||||
import com.gitee.sop.servercommon.bean.ServiceApiInfo; |
|
||||||
import com.gitee.sop.servercommon.bean.ZookeeperTool; |
|
||||||
import com.gitee.sop.servercommon.exception.ZookeeperPathNotExistException; |
|
||||||
import com.gitee.sop.servercommon.route.GatewayPredicateDefinition; |
|
||||||
import com.gitee.sop.servercommon.route.GatewayRouteDefinition; |
|
||||||
import com.gitee.sop.servercommon.route.ServiceRouteInfo; |
|
||||||
import lombok.Getter; |
|
||||||
import lombok.Setter; |
|
||||||
import lombok.extern.slf4j.Slf4j; |
|
||||||
import org.springframework.beans.BeanUtils; |
|
||||||
import org.springframework.core.env.Environment; |
|
||||||
import org.springframework.util.DigestUtils; |
|
||||||
import org.springframework.util.StringUtils; |
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets; |
|
||||||
import java.util.ArrayList; |
|
||||||
import java.util.Arrays; |
|
||||||
import java.util.Collections; |
|
||||||
import java.util.List; |
|
||||||
import java.util.Objects; |
|
||||||
import java.util.stream.Collectors; |
|
||||||
|
|
||||||
/** |
|
||||||
* 上传路由到zookeeper |
|
||||||
* |
|
||||||
* @author tanghc |
|
||||||
*/ |
|
||||||
@Slf4j |
|
||||||
@Getter |
|
||||||
@Setter |
|
||||||
public class ServiceZookeeperApiMetaManager implements ApiMetaManager { |
|
||||||
|
|
||||||
/** 网关对应的LoadBalance协议 */ |
|
||||||
private static final String PROTOCOL_LOAD_BALANCE = "lb://"; |
|
||||||
|
|
||||||
private static final String PATH_SPLIT = "/"; |
|
||||||
|
|
||||||
private static final String DEFAULT_CONTEXT_PATH = "/"; |
|
||||||
|
|
||||||
/** |
|
||||||
* NameVersion=alipay.story.get1.0 |
|
||||||
* see com.gitee.sop.gatewaycommon.routeDefinition.NameVersionRoutePredicateFactory |
|
||||||
*/ |
|
||||||
private static String QUERY_PREDICATE_DEFINITION_TPL = "NameVersion=%s"; |
|
||||||
|
|
||||||
private static ServiceApiInfo.ApiMeta FIRST_API_META = new ServiceApiInfo.ApiMeta("_first.route_", "/", "v_000"); |
|
||||||
|
|
||||||
|
|
||||||
private Environment environment; |
|
||||||
|
|
||||||
private ZookeeperTool zookeeperTool; |
|
||||||
|
|
||||||
private String serviceId; |
|
||||||
|
|
||||||
public ServiceZookeeperApiMetaManager(Environment environment) { |
|
||||||
this.environment = environment; |
|
||||||
serviceId = environment.getProperty("spring.application.name"); |
|
||||||
if (StringUtils.isEmpty(serviceId)) { |
|
||||||
throw new IllegalArgumentException("请在application.properties中指定spring.application.name属性"); |
|
||||||
} |
|
||||||
this.zookeeperTool = new ZookeeperTool(environment); |
|
||||||
|
|
||||||
this.uploadServiceId(environment); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 上传serviceId目录 |
|
||||||
* |
|
||||||
* @param environment environment |
|
||||||
*/ |
|
||||||
protected void uploadServiceId(Environment environment) { |
|
||||||
try { |
|
||||||
this.checkZookeeperNode(serviceId, "serviceId(" + serviceId + ")不能有斜杠字符'/'"); |
|
||||||
ServiceRouteInfo serviceRouteInfo = this.buildServiceRouteInfo(); |
|
||||||
// 保存路径
|
|
||||||
String savePath = serviceRouteInfo.getZookeeperPath(); |
|
||||||
String nodeData = JSON.toJSONString(serviceRouteInfo); |
|
||||||
log.info("serviceId:{}, zookeeper保存路径:{}", serviceId, savePath); |
|
||||||
this.zookeeperTool.createPath(savePath, nodeData); |
|
||||||
this.zookeeperTool.createPath(serviceRouteInfo.getZookeeperTempServiceIdPath(), ""); |
|
||||||
} catch (Exception e) { |
|
||||||
throw new IllegalStateException("zookeeper操作失败"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void uploadApi(ServiceApiInfo serviceApiInfo) { |
|
||||||
try { |
|
||||||
ServiceRouteInfo serviceRouteInfo = this.buildServiceGatewayInfo(serviceApiInfo); |
|
||||||
this.uploadServiceRouteInfoToZookeeper(serviceRouteInfo); |
|
||||||
// 同时上传一个临时节点
|
|
||||||
String tempPath = serviceRouteInfo.getZookeeperTempServiceIdChildPath(); |
|
||||||
this.zookeeperTool.createOrUpdateEphemeralSequentialPath(tempPath, JSON.toJSONString(serviceRouteInfo)); |
|
||||||
} catch (Exception e) { |
|
||||||
log.error("上传一个临时节点失败, serviceApiInfo:{}", serviceApiInfo, e); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 构建接口信息,符合spring cloud gateway的格式 |
|
||||||
* |
|
||||||
* @param serviceApiInfo 服务接口信息 |
|
||||||
* @return 返回服务路由信息 |
|
||||||
*/ |
|
||||||
protected ServiceRouteInfo buildServiceGatewayInfo(ServiceApiInfo serviceApiInfo) { |
|
||||||
List<ServiceApiInfo.ApiMeta> apis = serviceApiInfo.getApis(); |
|
||||||
List<GatewayRouteDefinition> routeDefinitionList = new ArrayList<>(apis.size()); |
|
||||||
routeDefinitionList.add(this.buildReadBodyRouteDefinition(serviceApiInfo)); |
|
||||||
for (ServiceApiInfo.ApiMeta apiMeta : apis) { |
|
||||||
GatewayRouteDefinition gatewayRouteDefinition = this.buildGatewayRouteDefinition(serviceApiInfo, apiMeta); |
|
||||||
routeDefinitionList.add(gatewayRouteDefinition); |
|
||||||
} |
|
||||||
ServiceRouteInfo serviceRouteInfo = this.buildServiceRouteInfo(); |
|
||||||
serviceRouteInfo.setRouteDefinitionList(routeDefinitionList); |
|
||||||
String md5 = buildMd5(routeDefinitionList); |
|
||||||
serviceRouteInfo.setMd5(md5); |
|
||||||
return serviceRouteInfo; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 构建路由id MD5 |
|
||||||
* |
|
||||||
* @param routeDefinitionList 路由列表 |
|
||||||
* @return 返回MD5 |
|
||||||
*/ |
|
||||||
protected String buildMd5(List<GatewayRouteDefinition> routeDefinitionList) { |
|
||||||
List<String> routeIdList = routeDefinitionList.stream() |
|
||||||
.map(JSON::toJSONString) |
|
||||||
.sorted() |
|
||||||
.collect(Collectors.toList()); |
|
||||||
String md5Source = org.apache.commons.lang3.StringUtils.join(routeIdList, ""); |
|
||||||
return DigestUtils.md5DigestAsHex(md5Source.getBytes(StandardCharsets.UTF_8)); |
|
||||||
} |
|
||||||
|
|
||||||
protected ServiceRouteInfo buildServiceRouteInfo() { |
|
||||||
ServiceRouteInfo serviceRouteInfo = new ServiceRouteInfo(); |
|
||||||
serviceRouteInfo.setServiceId(serviceId); |
|
||||||
String description = environment.getProperty("spring.application.description"); |
|
||||||
serviceRouteInfo.setDescription(description); |
|
||||||
return serviceRouteInfo; |
|
||||||
} |
|
||||||
|
|
||||||
protected GatewayRouteDefinition buildGatewayRouteDefinition(ServiceApiInfo serviceApiInfo, ServiceApiInfo.ApiMeta apiMeta) { |
|
||||||
GatewayRouteDefinition gatewayRouteDefinition = new GatewayRouteDefinition(); |
|
||||||
// 唯一id规则:接口名 + 版本号
|
|
||||||
String routeId = apiMeta.fetchNameVersion(); |
|
||||||
this.checkZookeeperNode(routeId, "接口定义(" + routeId + ")不能有斜杠字符'/'"); |
|
||||||
BeanUtils.copyProperties(apiMeta, gatewayRouteDefinition); |
|
||||||
gatewayRouteDefinition.setId(routeId); |
|
||||||
gatewayRouteDefinition.setFilters(Collections.emptyList()); |
|
||||||
gatewayRouteDefinition.setPredicates(this.buildPredicates(apiMeta)); |
|
||||||
String uri = this.buildUri(serviceApiInfo, apiMeta); |
|
||||||
String path = this.buildServletPath(serviceApiInfo, apiMeta); |
|
||||||
gatewayRouteDefinition.setUri(uri); |
|
||||||
gatewayRouteDefinition.setPath(path); |
|
||||||
return gatewayRouteDefinition; |
|
||||||
} |
|
||||||
|
|
||||||
protected List<GatewayPredicateDefinition> buildPredicates(ServiceApiInfo.ApiMeta apiMeta) { |
|
||||||
GatewayPredicateDefinition gatewayPredicateDefinition = new GatewayPredicateDefinition(); |
|
||||||
gatewayPredicateDefinition.setName("ReadBody"); |
|
||||||
return Arrays.asList(gatewayPredicateDefinition, this.buildNameVersionPredicateDefinition(apiMeta)); |
|
||||||
} |
|
||||||
|
|
||||||
protected GatewayPredicateDefinition buildNameVersionPredicateDefinition(ServiceApiInfo.ApiMeta apiMeta) { |
|
||||||
return new GatewayPredicateDefinition(String.format(QUERY_PREDICATE_DEFINITION_TPL, apiMeta.fetchNameVersion())); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 添加com.gitee.sop.gatewaycommon.routeDefinition.ReadBodyRoutePredicateFactory,解决form表单获取不到问题 |
|
||||||
* |
|
||||||
* @return 返回路由定义 |
|
||||||
*/ |
|
||||||
protected GatewayRouteDefinition buildReadBodyRouteDefinition(ServiceApiInfo serviceApiInfo) { |
|
||||||
GatewayRouteDefinition readBodyRouteDefinition = this.buildGatewayRouteDefinition(serviceApiInfo, FIRST_API_META); |
|
||||||
readBodyRouteDefinition.setOrder(Integer.MIN_VALUE); |
|
||||||
|
|
||||||
readBodyRouteDefinition.setPredicates(this.buildPredicates(FIRST_API_META)); |
|
||||||
|
|
||||||
return readBodyRouteDefinition; |
|
||||||
} |
|
||||||
|
|
||||||
protected String buildUri(ServiceApiInfo serviceApiInfo, ServiceApiInfo.ApiMeta apiMeta) { |
|
||||||
return PROTOCOL_LOAD_BALANCE + serviceApiInfo.getServiceId(); |
|
||||||
} |
|
||||||
|
|
||||||
protected String buildServletPath(ServiceApiInfo serviceApiInfo, ServiceApiInfo.ApiMeta apiMeta) { |
|
||||||
String contextPath = environment.getProperty("server.servlet.context-path", DEFAULT_CONTEXT_PATH); |
|
||||||
String servletPath = apiMeta.getPath(); |
|
||||||
if (servletPath == null) { |
|
||||||
servletPath = ""; |
|
||||||
} |
|
||||||
servletPath = StringUtils.trimLeadingCharacter(servletPath, '/'); |
|
||||||
return contextPath + servletPath; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 上传接口信息到zookeeper |
|
||||||
* |
|
||||||
* @param serviceRouteInfo 路由服务信息 |
|
||||||
*/ |
|
||||||
protected void uploadServiceRouteInfoToZookeeper(ServiceRouteInfo serviceRouteInfo) { |
|
||||||
String savePath = serviceRouteInfo.getZookeeperPath(); |
|
||||||
try { |
|
||||||
String existServiceRouteInfoData = zookeeperTool.getData(savePath); |
|
||||||
ServiceRouteInfo serviceRouteInfoExist = JSON.parseObject(existServiceRouteInfoData, ServiceRouteInfo.class); |
|
||||||
String oldMD5 = serviceRouteInfoExist.getMd5(); |
|
||||||
String newMD5 = serviceRouteInfo.getMd5(); |
|
||||||
if (Objects.equals(oldMD5, newMD5)) { |
|
||||||
log.info("接口没有改变,无需上传路由信息"); |
|
||||||
return; |
|
||||||
} |
|
||||||
} catch (ZookeeperPathNotExistException e) { |
|
||||||
log.warn("服务路径不存在,path:{}", savePath); |
|
||||||
} |
|
||||||
try { |
|
||||||
log.info("上传接口信息到zookeeper,path:{}, serviceId:{}, 接口数量:{}", |
|
||||||
savePath, |
|
||||||
serviceRouteInfo.getServiceId(), |
|
||||||
serviceRouteInfo.getRouteDefinitionList().size()); |
|
||||||
|
|
||||||
String parentPath = this.uploadFolder(serviceRouteInfo); |
|
||||||
this.uploadRouteItems(serviceRouteInfo, parentPath); |
|
||||||
} catch (Exception e) { |
|
||||||
log.error("更新接口信息到zookeeper失败, serviceId:{}", serviceRouteInfo.getServiceId(), e); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 上传文件夹内容 |
|
||||||
* |
|
||||||
* @param serviceRouteInfo 路由服务信息 |
|
||||||
* @return 返回文件夹路径 |
|
||||||
*/ |
|
||||||
protected String uploadFolder(ServiceRouteInfo serviceRouteInfo) throws Exception { |
|
||||||
// 保存路径
|
|
||||||
String savePath = serviceRouteInfo.getZookeeperPath(); |
|
||||||
String serviceRouteInfoJson = JSON.toJSONString(serviceRouteInfo); |
|
||||||
log.info("上传service目录到zookeeper,路径:{},内容:{}", savePath, serviceRouteInfoJson); |
|
||||||
this.zookeeperTool.createOrUpdateData(savePath, serviceRouteInfoJson); |
|
||||||
return savePath; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 上传路由信息 |
|
||||||
* |
|
||||||
* @param serviceRouteInfo 路由服务 |
|
||||||
* @param parentPath 父节点 |
|
||||||
* @throws Exception |
|
||||||
*/ |
|
||||||
protected void uploadRouteItems(ServiceRouteInfo serviceRouteInfo, String parentPath) throws Exception { |
|
||||||
List<GatewayRouteDefinition> routeDefinitionList = serviceRouteInfo.getRouteDefinitionList(); |
|
||||||
for (GatewayRouteDefinition routeDefinition : routeDefinitionList) { |
|
||||||
// 父目录/子目录
|
|
||||||
String savePath = parentPath + PATH_SPLIT + routeDefinition.getId(); |
|
||||||
String routeDefinitionJson = JSON.toJSONString(routeDefinition); |
|
||||||
log.info("上传路由配置到zookeeper,路径:{},路由数据:{}", savePath, routeDefinitionJson); |
|
||||||
this.zookeeperTool.createOrUpdateData(savePath, routeDefinitionJson); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private void checkZookeeperNode(String path, String errorMsg) { |
|
||||||
if (path.contains(PATH_SPLIT)) { |
|
||||||
throw new IllegalArgumentException(errorMsg); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,19 +0,0 @@ |
|||||||
package com.gitee.sop.gateway.manager; |
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author tanghc |
|
||||||
*/ |
|
||||||
public class ManagerInitializer { |
|
||||||
|
|
||||||
public ManagerInitializer() { |
|
||||||
ApiConfig apiConfig = ApiConfig.getInstance(); |
|
||||||
apiConfig.setIsvManager(new DbIsvManager()); |
|
||||||
apiConfig.setIsvRoutePermissionManager(new DbIsvRoutePermissionManager()); |
|
||||||
apiConfig.setRouteConfigManager(new DbRouteConfigManager()); |
|
||||||
apiConfig.setLimitConfigManager(new DbLimitConfigManager()); |
|
||||||
apiConfig.setIpBlacklistManager(new DbIPBlacklistManager()); |
|
||||||
apiConfig.setUserKeyManager(new DbEnvGrayManager()); |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,3 @@ |
|||||||
|
spring.application.name=api-gateway |
||||||
|
# nacos的config需要放在bootstrap.properties中 |
||||||
|
spring.cloud.nacos.config.server-addr=127.0.0.1:8848 |
@ -1,52 +0,0 @@ |
|||||||
package com.gitee.sop.gateway; |
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.SopConstants; |
|
||||||
import junit.framework.TestCase; |
|
||||||
import org.apache.curator.framework.CuratorFramework; |
|
||||||
import org.apache.curator.framework.CuratorFrameworkFactory; |
|
||||||
import org.apache.curator.retry.ExponentialBackoffRetry; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author tanghc |
|
||||||
*/ |
|
||||||
public class CuratorTest extends TestCase { |
|
||||||
|
|
||||||
private String zookeeperServerAddr = "localhost:2181"; |
|
||||||
|
|
||||||
/** |
|
||||||
* 递归删除节点,只能在测试环境用。 |
|
||||||
* |
|
||||||
* @throws Exception |
|
||||||
*/ |
|
||||||
public void testDel() throws Exception { |
|
||||||
CuratorFramework client = CuratorFrameworkFactory.builder() |
|
||||||
.connectString(zookeeperServerAddr) |
|
||||||
.retryPolicy(new ExponentialBackoffRetry(1000, 3)) |
|
||||||
.build(); |
|
||||||
|
|
||||||
client.start(); |
|
||||||
|
|
||||||
try { |
|
||||||
client.delete().deletingChildrenIfNeeded().forPath(SopConstants.SOP_SERVICE_ROUTE_PATH); |
|
||||||
} catch (Exception e) { |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 删除临时节点 |
|
||||||
*/ |
|
||||||
public void testDelTemp() { |
|
||||||
String tempRoot = "/com.gitee.sop.service.tmp"; |
|
||||||
CuratorFramework client = CuratorFrameworkFactory.builder() |
|
||||||
.connectString(zookeeperServerAddr) |
|
||||||
.retryPolicy(new ExponentialBackoffRetry(1000, 3)) |
|
||||||
.build(); |
|
||||||
|
|
||||||
client.start(); |
|
||||||
|
|
||||||
try { |
|
||||||
client.delete().deletingChildrenIfNeeded().forPath(tempRoot); |
|
||||||
} catch (Exception e) { |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,381 @@ |
|||||||
|
package com.gitee.sop.websiteserver.bean; |
||||||
|
|
||||||
|
import lombok.Data; |
||||||
|
import lombok.Getter; |
||||||
|
import lombok.Setter; |
||||||
|
import okhttp3.Cookie; |
||||||
|
import okhttp3.CookieJar; |
||||||
|
import okhttp3.FormBody; |
||||||
|
import okhttp3.HttpUrl; |
||||||
|
import okhttp3.MediaType; |
||||||
|
import okhttp3.MultipartBody; |
||||||
|
import okhttp3.OkHttpClient; |
||||||
|
import okhttp3.Request; |
||||||
|
import okhttp3.RequestBody; |
||||||
|
import okhttp3.Response; |
||||||
|
import org.apache.commons.codec.digest.DigestUtils; |
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream; |
||||||
|
import java.io.File; |
||||||
|
import java.io.FileInputStream; |
||||||
|
import java.io.FileNotFoundException; |
||||||
|
import java.io.IOException; |
||||||
|
import java.io.InputStream; |
||||||
|
import java.io.Serializable; |
||||||
|
import java.nio.charset.StandardCharsets; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Set; |
||||||
|
import java.util.concurrent.TimeUnit; |
||||||
|
|
||||||
|
/** |
||||||
|
* http请求工具,基于OKHTTP3 |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
public class HttpTool { |
||||||
|
private static final MediaType MEDIA_TYPE_JSON = MediaType.parse("application/json; charset=utf-8"); |
||||||
|
|
||||||
|
private Map<String, List<Cookie>> cookieStore = new HashMap<String, List<Cookie>>(); |
||||||
|
|
||||||
|
private OkHttpClient httpClient; |
||||||
|
|
||||||
|
public HttpTool() { |
||||||
|
this(new HttpToolConfig()); |
||||||
|
} |
||||||
|
|
||||||
|
public HttpTool(HttpToolConfig httpToolConfig) { |
||||||
|
this.initHttpClient(httpToolConfig); |
||||||
|
} |
||||||
|
|
||||||
|
protected void initHttpClient(HttpToolConfig httpToolConfig) { |
||||||
|
httpClient = new OkHttpClient.Builder() |
||||||
|
.connectTimeout(httpToolConfig.connectTimeoutSeconds, TimeUnit.SECONDS) // 设置链接超时时间,默认10秒
|
||||||
|
.readTimeout(httpToolConfig.readTimeoutSeconds, TimeUnit.SECONDS) |
||||||
|
.writeTimeout(httpToolConfig.writeTimeoutSeconds, TimeUnit.SECONDS) |
||||||
|
.cookieJar(new CookieJar() { |
||||||
|
public void saveFromResponse(HttpUrl httpUrl, List<Cookie> list) { |
||||||
|
cookieStore.put(httpUrl.host(), list); |
||||||
|
} |
||||||
|
|
||||||
|
public List<Cookie> loadForRequest(HttpUrl httpUrl) { |
||||||
|
List<Cookie> cookies = cookieStore.get(httpUrl.host()); |
||||||
|
return cookies != null ? cookies : new ArrayList<Cookie>(); |
||||||
|
} |
||||||
|
}).build(); |
||||||
|
} |
||||||
|
|
||||||
|
@Data |
||||||
|
public static class HttpToolConfig { |
||||||
|
/** |
||||||
|
* 请求超时时间 |
||||||
|
*/ |
||||||
|
private int connectTimeoutSeconds = 10; |
||||||
|
/** |
||||||
|
* http读取超时时间 |
||||||
|
*/ |
||||||
|
private int readTimeoutSeconds = 10; |
||||||
|
/** |
||||||
|
* http写超时时间 |
||||||
|
*/ |
||||||
|
private int writeTimeoutSeconds = 10; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* get请求 |
||||||
|
* |
||||||
|
* @param url |
||||||
|
* @param header |
||||||
|
* @return |
||||||
|
* @throws IOException |
||||||
|
*/ |
||||||
|
public String get(String url, Map<String, String> header) throws IOException { |
||||||
|
Request.Builder builder = new Request.Builder().url(url).get(); |
||||||
|
// 添加header
|
||||||
|
addHeader(builder, header); |
||||||
|
|
||||||
|
Request request = builder.build(); |
||||||
|
Response response = httpClient.newCall(request).execute(); |
||||||
|
return response.body().string(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 提交表单 |
||||||
|
* |
||||||
|
* @param url url |
||||||
|
* @param form 参数 |
||||||
|
* @param header header |
||||||
|
* @param method 请求方式,post,get等 |
||||||
|
* @return |
||||||
|
* @throws IOException |
||||||
|
*/ |
||||||
|
public String request(String url, Map<String, ?> form, Map<String, String> header, HTTPMethod method) throws IOException { |
||||||
|
Request.Builder requestBuilder = buildRequestBuilder(url, form, method); |
||||||
|
// 添加header
|
||||||
|
addHeader(requestBuilder, header); |
||||||
|
|
||||||
|
Request request = requestBuilder.build(); |
||||||
|
Response response = httpClient |
||||||
|
.newCall(request) |
||||||
|
.execute(); |
||||||
|
try { |
||||||
|
return response.body().string(); |
||||||
|
} finally { |
||||||
|
response.close(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 请求json数据,contentType=application/json |
||||||
|
* @param url 请求路径 |
||||||
|
* @param json json数据 |
||||||
|
* @param header header |
||||||
|
* @return 返回响应结果 |
||||||
|
* @throws IOException |
||||||
|
*/ |
||||||
|
public String requestJson(String url, String json, Map<String, String> header) throws IOException { |
||||||
|
RequestBody body = RequestBody.create(MEDIA_TYPE_JSON, json); |
||||||
|
Request.Builder requestBuilder = new Request.Builder() |
||||||
|
.url(url) |
||||||
|
.post(body); |
||||||
|
// 添加header
|
||||||
|
addHeader(requestBuilder, header); |
||||||
|
|
||||||
|
Request request = requestBuilder.build(); |
||||||
|
Response response = httpClient |
||||||
|
.newCall(request) |
||||||
|
.execute(); |
||||||
|
try { |
||||||
|
return response.body().string(); |
||||||
|
} finally { |
||||||
|
response.close(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static Request.Builder buildRequestBuilder(String url, Map<String, ?> form, HTTPMethod method) { |
||||||
|
switch (method) { |
||||||
|
case GET: |
||||||
|
return new Request.Builder() |
||||||
|
.url(buildHttpUrl(url, form)) |
||||||
|
.get(); |
||||||
|
case HEAD: |
||||||
|
return new Request.Builder() |
||||||
|
.url(buildHttpUrl(url, form)) |
||||||
|
.head(); |
||||||
|
case PUT: |
||||||
|
return new Request.Builder() |
||||||
|
.url(url) |
||||||
|
.put(buildFormBody(form)); |
||||||
|
case DELETE: |
||||||
|
return new Request.Builder() |
||||||
|
.url(url) |
||||||
|
.delete(buildFormBody(form)); |
||||||
|
default: |
||||||
|
return new Request.Builder() |
||||||
|
.url(url) |
||||||
|
.post(buildFormBody(form)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static HttpUrl buildHttpUrl(String url, Map<String, ?> form) { |
||||||
|
HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder(); |
||||||
|
for (Map.Entry<String, ?> entry : form.entrySet()) { |
||||||
|
urlBuilder.addQueryParameter(entry.getKey(), String.valueOf(entry.getValue())); |
||||||
|
} |
||||||
|
return urlBuilder.build(); |
||||||
|
} |
||||||
|
|
||||||
|
public static FormBody buildFormBody(Map<String, ?> form) { |
||||||
|
FormBody.Builder paramBuilder = new FormBody.Builder(StandardCharsets.UTF_8); |
||||||
|
for (Map.Entry<String, ?> entry : form.entrySet()) { |
||||||
|
paramBuilder.add(entry.getKey(), String.valueOf(entry.getValue())); |
||||||
|
} |
||||||
|
return paramBuilder.build(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 提交表单,并且上传文件 |
||||||
|
* |
||||||
|
* @param url |
||||||
|
* @param form |
||||||
|
* @param header |
||||||
|
* @param files |
||||||
|
* @return |
||||||
|
* @throws IOException |
||||||
|
*/ |
||||||
|
public String requestFile(String url, Map<String, String> form, Map<String, String> header, List<UploadFile> files) |
||||||
|
throws IOException { |
||||||
|
// 创建MultipartBody.Builder,用于添加请求的数据
|
||||||
|
MultipartBody.Builder bodyBuilder = new MultipartBody.Builder(); |
||||||
|
bodyBuilder.setType(MultipartBody.FORM); |
||||||
|
|
||||||
|
for (UploadFile uploadFile : files) { |
||||||
|
bodyBuilder.addFormDataPart(uploadFile.getName(), // 请求的名字
|
||||||
|
uploadFile.getFileName(), // 文件的文字,服务器端用来解析的
|
||||||
|
RequestBody.create(null, uploadFile.getFileData()) // 创建RequestBody,把上传的文件放入
|
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
Set<Map.Entry<String, String>> entrySet = form.entrySet(); |
||||||
|
for (Map.Entry<String, String> entry : entrySet) { |
||||||
|
bodyBuilder.addFormDataPart(entry.getKey(), entry.getValue()); |
||||||
|
} |
||||||
|
|
||||||
|
RequestBody requestBody = bodyBuilder.build(); |
||||||
|
|
||||||
|
Request.Builder builder = new Request.Builder().url(url).post(requestBody); |
||||||
|
|
||||||
|
// 添加header
|
||||||
|
addHeader(builder, header); |
||||||
|
|
||||||
|
Request request = builder.build(); |
||||||
|
Response response = httpClient.newCall(request).execute(); |
||||||
|
try { |
||||||
|
return response.body().string(); |
||||||
|
} finally { |
||||||
|
response.close(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void addHeader(Request.Builder builder, Map<String, String> header) { |
||||||
|
if (header != null) { |
||||||
|
Set<Map.Entry<String, String>> entrySet = header.entrySet(); |
||||||
|
for (Map.Entry<String, String> entry : entrySet) { |
||||||
|
builder.addHeader(entry.getKey(), String.valueOf(entry.getValue())); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void setCookieStore(Map<String, List<Cookie>> cookieStore) { |
||||||
|
this.cookieStore = cookieStore; |
||||||
|
} |
||||||
|
|
||||||
|
public void setHttpClient(OkHttpClient httpClient) { |
||||||
|
this.httpClient = httpClient; |
||||||
|
} |
||||||
|
|
||||||
|
public enum HTTPMethod { |
||||||
|
GET, |
||||||
|
POST, |
||||||
|
PUT, |
||||||
|
HEAD, |
||||||
|
DELETE; |
||||||
|
|
||||||
|
private HTTPMethod() { |
||||||
|
} |
||||||
|
|
||||||
|
public String value() { |
||||||
|
return this.name(); |
||||||
|
} |
||||||
|
|
||||||
|
public static HTTPMethod fromValue(String v) { |
||||||
|
return valueOf(v.toUpperCase()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 文件上传类 |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Getter |
||||||
|
@Setter |
||||||
|
public static class UploadFile implements Serializable { |
||||||
|
private static final long serialVersionUID = -1100614660944996398L; |
||||||
|
|
||||||
|
/** |
||||||
|
* @param name 表单名称,不能重复 |
||||||
|
* @param file 文件 |
||||||
|
* @throws IOException |
||||||
|
*/ |
||||||
|
public UploadFile(String name, File file) throws IOException { |
||||||
|
this(name, file.getName(), FileUtil.toBytes(file)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param name 表单名称,不能重复 |
||||||
|
* @param fileName 文件名 |
||||||
|
* @param input 文件流 |
||||||
|
* @throws IOException |
||||||
|
*/ |
||||||
|
public UploadFile(String name, String fileName, InputStream input) throws IOException { |
||||||
|
this(name, fileName, FileUtil.toBytes(input)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param name 表单名称,不能重复 |
||||||
|
* @param fileName 文件名 |
||||||
|
* @param fileData 文件数据 |
||||||
|
*/ |
||||||
|
public UploadFile(String name, String fileName, byte[] fileData) { |
||||||
|
super(); |
||||||
|
this.name = name; |
||||||
|
this.fileName = fileName; |
||||||
|
this.fileData = fileData; |
||||||
|
this.md5 = DigestUtils.md5Hex(fileData); |
||||||
|
} |
||||||
|
|
||||||
|
private String name; |
||||||
|
private String fileName; |
||||||
|
private byte[] fileData; |
||||||
|
private String md5; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public static class FileUtil { |
||||||
|
|
||||||
|
/** |
||||||
|
* The default buffer size to use. |
||||||
|
*/ |
||||||
|
private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; |
||||||
|
private static final int EOF = -1; |
||||||
|
|
||||||
|
/** |
||||||
|
* 将文件流转换成byte[] |
||||||
|
* @param input |
||||||
|
* @return |
||||||
|
* @throws IOException |
||||||
|
*/ |
||||||
|
public static byte[] toBytes(InputStream input) throws IOException { |
||||||
|
ByteArrayOutputStream output = new ByteArrayOutputStream(); |
||||||
|
int n = 0; |
||||||
|
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; |
||||||
|
|
||||||
|
while (EOF != (n = input.read(buffer))) { |
||||||
|
output.write(buffer, 0, n); |
||||||
|
} |
||||||
|
return output.toByteArray(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 将文件转换成数据流 |
||||||
|
* @param file 文件 |
||||||
|
* @return 返回数据流 |
||||||
|
* @throws IOException |
||||||
|
*/ |
||||||
|
public static byte[] toBytes(File file) throws IOException { |
||||||
|
if (file.exists()) { |
||||||
|
if (file.isDirectory()) { |
||||||
|
throw new IOException("File '" + file + "' exists but is a directory"); |
||||||
|
} |
||||||
|
if (file.canRead() == false) { |
||||||
|
throw new IOException("File '" + file + "' cannot be read"); |
||||||
|
} |
||||||
|
} else { |
||||||
|
throw new FileNotFoundException("File '" + file + "' does not exist"); |
||||||
|
} |
||||||
|
InputStream input = null; |
||||||
|
try { |
||||||
|
input = new FileInputStream(file); |
||||||
|
return toBytes(input); |
||||||
|
} finally { |
||||||
|
try { |
||||||
|
if (input != null) { |
||||||
|
input.close(); |
||||||
|
} |
||||||
|
} catch (IOException ioe) { |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -1,179 +0,0 @@ |
|||||||
package com.gitee.sop.websiteserver.bean; |
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j; |
|
||||||
import org.apache.commons.lang.StringUtils; |
|
||||||
import org.apache.commons.lang.math.NumberUtils; |
|
||||||
import org.apache.curator.framework.CuratorFramework; |
|
||||||
import org.apache.curator.framework.CuratorFrameworkFactory; |
|
||||||
import org.apache.curator.framework.recipes.cache.ChildData; |
|
||||||
import org.apache.curator.framework.recipes.cache.PathChildrenCache; |
|
||||||
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener; |
|
||||||
import org.apache.curator.framework.recipes.cache.TreeCache; |
|
||||||
import org.apache.curator.framework.recipes.cache.TreeCacheListener; |
|
||||||
import org.apache.curator.retry.ExponentialBackoffRetry; |
|
||||||
import org.springframework.core.env.Environment; |
|
||||||
import org.springframework.util.Assert; |
|
||||||
import org.springframework.util.CollectionUtils; |
|
||||||
|
|
||||||
import java.util.List; |
|
||||||
import java.util.function.Consumer; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author tanghc |
|
||||||
*/ |
|
||||||
@Slf4j |
|
||||||
public class ZookeeperContext { |
|
||||||
|
|
||||||
private static CuratorFramework client; |
|
||||||
|
|
||||||
public static void setEnvironment(Environment environment) { |
|
||||||
Assert.notNull(environment, "environment不能为null"); |
|
||||||
initZookeeperClient(environment); |
|
||||||
} |
|
||||||
|
|
||||||
public synchronized static void initZookeeperClient(Environment environment) { |
|
||||||
if (client != null) { |
|
||||||
return; |
|
||||||
} |
|
||||||
String zookeeperServerAddr = environment.getProperty("spring.cloud.zookeeper.connect-string"); |
|
||||||
if (StringUtils.isBlank(zookeeperServerAddr)) { |
|
||||||
throw new RuntimeException("未指定spring.cloud.zookeeper.connect-string参数"); |
|
||||||
} |
|
||||||
String baseSleepTimeMs = environment.getProperty("spring.cloud.zookeeper.baseSleepTimeMs"); |
|
||||||
String maxRetries = environment.getProperty("spring.cloud.zookeeper.maxRetries"); |
|
||||||
log.info("初始化zookeeper客户端,zookeeperServerAddr:{}, baseSleepTimeMs:{}, maxRetries:{}", |
|
||||||
zookeeperServerAddr, baseSleepTimeMs, maxRetries); |
|
||||||
CuratorFramework client = CuratorFrameworkFactory.builder() |
|
||||||
.connectString(zookeeperServerAddr) |
|
||||||
.retryPolicy(new ExponentialBackoffRetry(NumberUtils.toInt(baseSleepTimeMs, 3000), NumberUtils.toInt(maxRetries, 3))) |
|
||||||
.build(); |
|
||||||
|
|
||||||
client.start(); |
|
||||||
|
|
||||||
setClient(client); |
|
||||||
} |
|
||||||
|
|
||||||
public static String getRouteRootPath() { |
|
||||||
return WebsiteConstants.SOP_SERVICE_ROUTE_PATH; |
|
||||||
} |
|
||||||
|
|
||||||
public static String getServiceTempRootPath() { |
|
||||||
return WebsiteConstants.SOP_SERVICE_TEMP_PATH; |
|
||||||
} |
|
||||||
|
|
||||||
public static CuratorFramework getClient() { |
|
||||||
return client; |
|
||||||
} |
|
||||||
|
|
||||||
public static void setClient(CuratorFramework client) { |
|
||||||
ZookeeperContext.client = client; |
|
||||||
} |
|
||||||
|
|
||||||
public static boolean isPathExist(String path) { |
|
||||||
try { |
|
||||||
return client.checkExists().forPath(path) != null; |
|
||||||
} catch (Exception e) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 是否有子节点 |
|
||||||
* @param parentPath |
|
||||||
* @return |
|
||||||
* @throws Exception |
|
||||||
*/ |
|
||||||
public static boolean hasChildren(String parentPath) throws Exception { |
|
||||||
List<String> children = client.getChildren().forPath(parentPath); |
|
||||||
return !CollectionUtils.isEmpty(children); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 创建path,如果path存在不报错,静默返回path名称 |
|
||||||
* |
|
||||||
* @param path |
|
||||||
* @param data |
|
||||||
* @return |
|
||||||
* @throws Exception |
|
||||||
*/ |
|
||||||
public static String createPath(String path, String data) throws Exception { |
|
||||||
if (isPathExist(path)) { |
|
||||||
return path; |
|
||||||
} |
|
||||||
return getClient().create() |
|
||||||
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
|
|
||||||
.creatingParentContainersIfNeeded() |
|
||||||
.forPath(path, data.getBytes()); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 获取子节点信息并监听子节点 |
|
||||||
* |
|
||||||
* @param parentPath 父节点路径 |
|
||||||
* @param listConsumer 子节点数据 |
|
||||||
* @param listener 监听事件 |
|
||||||
* @throws Exception |
|
||||||
*/ |
|
||||||
public static void getChildrenAndListen(String parentPath, Consumer<List<ChildData>> listConsumer, PathChildrenCacheListener listener) throws Exception { |
|
||||||
// 为子节点添加watcher
|
|
||||||
// PathChildrenCache: 监听数据节点的增删改,可以设置触发的事件
|
|
||||||
PathChildrenCache childrenCache = new PathChildrenCache(client, parentPath, true); |
|
||||||
|
|
||||||
/** |
|
||||||
* StartMode: 初始化方式 |
|
||||||
* POST_INITIALIZED_EVENT:异步初始化,初始化之后会触发事件 |
|
||||||
* NORMAL:异步初始化 |
|
||||||
* BUILD_INITIAL_CACHE:同步初始化 |
|
||||||
*/ |
|
||||||
childrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE); |
|
||||||
|
|
||||||
// 列出子节点数据列表,需要使用BUILD_INITIAL_CACHE同步初始化模式才能获得,异步是获取不到的
|
|
||||||
List<ChildData> childDataList = childrenCache.getCurrentData(); |
|
||||||
listConsumer.accept(childDataList); |
|
||||||
log.info("监听子节点增删改,监听路径:{}", parentPath); |
|
||||||
// 监听根节点下面的子节点
|
|
||||||
childrenCache.getListenable().addListener(listener); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 监听子节点的增删改 |
|
||||||
* |
|
||||||
* @param parentPath 父节点路径 |
|
||||||
* @param listener |
|
||||||
* @throws Exception |
|
||||||
*/ |
|
||||||
public static void listenChildren(String parentPath, PathChildrenCacheListener listener) throws Exception { |
|
||||||
// 为子节点添加watcher
|
|
||||||
// PathChildrenCache: 监听数据节点的增删改,可以设置触发的事件
|
|
||||||
PathChildrenCache childrenCache = new PathChildrenCache(client, parentPath, true); |
|
||||||
|
|
||||||
/** |
|
||||||
* StartMode: 初始化方式 |
|
||||||
* POST_INITIALIZED_EVENT:异步初始化,初始化之后会触发事件 |
|
||||||
* NORMAL:异步初始化 |
|
||||||
* BUILD_INITIAL_CACHE:同步初始化 |
|
||||||
*/ |
|
||||||
childrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE); |
|
||||||
// 监听根节点下面的子节点
|
|
||||||
childrenCache.getListenable().addListener(listener); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 监听子节点,可以自定义层级 |
|
||||||
* @param parentPath 父节点路径 |
|
||||||
* @param maxDepth 层级,从1开始。比如当前监听节点/t1,目录最深为/t1/t2/t3/t4,则maxDepth=3,说明下面3级子目录全部监听 |
|
||||||
* @param listener |
|
||||||
* @throws Exception |
|
||||||
*/ |
|
||||||
public static void listenChildren(String parentPath, int maxDepth, TreeCacheListener listener) throws Exception { |
|
||||||
final TreeCache treeCache = TreeCache |
|
||||||
.newBuilder(client, parentPath) |
|
||||||
.setCacheData(true) |
|
||||||
.setMaxDepth(maxDepth) |
|
||||||
.build(); |
|
||||||
|
|
||||||
treeCache.getListenable().addListener(listener); |
|
||||||
treeCache.start(); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,12 +0,0 @@ |
|||||||
package com.gitee.sop.websiteserver.config; |
|
||||||
|
|
||||||
import com.gitee.sop.registryapi.config.BaseRegistryConfig; |
|
||||||
import org.springframework.context.annotation.Configuration; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author tanghc |
|
||||||
*/ |
|
||||||
@Configuration |
|
||||||
public class RegistryConfig extends BaseRegistryConfig { |
|
||||||
|
|
||||||
} |
|
@ -0,0 +1,99 @@ |
|||||||
|
package com.gitee.sop.websiteserver.manager; |
||||||
|
|
||||||
|
import com.alibaba.nacos.api.exception.NacosException; |
||||||
|
import com.alibaba.nacos.api.naming.NamingService; |
||||||
|
import com.alibaba.nacos.api.naming.pojo.Instance; |
||||||
|
import com.alibaba.nacos.api.naming.pojo.ServiceInfo; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.cloud.alibaba.nacos.NacosDiscoveryProperties; |
||||||
|
import org.springframework.http.HttpStatus; |
||||||
|
import org.springframework.http.ResponseEntity; |
||||||
|
import org.springframework.stereotype.Service; |
||||||
|
import org.springframework.util.CollectionUtils; |
||||||
|
import org.springframework.util.DigestUtils; |
||||||
|
import org.springframework.web.client.RestTemplate; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
import java.util.Objects; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Slf4j |
||||||
|
@Service |
||||||
|
public class DocDiscovery { |
||||||
|
|
||||||
|
private static final String SECRET = "b749a2ec000f4f29"; |
||||||
|
|
||||||
|
private static final int FIVE_SECONDS = 1000 * 5; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private NacosDiscoveryProperties nacosDiscoveryProperties; |
||||||
|
|
||||||
|
private RestTemplate restTemplate = new RestTemplate(); |
||||||
|
|
||||||
|
private volatile long lastUpdateTime; |
||||||
|
|
||||||
|
public synchronized void refresh(DocManager docManager) { |
||||||
|
long now = System.currentTimeMillis(); |
||||||
|
// 5秒内只能执行一次,解决重启应用连续加载4次问题
|
||||||
|
if (now - lastUpdateTime < FIVE_SECONDS) { |
||||||
|
return; |
||||||
|
} |
||||||
|
lastUpdateTime = now; |
||||||
|
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance(); |
||||||
|
List<ServiceInfo> subscribes = null; |
||||||
|
try { |
||||||
|
subscribes = namingService.getSubscribeServices(); |
||||||
|
} catch (NacosException e) { |
||||||
|
log.error("namingService.getSubscribeServices()错误", e); |
||||||
|
} |
||||||
|
if (CollectionUtils.isEmpty(subscribes)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
// subscribe
|
||||||
|
String thisServiceId = nacosDiscoveryProperties.getService(); |
||||||
|
for (ServiceInfo serviceInfo : subscribes) { |
||||||
|
String serviceName = serviceInfo.getName(); |
||||||
|
// 如果是本机服务,跳过
|
||||||
|
if (Objects.equals(thisServiceId, serviceName) || "api-gateway".equalsIgnoreCase(serviceName)) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
try { |
||||||
|
List<Instance> allInstances = namingService.getAllInstances(serviceName); |
||||||
|
if (CollectionUtils.isEmpty(allInstances)) { |
||||||
|
log.info("{}服务下线,删除文档信息", serviceName); |
||||||
|
// 如果没有服务列表,则删除所有路由信息
|
||||||
|
docManager.remove(serviceName); |
||||||
|
} else { |
||||||
|
for (Instance instance : allInstances) { |
||||||
|
log.info("加载服务文档,instance:{}", instance); |
||||||
|
String url = getRouteRequestUrl(instance); |
||||||
|
ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class); |
||||||
|
if (responseEntity.getStatusCode() == HttpStatus.OK) { |
||||||
|
String body = responseEntity.getBody(); |
||||||
|
log.debug("加载{}文档,文档信息:{}", serviceName, body); |
||||||
|
docManager.addDocInfo(serviceName, body); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} catch (NacosException e) { |
||||||
|
log.error("选择服务实例失败,serviceName:{}", serviceName, e); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static String getRouteRequestUrl(Instance instance) { |
||||||
|
String query = buildQuery(SECRET); |
||||||
|
return "http://" + instance.getIp() + ":" + instance.getPort() + "/v2/api-docs" + query; |
||||||
|
} |
||||||
|
|
||||||
|
private static String buildQuery(String secret) { |
||||||
|
String time = String.valueOf(System.currentTimeMillis()); |
||||||
|
String source = secret + time + secret; |
||||||
|
String sign = DigestUtils.md5DigestAsHex(source.getBytes()); |
||||||
|
return "?time=" + time + "&sign=" + sign; |
||||||
|
} |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue