mirror of
https://gitee.com/durcframework/SOP.git
synced 2025-08-11 21:57:56 +08:00
适配eureka
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
## 2.2.0
|
## 2.2.0
|
||||||
|
|
||||||
- 支持eureka注册中心,需要执行`sop-2.2.0.sql`升级文件
|
- 支持eureka注册中心,需要执行`sop-2.2.0.sql`升级文件
|
||||||
|
- 签名内容支持urlencode(设置`sign.urlencode=true`)
|
||||||
|
|
||||||
## 2.1.3
|
## 2.1.3
|
||||||
|
|
||||||
|
@@ -8,15 +8,17 @@ import com.gitee.sop.gatewaycommon.limit.LimitManager;
|
|||||||
import com.gitee.sop.gatewaycommon.loadbalancer.SopPropertiesFactory;
|
import com.gitee.sop.gatewaycommon.loadbalancer.SopPropertiesFactory;
|
||||||
import com.gitee.sop.gatewaycommon.message.ErrorFactory;
|
import com.gitee.sop.gatewaycommon.message.ErrorFactory;
|
||||||
import com.gitee.sop.gatewaycommon.param.ParameterFormatter;
|
import com.gitee.sop.gatewaycommon.param.ParameterFormatter;
|
||||||
import com.gitee.sop.gatewaycommon.route.EurekaRoutesListener;
|
import com.gitee.sop.gatewaycommon.route.ServiceRouteListener;
|
||||||
import com.gitee.sop.gatewaycommon.route.NacosRoutesListener;
|
import com.gitee.sop.gatewaycommon.route.EurekaRegistryListener;
|
||||||
|
import com.gitee.sop.gatewaycommon.route.NacosRegistryListener;
|
||||||
import com.gitee.sop.gatewaycommon.route.RegistryListener;
|
import com.gitee.sop.gatewaycommon.route.RegistryListener;
|
||||||
|
import com.gitee.sop.gatewaycommon.route.ServiceListener;
|
||||||
import com.gitee.sop.gatewaycommon.secret.IsvManager;
|
import com.gitee.sop.gatewaycommon.secret.IsvManager;
|
||||||
import com.gitee.sop.gatewaycommon.session.SessionManager;
|
import com.gitee.sop.gatewaycommon.session.SessionManager;
|
||||||
|
import com.gitee.sop.gatewaycommon.validate.SignConfig;
|
||||||
import com.gitee.sop.gatewaycommon.validate.Validator;
|
import com.gitee.sop.gatewaycommon.validate.Validator;
|
||||||
import org.springframework.beans.BeansException;
|
import org.springframework.beans.BeansException;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
|
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
|
||||||
@@ -72,13 +74,19 @@ public class AbstractConfiguration implements ApplicationContextAware {
|
|||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty("spring.cloud.nacos.discovery.server-addr")
|
@ConditionalOnProperty("spring.cloud.nacos.discovery.server-addr")
|
||||||
RegistryListener registryListenerNacos() {
|
RegistryListener registryListenerNacos() {
|
||||||
return new NacosRoutesListener();
|
return new NacosRegistryListener();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty("eureka.client.serviceUrl.defaultZone")
|
@ConditionalOnProperty("eureka.client.serviceUrl.defaultZone")
|
||||||
RegistryListener registryListenerEureka() {
|
RegistryListener registryListenerEureka() {
|
||||||
return new EurekaRoutesListener();
|
return new EurekaRegistryListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
ServiceListener serviceListener() {
|
||||||
|
return new ServiceRouteListener();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -174,6 +182,14 @@ public class AbstractConfiguration implements ApplicationContextAware {
|
|||||||
if (RouteRepositoryContext.getRouteRepository() == null) {
|
if (RouteRepositoryContext.getRouteRepository() == null) {
|
||||||
throw new IllegalArgumentException("RouteRepositoryContext.setRouteRepository()方法未使用");
|
throw new IllegalArgumentException("RouteRepositoryContext.setRouteRepository()方法未使用");
|
||||||
}
|
}
|
||||||
|
String serverName = environment.getProperty("spring.application.name");
|
||||||
|
if (!"api-gateway".equals(serverName)) {
|
||||||
|
throw new IllegalArgumentException("spring.application.name必须为api-gateway");
|
||||||
|
}
|
||||||
|
String urlencode = environment.getProperty("sign.urlencode");
|
||||||
|
if ("true".equals(urlencode)) {
|
||||||
|
SignConfig.enableUrlencodeMode();
|
||||||
|
}
|
||||||
EnvironmentContext.setEnvironment(environment);
|
EnvironmentContext.setEnvironment(environment);
|
||||||
|
|
||||||
initMessage();
|
initMessage();
|
||||||
|
@@ -14,7 +14,9 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用不到了
|
* 用不到了,这个类的作用是监听消息推送用的。由admin推送一条config配置,然后这里触发事件。
|
||||||
|
* 现在改为直接由admin请求网关提供的接口进行配置修改。
|
||||||
|
* 考虑
|
||||||
*
|
*
|
||||||
* @author tanghc
|
* @author tanghc
|
||||||
*/
|
*/
|
||||||
|
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
@@ -7,7 +7,6 @@ import com.netflix.appinfo.InstanceInfo;
|
|||||||
import com.netflix.discovery.shared.Application;
|
import com.netflix.discovery.shared.Application;
|
||||||
import com.netflix.discovery.shared.Applications;
|
import com.netflix.discovery.shared.Applications;
|
||||||
import org.apache.commons.collections.CollectionUtils;
|
import org.apache.commons.collections.CollectionUtils;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.cloud.netflix.eureka.CloudEurekaClient;
|
import org.springframework.cloud.netflix.eureka.CloudEurekaClient;
|
||||||
import org.springframework.context.ApplicationEvent;
|
import org.springframework.context.ApplicationEvent;
|
||||||
|
|
||||||
@@ -22,7 +21,7 @@ import java.util.stream.Collectors;
|
|||||||
*
|
*
|
||||||
* @author tanghc
|
* @author tanghc
|
||||||
*/
|
*/
|
||||||
public class EurekaRoutesListener extends BaseRoutesListener {
|
public class EurekaRegistryListener extends BaseRegistryListener {
|
||||||
|
|
||||||
static {
|
static {
|
||||||
System.setProperty(SopPropertiesFactory.PROPERTIES_KEY, EurekaEnvironmentServerChooser.class.getName());
|
System.setProperty(SopPropertiesFactory.PROPERTIES_KEY, EurekaEnvironmentServerChooser.class.getName());
|
||||||
@@ -30,9 +29,6 @@ public class EurekaRoutesListener extends BaseRoutesListener {
|
|||||||
|
|
||||||
private Set<String> cacheServices = new HashSet<>();
|
private Set<String> cacheServices = new HashSet<>();
|
||||||
|
|
||||||
@Value("${spring.application.name:api-gateway}")
|
|
||||||
private String appName;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEvent(ApplicationEvent applicationEvent) {
|
public void onEvent(ApplicationEvent applicationEvent) {
|
||||||
Object source = applicationEvent.getSource();
|
Object source = applicationEvent.getSource();
|
||||||
@@ -44,15 +40,19 @@ public class EurekaRoutesListener extends BaseRoutesListener {
|
|||||||
.map(Application::getName)
|
.map(Application::getName)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
Set<String> currentServices = new HashSet<>(serviceList);
|
final Set<String> currentServices = new HashSet<>(serviceList);
|
||||||
currentServices.removeAll(cacheServices);
|
currentServices.removeAll(cacheServices);
|
||||||
// 如果有新的服务注册进来
|
// 如果有新的服务注册进来
|
||||||
if (currentServices.size() > 0) {
|
if (currentServices.size() > 0) {
|
||||||
this.doRegister(registeredApplications, currentServices);
|
List<Application> newApplications = registeredApplications.stream()
|
||||||
|
.filter(application -> this.canOperator(application.getName())
|
||||||
|
&& currentServices.contains(application.getName()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
this.doRegister(newApplications);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentServices = new HashSet<>(serviceList);
|
cacheServices.removeAll(new HashSet<>(serviceList));
|
||||||
cacheServices.removeAll(currentServices);
|
|
||||||
// 如果有服务删除
|
// 如果有服务删除
|
||||||
if (cacheServices.size() > 0) {
|
if (cacheServices.size() > 0) {
|
||||||
this.doRemove(cacheServices);
|
this.doRemove(cacheServices);
|
||||||
@@ -61,12 +61,8 @@ public class EurekaRoutesListener extends BaseRoutesListener {
|
|||||||
cacheServices = new HashSet<>(serviceList);
|
cacheServices = new HashSet<>(serviceList);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doRegister(List<Application> registeredApplications, Set<String> newServices) {
|
private void doRegister(List<Application> registeredApplications) {
|
||||||
registeredApplications
|
registeredApplications.forEach(application -> {
|
||||||
.stream()
|
|
||||||
.filter(application -> !appName.equalsIgnoreCase(application.getName())
|
|
||||||
&& newServices.contains(application.getName()))
|
|
||||||
.forEach(application -> {
|
|
||||||
List<InstanceInfo> instances = application.getInstances();
|
List<InstanceInfo> instances = application.getInstances();
|
||||||
if (CollectionUtils.isNotEmpty(instances)) {
|
if (CollectionUtils.isNotEmpty(instances)) {
|
||||||
instances.sort(Comparator.comparing(InstanceInfo::getLastUpdatedTimestamp).reversed());
|
instances.sort(Comparator.comparing(InstanceInfo::getLastUpdatedTimestamp).reversed());
|
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -5,6 +5,7 @@
|
|||||||
package com.gitee.sop.gatewaycommon.validate.alipay;
|
package com.gitee.sop.gatewaycommon.validate.alipay;
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.message.ErrorEnum;
|
import com.gitee.sop.gatewaycommon.message.ErrorEnum;
|
||||||
|
import com.gitee.sop.gatewaycommon.validate.SignConfig;
|
||||||
import org.apache.commons.codec.binary.Base64;
|
import org.apache.commons.codec.binary.Base64;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
@@ -188,13 +189,13 @@ public class AlipaySignature {
|
|||||||
params.remove("sign");
|
params.remove("sign");
|
||||||
params.remove("sign_type");
|
params.remove("sign_type");
|
||||||
|
|
||||||
StringBuffer content = new StringBuffer();
|
StringBuilder content = new StringBuilder();
|
||||||
List<String> keys = new ArrayList<String>(params.keySet());
|
List<String> keys = new ArrayList<String>(params.keySet());
|
||||||
Collections.sort(keys);
|
Collections.sort(keys);
|
||||||
|
|
||||||
for (int i = 0; i < keys.size(); i++) {
|
for (int i = 0; i < keys.size(); i++) {
|
||||||
String key = keys.get(i);
|
String key = keys.get(i);
|
||||||
String value = params.get(key);
|
String value = SignConfig.wrapVal(params.get(key));
|
||||||
content.append((i == 0 ? "" : "&") + key + "=" + value);
|
content.append((i == 0 ? "" : "&") + key + "=" + value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,13 +209,13 @@ public class AlipaySignature {
|
|||||||
|
|
||||||
params.remove("sign");
|
params.remove("sign");
|
||||||
|
|
||||||
StringBuffer content = new StringBuffer();
|
StringBuilder content = new StringBuilder();
|
||||||
List<String> keys = new ArrayList<String>(params.keySet());
|
List<String> keys = new ArrayList<String>(params.keySet());
|
||||||
Collections.sort(keys);
|
Collections.sort(keys);
|
||||||
|
|
||||||
for (int i = 0; i < keys.size(); i++) {
|
for (int i = 0; i < keys.size(); i++) {
|
||||||
String key = keys.get(i);
|
String key = keys.get(i);
|
||||||
String value = String.valueOf(params.get(key));
|
String value = SignConfig.wrapVal(params.get(key));
|
||||||
content.append((i == 0 ? "" : "&") + key + "=" + value);
|
content.append((i == 0 ? "" : "&") + key + "=" + value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -4,6 +4,7 @@ package com.gitee.sop.gatewaycommon.validate.taobao;
|
|||||||
import com.gitee.sop.gatewaycommon.message.ErrorEnum;
|
import com.gitee.sop.gatewaycommon.message.ErrorEnum;
|
||||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||||
import com.gitee.sop.gatewaycommon.validate.AbstractSigner;
|
import com.gitee.sop.gatewaycommon.validate.AbstractSigner;
|
||||||
|
import com.gitee.sop.gatewaycommon.validate.SignConfig;
|
||||||
import com.gitee.sop.gatewaycommon.validate.SignEncipher;
|
import com.gitee.sop.gatewaycommon.validate.SignEncipher;
|
||||||
import com.gitee.sop.gatewaycommon.validate.SignEncipherHMAC_MD5;
|
import com.gitee.sop.gatewaycommon.validate.SignEncipherHMAC_MD5;
|
||||||
import com.gitee.sop.gatewaycommon.validate.SignEncipherMD5;
|
import com.gitee.sop.gatewaycommon.validate.SignEncipherMD5;
|
||||||
@@ -47,8 +48,8 @@ public class TaobaoSigner extends AbstractSigner {
|
|||||||
// 第二步:把所有参数名和参数值串在一起
|
// 第二步:把所有参数名和参数值串在一起
|
||||||
StringBuilder paramNameValue = new StringBuilder();
|
StringBuilder paramNameValue = new StringBuilder();
|
||||||
for (String paramName : paramNames) {
|
for (String paramName : paramNames) {
|
||||||
Object val = param.get(paramName);
|
String val = SignConfig.wrapVal(param.get(paramName));
|
||||||
if (val != null && StringUtils.isNotBlank(String.valueOf(val))) {
|
if (StringUtils.isNotBlank(val)) {
|
||||||
paramNameValue.append(paramName).append(val);
|
paramNameValue.append(paramName).append(val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-parent</artifactId>
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
<version>2.1.4.RELEASE</version>
|
<version>2.1.2.RELEASE</version>
|
||||||
<relativePath/> <!-- lookup parent from repository -->
|
<relativePath/> <!-- lookup parent from repository -->
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.gitee.sop</groupId>
|
<groupId>com.gitee.sop</groupId>
|
||||||
@@ -16,13 +16,31 @@
|
|||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<java.version>1.8</java.version>
|
<java.version>1.8</java.version>
|
||||||
|
<spring-cloud.version>Greenwich.RELEASE</spring-cloud.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.cloud</groupId>
|
<groupId>org.springframework.cloud</groupId>
|
||||||
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
|
<artifactId>spring-cloud-dependencies</artifactId>
|
||||||
<version>0.9.0.RELEASE</version>
|
<version>${spring-cloud.version}</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.gitee.sop</groupId>
|
||||||
|
<artifactId>sop-gateway-common</artifactId>
|
||||||
|
<version>2.1.3-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@@ -3,13 +3,23 @@ package com.gitee.sop.websiteserver.config;
|
|||||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||||
import com.alibaba.fastjson.support.config.FastJsonConfig;
|
import com.alibaba.fastjson.support.config.FastJsonConfig;
|
||||||
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
|
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
|
||||||
|
import com.gitee.sop.gatewaycommon.route.EurekaRegistryListener;
|
||||||
|
import com.gitee.sop.gatewaycommon.route.NacosRegistryListener;
|
||||||
|
import com.gitee.sop.gatewaycommon.route.RegistryListener;
|
||||||
|
import com.gitee.sop.gatewaycommon.route.ServiceListener;
|
||||||
|
import com.gitee.sop.websiteserver.listener.ServiceDocListener;
|
||||||
import com.gitee.sop.websiteserver.manager.DocManager;
|
import com.gitee.sop.websiteserver.manager.DocManager;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.ApplicationArguments;
|
import org.springframework.boot.ApplicationArguments;
|
||||||
import org.springframework.boot.ApplicationRunner;
|
import org.springframework.boot.ApplicationRunner;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
|
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
|
||||||
|
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
|
||||||
|
import org.springframework.context.ApplicationEvent;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.event.EventListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author tanghc
|
* @author tanghc
|
||||||
@@ -20,6 +30,9 @@ public class WebsiteConfig implements ApplicationRunner {
|
|||||||
@Autowired
|
@Autowired
|
||||||
DocManager docManager;
|
DocManager docManager;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RegistryListener registryListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 使用fastjson代替jackson
|
* 使用fastjson代替jackson
|
||||||
* @return
|
* @return
|
||||||
@@ -35,6 +48,37 @@ public class WebsiteConfig implements ApplicationRunner {
|
|||||||
return new HttpMessageConverters(converter);
|
return new HttpMessageConverters(converter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* nacos事件监听
|
||||||
|
*
|
||||||
|
* @param heartbeatEvent
|
||||||
|
*/
|
||||||
|
@EventListener(classes = HeartbeatEvent.class)
|
||||||
|
public void listenNacosEvent(ApplicationEvent heartbeatEvent) {
|
||||||
|
registryListener.onEvent(heartbeatEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微服务路由加载
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty("spring.cloud.nacos.discovery.server-addr")
|
||||||
|
RegistryListener registryListenerNacos() {
|
||||||
|
return new NacosRegistryListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty("eureka.client.serviceUrl.defaultZone")
|
||||||
|
RegistryListener registryListenerEureka() {
|
||||||
|
return new EurekaRegistryListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
ServiceListener serviceListener() {
|
||||||
|
return new ServiceDocListener();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SpringBoot启动完毕执行
|
* SpringBoot启动完毕执行
|
||||||
*/
|
*/
|
||||||
|
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -5,9 +5,6 @@ import com.alibaba.fastjson.JSONObject;
|
|||||||
import com.alibaba.fastjson.parser.Feature;
|
import com.alibaba.fastjson.parser.Feature;
|
||||||
import com.gitee.sop.websiteserver.bean.DocInfo;
|
import com.gitee.sop.websiteserver.bean.DocInfo;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
|
|
||||||
import org.springframework.context.ApplicationListener;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.util.DigestUtils;
|
import org.springframework.util.DigestUtils;
|
||||||
|
|
||||||
@@ -23,7 +20,7 @@ import java.util.function.Consumer;
|
|||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class DocManagerImpl implements DocManager, ApplicationListener<HeartbeatEvent> {
|
public class DocManagerImpl implements DocManager {
|
||||||
|
|
||||||
// key:title
|
// key:title
|
||||||
private Map<String, DocInfo> docDefinitionMap = new HashMap<>();
|
private Map<String, DocInfo> docDefinitionMap = new HashMap<>();
|
||||||
@@ -37,9 +34,6 @@ public class DocManagerImpl implements DocManager, ApplicationListener<Heartbeat
|
|||||||
|
|
||||||
private DocParser easyopenDocParser = new EasyopenDocParser();
|
private DocParser easyopenDocParser = new EasyopenDocParser();
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private DocDiscovery docDiscovery;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addDocInfo(String serviceId, String docInfoJson, Consumer<DocInfo> callback) {
|
public void addDocInfo(String serviceId, String docInfoJson, Consumer<DocInfo> callback) {
|
||||||
String newMd5 = DigestUtils.md5DigestAsHex(docInfoJson.getBytes(StandardCharsets.UTF_8));
|
String newMd5 = DigestUtils.md5DigestAsHex(docInfoJson.getBytes(StandardCharsets.UTF_8));
|
||||||
@@ -80,10 +74,4 @@ public class DocManagerImpl implements DocManager, ApplicationListener<Heartbeat
|
|||||||
serviceIdMd5Map.remove(serviceId);
|
serviceIdMd5Map.remove(serviceId);
|
||||||
docDefinitionMap.entrySet().removeIf(entry -> serviceId.equalsIgnoreCase(entry.getValue().getServiceId()));
|
docDefinitionMap.entrySet().removeIf(entry -> serviceId.equalsIgnoreCase(entry.getValue().getServiceId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onApplicationEvent(HeartbeatEvent heartbeatEvent) {
|
|
||||||
docDiscovery.refresh(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -2,12 +2,14 @@ server.port=8083
|
|||||||
spring.application.name=website-server
|
spring.application.name=website-server
|
||||||
|
|
||||||
# ------- 需要改的配置 -------
|
# ------- 需要改的配置 -------
|
||||||
# nacos地址
|
eureka.url=http://localhost:1111/eureka/
|
||||||
nacos.url=127.0.0.1:8848
|
|
||||||
# ------- 需要改的配置end -------
|
# ------- 需要改的配置end -------
|
||||||
|
|
||||||
# nacos cloud配置
|
# eureka注册中心
|
||||||
spring.cloud.nacos.discovery.server-addr=${nacos.url}
|
eureka.client.serviceUrl.defaultZone=${eureka.url}
|
||||||
|
|
||||||
|
## nacos cloud配置
|
||||||
|
#spring.cloud.nacos.discovery.server-addr=${nacos.url}
|
||||||
|
|
||||||
# 测试环境
|
# 测试环境
|
||||||
api.url-test=http://open-test.yourdomain.com
|
api.url-test=http://open-test.yourdomain.com
|
||||||
|
Reference in New Issue
Block a user