From fe8665390379fd070fa5a1a3520f3235b8e0ac25 Mon Sep 17 00:00:00 2001
From: SerLiunx-ctrl <17689543@qq.com>
Date: Mon, 25 Nov 2024 15:37:07 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E8=A7=A3=E8=80=A6ip=E6=9B=B4=E6=96=B0?=
=?UTF-8?q?=E6=BA=90.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../ddns/config/ConfigurationKeys.java | 5 +
.../ddns/constant/IpProviderType.java | 32 +++++
.../com/serliunx/ddns/support/Assert.java | 12 +-
.../ddns/support/SystemInitializer.java | 38 +++---
.../support/ipprovider/AbstractProvider.java | 73 ++++++++++++
.../support/ipprovider/IpApiProvider.java | 24 ++++
.../ddns/support/ipprovider/Provider.java | 46 +++++++
.../support/ipprovider/ScheduledProvider.java | 112 ++++++++++++++++++
.../ddns/support/okhttp/HttpClient.java | 11 +-
src/main/resources/settings.properties | 3 +-
.../ddns/test/support/ProviderTest.java | 31 +++++
11 files changed, 368 insertions(+), 19 deletions(-)
create mode 100644 src/main/java/com/serliunx/ddns/constant/IpProviderType.java
create mode 100644 src/main/java/com/serliunx/ddns/support/ipprovider/AbstractProvider.java
create mode 100644 src/main/java/com/serliunx/ddns/support/ipprovider/IpApiProvider.java
create mode 100644 src/main/java/com/serliunx/ddns/support/ipprovider/Provider.java
create mode 100644 src/main/java/com/serliunx/ddns/support/ipprovider/ScheduledProvider.java
create mode 100644 src/test/java/com/serliunx/ddns/test/support/ProviderTest.java
diff --git a/src/main/java/com/serliunx/ddns/config/ConfigurationKeys.java b/src/main/java/com/serliunx/ddns/config/ConfigurationKeys.java
index a9171d2..515384a 100644
--- a/src/main/java/com/serliunx/ddns/config/ConfigurationKeys.java
+++ b/src/main/java/com/serliunx/ddns/config/ConfigurationKeys.java
@@ -35,4 +35,9 @@ public final class ConfigurationKeys {
* http请求超时时间(秒)
*/
public static final String KEY_HTTP_OVERTIME = "system.http.overtime";
+
+ /**
+ * ip地址提供器类型
+ */
+ public static final String KEY_IP_PROVIDER_TYPE = "system.ip.provider.type";
}
diff --git a/src/main/java/com/serliunx/ddns/constant/IpProviderType.java b/src/main/java/com/serliunx/ddns/constant/IpProviderType.java
new file mode 100644
index 0000000..f039f41
--- /dev/null
+++ b/src/main/java/com/serliunx/ddns/constant/IpProviderType.java
@@ -0,0 +1,32 @@
+package com.serliunx.ddns.constant;
+
+import com.serliunx.ddns.support.ipprovider.IpApiProvider;
+import com.serliunx.ddns.support.ipprovider.Provider;
+
+/**
+ * ip供应器类型
+ *
+ * @author SerLiunx
+ * @version 1.0.3
+ * @since 2024/11/25
+ */
+public enum IpProviderType {
+
+ /**
+ * ip数据提供商 ip-api
+ *
国外的数据, 国内访问不稳定.
+ */
+ IP_API(new IpApiProvider()),
+
+ ;
+
+ private final Provider provider;
+
+ IpProviderType(Provider provider) {
+ this.provider = provider;
+ }
+
+ public Provider getProvider() {
+ return provider;
+ }
+}
diff --git a/src/main/java/com/serliunx/ddns/support/Assert.java b/src/main/java/com/serliunx/ddns/support/Assert.java
index b049307..7e83da6 100644
--- a/src/main/java/com/serliunx/ddns/support/Assert.java
+++ b/src/main/java/com/serliunx/ddns/support/Assert.java
@@ -18,7 +18,7 @@ public final class Assert {
}
public static void notNull(Object object, String msg) {
- if(object == null) {
+ if (object == null) {
throw new NullPointerException(msg);
}
}
@@ -36,6 +36,12 @@ public final class Assert {
}
public static void isLargerThan(int source, int target) {
+ if (source <= target) {
+ throw new IllegalArgumentException(String.format("%s太小了, 它必须大于%s", source, target));
+ }
+ }
+
+ public static void isLargerThan(long source, long target) {
if(source <= target) {
throw new IllegalArgumentException(String.format("%s太小了, 它必须大于%s", source, target));
}
@@ -43,13 +49,13 @@ public final class Assert {
public static void notEmpty(Collection> collection) {
notNull(collection);
- if(collection.isEmpty())
+ if (collection.isEmpty())
throw new IllegalArgumentException("参数不能为空!");
}
public static void notEmpty(CharSequence charSequence) {
notNull(charSequence);
- if(charSequence.length() == 0)
+ if (charSequence.length() == 0)
throw new IllegalArgumentException("参数不能为空!");
}
}
diff --git a/src/main/java/com/serliunx/ddns/support/SystemInitializer.java b/src/main/java/com/serliunx/ddns/support/SystemInitializer.java
index 011bd4d..edfd3cd 100644
--- a/src/main/java/com/serliunx/ddns/support/SystemInitializer.java
+++ b/src/main/java/com/serliunx/ddns/support/SystemInitializer.java
@@ -1,11 +1,16 @@
package com.serliunx.ddns.support;
import com.serliunx.ddns.config.Configuration;
+import com.serliunx.ddns.config.ConfigurationKeys;
+import com.serliunx.ddns.constant.IpProviderType;
import com.serliunx.ddns.constant.SystemConstants;
import com.serliunx.ddns.core.Clearable;
import com.serliunx.ddns.core.Refreshable;
import com.serliunx.ddns.core.context.MultipleSourceInstanceContext;
import com.serliunx.ddns.core.instance.Instance;
+import com.serliunx.ddns.support.ipprovider.IpApiProvider;
+import com.serliunx.ddns.support.ipprovider.Provider;
+import com.serliunx.ddns.support.ipprovider.ScheduledProvider;
import com.serliunx.ddns.support.okhttp.IPAddressResponse;
import com.serliunx.ddns.support.okhttp.HttpClient;
import com.serliunx.ddns.thread.TaskThreadFactory;
@@ -46,6 +51,7 @@ public final class SystemInitializer implements Refreshable, Clearable {
private ScheduledThreadPoolExecutor scheduledThreadPoolExecutor;
private Set instances;
private final Map> runningInstances = new HashMap<>(64);
+ private ScheduledProvider scheduledProvider;
SystemInitializer(Configuration configuration, MultipleSourceInstanceContext instanceContext, boolean clearCache) {
this.configuration = configuration;
@@ -79,6 +85,9 @@ public final class SystemInitializer implements Refreshable, Clearable {
// 获取核心线程数量, 默认为CPU核心数量
int coreSize = configuration.getInteger(KEY_THREAD_POOL_CORE_SIZE, Runtime.getRuntime().availableProcessors());
+ // 初始化ip地址更新任务
+ initIpTask();
+
// 初始化线程池
initThreadPool(coreSize);
@@ -165,20 +174,6 @@ public final class SystemInitializer implements Refreshable, Clearable {
// 初始化一个线程保活
scheduledThreadPoolExecutor.submit(() -> {});
- // 提交定时获取网络IP的定时任务
- scheduledThreadPoolExecutor.scheduleAtFixedRate(() -> {
- InstanceContextHolder.setAdditional("ip-update");
- log.info("正在尝试获取本机最新的IP地址.");
- IPAddressResponse response = HttpClient.getIPAddress();
- String ip;
- if(response != null
- && (ip = response.getQuery()) != null) {
- NetworkContextHolder.setIpAddress(ip);
- log.info("本机最新公网IP地址 => {}", ip);
- }
- InstanceContextHolder.clearAdditional();
- }, 0, configuration.getLong(KEY_TASK_REFRESH_INTERVAL_IP, 300L), TimeUnit.SECONDS);
-
// 添加进程结束钩子函数
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
InstanceContextHolder.setAdditional("stopping");
@@ -189,6 +184,21 @@ public final class SystemInitializer implements Refreshable, Clearable {
}, "DDNS-ShutDownHook"));
}
+ private void initIpTask() {
+ scheduledProvider = new ScheduledProvider(getInternalProvider(),
+ configuration.getLong(KEY_TASK_REFRESH_INTERVAL_IP, 300L));
+
+ scheduledProvider.whenUpdate(ip -> {
+ NetworkContextHolder.setIpAddress(ip);
+ log.info("本机最新公网IP地址 => {}", ip);
+ });
+ }
+
+ private Provider getInternalProvider() {
+ return configuration.getEnum(IpProviderType.class, ConfigurationKeys.KEY_IP_PROVIDER_TYPE,
+ IpProviderType.IP_API).getProvider();
+ }
+
private void checkAndCloseSafely() {
if (scheduledThreadPoolExecutor == null)
return;
diff --git a/src/main/java/com/serliunx/ddns/support/ipprovider/AbstractProvider.java b/src/main/java/com/serliunx/ddns/support/ipprovider/AbstractProvider.java
new file mode 100644
index 0000000..22678c1
--- /dev/null
+++ b/src/main/java/com/serliunx/ddns/support/ipprovider/AbstractProvider.java
@@ -0,0 +1,73 @@
+package com.serliunx.ddns.support.ipprovider;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 抽象的ip提供器, 定义公共逻辑
+ *
+ * @author SerLiunx
+ * @version 1.0.3
+ * @since 2024/11/25
+ */
+public abstract class AbstractProvider implements Provider {
+
+ protected final Logger log = LoggerFactory.getLogger(getClass());
+
+ /**
+ * 运行期间总共的ip查询次数
+ */
+ protected long total;
+ /**
+ * 上次发生的变动的ip地址
+ */
+ protected String last = null;
+ /**
+ * 缓存的最新ip地址
+ * !!!该地址为上次获得最新地址, 不一定为当前最新的地址
+ */
+ protected String cache = null;
+
+ @Override
+ public long getCount() {
+ return total;
+ }
+
+ @Override
+ public String getLast() {
+ return last;
+ }
+
+ @Override
+ public void init() {
+ //do nothing.
+ }
+
+ @Override
+ public String get() {
+ String ipAddress = doGet();
+ if (ipAddress == null) {
+ log.error("ip地址获取失败!");
+ return null;
+ }
+ total++;
+
+ if (cache == null ||
+ !cache.equals(ipAddress)) {
+ last = cache;
+ cache = ipAddress;
+ }
+
+ return ipAddress;
+ }
+
+ @Override
+ public String getCache() {
+ return cache;
+ }
+
+ /**
+ * 获取具体的ip地址
+ */
+ protected abstract String doGet();
+}
diff --git a/src/main/java/com/serliunx/ddns/support/ipprovider/IpApiProvider.java b/src/main/java/com/serliunx/ddns/support/ipprovider/IpApiProvider.java
new file mode 100644
index 0000000..5d7e20b
--- /dev/null
+++ b/src/main/java/com/serliunx/ddns/support/ipprovider/IpApiProvider.java
@@ -0,0 +1,24 @@
+package com.serliunx.ddns.support.ipprovider;
+
+import com.serliunx.ddns.support.okhttp.HttpClient;
+import com.serliunx.ddns.support.okhttp.IPAddressResponse;
+
+/**
+ * ip数据提供商 ip-api
+ * 国外的数据, 国内访问不稳定.
+ *
+ * @author SerLiunx
+ * @version 1.0.3
+ * @since 2024/11/25
+ */
+public final class IpApiProvider extends AbstractProvider {
+
+ @Override
+ protected String doGet() {
+ IPAddressResponse response = HttpClient.getIPAddress();
+ if (response == null) {
+ return null;
+ }
+ return response.getQuery();
+ }
+}
diff --git a/src/main/java/com/serliunx/ddns/support/ipprovider/Provider.java b/src/main/java/com/serliunx/ddns/support/ipprovider/Provider.java
new file mode 100644
index 0000000..443a9ac
--- /dev/null
+++ b/src/main/java/com/serliunx/ddns/support/ipprovider/Provider.java
@@ -0,0 +1,46 @@
+package com.serliunx.ddns.support.ipprovider;
+
+/**
+ * ip供应器接口定义
+ *
+ * @author SerLiunx
+ * @version 1.0.3
+ * @since 2024/11/25
+ */
+public interface Provider {
+
+ /**
+ * 获取本次运行期间ip的查询次数
+ *
+ * @return 查询次数
+ */
+ long getCount();
+
+ /**
+ * 获取上次发生变动的ip地址
+ *
+ * @return 上次发生变动的ip地址
+ */
+ String getLast();
+
+ /**
+ * 获取最新的ip
+ *
+ * @return 最新的ip
+ */
+ String get();
+
+ /**
+ * 获取缓存的最新ip地址
+ * !!!该地址为上次获得最新地址, 不一定为当前最新的地址
+ *
+ * @return 缓存的最新ip地址
+ */
+ String getCache();
+
+ /**
+ * 初始化
+ */
+ void init();
+
+}
diff --git a/src/main/java/com/serliunx/ddns/support/ipprovider/ScheduledProvider.java b/src/main/java/com/serliunx/ddns/support/ipprovider/ScheduledProvider.java
new file mode 100644
index 0000000..c81b1bf
--- /dev/null
+++ b/src/main/java/com/serliunx/ddns/support/ipprovider/ScheduledProvider.java
@@ -0,0 +1,112 @@
+package com.serliunx.ddns.support.ipprovider;
+
+import com.serliunx.ddns.support.Assert;
+import com.serliunx.ddns.support.InstanceContextHolder;
+
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * 自动更新的ip供应器
+ * 异步更新ip, 获取到的ip地址不一定为最新可用的。
+ *
+ * @author SerLiunx
+ * @version 1.0.3
+ * @since 2024/11/25
+ */
+public class ScheduledProvider extends AbstractProvider {
+
+ private final Provider internalProvider;
+
+ /**
+ * 执行周期(秒)
+ */
+ private volatile long timePeriod;
+ /**
+ * 任务
+ */
+ private volatile ScheduledFuture> task;
+
+ /**
+ * 内置线程池
+ */
+ private ScheduledThreadPoolExecutor poolExecutor = null;
+ /**
+ * 处理器
+ */
+ private Consumer valueConsumer = null;
+
+ public ScheduledProvider(Provider internalProvider, long timePeriod) {
+ Assert.notNull(internalProvider);
+ Assert.isLargerThan(timePeriod, 0);
+ this.internalProvider = internalProvider;
+ this.timePeriod = timePeriod;
+ init();
+ }
+
+ public ScheduledProvider(Provider internalProvider) {
+ this(internalProvider, 60);
+ }
+
+ @Override
+ public String get() {
+ return cache;
+ }
+
+ @Override
+ public void init() {
+ poolExecutor = new ScheduledThreadPoolExecutor(2);
+ // 提交
+ submitTask();
+ }
+
+ /**
+ * 更新执行周期
+ * 回替换掉现有的更新任务
+ *
+ * @param timePeriod 新的执行周期
+ */
+ public void changeTimePeriod(long timePeriod) {
+ Assert.isLargerThan(timePeriod, 0);
+ this.timePeriod = timePeriod;
+ // 取消现有的任务
+ task.cancel(true);
+ submitTask();
+ }
+
+ /**
+ * ip更新时需要执行的逻辑
+ *
+ * @param valueConsumer 逻辑
+ */
+ public void whenUpdate(Consumer valueConsumer) {
+ this.valueConsumer = valueConsumer;
+ }
+
+ @Override
+ protected String doGet() {
+ // 不应该执行到这里
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * 提交任务逻辑
+ */
+ private void submitTask() {
+ task = poolExecutor.scheduleAtFixedRate(() -> {
+ // 打断时, 终止已有的任务. (逻辑上不应该发生)
+ if (Thread.currentThread().isInterrupted()) {
+ log.debug("上一个ip更新任务已终止.");
+ return;
+ }
+ InstanceContextHolder.setAdditional("ip-update");
+ cache = internalProvider.get();
+
+ if (valueConsumer != null) {
+ valueConsumer.accept(cache);
+ }
+ }, 0, timePeriod, TimeUnit.SECONDS);
+ }
+}
diff --git a/src/main/java/com/serliunx/ddns/support/okhttp/HttpClient.java b/src/main/java/com/serliunx/ddns/support/okhttp/HttpClient.java
index aa92f91..130a961 100644
--- a/src/main/java/com/serliunx/ddns/support/okhttp/HttpClient.java
+++ b/src/main/java/com/serliunx/ddns/support/okhttp/HttpClient.java
@@ -26,9 +26,18 @@ public final class HttpClient {
private static final Logger log = LoggerFactory.getLogger(HttpClient.class);
private static final ObjectMapper JSON_MAPPER = new JsonMapper();
+ private static final int DEFAULT_OVERTIME = 3;
private HttpClient() {throw new UnsupportedOperationException();}
+ static {
+ CLIENT = new OkHttpClient.Builder()
+ .connectTimeout(DEFAULT_OVERTIME, TimeUnit.SECONDS)
+ .readTimeout(DEFAULT_OVERTIME, TimeUnit.SECONDS)
+ .writeTimeout(DEFAULT_OVERTIME, TimeUnit.SECONDS)
+ .build();
+ }
+
/**
* 获取本机的ip地址
*
@@ -63,7 +72,7 @@ public final class HttpClient {
* @param configuration 配置信息
*/
public static void init(Configuration configuration) {
- Integer overtime = configuration.getInteger(ConfigurationKeys.KEY_HTTP_OVERTIME, 3);
+ Integer overtime = configuration.getInteger(ConfigurationKeys.KEY_HTTP_OVERTIME, DEFAULT_OVERTIME);
CLIENT = new OkHttpClient.Builder()
.connectTimeout(overtime, TimeUnit.SECONDS)
diff --git a/src/main/resources/settings.properties b/src/main/resources/settings.properties
index 201698c..9b00ff4 100644
--- a/src/main/resources/settings.properties
+++ b/src/main/resources/settings.properties
@@ -1,5 +1,6 @@
system.cfg.log.onstart=true
system.pool.core.size=4
system.task.refresh.interval.ip=300
+system.ip.provider.type=IP_API
+system.http.overtime=3
instance.aliyun.endpoint.url=alidns.cn-hangzhou.aliyuncs.com
-system.http.overtime=3
\ No newline at end of file
diff --git a/src/test/java/com/serliunx/ddns/test/support/ProviderTest.java b/src/test/java/com/serliunx/ddns/test/support/ProviderTest.java
new file mode 100644
index 0000000..86dedd4
--- /dev/null
+++ b/src/test/java/com/serliunx/ddns/test/support/ProviderTest.java
@@ -0,0 +1,31 @@
+package com.serliunx.ddns.test.support;
+
+import com.serliunx.ddns.support.ipprovider.IpApiProvider;
+import com.serliunx.ddns.support.ipprovider.Provider;
+import com.serliunx.ddns.support.ipprovider.ScheduledProvider;
+import org.junit.Test;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 供应器测试
+ *
+ * @author SerLiunx
+ * @version 1.0.3
+ * @since 2024/11/25
+ */
+public class ProviderTest {
+
+ @Test
+ public void testIpApiProvider() {
+ Provider provider = new IpApiProvider();
+ System.out.println(provider.get());
+ }
+
+ @Test
+ public void testScheduledProvider() throws Exception {
+ ScheduledProvider provider = new ScheduledProvider(new IpApiProvider(), 3);
+ provider.changeTimePeriod(10);
+ TimeUnit.SECONDS.sleep(60);
+ }
+}