6 Commits

Author SHA1 Message Date
a729a5d99c change: 废弃yaml配置文件, 待重构. 2024-11-26 11:17:18 +08:00
SerLiunx-ctrl
d0e1792e4b Merge pull request #1 from SerLiunx-ctrl/ip_provider
feat: 解耦ip更新源.
2024-11-25 16:04:07 +08:00
fe86653903 feat: 解耦ip更新源. 2024-11-25 15:37:07 +08:00
eefd907866 feat: 初始化SQLite连接. 2024-11-20 13:08:38 +08:00
f70db6ef90 fix: 首次根据类型获取示例异常. 2024-11-20 10:50:38 +08:00
d929975809 feat: 新增数据库支持. 2024-11-20 10:43:24 +08:00
19 changed files with 529 additions and 22 deletions

12
pom.xml
View File

@@ -6,7 +6,7 @@
<groupId>com.serliunx.ddns</groupId>
<artifactId>ddns-manager-lite</artifactId>
<version>1.0.2</version>
<version>1.0.3-alpha</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
@@ -19,6 +19,8 @@
<snakeyaml.version>1.30</snakeyaml.version>
<aliyundns.sdk.version>3.0.14</aliyundns.sdk.version>
<tencent.dnspod.sdk.version>3.1.1002</tencent.dnspod.sdk.version>
<junit.version>4.13.2</junit.version>
<sqlite.jdbc.version>3.47.0.0</sqlite.jdbc.version>
</properties>
<dependencies>
@@ -55,9 +57,15 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- sqlite-jdbc -->
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>${sqlite.jdbc.version}</version>
</dependency>
</dependencies>
<build>

View File

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

View File

@@ -9,11 +9,13 @@ import java.util.Map;
/**
* yml/yaml格式的配置文件目前用于语言文件
* TODO 待重构
*
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
* @version 1.0.0
* @since 2024/6/17
*/
@Deprecated
public class YamlConfiguration extends FileConfiguration {
public YamlConfiguration(String path, boolean refresh) {

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

@@ -82,4 +82,9 @@ public final class SystemConstants {
* 用户目录下的.yml配置文件
*/
public static final String USER_SETTINGS_YAML_PATH = USER_DIR + File.separator + CONFIG_YAML_FILE;
/**
* 程序数据库
*/
public static final String SQLITE_URL = "jdbc:sqlite:data.db";
}

View File

@@ -43,7 +43,9 @@ public abstract class AbstractInstanceFactory implements InstanceFactory, Listab
@Override
public Map<String, Instance> getInstanceOfType(InstanceType type) {
Assert.notNull(instanceMap);
if (instanceMap == null) {
return Collections.emptyMap();
}
return instanceMap.values()
.stream()
.filter(i -> i.getType().equals(type))

View File

@@ -0,0 +1,36 @@
package com.serliunx.ddns.core.factory;
import com.serliunx.ddns.core.instance.Instance;
import com.serliunx.ddns.support.Assert;
import com.serliunx.ddns.support.sqlite.SQLiteConnector;
import java.util.Collections;
import java.util.Set;
/**
* 数据库示例工厂: 从数据库中(sqlite)存储、加载示例信息
*
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
* @version 1.0.3
* @since 2024/11/20
*/
public final class DatabaseInstanceFactory extends AbstractInstanceFactory implements PersistentInstanceFactory {
private final SQLiteConnector connector;
public DatabaseInstanceFactory(SQLiteConnector connector) {
this.connector = connector;
}
@Override
protected Set<Instance> load() {
Assert.notNull(connector, "数据库连接不能为空!");
connector.refresh();
return Collections.emptySet();
}
@Override
public boolean save(Instance instance) {
return false;
}
}

View File

@@ -0,0 +1,21 @@
package com.serliunx.ddns.core.factory;
import com.serliunx.ddns.core.instance.Instance;
/**
* 可持久化的实例工厂, 支持编辑、保存实例数据.
*
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
* @version 1.0.3
* @since 2024/11/20
*/
public interface PersistentInstanceFactory extends InstanceFactory {
/**
* 保存实例信息
*
* @param instance 实例
* @return 成功保存返回真, 否则返回假.
*/
boolean save(Instance instance);
}

View File

@@ -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("参数不能为空!");
}
}

View File

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

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 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)

View File

@@ -0,0 +1,76 @@
package com.serliunx.ddns.support.sqlite;
import com.serliunx.ddns.constant.SystemConstants;
import com.serliunx.ddns.core.Refreshable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* SQLite 数据库连接
*
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
* @version 1.0.3
* @since 2024/11/20
*/
public final class SQLiteConnector implements Refreshable {
private volatile Connection connection;
private final Lock initLock = new ReentrantLock();
private volatile boolean initialized = false;
private static final Logger log = LoggerFactory.getLogger(SQLiteConnector.class);
private static final SQLiteConnector INSTANCE = new SQLiteConnector();
// private-ctor
private SQLiteConnector() {}
@Override
public void refresh() {
init();
}
/**
* 连接初始化
*/
private void init() {
if (initialized) {
log.warn("sql connection already initialized");
return;
}
if (!initLock.tryLock()) {
log.error("sql connection already initialing");
}
try {
log.info("initialing sqlite connection.");
connection = DriverManager.getConnection(SystemConstants.SQLITE_URL);
initialized = true;
log.info("sqlite connection successfully initialized.");
} catch (Exception e) {
initialized = false;
log.error("sql connection initialization exception: ", e);
} finally {
initLock.unlock();
}
}
/**
* 是否已经初始化
*/
public boolean isInitialized() {
return initialized;
}
public static SQLiteConnector getInstance() {
return INSTANCE;
}
}

View File

@@ -1,5 +1,6 @@
system.cfg.log.onstart=true
system.pool.core.size=4
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
instance.aliyun.endpoint.url=alidns.cn-hangzhou.aliyuncs.com

View File

@@ -2,10 +2,12 @@ package com.serliunx.ddns.test;
import com.serliunx.ddns.constant.InstanceType;
import com.serliunx.ddns.constant.SystemConstants;
import com.serliunx.ddns.core.factory.DatabaseInstanceFactory;
import com.serliunx.ddns.core.factory.InstanceFactory;
import com.serliunx.ddns.core.factory.ListableInstanceFactory;
import com.serliunx.ddns.core.factory.YamlFileInstanceFactory;
import com.serliunx.ddns.core.instance.Instance;
import com.serliunx.ddns.support.sqlite.SQLiteConnector;
import org.junit.Test;
import java.util.Map;
@@ -28,4 +30,10 @@ public class FactoryTest {
System.out.println(k + ": " + v);
});
}
@Test
public void testDatabaseFactory() {
ListableInstanceFactory factory = new DatabaseInstanceFactory(SQLiteConnector.getInstance());
factory.refresh();
}
}

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