parent
3e506b27ef
commit
c0fb37255a
@ -0,0 +1,68 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.route; |
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.InstanceDefinition; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Slf4j |
||||||
|
public abstract class BaseRegistryListener implements RegistryListener { |
||||||
|
|
||||||
|
private static final int FIVE_SECONDS = 1000 * 5; |
||||||
|
|
||||||
|
private Map<String, Long> updateTimeMap = new ConcurrentHashMap<>(16); |
||||||
|
|
||||||
|
public static List<String> EXCLUDE_SERVICE_ID_LIST = new ArrayList<>(8); |
||||||
|
|
||||||
|
static { |
||||||
|
EXCLUDE_SERVICE_ID_LIST.add("api-gateway"); |
||||||
|
EXCLUDE_SERVICE_ID_LIST.add("website-server"); |
||||||
|
} |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private ServiceListener serviceListener; |
||||||
|
|
||||||
|
/** |
||||||
|
* 移除路由信息 |
||||||
|
* |
||||||
|
* @param serviceId serviceId |
||||||
|
*/ |
||||||
|
public void removeRoutes(String serviceId) { |
||||||
|
serviceListener.onRemoveService(serviceId.toLowerCase()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 拉取路由信息 |
||||||
|
* |
||||||
|
* @param instance 服务实例 |
||||||
|
*/ |
||||||
|
public void pullRoutes(InstanceDefinition instance) { |
||||||
|
// serviceId统一小写
|
||||||
|
instance.setServiceId(instance.getServiceId().toLowerCase()); |
||||||
|
serviceListener.onAddInstance(instance); |
||||||
|
} |
||||||
|
|
||||||
|
protected boolean canOperator(String serviceId) { |
||||||
|
for (String excludeServiceId : EXCLUDE_SERVICE_ID_LIST) { |
||||||
|
if (excludeServiceId.equalsIgnoreCase(serviceId)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
// nacos会不停的触发事件,这里做了一层拦截
|
||||||
|
// 同一个serviceId5秒内允许访问一次
|
||||||
|
Long lastUpdateTime = updateTimeMap.getOrDefault(serviceId, 0L); |
||||||
|
long now = System.currentTimeMillis(); |
||||||
|
boolean can = now - lastUpdateTime > FIVE_SECONDS; |
||||||
|
if (can) { |
||||||
|
updateTimeMap.put(serviceId, now); |
||||||
|
} |
||||||
|
return can; |
||||||
|
} |
||||||
|
} |
@ -1,154 +0,0 @@ |
|||||||
package com.gitee.sop.gatewaycommon.route; |
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSON; |
|
||||||
import com.gitee.sop.gatewaycommon.bean.InstanceDefinition; |
|
||||||
import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo; |
|
||||||
import com.gitee.sop.gatewaycommon.manager.BaseRouteCache; |
|
||||||
import lombok.extern.slf4j.Slf4j; |
|
||||||
import org.apache.commons.lang.StringUtils; |
|
||||||
import org.springframework.beans.factory.annotation.Autowired; |
|
||||||
import org.springframework.http.HttpStatus; |
|
||||||
import org.springframework.http.ResponseEntity; |
|
||||||
import org.springframework.util.DigestUtils; |
|
||||||
import org.springframework.web.client.DefaultResponseErrorHandler; |
|
||||||
import org.springframework.web.client.RestTemplate; |
|
||||||
|
|
||||||
import java.util.Map; |
|
||||||
import java.util.concurrent.ConcurrentHashMap; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author tanghc |
|
||||||
*/ |
|
||||||
@Slf4j |
|
||||||
public abstract class BaseRoutesListener implements RegistryListener { |
|
||||||
|
|
||||||
private static final String SOP_ROUTES_PATH = "/sop/routes"; |
|
||||||
|
|
||||||
private static final String SECRET = "a3d9sf!1@odl90zd>fkASwq"; |
|
||||||
|
|
||||||
private static final int FIVE_SECONDS = 1000 * 5; |
|
||||||
|
|
||||||
private static final String METADATA_SERVER_CONTEXT_PATH = "server.servlet.context-path"; |
|
||||||
|
|
||||||
private static final String METADATA_SOP_ROUTES_PATH = "sop.routes.path"; |
|
||||||
|
|
||||||
private static RestTemplate restTemplate = new RestTemplate(); |
|
||||||
|
|
||||||
static { |
|
||||||
// 解决statusCode不等于200,就抛异常问题
|
|
||||||
restTemplate.setErrorHandler(new DefaultResponseErrorHandler() { |
|
||||||
@Override |
|
||||||
protected boolean hasError(HttpStatus statusCode) { |
|
||||||
return statusCode == null; |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
private Map<String, Long> updateTimeMap = new ConcurrentHashMap<>(16); |
|
||||||
|
|
||||||
@Autowired |
|
||||||
private BaseRouteCache<?> baseRouteCache; |
|
||||||
|
|
||||||
@Autowired |
|
||||||
private RoutesProcessor routesProcessor; |
|
||||||
|
|
||||||
/** |
|
||||||
* 移除路由信息 |
|
||||||
* |
|
||||||
* @param serviceId serviceId |
|
||||||
*/ |
|
||||||
public void removeRoutes(String serviceId) { |
|
||||||
doOperator(serviceId, () -> { |
|
||||||
log.info("服务下线,删除路由配置,serviceId: {}", serviceId); |
|
||||||
baseRouteCache.remove(serviceId); |
|
||||||
routesProcessor.removeAllRoutes(serviceId); |
|
||||||
}); |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 拉取路由信息 |
|
||||||
* |
|
||||||
* @param instance 服务实例 |
|
||||||
*/ |
|
||||||
public void pullRoutes(InstanceDefinition instance) { |
|
||||||
String serviceName = instance.getServiceId(); |
|
||||||
doOperator(serviceName, () -> { |
|
||||||
String url = getRouteRequestUrl(instance); |
|
||||||
log.info("拉取路由配置,serviceId: {}, url: {}", serviceName, url); |
|
||||||
ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class); |
|
||||||
if (responseEntity.getStatusCode() == HttpStatus.OK) { |
|
||||||
String body = responseEntity.getBody(); |
|
||||||
ServiceRouteInfo serviceRouteInfo = JSON.parseObject(body, ServiceRouteInfo.class); |
|
||||||
baseRouteCache.load(serviceRouteInfo, callback -> routesProcessor.saveRoutes(serviceRouteInfo, instance)); |
|
||||||
} else { |
|
||||||
log.error("拉取路由配置异常,url: {}, status: {}, body: {}", url, responseEntity.getStatusCodeValue(), responseEntity.getBody()); |
|
||||||
} |
|
||||||
}); |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
private void doOperator(String serviceId, Runnable runnable) { |
|
||||||
if (canOperator(serviceId)) { |
|
||||||
runnable.run(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private boolean canOperator(String serviceId) { |
|
||||||
// nacos会不停的触发事件,这里做了一层拦截
|
|
||||||
// 同一个serviceId5秒内允许访问一次
|
|
||||||
Long lastUpdateTime = updateTimeMap.getOrDefault(serviceId, 0L); |
|
||||||
long now = System.currentTimeMillis(); |
|
||||||
boolean can = now - lastUpdateTime > FIVE_SECONDS; |
|
||||||
if (can) { |
|
||||||
updateTimeMap.put(serviceId, now); |
|
||||||
} |
|
||||||
return can; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 拉取路由请求url |
|
||||||
* |
|
||||||
* @param instance 服务实例 |
|
||||||
* @return 返回最终url |
|
||||||
*/ |
|
||||||
private static String getRouteRequestUrl(InstanceDefinition instance) { |
|
||||||
Map<String, String> metadata = instance.getMetadata(); |
|
||||||
String customPath = metadata.get(METADATA_SOP_ROUTES_PATH); |
|
||||||
String homeUrl; |
|
||||||
String servletPath; |
|
||||||
// 如果metadata中指定了获取路由的url
|
|
||||||
if (StringUtils.isNotBlank(customPath)) { |
|
||||||
// 自定义完整的url
|
|
||||||
if (customPath.startsWith("http")) { |
|
||||||
homeUrl = customPath; |
|
||||||
servletPath = ""; |
|
||||||
} else { |
|
||||||
homeUrl = getHomeUrl(instance); |
|
||||||
servletPath = customPath; |
|
||||||
} |
|
||||||
} else { |
|
||||||
// 默认处理
|
|
||||||
homeUrl = getHomeUrl(instance); |
|
||||||
String contextPath = metadata.getOrDefault(METADATA_SERVER_CONTEXT_PATH, ""); |
|
||||||
servletPath = contextPath + SOP_ROUTES_PATH; |
|
||||||
} |
|
||||||
if (StringUtils.isNotBlank(servletPath) && !servletPath.startsWith("/")) { |
|
||||||
servletPath = '/' + servletPath; |
|
||||||
} |
|
||||||
String query = buildQuery(SECRET); |
|
||||||
return homeUrl + servletPath + query; |
|
||||||
} |
|
||||||
|
|
||||||
private static String getHomeUrl(InstanceDefinition instance) { |
|
||||||
return "http://" + instance.getIp() + ":" + instance.getPort(); |
|
||||||
} |
|
||||||
|
|
||||||
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; |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,41 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.route; |
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus; |
||||||
|
import org.springframework.util.DigestUtils; |
||||||
|
import org.springframework.web.client.DefaultResponseErrorHandler; |
||||||
|
import org.springframework.web.client.RestTemplate; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
public abstract class BaseServiceListener implements ServiceListener { |
||||||
|
|
||||||
|
private static final String SECRET = "a3d9sf!1@odl90zd>fkASwq"; |
||||||
|
|
||||||
|
private static RestTemplate restTemplate = new RestTemplate(); |
||||||
|
|
||||||
|
static { |
||||||
|
// 解决statusCode不等于200,就抛异常问题
|
||||||
|
restTemplate.setErrorHandler(new DefaultResponseErrorHandler() { |
||||||
|
@Override |
||||||
|
protected boolean hasError(HttpStatus statusCode) { |
||||||
|
return statusCode == null; |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
protected 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; |
||||||
|
} |
||||||
|
|
||||||
|
protected static String buildQuery() { |
||||||
|
return buildQuery(SECRET); |
||||||
|
} |
||||||
|
|
||||||
|
public static RestTemplate getRestTemplate() { |
||||||
|
return restTemplate; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,71 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.route; |
||||||
|
|
||||||
|
import com.alibaba.nacos.api.annotation.NacosInjected; |
||||||
|
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.InstanceDefinition; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.cloud.alibaba.nacos.NacosDiscoveryProperties; |
||||||
|
import org.springframework.context.ApplicationEvent; |
||||||
|
import org.springframework.util.CollectionUtils; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
import java.util.Objects; |
||||||
|
|
||||||
|
/** |
||||||
|
* 加载服务路由,nacos实现 |
||||||
|
* |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Slf4j |
||||||
|
public class NacosRegistryListener extends BaseRegistryListener { |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private NacosDiscoveryProperties nacosDiscoveryProperties; |
||||||
|
|
||||||
|
@NacosInjected |
||||||
|
private ConfigService configService; |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onEvent(ApplicationEvent applicationEvent) { |
||||||
|
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; |
||||||
|
} |
||||||
|
subscribes.stream() |
||||||
|
.filter(serviceInfo -> this.canOperator(serviceInfo.getName())) |
||||||
|
.forEach(serviceInfo -> { |
||||||
|
String serviceName = serviceInfo.getName(); |
||||||
|
try { |
||||||
|
List<Instance> allInstances = namingService.getAllInstances(serviceName); |
||||||
|
if (CollectionUtils.isEmpty(allInstances)) { |
||||||
|
// 如果没有服务列表,则删除所有路由信息
|
||||||
|
removeRoutes(serviceName); |
||||||
|
} else { |
||||||
|
for (Instance instance : allInstances) { |
||||||
|
InstanceDefinition instanceDefinition = new InstanceDefinition(); |
||||||
|
instanceDefinition.setInstanceId(instance.getInstanceId()); |
||||||
|
instanceDefinition.setServiceId(serviceName); |
||||||
|
instanceDefinition.setIp(instance.getIp()); |
||||||
|
instanceDefinition.setPort(instance.getPort()); |
||||||
|
instanceDefinition.setMetadata(instance.getMetadata()); |
||||||
|
pullRoutes(instanceDefinition); |
||||||
|
} |
||||||
|
} |
||||||
|
} catch (Exception e) { |
||||||
|
log.error("选择服务实例失败,serviceName: {}", serviceName, e); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -1,75 +0,0 @@ |
|||||||
package com.gitee.sop.gatewaycommon.route; |
|
||||||
|
|
||||||
import com.alibaba.nacos.api.annotation.NacosInjected; |
|
||||||
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.InstanceDefinition; |
|
||||||
import lombok.extern.slf4j.Slf4j; |
|
||||||
import org.springframework.beans.factory.annotation.Autowired; |
|
||||||
import org.springframework.cloud.alibaba.nacos.NacosDiscoveryProperties; |
|
||||||
import org.springframework.context.ApplicationEvent; |
|
||||||
import org.springframework.util.CollectionUtils; |
|
||||||
|
|
||||||
import java.util.List; |
|
||||||
import java.util.Objects; |
|
||||||
|
|
||||||
/** |
|
||||||
* 加载服务路由,nacos实现 |
|
||||||
* |
|
||||||
* @author tanghc |
|
||||||
*/ |
|
||||||
@Slf4j |
|
||||||
public class NacosRoutesListener extends BaseRoutesListener { |
|
||||||
|
|
||||||
@Autowired |
|
||||||
private NacosDiscoveryProperties nacosDiscoveryProperties; |
|
||||||
|
|
||||||
@NacosInjected |
|
||||||
private ConfigService configService; |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onEvent(ApplicationEvent applicationEvent) { |
|
||||||
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)) { |
|
||||||
continue; |
|
||||||
} |
|
||||||
try { |
|
||||||
List<Instance> allInstances = namingService.getAllInstances(serviceName); |
|
||||||
if (CollectionUtils.isEmpty(allInstances)) { |
|
||||||
// 如果没有服务列表,则删除所有路由信息
|
|
||||||
removeRoutes(serviceName); |
|
||||||
} else { |
|
||||||
for (Instance instance : allInstances) { |
|
||||||
InstanceDefinition instanceDefinition = new InstanceDefinition(); |
|
||||||
instanceDefinition.setInstanceId(instance.getInstanceId()); |
|
||||||
instanceDefinition.setServiceId(serviceName); |
|
||||||
instanceDefinition.setIp(instance.getIp()); |
|
||||||
instanceDefinition.setPort(instance.getPort()); |
|
||||||
instanceDefinition.setMetadata(instance.getMetadata()); |
|
||||||
pullRoutes(instanceDefinition); |
|
||||||
} |
|
||||||
} |
|
||||||
} catch (Exception e) { |
|
||||||
log.error("选择服务实例失败,serviceName: {}", serviceName, e); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -0,0 +1,12 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.route; |
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.InstanceDefinition; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
public interface ServiceListener { |
||||||
|
void onRemoveService(String serviceId); |
||||||
|
|
||||||
|
void onAddInstance(InstanceDefinition instance); |
||||||
|
} |
@ -0,0 +1,92 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.route; |
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON; |
||||||
|
import com.gitee.sop.gatewaycommon.bean.InstanceDefinition; |
||||||
|
import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo; |
||||||
|
import com.gitee.sop.gatewaycommon.manager.BaseRouteCache; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.apache.commons.lang.StringUtils; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.http.HttpStatus; |
||||||
|
import org.springframework.http.ResponseEntity; |
||||||
|
|
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Slf4j |
||||||
|
public class ServiceRouteListener extends BaseServiceListener { |
||||||
|
|
||||||
|
private static final String SOP_ROUTES_PATH = "/sop/routes"; |
||||||
|
|
||||||
|
private static final String METADATA_SERVER_CONTEXT_PATH = "server.servlet.context-path"; |
||||||
|
|
||||||
|
private static final String METADATA_SOP_ROUTES_PATH = "sop.routes.path"; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private BaseRouteCache<?> baseRouteCache; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private RoutesProcessor routesProcessor; |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onRemoveService(String serviceId) { |
||||||
|
log.info("服务下线,删除路由配置,serviceId: {}", serviceId); |
||||||
|
baseRouteCache.remove(serviceId); |
||||||
|
routesProcessor.removeAllRoutes(serviceId); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onAddInstance(InstanceDefinition instance) { |
||||||
|
String serviceName = instance.getServiceId(); |
||||||
|
String url = getRouteRequestUrl(instance); |
||||||
|
log.info("拉取路由配置,serviceId: {}, url: {}", serviceName, url); |
||||||
|
ResponseEntity<String> responseEntity = getRestTemplate().getForEntity(url, String.class); |
||||||
|
if (responseEntity.getStatusCode() == HttpStatus.OK) { |
||||||
|
String body = responseEntity.getBody(); |
||||||
|
ServiceRouteInfo serviceRouteInfo = JSON.parseObject(body, ServiceRouteInfo.class); |
||||||
|
baseRouteCache.load(serviceRouteInfo, callback -> routesProcessor.saveRoutes(serviceRouteInfo, instance)); |
||||||
|
} else { |
||||||
|
log.error("拉取路由配置异常,url: {}, status: {}, body: {}", url, responseEntity.getStatusCodeValue(), responseEntity.getBody()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 拉取路由请求url |
||||||
|
* |
||||||
|
* @param instance 服务实例 |
||||||
|
* @return 返回最终url |
||||||
|
*/ |
||||||
|
private static String getRouteRequestUrl(InstanceDefinition instance) { |
||||||
|
Map<String, String> metadata = instance.getMetadata(); |
||||||
|
String customPath = metadata.get(METADATA_SOP_ROUTES_PATH); |
||||||
|
String homeUrl; |
||||||
|
String servletPath; |
||||||
|
// 如果metadata中指定了获取路由的url
|
||||||
|
if (StringUtils.isNotBlank(customPath)) { |
||||||
|
// 自定义完整的url
|
||||||
|
if (customPath.startsWith("http")) { |
||||||
|
homeUrl = customPath; |
||||||
|
servletPath = ""; |
||||||
|
} else { |
||||||
|
homeUrl = getHomeUrl(instance); |
||||||
|
servletPath = customPath; |
||||||
|
} |
||||||
|
} else { |
||||||
|
// 默认处理
|
||||||
|
homeUrl = getHomeUrl(instance); |
||||||
|
String contextPath = metadata.getOrDefault(METADATA_SERVER_CONTEXT_PATH, ""); |
||||||
|
servletPath = contextPath + SOP_ROUTES_PATH; |
||||||
|
} |
||||||
|
if (StringUtils.isNotBlank(servletPath) && !servletPath.startsWith("/")) { |
||||||
|
servletPath = '/' + servletPath; |
||||||
|
} |
||||||
|
String query = buildQuery(); |
||||||
|
return homeUrl + servletPath + query; |
||||||
|
} |
||||||
|
|
||||||
|
private static String getHomeUrl(InstanceDefinition instance) { |
||||||
|
return "http://" + instance.getIp() + ":" + instance.getPort(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,38 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.validate; |
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.SopConstants; |
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException; |
||||||
|
import java.net.URLEncoder; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
public class SignConfig { |
||||||
|
private static volatile Wrapper wrapper = new Wrapper() {}; |
||||||
|
|
||||||
|
public static void enableUrlencodeMode() { |
||||||
|
wrapper = new Wrapper() { |
||||||
|
@Override |
||||||
|
public String wrapVal(Object val) { |
||||||
|
String valStr = String.valueOf(val); |
||||||
|
try { |
||||||
|
return URLEncoder.encode(valStr, SopConstants.UTF8); |
||||||
|
} catch (UnsupportedEncodingException e) { |
||||||
|
return valStr; |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
public static String wrapVal(Object val) { |
||||||
|
return wrapper.wrapVal(val); |
||||||
|
} |
||||||
|
|
||||||
|
interface Wrapper { |
||||||
|
default String wrapVal(Object val) { |
||||||
|
return String.valueOf(val); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,50 @@ |
|||||||
|
package com.gitee.sop.websiteserver.listener; |
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.InstanceDefinition; |
||||||
|
import com.gitee.sop.gatewaycommon.route.BaseServiceListener; |
||||||
|
import com.gitee.sop.websiteserver.manager.DocManager; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.http.HttpStatus; |
||||||
|
import org.springframework.http.ResponseEntity; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Slf4j |
||||||
|
public class ServiceDocListener extends BaseServiceListener { |
||||||
|
|
||||||
|
private static final String SECRET = "b749a2ec000f4f29"; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private DocManager docManager; |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onRemoveService(String serviceId) { |
||||||
|
docManager.remove(serviceId); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onAddInstance(InstanceDefinition instance) { |
||||||
|
String serviceId = instance.getServiceId(); |
||||||
|
String url = getRouteRequestUrl(instance); |
||||||
|
ResponseEntity<String> responseEntity = getRestTemplate().getForEntity(url, String.class); |
||||||
|
if (responseEntity.getStatusCode() == HttpStatus.OK) { |
||||||
|
String body = responseEntity.getBody(); |
||||||
|
docManager.addDocInfo( |
||||||
|
serviceId |
||||||
|
, body |
||||||
|
, callback -> log.info("加载服务文档,serviceId={}, 机器={}" |
||||||
|
, serviceId |
||||||
|
, instance.getIp() + ":" + instance.getPort()) |
||||||
|
); |
||||||
|
} else { |
||||||
|
log.error("加载文档失败, status:{}, body:{}", responseEntity.getStatusCodeValue(), responseEntity.getBody()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static String getRouteRequestUrl(InstanceDefinition instance) { |
||||||
|
String query = buildQuery(SECRET); |
||||||
|
return "http://" + instance.getIp() + ":" + instance.getPort() + "/v2/api-docs" + query; |
||||||
|
} |
||||||
|
} |
@ -1,116 +0,0 @@ |
|||||||
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.DefaultResponseErrorHandler; |
|
||||||
import org.springframework.web.client.RestTemplate; |
|
||||||
|
|
||||||
import java.util.HashMap; |
|
||||||
import java.util.List; |
|
||||||
import java.util.Map; |
|
||||||
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 Map<String, Long> updateTimeMap = new HashMap<>(16); |
|
||||||
|
|
||||||
public DocDiscovery() { |
|
||||||
// 解决statusCode不等于200,就抛异常问题
|
|
||||||
restTemplate.setErrorHandler(new DefaultResponseErrorHandler() { |
|
||||||
@Override |
|
||||||
protected boolean hasError(HttpStatus statusCode) { |
|
||||||
return statusCode == null; |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
public synchronized void refresh(DocManager docManager) { |
|
||||||
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 serviceId = serviceInfo.getName(); |
|
||||||
// 如果是本机服务,跳过
|
|
||||||
if (Objects.equals(thisServiceId, serviceId) || "api-gateway".equalsIgnoreCase(serviceId)) { |
|
||||||
continue; |
|
||||||
} |
|
||||||
// nacos会不停的触发事件,这里做了一层拦截
|
|
||||||
// 同一个serviceId5秒内允许访问一次
|
|
||||||
Long lastUpdateTime = updateTimeMap.getOrDefault(serviceId, 0L); |
|
||||||
long now = System.currentTimeMillis(); |
|
||||||
if (now - lastUpdateTime < FIVE_SECONDS) { |
|
||||||
continue; |
|
||||||
} |
|
||||||
updateTimeMap.put(serviceId, now); |
|
||||||
try { |
|
||||||
List<Instance> allInstances = namingService.getAllInstances(serviceId); |
|
||||||
if (CollectionUtils.isEmpty(allInstances)) { |
|
||||||
// 如果没有服务列表,则删除所有路由信息
|
|
||||||
docManager.remove(serviceId); |
|
||||||
} else { |
|
||||||
for (Instance instance : allInstances) { |
|
||||||
String url = getRouteRequestUrl(instance); |
|
||||||
ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class); |
|
||||||
if (responseEntity.getStatusCode() == HttpStatus.OK) { |
|
||||||
String body = responseEntity.getBody(); |
|
||||||
docManager.addDocInfo( |
|
||||||
serviceId |
|
||||||
, body |
|
||||||
, callback -> log.info("加载服务文档,serviceId={}, 机器={}" |
|
||||||
, serviceId, instance.getIp() + ":" + instance.getPort()) |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} catch (NacosException e) { |
|
||||||
log.error("选择服务实例失败,serviceId:{}", serviceId, 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