feat: 解耦ip更新源.
This commit is contained in:
@@ -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";
|
||||
}
|
||||
|
||||
32
src/main/java/com/serliunx/ddns/constant/IpProviderType.java
Normal file
32
src/main/java/com/serliunx/ddns/constant/IpProviderType.java
Normal file
@@ -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 <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
||||
* @version 1.0.3
|
||||
* @since 2024/11/25
|
||||
*/
|
||||
public enum IpProviderType {
|
||||
|
||||
/**
|
||||
* ip数据提供商 <a href="https://ip-api.com/">ip-api</a>
|
||||
* <li> 国外的数据, 国内访问不稳定.
|
||||
*/
|
||||
IP_API(new IpApiProvider()),
|
||||
|
||||
;
|
||||
|
||||
private final Provider provider;
|
||||
|
||||
IpProviderType(Provider provider) {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
public Provider getProvider() {
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
@@ -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("参数不能为空!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Instance> instances;
|
||||
private final Map<String, ScheduledFuture<?>> 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;
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.serliunx.ddns.support.ipprovider;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* 抽象的ip提供器, 定义公共逻辑
|
||||
*
|
||||
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
||||
* @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地址
|
||||
* <li> !!!该地址为上次获得最新地址, 不一定为当前最新的地址
|
||||
*/
|
||||
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();
|
||||
}
|
||||
@@ -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数据提供商 <a href="https://ip-api.com/">ip-api</a>
|
||||
* <li> 国外的数据, 国内访问不稳定.
|
||||
*
|
||||
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.serliunx.ddns.support.ipprovider;
|
||||
|
||||
/**
|
||||
* ip供应器接口定义
|
||||
*
|
||||
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
||||
* @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地址
|
||||
* <li> !!!该地址为上次获得最新地址, 不一定为当前最新的地址
|
||||
*
|
||||
* @return 缓存的最新ip地址
|
||||
*/
|
||||
String getCache();
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
void init();
|
||||
|
||||
}
|
||||
@@ -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供应器
|
||||
* <li> 异步更新ip, 获取到的ip地址不一定为最新可用的。
|
||||
*
|
||||
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
||||
* @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<String> 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新执行周期
|
||||
* <li> 回替换掉现有的更新任务
|
||||
*
|
||||
* @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<String> 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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user