diff --git a/pom.xml b/pom.xml
index 39f97ab43a..4a49c16953 100644
--- a/pom.xml
+++ b/pom.xml
@@ -222,6 +222,13 @@
${easyexcel.verion}
+
+
+ org.springframework.cloud
+ spring-cloud-commons
+ 3.0.0
+
+
diff --git a/src/main/java/cn/iocoder/dashboard/framework/register/HeartBeatAuoConfiguration.java b/src/main/java/cn/iocoder/dashboard/framework/register/HeartBeatAuoConfiguration.java
new file mode 100644
index 0000000000..78a3d02b4c
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/framework/register/HeartBeatAuoConfiguration.java
@@ -0,0 +1,20 @@
+package cn.iocoder.dashboard.framework.register;
+
+import cn.iocoder.dashboard.framework.register.registry.RedisRegistration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author Hccake 2021/1/29
+ * @version 1.0
+ */
+@Configuration(proxyBeanMethods = false)
+public class HeartBeatAuoConfiguration {
+
+
+ @Bean
+ public RedisHeartbeatDetection redisHeartbeatDetection(RedisRegisterManagement redisRegisterManagement, RedisRegistration redisRegistration){
+ return new RedisHeartbeatDetection(redisRegisterManagement, redisRegistration);
+ }
+
+}
diff --git a/src/main/java/cn/iocoder/dashboard/framework/register/RedisHeartbeatDetection.java b/src/main/java/cn/iocoder/dashboard/framework/register/RedisHeartbeatDetection.java
new file mode 100644
index 0000000000..9878156d00
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/framework/register/RedisHeartbeatDetection.java
@@ -0,0 +1,51 @@
+package cn.iocoder.dashboard.framework.register;
+
+import cn.iocoder.dashboard.framework.register.registry.RedisRegistration;
+import cn.iocoder.dashboard.framework.register.util.NamedThreadFactory;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 心跳检测线程
+ * TODO 目前是在每一个客户端都维护一个线程,可剥离出来放在一个中心去处理
+ * @author Hccake 2021/1/29
+ * @version 1.0
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class RedisHeartbeatDetection {
+ private final RedisRegisterManagement redisRegisterManagement;
+ private final RedisRegistration redisRegistration;
+
+ private final ScheduledExecutorService detectionExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("RedisRegistryExpireTimer", true));
+
+ private ScheduledFuture> detectionFuture;
+
+ @PostConstruct
+ public void init(){
+ // 心跳检测线程
+ int detectionInterval = 60 * 1000;
+ this.detectionFuture = detectionExecutor.scheduleWithFixedDelay(() -> {
+ try {
+ redisRegisterManagement.removeExpiredInstances(redisRegistration.getServiceId()); // Extend the expiration time
+ } catch (Throwable t) { // Defensive fault tolerance
+ log.error("Unexpected exception occur at defer expire time, cause: " + t.getMessage(), t);
+ }
+ }, detectionInterval, detectionInterval, TimeUnit.MILLISECONDS);
+ }
+
+
+ @PreDestroy
+ public void destroy(){
+ detectionFuture.cancel(true);
+ detectionExecutor.shutdown();
+ }
+
+}
diff --git a/src/main/java/cn/iocoder/dashboard/framework/register/RedisRegisterAutoConfiguration.java b/src/main/java/cn/iocoder/dashboard/framework/register/RedisRegisterAutoConfiguration.java
new file mode 100644
index 0000000000..57e3ec560f
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/framework/register/RedisRegisterAutoConfiguration.java
@@ -0,0 +1,23 @@
+package cn.iocoder.dashboard.framework.register;
+
+import cn.iocoder.dashboard.framework.register.registry.RedisDiscoveryProperties;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.core.StringRedisTemplate;
+
+/**
+ * @author Hccake 2021/1/27
+ * @version 1.0
+ */
+@RequiredArgsConstructor
+@Configuration(proxyBeanMethods = false)
+public class RedisRegisterAutoConfiguration {
+ private final RedisDiscoveryProperties redisDiscoveryProperties;
+
+ @Bean
+ public RedisRegisterManagement redisRegisterManagement(ObjectMapper objectMapper, StringRedisTemplate stringRedisTemplate){
+ return new RedisRegisterManagement(objectMapper, stringRedisTemplate, redisDiscoveryProperties);
+ }
+}
diff --git a/src/main/java/cn/iocoder/dashboard/framework/register/RedisRegisterManagement.java b/src/main/java/cn/iocoder/dashboard/framework/register/RedisRegisterManagement.java
new file mode 100644
index 0000000000..c6ac492c3b
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/framework/register/RedisRegisterManagement.java
@@ -0,0 +1,160 @@
+package cn.iocoder.dashboard.framework.register;
+
+import cn.iocoder.dashboard.framework.register.registry.RedisDiscoveryProperties;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.RequiredArgsConstructor;
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.cloud.client.serviceregistry.Registration;
+import org.springframework.data.redis.core.HashOperations;
+import org.springframework.data.redis.core.SetOperations;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.data.redis.core.ZSetOperations;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+
+import java.util.*;
+
+/**
+ * redis注册中心的数据操作管理
+ * TODO 原子操作,移除过期实例同时移除无实例的服务
+ * @author Hccake 2021/1/26
+ * @version 1.0
+ */
+@RequiredArgsConstructor
+public class RedisRegisterManagement {
+ private final ObjectMapper objectMapper;
+ private final StringRedisTemplate stringRedisTemplate;
+ private final RedisDiscoveryProperties redisDiscoveryProperties;
+
+ /**
+ * 服务列表,SET 类型,member 是 serviceId
+ */
+ private final static String SERVICES_KEY = "redis-register-services";
+
+ /**
+ * 服务实例列表,HASH类型,field 是 instanceId, value 是实例信息
+ */
+ private final static String SERVICES_INSTANCE_KEY_PREFIX = "redis-register-service-instance:";
+
+ private static String getInstancesKey(String serviceId) {
+ return SERVICES_INSTANCE_KEY_PREFIX + serviceId;
+ }
+
+ /**
+ * 服务实例心跳维护,ZSET 类型,member instanceId, value 是过期时间
+ */
+ private final static String SERVICES_INSTANCE_EXPIRES_KEY_PREFIX = "redis-register-service-instance-expires:";
+
+ private static String getInstanceExpiresKey(String serviceId) {
+ return SERVICES_INSTANCE_EXPIRES_KEY_PREFIX + serviceId;
+ }
+
+
+ /**
+ * 添加一个服务实例
+ *
+ * @param redisServiceInstance redis服务实例对象
+ * @throws JsonProcessingException json 序列化异常
+ */
+ public void addServiceInstance(RedisServiceInstance redisServiceInstance) throws JsonProcessingException {
+ String serviceId = redisServiceInstance.getServiceId();
+ String instanceId = redisServiceInstance.getInstanceId();
+
+ // 服务列表
+ SetOperations opsForSet = stringRedisTemplate.opsForSet();
+ opsForSet.add(SERVICES_KEY, serviceId);
+
+ // 服务实例
+ HashOperations opsForHash = stringRedisTemplate.opsForHash();
+ String value = new ObjectMapper().writeValueAsString(redisServiceInstance);
+ opsForHash.put(getInstancesKey(serviceId), instanceId, value);
+
+ // 服务实例的过期时间
+ ZSetOperations opsForZSet = stringRedisTemplate.opsForZSet();
+ opsForZSet.add(getInstanceExpiresKey(serviceId), instanceId, System.currentTimeMillis() + redisDiscoveryProperties.getHeartbeatInterval());
+
+ }
+
+
+ /**
+ * 移除一个服务实例
+ *
+ * @param registration 注册数据
+ */
+ public void removeServiceInstance(Registration registration) {
+ String serviceId = registration.getServiceId();
+ String instanceId = registration.getInstanceId();
+
+ // 服务实例
+ HashOperations opsForHash = stringRedisTemplate.opsForHash();
+ opsForHash.delete(getInstancesKey(serviceId), instanceId);
+
+ // 服务实例的过期时间
+ ZSetOperations opsForZSet = stringRedisTemplate.opsForZSet();
+ opsForZSet.remove(getInstanceExpiresKey(serviceId), instanceId);
+ }
+
+ /**
+ * 根据 服务id 获取其实例列表
+ *
+ * @param serviceId 服务id
+ * @return List 服务实例列表
+ * @throws JsonProcessingException json 序列化异常
+ */
+ public List getInstances(String serviceId) throws JsonProcessingException {
+ List serviceInstances = new ArrayList<>();
+ HashOperations opsForHash = stringRedisTemplate.opsForHash();
+ Map entries = opsForHash.entries(getInstancesKey(serviceId));
+ for (Map.Entry entry : entries.entrySet()) {
+ RedisServiceInstance redisServiceInstance = objectMapper.readValue(entry.getValue(), RedisServiceInstance.class);
+ serviceInstances.add(redisServiceInstance);
+ }
+ return serviceInstances;
+ }
+
+
+ /**
+ * 获取服务列表
+ *
+ * @return List 服务列表
+ */
+ public List getServices() {
+ // 服务列表
+ SetOperations opsForSet = stringRedisTemplate.opsForSet();
+ Set members = opsForSet.members(SERVICES_KEY);
+ return members == null ? new ArrayList<>() : new ArrayList<>(members);
+ }
+
+ /**
+ * 延迟实例过期时间
+ * @param serviceId 服务ID
+ * @param instanceId 实例ID
+ */
+ public void deferInstanceExpired(String serviceId, String instanceId) {
+ // 服务列表更新,因为可能被别的线程移除了
+ SetOperations opsForSet = stringRedisTemplate.opsForSet();
+ opsForSet.add(SERVICES_KEY, serviceId);
+ // 服务实例的过期时间
+ ZSetOperations opsForZSet = stringRedisTemplate.opsForZSet();
+ opsForZSet.add(getInstanceExpiresKey(serviceId), instanceId, System.currentTimeMillis() + redisDiscoveryProperties.getHeartbeatInterval());
+ }
+
+
+ private final static String REMOVE_EXPIRED_INSTANCES_SCRIPT = "local instanceIds = redis.call('zrangeByScore', KEYS[1], 0, ARGV[1])\n" +
+ "if next(instanceIds) ~= nil then\n" +
+ " redis.call('zremrangeByScore', KEYS[1], 0, ARGV[1])\n" +
+ " redis.call('hdel', KEYS[2], unpack(instanceIds))\n" +
+ "end";
+
+ /**
+ * 移除过期实例
+ */
+ public void removeExpiredInstances(String serviceId) {
+ DefaultRedisScript redisScript = new DefaultRedisScript<>(REMOVE_EXPIRED_INSTANCES_SCRIPT);
+ stringRedisTemplate.execute(redisScript,
+ Arrays.asList(getInstanceExpiresKey(serviceId), getInstancesKey(serviceId)),
+ String.valueOf(System.currentTimeMillis())
+ );
+ }
+
+}
diff --git a/src/main/java/cn/iocoder/dashboard/framework/register/RedisServiceInstance.java b/src/main/java/cn/iocoder/dashboard/framework/register/RedisServiceInstance.java
new file mode 100644
index 0000000000..849cc2f577
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/framework/register/RedisServiceInstance.java
@@ -0,0 +1,88 @@
+package cn.iocoder.dashboard.framework.register;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.Setter;
+import org.springframework.cloud.client.DefaultServiceInstance;
+import org.springframework.cloud.client.ServiceInstance;
+
+import java.net.URI;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * @author Hccake 2021/1/26
+ * @version 1.0
+ */
+@Setter
+@JsonIgnoreProperties({"scheme", "uri"})
+public class RedisServiceInstance implements ServiceInstance {
+
+ private String serviceId;
+
+ private String instanceId;
+
+ private String host;
+
+ private int port;
+
+ private boolean secure;
+
+ private Map metadata = new LinkedHashMap<>();
+
+ public RedisServiceInstance(){
+ }
+
+ @Override
+ public String getServiceId() {
+ return serviceId;
+ }
+
+ @Override
+ public String getInstanceId() {
+ return this.instanceId;
+ }
+
+ public void setInstanceId(String instanceId) {
+ this.instanceId = instanceId;
+ }
+
+ @Override
+ public String getHost() {
+ return host;
+ }
+
+ @Override
+ public int getPort() {
+ return port;
+ }
+
+ @Override
+ public boolean isSecure() {
+ return secure;
+ }
+
+ @Override
+ public URI getUri() {
+ return DefaultServiceInstance.getUri(this);
+ }
+
+ @Override
+ public Map getMetadata() {
+ return metadata;
+ }
+
+ @Override
+ public String getScheme() {
+ return this.getUri().getScheme();
+ }
+
+ public void setUri(URI uri) {
+ this.host = uri.getHost();
+ this.port = uri.getPort();
+ String scheme = uri.getScheme();
+ if ("https".equals(scheme)) {
+ this.secure = true;
+ }
+ }
+
+}
diff --git a/src/main/java/cn/iocoder/dashboard/framework/register/discovery/RedisDiscoveryAutoConfiguration.java b/src/main/java/cn/iocoder/dashboard/framework/register/discovery/RedisDiscoveryAutoConfiguration.java
new file mode 100644
index 0000000000..5c152ac0a3
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/framework/register/discovery/RedisDiscoveryAutoConfiguration.java
@@ -0,0 +1,19 @@
+package cn.iocoder.dashboard.framework.register.discovery;
+
+import cn.iocoder.dashboard.framework.register.RedisRegisterManagement;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 服务发现自动配置类
+ * @author Hccake 2021/1/26
+ * @version 1.0
+ */
+@Configuration(proxyBeanMethods = false)
+public class RedisDiscoveryAutoConfiguration {
+
+ @Bean
+ public RedisDiscoveryClient redisDiscoveryClient(RedisRegisterManagement redisRegisterManagement){
+ return new RedisDiscoveryClient(redisRegisterManagement);
+ }
+}
diff --git a/src/main/java/cn/iocoder/dashboard/framework/register/discovery/RedisDiscoveryClient.java b/src/main/java/cn/iocoder/dashboard/framework/register/discovery/RedisDiscoveryClient.java
new file mode 100644
index 0000000000..90c0ea9546
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/framework/register/discovery/RedisDiscoveryClient.java
@@ -0,0 +1,36 @@
+package cn.iocoder.dashboard.framework.register.discovery;
+
+import cn.iocoder.dashboard.framework.register.RedisRegisterManagement;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.cloud.client.discovery.DiscoveryClient;
+
+import java.util.List;
+
+/**
+ * 服务发现客户端
+ * @author Hccake 2021/1/26
+ * @version 1.0
+ */
+@RequiredArgsConstructor
+public class RedisDiscoveryClient implements DiscoveryClient {
+ private final RedisRegisterManagement redisRegisterManagement;
+
+ @Override
+ public String description() {
+ return "Redis Discovery Client";
+ }
+
+ @SneakyThrows
+ @Override
+ public List getInstances(String serviceId) {
+ return redisRegisterManagement.getInstances(serviceId);
+ }
+
+ @Override
+ public List getServices() {
+ return redisRegisterManagement.getServices();
+
+ }
+}
diff --git a/src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisAutoServiceRegistration.java b/src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisAutoServiceRegistration.java
new file mode 100644
index 0000000000..29d007224f
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisAutoServiceRegistration.java
@@ -0,0 +1,46 @@
+package cn.iocoder.dashboard.framework.register.registry;
+
+import org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration;
+import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties;
+import org.springframework.cloud.client.serviceregistry.Registration;
+import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
+
+/**
+ * @author Hccake 2021/1/26
+ * @version 1.0
+ */
+public class RedisAutoServiceRegistration extends AbstractAutoServiceRegistration {
+
+ private final RedisRegistration redisRegistration;
+
+ protected RedisAutoServiceRegistration(ServiceRegistry serviceRegistry, AutoServiceRegistrationProperties properties, RedisRegistration registration) {
+ super(serviceRegistry, properties);
+ this.redisRegistration = registration;
+ }
+
+ /**
+ * @return The object used to configure the registration.
+ */
+ @Override
+ protected Object getConfiguration() {
+ return null;
+ }
+
+ /**
+ * @return True, if this is enabled.
+ */
+ @Override
+ protected boolean isEnabled() {
+ return true;
+ }
+
+ @Override
+ protected RedisRegistration getRegistration() {
+ return redisRegistration;
+ }
+
+ @Override
+ protected RedisRegistration getManagementRegistration() {
+ return null;
+ }
+}
diff --git a/src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisDiscoveryProperties.java b/src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisDiscoveryProperties.java
new file mode 100644
index 0000000000..066c053b8a
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisDiscoveryProperties.java
@@ -0,0 +1,19 @@
+package cn.iocoder.dashboard.framework.register.registry;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * @author Hccake 2021/1/26
+ * @version 1.0
+ */
+@Data
+@ConfigurationProperties("spring.cloud.redis.discovery")
+public class RedisDiscoveryProperties {
+
+ /**
+ * 心跳间隔时间
+ */
+ private int heartbeatInterval = 60 * 1000;
+
+}
diff --git a/src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisRegistration.java b/src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisRegistration.java
new file mode 100644
index 0000000000..72f46e4e98
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisRegistration.java
@@ -0,0 +1,180 @@
+package cn.iocoder.dashboard.framework.register.registry;
+
+import cn.iocoder.dashboard.framework.register.RedisRegisterManagement;
+import cn.iocoder.dashboard.framework.register.util.InetUtil;
+import cn.iocoder.dashboard.framework.register.util.NamedThreadFactory;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.cloud.client.discovery.ManagementServerPortUtils;
+import org.springframework.cloud.client.serviceregistry.Registration;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.core.env.Environment;
+import org.springframework.util.StringUtils;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.net.InetAddress;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author Hccake 2021/1/26
+ * @version 1.0
+ */
+@Slf4j
+@Setter
+public class RedisRegistration implements Registration, ServiceInstance, ApplicationContextAware {
+
+ private static final String MANAGEMENT_SCHEME = "management.scheme";
+
+ private static final String MANAGEMENT_ADDRESS = "management.address";
+
+ private static final String MANAGEMENT_PORT = "management.port";
+
+ private static final String MANAGEMENT_CONTEXT_PATH = "management.context-path";
+
+ private static final String KEY_HEALTH_PATH = "health.path";
+
+ private final RedisRegisterManagement redisRegisterManagement;
+
+ private final RedisDiscoveryProperties redisDiscoveryProperties;
+
+ private ApplicationContext context;
+
+ @Value("${spring.application.name}")
+ private String applicationName;
+
+ @Value("${server.port: 8080}")
+ private Integer port;
+
+
+ private String host;
+
+ private Map metadata = new HashMap<>();
+
+ private final ScheduledExecutorService expireExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("RedisRegistryExpireTimer", true));
+
+ private ScheduledFuture> expireFuture;
+
+
+ public RedisRegistration(RedisRegisterManagement redisRegisterManagement, RedisDiscoveryProperties redisDiscoveryProperties) {
+ this.redisRegisterManagement = redisRegisterManagement;
+ this.redisDiscoveryProperties = redisDiscoveryProperties;
+ }
+
+ @PostConstruct
+ public void init() {
+
+ Environment env = context.getEnvironment();
+
+ Integer managementPort = ManagementServerPortUtils.getPort(context);
+ if (null != managementPort) {
+ metadata.put(MANAGEMENT_PORT, managementPort.toString());
+ String contextPath = env
+ .getProperty("management.server.servlet.context-path");
+ String address = env.getProperty("management.server.address");
+ if (!StringUtils.hasText(contextPath)) {
+ metadata.put(MANAGEMENT_CONTEXT_PATH, contextPath);
+ }
+ if (!StringUtils.hasText(address)) {
+ metadata.put(MANAGEMENT_ADDRESS, address);
+ }
+ }
+
+ // 过期时间延长线程
+ int heartbeatInterval = redisDiscoveryProperties.getHeartbeatInterval();
+ this.expireFuture = expireExecutor.scheduleWithFixedDelay(() -> {
+ try {
+ redisRegisterManagement.deferInstanceExpired(this.getServiceId(), this.getInstanceId()); // Extend the expiration time
+ } catch (Throwable t) { // Defensive fault tolerance
+ log.error("Unexpected exception occur at defer expire time, cause: " + t.getMessage(), t);
+ }
+ }, heartbeatInterval / 2, heartbeatInterval / 2, TimeUnit.MILLISECONDS);
+ }
+
+
+ @PreDestroy
+ public void destroy(){
+ expireFuture.cancel(true);
+ expireExecutor.shutdown();
+ }
+
+ /**
+ * @return The service ID as registered.
+ */
+ @Override
+ public String getServiceId() {
+ return applicationName;
+ }
+
+ @Override
+ public String getInstanceId() {
+ return this.host + '-' + this.applicationName + '-' + this.port;
+ }
+
+ /**
+ * @return The hostname of the registered service instance.
+ */
+ @Override
+ public String getHost() {
+ try {
+ if (this.host == null) {
+ InetAddress address = InetUtil.getLocalHostLANAddress();
+ if (address != null){
+ this.host = address.getHostAddress();
+ }
+ }
+ return host;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return host;
+ }
+
+ /**
+ * @return The port of the registered service instance.
+ */
+ @Override
+ public int getPort() {
+ return port;
+ }
+
+ /**
+ * @return Whether the port of the registered service instance uses HTTPS.
+ */
+ @Override
+ public boolean isSecure() {
+ return false;
+ }
+
+ /**
+ * @return The service URI address.
+ */
+ @Override
+ public URI getUri() {
+ return null;
+ }
+
+ /**
+ * @return The key / value pair metadata associated with the service instance.
+ */
+ @Override
+ public Map getMetadata() {
+ return metadata;
+ }
+
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ this.context = applicationContext;
+ }
+}
diff --git a/src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisServiceRegistry.java b/src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisServiceRegistry.java
new file mode 100644
index 0000000000..2c3f57dec9
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisServiceRegistry.java
@@ -0,0 +1,81 @@
+package cn.iocoder.dashboard.framework.register.registry;
+
+import cn.iocoder.dashboard.framework.register.RedisRegisterManagement;
+import cn.iocoder.dashboard.framework.register.RedisServiceInstance;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cloud.client.serviceregistry.Registration;
+import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
+import org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint;
+
+/**
+ * @author Hccake 2021/1/26
+ * @version 1.0
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class RedisServiceRegistry implements ServiceRegistry {
+ private final RedisRegisterManagement redisRegisterManagement;
+
+ /**
+ * Registers the registration. A registration typically has information about an
+ * instance, such as its hostname and port.
+ *
+ * @param registration registration meta data
+ */
+ @SneakyThrows
+ @Override
+ public void register(Registration registration) {
+ RedisServiceInstance redisServiceInstance = new RedisServiceInstance();
+ String serviceId = registration.getServiceId();
+ redisServiceInstance.setServiceId(serviceId);
+ redisServiceInstance.setHost(registration.getHost());
+ redisServiceInstance.setPort(registration.getPort());
+ redisServiceInstance.setMetadata(registration.getMetadata());
+ redisServiceInstance.setInstanceId(registration.getInstanceId());
+ redisRegisterManagement.addServiceInstance(redisServiceInstance);
+ }
+
+ /**
+ * Deregisters the registration.
+ *
+ * @param registration registration meta data
+ */
+ @Override
+ public void deregister(Registration registration) {
+ redisRegisterManagement.removeServiceInstance(registration);
+ }
+
+ /**
+ * Closes the ServiceRegistry. This is a lifecycle method.
+ */
+ @Override
+ public void close() {
+ }
+
+ /**
+ * Sets the status of the registration. The status values are determined by the
+ * individual implementations.
+ *
+ * @param registration The registration to update.
+ * @param status The status to set.
+ * @see ServiceRegistryEndpoint
+ */
+ @Override
+ public void setStatus(Registration registration, String status) {
+
+ }
+
+ /**
+ * Gets the status of a particular registration.
+ *
+ * @param registration The registration to query.
+ * @return The status of the registration.
+ * @see ServiceRegistryEndpoint
+ */
+ @Override
+ public T getStatus(Registration registration) {
+ return null;
+ }
+}
diff --git a/src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisServiceRegistryAutoConfiguration.java b/src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisServiceRegistryAutoConfiguration.java
new file mode 100644
index 0000000000..ae2fb1b05e
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/framework/register/registry/RedisServiceRegistryAutoConfiguration.java
@@ -0,0 +1,47 @@
+package cn.iocoder.dashboard.framework.register.registry;
+
+import cn.iocoder.dashboard.framework.register.RedisRegisterManagement;
+import lombok.RequiredArgsConstructor;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration;
+import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration;
+import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author Hccake 2021/1/26
+ * @version 1.0
+ */
+@EnableConfigurationProperties(RedisDiscoveryProperties.class)
+@Configuration(proxyBeanMethods = false)
+@AutoConfigureAfter({ AutoServiceRegistrationConfiguration.class,
+ AutoServiceRegistrationAutoConfiguration.class })
+@RequiredArgsConstructor
+public class RedisServiceRegistryAutoConfiguration {
+ private final RedisRegisterManagement redisRegisterManagement;
+ private final RedisDiscoveryProperties redisDiscoveryProperties;
+
+ @Bean
+ public RedisServiceRegistry redisServiceRegistry() {
+ return new RedisServiceRegistry(redisRegisterManagement);
+ }
+
+ @Bean
+ public RedisRegistration redisRegistration() {
+ return new RedisRegistration(redisRegisterManagement, redisDiscoveryProperties);
+ }
+
+ @Bean
+ public RedisAutoServiceRegistration registration(
+ RedisServiceRegistry redisServiceRegistry,
+ AutoServiceRegistrationProperties autoServiceRegistrationProperties,
+ RedisRegistration redisRegistration) {
+ return new RedisAutoServiceRegistration(redisServiceRegistry,
+ autoServiceRegistrationProperties, redisRegistration);
+ }
+
+
+
+}
diff --git a/src/main/java/cn/iocoder/dashboard/framework/register/util/InetUtil.java b/src/main/java/cn/iocoder/dashboard/framework/register/util/InetUtil.java
new file mode 100644
index 0000000000..5ddf2f9923
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/framework/register/util/InetUtil.java
@@ -0,0 +1,50 @@
+package cn.iocoder.dashboard.framework.register.util;
+
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.util.Enumeration;
+
+/**
+ * @author Hccake 2021/1/26
+ * @version 1.0
+ */
+public class InetUtil {
+
+ public static InetAddress getLocalHostLANAddress() throws Exception {
+ try {
+ InetAddress candidateAddress = null;
+
+ Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces();
+ // 遍历所有的网络接口
+ while (networkInterfaces.hasMoreElements()){
+ NetworkInterface networkInterface = networkInterfaces.nextElement();
+ // 在所有的接口下再遍历IP
+ Enumeration inetAddresses = networkInterface.getInetAddresses();
+ while (inetAddresses.hasMoreElements()) {
+ InetAddress inetAddr = inetAddresses.nextElement();
+ // 排除loopback类型地址
+ if (inetAddr.isLoopbackAddress()) {
+ continue;
+ }
+ if (inetAddr.isSiteLocalAddress()) {
+ // 如果是site-local地址,就是它了
+ return inetAddr;
+ } else if (candidateAddress == null) {
+ // site-local类型的地址未被发现,先记录候选地址
+ candidateAddress = inetAddr;
+ }
+ }
+
+ }
+
+ if (candidateAddress != null) {
+ return candidateAddress;
+ }
+ // 如果没有发现 non-loopback地址.只能用最次选的方案
+ return InetAddress.getLocalHost();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/cn/iocoder/dashboard/framework/register/util/NamedThreadFactory.java b/src/main/java/cn/iocoder/dashboard/framework/register/util/NamedThreadFactory.java
new file mode 100644
index 0000000000..af62fbf5d1
--- /dev/null
+++ b/src/main/java/cn/iocoder/dashboard/framework/register/util/NamedThreadFactory.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package cn.iocoder.dashboard.framework.register.util;
+
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Copy form Dubbo
+ * InternalThreadFactory.
+ */
+public class NamedThreadFactory implements ThreadFactory {
+
+ protected static final AtomicInteger POOL_SEQ = new AtomicInteger(1);
+
+ protected final AtomicInteger mThreadNum = new AtomicInteger(1);
+
+ protected final String mPrefix;
+
+ protected final boolean mDaemon;
+
+ protected final ThreadGroup mGroup;
+
+ public NamedThreadFactory() {
+ this("pool-" + POOL_SEQ.getAndIncrement(), false);
+ }
+
+ public NamedThreadFactory(String prefix) {
+ this(prefix, false);
+ }
+
+ public NamedThreadFactory(String prefix, boolean daemon) {
+ mPrefix = prefix + "-thread-";
+ mDaemon = daemon;
+ SecurityManager s = System.getSecurityManager();
+ mGroup = (s == null) ? Thread.currentThread().getThreadGroup() : s.getThreadGroup();
+ }
+
+ @Override
+ public Thread newThread(Runnable runnable) {
+ String name = mPrefix + mThreadNum.getAndIncrement();
+ Thread ret = new Thread(mGroup, runnable, name, 0);
+ ret.setDaemon(mDaemon);
+ return ret;
+ }
+
+ public ThreadGroup getThreadGroup() {
+ return mGroup;
+ }
+}