适配eureka

This commit is contained in:
tanghc
2019-09-29 19:02:35 +08:00
parent 3e506b27ef
commit c0fb37255a
20 changed files with 501 additions and 405 deletions

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<version>2.1.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.gitee.sop</groupId>
@@ -16,13 +16,31 @@
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.RELEASE</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<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-alibaba-nacos-discovery</artifactId>
<version>0.9.0.RELEASE</version>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>

View File

@@ -3,13 +3,23 @@ package com.gitee.sop.websiteserver.config;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
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.cloud.client.discovery.event.HeartbeatEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
/**
* @author tanghc
@@ -20,6 +30,9 @@ public class WebsiteConfig implements ApplicationRunner {
@Autowired
DocManager docManager;
@Autowired
private RegistryListener registryListener;
/**
* 使用fastjson代替jackson
* @return
@@ -35,6 +48,37 @@ public class WebsiteConfig implements ApplicationRunner {
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启动完毕执行
*/

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -5,9 +5,6 @@ import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.gitee.sop.websiteserver.bean.DocInfo;
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.util.DigestUtils;
@@ -23,7 +20,7 @@ import java.util.function.Consumer;
*/
@Service
@Slf4j
public class DocManagerImpl implements DocManager, ApplicationListener<HeartbeatEvent> {
public class DocManagerImpl implements DocManager {
// key:title
private Map<String, DocInfo> docDefinitionMap = new HashMap<>();
@@ -37,9 +34,6 @@ public class DocManagerImpl implements DocManager, ApplicationListener<Heartbeat
private DocParser easyopenDocParser = new EasyopenDocParser();
@Autowired
private DocDiscovery docDiscovery;
@Override
public void addDocInfo(String serviceId, String docInfoJson, Consumer<DocInfo> callback) {
String newMd5 = DigestUtils.md5DigestAsHex(docInfoJson.getBytes(StandardCharsets.UTF_8));
@@ -80,10 +74,4 @@ public class DocManagerImpl implements DocManager, ApplicationListener<Heartbeat
serviceIdMd5Map.remove(serviceId);
docDefinitionMap.entrySet().removeIf(entry -> serviceId.equalsIgnoreCase(entry.getValue().getServiceId()));
}
@Override
public void onApplicationEvent(HeartbeatEvent heartbeatEvent) {
docDiscovery.refresh(this);
}
}

View File

@@ -2,12 +2,14 @@ server.port=8083
spring.application.name=website-server
# ------- 需要改的配置 -------
# nacos地址
nacos.url=127.0.0.1:8848
eureka.url=http://localhost:1111/eureka/
# ------- 需要改的配置end -------
# nacos cloud配置
spring.cloud.nacos.discovery.server-addr=${nacos.url}
# eureka注册中心
eureka.client.serviceUrl.defaultZone=${eureka.url}
## nacos cloud配置
#spring.cloud.nacos.discovery.server-addr=${nacos.url}
# 测试环境
api.url-test=http://open-test.yourdomain.com