Merge pull request #1 from SerLiunx-ctrl/ip_provider

feat: 解耦ip更新源.
This commit is contained in:
SerLiunx-ctrl
2024-11-25 16:04:07 +08:00
committed by GitHub
11 changed files with 368 additions and 19 deletions

View File

@@ -35,4 +35,9 @@ public final class ConfigurationKeys {
* http请求超时时间(秒) * http请求超时时间(秒)
*/ */
public static final String KEY_HTTP_OVERTIME = "system.http.overtime"; public static final String KEY_HTTP_OVERTIME = "system.http.overtime";
/**
* ip地址提供器类型
*/
public static final String KEY_IP_PROVIDER_TYPE = "system.ip.provider.type";
} }

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

View File

@@ -41,6 +41,12 @@ public final class Assert {
} }
} }
public static void isLargerThan(long source, long target) {
if(source <= target) {
throw new IllegalArgumentException(String.format("%s太小了, 它必须大于%s", source, target));
}
}
public static void notEmpty(Collection<?> collection) { public static void notEmpty(Collection<?> collection) {
notNull(collection); notNull(collection);
if (collection.isEmpty()) if (collection.isEmpty())

View File

@@ -1,11 +1,16 @@
package com.serliunx.ddns.support; package com.serliunx.ddns.support;
import com.serliunx.ddns.config.Configuration; 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.constant.SystemConstants;
import com.serliunx.ddns.core.Clearable; import com.serliunx.ddns.core.Clearable;
import com.serliunx.ddns.core.Refreshable; import com.serliunx.ddns.core.Refreshable;
import com.serliunx.ddns.core.context.MultipleSourceInstanceContext; import com.serliunx.ddns.core.context.MultipleSourceInstanceContext;
import com.serliunx.ddns.core.instance.Instance; 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.IPAddressResponse;
import com.serliunx.ddns.support.okhttp.HttpClient; import com.serliunx.ddns.support.okhttp.HttpClient;
import com.serliunx.ddns.thread.TaskThreadFactory; import com.serliunx.ddns.thread.TaskThreadFactory;
@@ -46,6 +51,7 @@ public final class SystemInitializer implements Refreshable, Clearable {
private ScheduledThreadPoolExecutor scheduledThreadPoolExecutor; private ScheduledThreadPoolExecutor scheduledThreadPoolExecutor;
private Set<Instance> instances; private Set<Instance> instances;
private final Map<String, ScheduledFuture<?>> runningInstances = new HashMap<>(64); private final Map<String, ScheduledFuture<?>> runningInstances = new HashMap<>(64);
private ScheduledProvider scheduledProvider;
SystemInitializer(Configuration configuration, MultipleSourceInstanceContext instanceContext, boolean clearCache) { SystemInitializer(Configuration configuration, MultipleSourceInstanceContext instanceContext, boolean clearCache) {
this.configuration = configuration; this.configuration = configuration;
@@ -79,6 +85,9 @@ public final class SystemInitializer implements Refreshable, Clearable {
// 获取核心线程数量, 默认为CPU核心数量 // 获取核心线程数量, 默认为CPU核心数量
int coreSize = configuration.getInteger(KEY_THREAD_POOL_CORE_SIZE, Runtime.getRuntime().availableProcessors()); int coreSize = configuration.getInteger(KEY_THREAD_POOL_CORE_SIZE, Runtime.getRuntime().availableProcessors());
// 初始化ip地址更新任务
initIpTask();
// 初始化线程池 // 初始化线程池
initThreadPool(coreSize); initThreadPool(coreSize);
@@ -165,20 +174,6 @@ public final class SystemInitializer implements Refreshable, Clearable {
// 初始化一个线程保活 // 初始化一个线程保活
scheduledThreadPoolExecutor.submit(() -> {}); 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(() -> { Runtime.getRuntime().addShutdownHook(new Thread(() -> {
InstanceContextHolder.setAdditional("stopping"); InstanceContextHolder.setAdditional("stopping");
@@ -189,6 +184,21 @@ public final class SystemInitializer implements Refreshable, Clearable {
}, "DDNS-ShutDownHook")); }, "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() { private void checkAndCloseSafely() {
if (scheduledThreadPoolExecutor == null) if (scheduledThreadPoolExecutor == null)
return; return;

View File

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

View File

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

View File

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

View File

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

View File

@@ -26,9 +26,18 @@ public final class HttpClient {
private static final Logger log = LoggerFactory.getLogger(HttpClient.class); private static final Logger log = LoggerFactory.getLogger(HttpClient.class);
private static final ObjectMapper JSON_MAPPER = new JsonMapper(); private static final ObjectMapper JSON_MAPPER = new JsonMapper();
private static final int DEFAULT_OVERTIME = 3;
private HttpClient() {throw new UnsupportedOperationException();} 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地址 * 获取本机的ip地址
* *
@@ -63,7 +72,7 @@ public final class HttpClient {
* @param configuration 配置信息 * @param configuration 配置信息
*/ */
public static void init(Configuration 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() CLIENT = new OkHttpClient.Builder()
.connectTimeout(overtime, TimeUnit.SECONDS) .connectTimeout(overtime, TimeUnit.SECONDS)

View File

@@ -1,5 +1,6 @@
system.cfg.log.onstart=true system.cfg.log.onstart=true
system.pool.core.size=4 system.pool.core.size=4
system.task.refresh.interval.ip=300 system.task.refresh.interval.ip=300
instance.aliyun.endpoint.url=alidns.cn-hangzhou.aliyuncs.com system.ip.provider.type=IP_API
system.http.overtime=3 system.http.overtime=3
instance.aliyun.endpoint.url=alidns.cn-hangzhou.aliyuncs.com

View File

@@ -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 <a href="mailto:serliunx@yeah.net">SerLiunx</a>
* @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);
}
}