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