Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5d8871eab0 | |||
| 727aa532c6 | |||
| 7b74b35794 | |||
| 2cd4eeabd2 | |||
| 4a05256c59 | |||
| 6f88950359 | |||
| a8e82b89f8 | |||
| 1bb78644d8 | |||
| fcc8d359c0 | |||
| 379f5c7032 | |||
| afa47c3ff3 | |||
| 39ac8e7b9f | |||
| 7f727b544e | |||
| 1bcdabc595 | |||
| b3c79f49a0 | |||
| 79216c27bb | |||
| 7ea0fc57b8 | |||
| ac852b9501 | |||
| e5061c1233 | |||
| c93309a7d2 | |||
| 4b974528b1 | |||
| 922135c3a7 | |||
| 42774eed50 | |||
| b3ca9c2647 | |||
| 05b637b9f1 | |||
| c2b71736cc | |||
| a0b0764ac3 | |||
| aa934121dc | |||
| 048c358800 | |||
| c7ea252159 | |||
| ac81e48d9c | |||
| 9900ccf1f5 | |||
| f66390e86b |
11
pom.xml
11
pom.xml
@@ -66,6 +66,17 @@
|
|||||||
<artifactId>sqlite-jdbc</artifactId>
|
<artifactId>sqlite-jdbc</artifactId>
|
||||||
<version>${sqlite.jdbc.version}</version>
|
<version>${sqlite.jdbc.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.jline/jline -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jline</groupId>
|
||||||
|
<artifactId>jline</artifactId>
|
||||||
|
<version>3.28.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>jul-to-slf4j</artifactId>
|
||||||
|
<version>1.7.36</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -1,12 +1,33 @@
|
|||||||
package com.serliunx.ddns;
|
package com.serliunx.ddns;
|
||||||
|
|
||||||
|
import com.serliunx.ddns.config.CommandLineConfiguration;
|
||||||
import com.serliunx.ddns.config.Configuration;
|
import com.serliunx.ddns.config.Configuration;
|
||||||
import com.serliunx.ddns.config.PropertiesConfiguration;
|
import com.serliunx.ddns.config.PropertiesConfiguration;
|
||||||
|
import com.serliunx.ddns.config.listener.IpRefreshIntervalListener;
|
||||||
|
import com.serliunx.ddns.config.listener.NotificationConfigListener;
|
||||||
import com.serliunx.ddns.constant.SystemConstants;
|
import com.serliunx.ddns.constant.SystemConstants;
|
||||||
import com.serliunx.ddns.core.context.FileInstanceContext;
|
import com.serliunx.ddns.core.context.FileInstanceContext;
|
||||||
import com.serliunx.ddns.core.context.MultipleSourceInstanceContext;
|
import com.serliunx.ddns.core.context.MultipleSourceInstanceContext;
|
||||||
|
import com.serliunx.ddns.support.InstanceContextHolder;
|
||||||
import com.serliunx.ddns.support.SystemInitializer;
|
import com.serliunx.ddns.support.SystemInitializer;
|
||||||
import com.serliunx.ddns.support.okhttp.HttpClient;
|
import com.serliunx.ddns.support.command.CommandCompleter;
|
||||||
|
import com.serliunx.ddns.support.command.CommandDispatcher;
|
||||||
|
import com.serliunx.ddns.support.command.target.HelpCommand;
|
||||||
|
import com.serliunx.ddns.support.command.target.ReloadCommand;
|
||||||
|
import com.serliunx.ddns.support.command.target.StopCommand;
|
||||||
|
import com.serliunx.ddns.support.command.target.config.ConfigCommand;
|
||||||
|
import com.serliunx.ddns.support.command.target.instance.InstanceCommand;
|
||||||
|
import com.serliunx.ddns.support.log.JLineAdaptAppender;
|
||||||
|
import org.jline.reader.LineReader;
|
||||||
|
import org.jline.reader.LineReaderBuilder;
|
||||||
|
import org.jline.reader.impl.history.DefaultHistory;
|
||||||
|
import org.jline.terminal.Terminal;
|
||||||
|
import org.jline.terminal.TerminalBuilder;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.slf4j.bridge.SLF4JBridgeHandler;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 启动类
|
* 启动类
|
||||||
@@ -17,6 +38,11 @@ import com.serliunx.ddns.support.okhttp.HttpClient;
|
|||||||
*/
|
*/
|
||||||
public final class ManagerLite {
|
public final class ManagerLite {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认的日志输出
|
||||||
|
*/
|
||||||
|
private static final Logger DEFAULT_LOGGER = LoggerFactory.getLogger(ManagerLite.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 配置信息
|
* 配置信息
|
||||||
*/
|
*/
|
||||||
@@ -29,20 +55,97 @@ public final class ManagerLite {
|
|||||||
* 系统初始化器
|
* 系统初始化器
|
||||||
*/
|
*/
|
||||||
private static SystemInitializer systemInitializer;
|
private static SystemInitializer systemInitializer;
|
||||||
|
/**
|
||||||
|
* 指令调度
|
||||||
|
*/
|
||||||
|
private static CommandDispatcher commandDispatcher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取默认的日志输出
|
||||||
|
*/
|
||||||
|
public static Logger getLogger() {
|
||||||
|
return DEFAULT_LOGGER;
|
||||||
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
// 初始化slf4j日志桥接
|
||||||
|
SLF4JBridgeHandler.removeHandlersForRootLogger();
|
||||||
|
SLF4JBridgeHandler.install();
|
||||||
|
|
||||||
// 配置初始化
|
// 配置初始化
|
||||||
initConfiguration();
|
initConfiguration(args);
|
||||||
|
|
||||||
// 相关工具初始化
|
|
||||||
initTools();
|
|
||||||
|
|
||||||
// 初始化实例容器
|
// 初始化实例容器
|
||||||
initContext();
|
initContext();
|
||||||
|
|
||||||
// 系统初始化
|
// 系统初始化
|
||||||
initSystem();
|
initSystem();
|
||||||
|
|
||||||
|
// 配置监听器初始化
|
||||||
|
initConfigurationListeners();
|
||||||
|
|
||||||
|
// 指令初始化
|
||||||
|
initCommands();
|
||||||
|
|
||||||
|
Terminal terminal;
|
||||||
|
try {
|
||||||
|
terminal = TerminalBuilder.builder()
|
||||||
|
.system(true)
|
||||||
|
.build();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// 不应该发生
|
||||||
|
System.exit(0);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
LineReader lineReader = LineReaderBuilder.builder()
|
||||||
|
.terminal(terminal)
|
||||||
|
.completer(new CommandCompleter(commandDispatcher))
|
||||||
|
// 如果想记录历史命令,可以配置一个 History
|
||||||
|
.history(new DefaultHistory())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
JLineAdaptAppender.setLineReader(lineReader);
|
||||||
|
|
||||||
|
final String prompt = "client> ";
|
||||||
|
|
||||||
|
InstanceContextHolder.setAdditional("command-process");
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
String cmd = lineReader.readLine(prompt);
|
||||||
|
commandDispatcher.onCommand(cmd);
|
||||||
|
terminal.flush();
|
||||||
|
} catch (Exception e) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置监听器初始化
|
||||||
|
*/
|
||||||
|
private static void initConfigurationListeners() {
|
||||||
|
// 配置监听器:IP更新间隔变动
|
||||||
|
configuration.addListener(new IpRefreshIntervalListener(systemInitializer.getScheduledProvider()));
|
||||||
|
// 配置监听器:通知变更
|
||||||
|
configuration.addListener(new NotificationConfigListener());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指令初始化
|
||||||
|
*/
|
||||||
|
private static void initCommands() {
|
||||||
|
commandDispatcher = CommandDispatcher.getInstance();
|
||||||
|
// help
|
||||||
|
commandDispatcher.register(new HelpCommand());
|
||||||
|
// reload
|
||||||
|
commandDispatcher.register(new ReloadCommand(configuration, systemInitializer));
|
||||||
|
// config
|
||||||
|
commandDispatcher.register(new ConfigCommand(configuration));
|
||||||
|
// stop
|
||||||
|
commandDispatcher.register(new StopCommand());
|
||||||
|
// instance
|
||||||
|
commandDispatcher.register(new InstanceCommand(systemInitializer));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,16 +158,10 @@ public final class ManagerLite {
|
|||||||
/**
|
/**
|
||||||
* 配置初始化
|
* 配置初始化
|
||||||
*/
|
*/
|
||||||
private static void initConfiguration() {
|
private static void initConfiguration(String[] args) {
|
||||||
configuration = new PropertiesConfiguration(SystemConstants.USER_SETTINGS_PROPERTIES_PATH);
|
final CommandLineConfiguration cc = new CommandLineConfiguration(args);
|
||||||
}
|
cc.from(new PropertiesConfiguration(SystemConstants.USER_SETTINGS_PROPERTIES_PATH));
|
||||||
|
configuration = cc;
|
||||||
/**
|
|
||||||
* 相关工具初始化
|
|
||||||
*/
|
|
||||||
private static void initTools() {
|
|
||||||
// http 工具类初始化
|
|
||||||
HttpClient.init(configuration);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package com.serliunx.ddns.config;
|
package com.serliunx.ddns.config;
|
||||||
|
|
||||||
|
import com.serliunx.ddns.constant.ConfigurationKeys;
|
||||||
import com.serliunx.ddns.support.Assert;
|
import com.serliunx.ddns.support.Assert;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.*;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
@@ -18,9 +18,28 @@ import java.util.concurrent.locks.ReentrantLock;
|
|||||||
*/
|
*/
|
||||||
public abstract class AbstractConfiguration implements Configuration {
|
public abstract class AbstractConfiguration implements Configuration {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志
|
||||||
|
*/
|
||||||
protected final Logger log = LoggerFactory.getLogger(this.getClass());
|
protected final Logger log = LoggerFactory.getLogger(this.getClass());
|
||||||
|
/**
|
||||||
|
* 配置值存储
|
||||||
|
*/
|
||||||
protected final Map<String, String> valueMap = new LinkedHashMap<>(16);
|
protected final Map<String, String> valueMap = new LinkedHashMap<>(16);
|
||||||
private final Lock loadLock = new ReentrantLock();
|
/**
|
||||||
|
* 上下文更改锁
|
||||||
|
*/
|
||||||
|
protected final Lock contextLock = new ReentrantLock();
|
||||||
|
/**
|
||||||
|
* 监听器
|
||||||
|
* <li> 仅初始化时做增删改操作.
|
||||||
|
*/
|
||||||
|
protected final Map<String, List<ConfigListener>> listeners = new HashMap<>(16);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听所有配置键的监听器标识符
|
||||||
|
*/
|
||||||
|
public static final String ALL_KEYS_LISTENERS_TAG = "ALL_KEYS_LISTENERS_TAG";
|
||||||
|
|
||||||
public AbstractConfiguration() {}
|
public AbstractConfiguration() {}
|
||||||
|
|
||||||
@@ -110,6 +129,77 @@ public abstract class AbstractConfiguration implements Configuration {
|
|||||||
return valueMap;
|
return valueMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean modify(String key, Object value) {
|
||||||
|
try {
|
||||||
|
contextLock.lock();
|
||||||
|
if (!valueMap.containsKey(key))
|
||||||
|
return false;
|
||||||
|
String oldVal = valueMap.get(key);
|
||||||
|
String newVal = String.valueOf(value);
|
||||||
|
valueMap.put(key, newVal);
|
||||||
|
|
||||||
|
try {
|
||||||
|
invokeListeners(key, oldVal, newVal);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("监听器执行出现异常 => {}", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} finally {
|
||||||
|
contextLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void modify(String key, Object value, boolean createIfAbsent) {
|
||||||
|
try {
|
||||||
|
contextLock.lock();
|
||||||
|
boolean invoke = false;
|
||||||
|
String oldVal = valueMap.get(key);
|
||||||
|
String newVal = String.valueOf(value);
|
||||||
|
if (!valueMap.containsKey(key)) {
|
||||||
|
if (createIfAbsent) {
|
||||||
|
valueMap.put(key, newVal);
|
||||||
|
invoke = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
valueMap.put(key, newVal);
|
||||||
|
invoke = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!invoke)
|
||||||
|
return;
|
||||||
|
try {
|
||||||
|
invokeListeners(key, oldVal, newVal);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("监听器执行出现异常[CIA] => {}", e.getMessage());
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
contextLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addListener(ConfigListener listener) {
|
||||||
|
Collection<String> keys = listener.interestedIn();
|
||||||
|
Assert.notNull(keys);
|
||||||
|
if (keys.isEmpty()) {
|
||||||
|
listeners.computeIfAbsent(ALL_KEYS_LISTENERS_TAG, key -> new ArrayList<>())
|
||||||
|
.add(listener);
|
||||||
|
} else {
|
||||||
|
keys.forEach(k -> {
|
||||||
|
listeners.computeIfAbsent(k, k1 -> new ArrayList<>())
|
||||||
|
.add(listener);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<ConfigListener>> getListeners() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getPriority() {
|
public int getPriority() {
|
||||||
return Integer.MAX_VALUE;
|
return Integer.MAX_VALUE;
|
||||||
@@ -120,12 +210,12 @@ public abstract class AbstractConfiguration implements Configuration {
|
|||||||
*/
|
*/
|
||||||
protected void load() {
|
protected void load() {
|
||||||
try {
|
try {
|
||||||
loadLock.lock();
|
contextLock.lock();
|
||||||
// 清空原有的配置信息
|
// 清空原有的配置信息
|
||||||
valueMap.clear();
|
valueMap.clear();
|
||||||
load0();
|
load0();
|
||||||
}finally {
|
}finally {
|
||||||
loadLock.unlock();
|
contextLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,4 +239,22 @@ public abstract class AbstractConfiguration implements Configuration {
|
|||||||
* 载入逻辑
|
* 载入逻辑
|
||||||
*/
|
*/
|
||||||
protected abstract void load0();
|
protected abstract void load0();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触发监听器
|
||||||
|
*/
|
||||||
|
private void invokeListeners(String key, Object oldVal, Object newVal) throws Exception {
|
||||||
|
// 触发监听了所有配置项的监听器
|
||||||
|
List<ConfigListener> all = listeners.get(ALL_KEYS_LISTENERS_TAG);
|
||||||
|
for (ConfigListener cl : all) {
|
||||||
|
cl.onChanged(this, key, oldVal, newVal);
|
||||||
|
}
|
||||||
|
// 触发其他监听器
|
||||||
|
List<ConfigListener> listenerList = listeners.get(key);
|
||||||
|
if (listenerList == null || listenerList.isEmpty())
|
||||||
|
return;
|
||||||
|
for (ConfigListener cl : listenerList) {
|
||||||
|
cl.onChanged(this, key, oldVal, newVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package com.serliunx.ddns.config;
|
||||||
|
|
||||||
|
import com.serliunx.ddns.core.Combination;
|
||||||
|
import com.serliunx.ddns.support.Assert;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从启动命令中读取的配置信息
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
||||||
|
* @version 1.0.3
|
||||||
|
* @since 2024/12/24
|
||||||
|
*/
|
||||||
|
public final class CommandLineConfiguration extends AbstractConfiguration implements Combination<Configuration> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原始参数
|
||||||
|
*/
|
||||||
|
private final String[] sourceArgs;
|
||||||
|
/**
|
||||||
|
* 待合并的其他配置信息
|
||||||
|
*/
|
||||||
|
private final Collection<Configuration> configurations;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置项标记
|
||||||
|
*/
|
||||||
|
private static final String TAG = "-D";
|
||||||
|
/**
|
||||||
|
* 配置赋值符号(=)
|
||||||
|
*/
|
||||||
|
private static final String EQUAL = "=";
|
||||||
|
/**
|
||||||
|
* 配置缓存
|
||||||
|
*/
|
||||||
|
private final Map<String, String> cache = new HashMap<>();
|
||||||
|
|
||||||
|
public CommandLineConfiguration(String[] sourceArgs) {
|
||||||
|
this(sourceArgs, new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandLineConfiguration(String[] sourceArgs, Collection<Configuration> configurations) {
|
||||||
|
this.sourceArgs = sourceArgs;
|
||||||
|
this.configurations = configurations;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取原始启动参数
|
||||||
|
*
|
||||||
|
* @return 原始启动参数
|
||||||
|
*/
|
||||||
|
public String[] getSourceArgs() {
|
||||||
|
return sourceArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPriority() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void from(Configuration configuration) {
|
||||||
|
configurations.add(configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void from(Collection<? extends Configuration> configurations) {
|
||||||
|
this.configurations.addAll(configurations);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Configuration getOriginal() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<? extends Configuration> getCombinations() {
|
||||||
|
return configurations;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void refresh0() {
|
||||||
|
if (sourceArgs == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String arg : sourceArgs) {
|
||||||
|
if (!arg.startsWith(TAG)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String key = arg.substring(TAG.length(), arg.indexOf(EQUAL));
|
||||||
|
String value = arg.substring(arg.indexOf(EQUAL) + 1);
|
||||||
|
cache.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 载入配置
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void load0() {
|
||||||
|
// 合并
|
||||||
|
merge();
|
||||||
|
// 更新
|
||||||
|
valueMap.putAll(cache);
|
||||||
|
// 清除缓存
|
||||||
|
cache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将其他配置信息合并到当前配置信息
|
||||||
|
* <li> 命令行参数的配置信息优先级高于其他配置信息
|
||||||
|
* <li> 仅在持有锁的情况下访问
|
||||||
|
*/
|
||||||
|
private void merge() {
|
||||||
|
Assert.notEmpty(configurations);
|
||||||
|
|
||||||
|
for (Configuration configuration : configurations) {
|
||||||
|
if (configuration instanceof AbstractConfiguration) {
|
||||||
|
AbstractConfiguration ac = (AbstractConfiguration) configuration;
|
||||||
|
ac.refresh0();
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<String, String> keyValue = configuration.getAllKeyAndValue();
|
||||||
|
keyValue.forEach((k, v) -> {
|
||||||
|
if (!cache.containsKey(k)) {
|
||||||
|
cache.put(k, v);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/main/java/com/serliunx/ddns/config/ConfigListener.java
Normal file
36
src/main/java/com/serliunx/ddns/config/ConfigListener.java
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package com.serliunx.ddns.config;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置监听器
|
||||||
|
* <li> 针对配置的变动所需要执行的逻辑
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
||||||
|
* @version 1.0.4
|
||||||
|
* @since 2025/2/3
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface ConfigListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指定当前监听器所感兴趣的配置项(可多个)
|
||||||
|
* <li> 为空时即监听所有配置项
|
||||||
|
*
|
||||||
|
* @return 感兴趣的配置项
|
||||||
|
*/
|
||||||
|
default Collection<String> interestedIn() {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置项发生了变动的回调
|
||||||
|
*
|
||||||
|
* @param configuration 配置
|
||||||
|
* @param key 配置键
|
||||||
|
* @param oldVal 旧值
|
||||||
|
* @param newVal 新值
|
||||||
|
*/
|
||||||
|
void onChanged(Configuration configuration, String key, Object oldVal, Object newVal) throws Exception;
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package com.serliunx.ddns.config;
|
|||||||
import com.serliunx.ddns.core.Priority;
|
import com.serliunx.ddns.core.Priority;
|
||||||
import com.serliunx.ddns.core.Refreshable;
|
import com.serliunx.ddns.core.Refreshable;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -100,4 +101,37 @@ public interface Configuration extends Refreshable, Priority {
|
|||||||
* @return 配置文件所有成功加载的键值对
|
* @return 配置文件所有成功加载的键值对
|
||||||
*/
|
*/
|
||||||
Map<String, String> getAllKeyAndValue();
|
Map<String, String> getAllKeyAndValue();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改配置项(锁)
|
||||||
|
*
|
||||||
|
* @param key 配置键
|
||||||
|
* @param value 新的值
|
||||||
|
* @return 成功修改返回真, 否则返回假; 指定键不存在时则修改失败
|
||||||
|
*/
|
||||||
|
boolean modify(String key, Object value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改配置项(锁)
|
||||||
|
* <li> 指定配置键不存在时则会根绝是否需要创建而新增
|
||||||
|
*
|
||||||
|
* @param key 配置键
|
||||||
|
* @param value 新的值
|
||||||
|
* @param createIfAbsent 是否在不存在指定键时创建
|
||||||
|
*/
|
||||||
|
void modify(String key, Object value, boolean createIfAbsent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加配置监听器
|
||||||
|
*
|
||||||
|
* @param listener 监听器
|
||||||
|
*/
|
||||||
|
void addListener(ConfigListener listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有配置监听器
|
||||||
|
*
|
||||||
|
* @return 所有监听器
|
||||||
|
*/
|
||||||
|
Map<String, List<ConfigListener>> getListeners();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.serliunx.ddns.config.listener;
|
||||||
|
|
||||||
|
import com.serliunx.ddns.config.ConfigListener;
|
||||||
|
import com.serliunx.ddns.config.Configuration;
|
||||||
|
import com.serliunx.ddns.constant.ConfigurationKeys;
|
||||||
|
import com.serliunx.ddns.support.Assert;
|
||||||
|
import com.serliunx.ddns.support.ipprovider.ScheduledProvider;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置监听器:IP更新间隔变动
|
||||||
|
* <li> 刷新间隔发生变更时通知定时器结束并重新开始计时.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
||||||
|
* @version 1.0.4
|
||||||
|
* @since 2025/2/3
|
||||||
|
*/
|
||||||
|
public final class IpRefreshIntervalListener implements ConfigListener {
|
||||||
|
|
||||||
|
private final ScheduledProvider scheduledProvider;
|
||||||
|
|
||||||
|
public IpRefreshIntervalListener(ScheduledProvider scheduledProvider) {
|
||||||
|
Assert.notNull(scheduledProvider);
|
||||||
|
this.scheduledProvider = scheduledProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<String> interestedIn() {
|
||||||
|
return Collections.singletonList(ConfigurationKeys.KEY_TASK_REFRESH_INTERVAL_IP);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChanged(Configuration configuration, String key, Object oldVal, Object newVal) throws Exception {
|
||||||
|
if (key == null
|
||||||
|
|| !key.equals(ConfigurationKeys.KEY_TASK_REFRESH_INTERVAL_IP)
|
||||||
|
|| oldVal == null
|
||||||
|
|| newVal == null
|
||||||
|
|| oldVal.equals(newVal))
|
||||||
|
return;
|
||||||
|
Long newInterval = configuration.getLong(ConfigurationKeys.KEY_TASK_REFRESH_INTERVAL_IP);
|
||||||
|
scheduledProvider.changeTimePeriod(newInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.serliunx.ddns.config.listener;
|
||||||
|
|
||||||
|
import com.serliunx.ddns.config.ConfigListener;
|
||||||
|
import com.serliunx.ddns.config.Configuration;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置监听器:通知变更
|
||||||
|
* <li> 仅输出变更信息
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
||||||
|
* @version 1.0.4
|
||||||
|
* @since 2025/2/3
|
||||||
|
*/
|
||||||
|
public final class NotificationConfigListener implements ConfigListener {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(NotificationConfigListener.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChanged(Configuration configuration, String key, Object oldVal, Object newVal) throws Exception {
|
||||||
|
if (log.isDebugEnabled())
|
||||||
|
log.debug("配置更新: 配置项 {} 由 {} 调整至 {}", key, oldVal, newVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.serliunx.ddns.config;
|
package com.serliunx.ddns.constant;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 配置文件键常量信息
|
* 配置文件键常量信息
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.serliunx.ddns.constant;
|
package com.serliunx.ddns.constant;
|
||||||
|
|
||||||
|
import com.serliunx.ddns.support.ipprovider.IcanhazipProvider;
|
||||||
import com.serliunx.ddns.support.ipprovider.IpApiProvider;
|
import com.serliunx.ddns.support.ipprovider.IpApiProvider;
|
||||||
import com.serliunx.ddns.support.ipprovider.Provider;
|
import com.serliunx.ddns.support.ipprovider.Provider;
|
||||||
|
|
||||||
@@ -18,6 +19,11 @@ public enum IpProviderType {
|
|||||||
*/
|
*/
|
||||||
IP_API(new IpApiProvider()),
|
IP_API(new IpApiProvider()),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ip数据提供商 <a href="https://icanhazip.com/">icanhazip</a>
|
||||||
|
*/
|
||||||
|
I_CAN_HAZ_IP(new IcanhazipProvider()),
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|
||||||
private final Provider provider;
|
private final Provider provider;
|
||||||
|
|||||||
43
src/main/java/com/serliunx/ddns/core/Combination.java
Normal file
43
src/main/java/com/serliunx/ddns/core/Combination.java
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package com.serliunx.ddns.core;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组合, 将多个对象以一定的逻辑相组合
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
||||||
|
* @version 1.0.3
|
||||||
|
* @since 2024/12/24
|
||||||
|
*
|
||||||
|
* @param <E> 合并的类型
|
||||||
|
*/
|
||||||
|
public interface Combination<E> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组合指定对象
|
||||||
|
*
|
||||||
|
* @param e 对象
|
||||||
|
*/
|
||||||
|
void from(E e);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量组合对象
|
||||||
|
*
|
||||||
|
* @param es 对象集合
|
||||||
|
*/
|
||||||
|
void from(Collection<? extends E> es);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取原始对象(未组合前的)
|
||||||
|
*
|
||||||
|
* @return 原始对象
|
||||||
|
*/
|
||||||
|
E getOriginal();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取该对象中所有组合的来源
|
||||||
|
*
|
||||||
|
* @return 所有组合来源
|
||||||
|
*/
|
||||||
|
Collection<? extends E> getCombinations();
|
||||||
|
}
|
||||||
@@ -109,7 +109,7 @@ public abstract class AbstractInstanceContext implements InstanceContext, Multip
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<Instance> getInstances() {
|
public Set<Instance> getInstances() {
|
||||||
return instanceMap == null ? Collections.emptySet() : new HashSet<>(instanceMap.values());
|
return instanceMap == null ? Collections.emptySet() : new LinkedHashSet<>(instanceMap.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
@@ -16,7 +16,7 @@ import java.util.concurrent.ExecutionException;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
import static com.serliunx.ddns.config.ConfigurationKeys.KEY_ALIYUN_ENDPOINT;
|
import static com.serliunx.ddns.constant.ConfigurationKeys.KEY_ALIYUN_ENDPOINT;
|
||||||
import static com.serliunx.ddns.constant.SystemConstants.XML_ROOT_INSTANCE_NAME;
|
import static com.serliunx.ddns.constant.SystemConstants.XML_ROOT_INSTANCE_NAME;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package com.serliunx.ddns.support;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 控制台样式助手
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
||||||
|
* @version 1.0.4
|
||||||
|
* @since 2025/2/4
|
||||||
|
*/
|
||||||
|
public final class ConsoleStyleHelper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ANSI 控制台经典样式编码映射表
|
||||||
|
*/
|
||||||
|
private static final Map<String, String> CLASSIC_STYLE_MAP = new HashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
// 格式
|
||||||
|
CLASSIC_STYLE_MAP.put("&r", "\033[0m"); // 重置
|
||||||
|
CLASSIC_STYLE_MAP.put("&l", "\033[1m"); // 加粗或高亮
|
||||||
|
|
||||||
|
// 颜色
|
||||||
|
CLASSIC_STYLE_MAP.put("&0", "\033[30m"); // 黑色
|
||||||
|
CLASSIC_STYLE_MAP.put("&1", "\033[31m"); // 红色
|
||||||
|
CLASSIC_STYLE_MAP.put("&2", "\033[32m"); // 绿色
|
||||||
|
CLASSIC_STYLE_MAP.put("&3", "\033[33m"); // 黄色
|
||||||
|
CLASSIC_STYLE_MAP.put("&4", "\033[34m"); // 蓝色
|
||||||
|
CLASSIC_STYLE_MAP.put("&5", "\033[35m"); // 品红
|
||||||
|
CLASSIC_STYLE_MAP.put("&6", "\033[36m"); // 青色
|
||||||
|
CLASSIC_STYLE_MAP.put("&7", "\033[37m"); // 白色
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化输出, 支持颜色代码
|
||||||
|
*
|
||||||
|
* @param format 格式
|
||||||
|
* @param args 参数
|
||||||
|
*/
|
||||||
|
public static void coloredPrintf(String format, final Object... args) {
|
||||||
|
if (!format.endsWith("%n")) {
|
||||||
|
format = format + "%n";
|
||||||
|
}
|
||||||
|
if (!format.endsWith("&r")) {
|
||||||
|
format = format + "&r";
|
||||||
|
}
|
||||||
|
System.out.printf(replaceStyleCode(format), args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 替换样式代码
|
||||||
|
*
|
||||||
|
* @param original 原始文本
|
||||||
|
* @return 替换后的文本
|
||||||
|
*/
|
||||||
|
private static String replaceStyleCode(String original) {
|
||||||
|
Set<Map.Entry<String, String>> entries = CLASSIC_STYLE_MAP.entrySet();
|
||||||
|
for (Map.Entry<String, String> entry : entries) {
|
||||||
|
original = original.replace(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
@@ -24,10 +25,28 @@ public final class NetworkContextHolder {
|
|||||||
// 外网IP地址获取
|
// 外网IP地址获取
|
||||||
private static final Integer IP_CONTEXT_TIME_OUT = 5;
|
private static final Integer IP_CONTEXT_TIME_OUT = 5;
|
||||||
private static volatile String IP_ADDRESS;
|
private static volatile String IP_ADDRESS;
|
||||||
|
/**
|
||||||
|
* 失败计数
|
||||||
|
* <p>
|
||||||
|
* 失败次数过多后, 会影响
|
||||||
|
*/
|
||||||
|
private static final AtomicInteger FAILED_COUNTS = new AtomicInteger();
|
||||||
|
|
||||||
|
// private-ctor
|
||||||
private NetworkContextHolder() {throw new UnsupportedOperationException();}
|
private NetworkContextHolder() {throw new UnsupportedOperationException();}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 尝试设置IP地址
|
||||||
|
*
|
||||||
|
* @param i 新的ip地址, 为空时设置会失败
|
||||||
|
*/
|
||||||
public static void setIpAddress(String i) {
|
public static void setIpAddress(String i) {
|
||||||
|
if (i == null
|
||||||
|
|| i.isEmpty()) {
|
||||||
|
log.error("IP 地址不能为空!");
|
||||||
|
FAILED_COUNTS.incrementAndGet();
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
IP_LOCK.lock();
|
IP_LOCK.lock();
|
||||||
IP_ADDRESS = i;
|
IP_ADDRESS = i;
|
||||||
@@ -35,11 +54,23 @@ public final class NetworkContextHolder {
|
|||||||
IP_CONTEXT_WAIT_LATCH.countDown();
|
IP_CONTEXT_WAIT_LATCH.countDown();
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
FAILED_COUNTS.set(0);
|
||||||
IP_LOCK.unlock();
|
IP_LOCK.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所缓存的ip的地址
|
||||||
|
* <p>
|
||||||
|
* 设置失败次数过多时将忽略已保存的缓存值,防止多次将旧IP重复更新.
|
||||||
|
*
|
||||||
|
* @return ip地址
|
||||||
|
*/
|
||||||
public static String getIpAddress() {
|
public static String getIpAddress() {
|
||||||
|
if (FAILED_COUNTS.get() > 10) {
|
||||||
|
log.warn("更新失败次数过多, 不在返回IP地址直到下次成功更新!");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
if (IP_ADDRESS != null) {
|
if (IP_ADDRESS != null) {
|
||||||
return IP_ADDRESS;
|
return IP_ADDRESS;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
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.ConfigurationKeys;
|
||||||
import com.serliunx.ddns.constant.IpProviderType;
|
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.Provider;
|
||||||
import com.serliunx.ddns.support.ipprovider.ScheduledProvider;
|
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.support.okhttp.HttpClient;
|
||||||
import com.serliunx.ddns.thread.TaskThreadFactory;
|
import com.serliunx.ddns.support.thread.ThreadFactoryBuilder;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -30,8 +28,8 @@ import java.util.concurrent.ScheduledFuture;
|
|||||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import static com.serliunx.ddns.config.ConfigurationKeys.KEY_TASK_REFRESH_INTERVAL_IP;
|
import static com.serliunx.ddns.constant.ConfigurationKeys.KEY_TASK_REFRESH_INTERVAL_IP;
|
||||||
import static com.serliunx.ddns.config.ConfigurationKeys.KEY_THREAD_POOL_CORE_SIZE;
|
import static com.serliunx.ddns.constant.ConfigurationKeys.KEY_THREAD_POOL_CORE_SIZE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 系统初始化
|
* 系统初始化
|
||||||
@@ -82,6 +80,9 @@ public final class SystemInitializer implements Refreshable, Clearable {
|
|||||||
configuration.refresh();
|
configuration.refresh();
|
||||||
ConfigurationContextHolder.setConfiguration(configuration);
|
ConfigurationContextHolder.setConfiguration(configuration);
|
||||||
|
|
||||||
|
// 初始化工具类
|
||||||
|
HttpClient.init(configuration);
|
||||||
|
|
||||||
// 获取核心线程数量, 默认为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());
|
||||||
|
|
||||||
@@ -118,11 +119,17 @@ public final class SystemInitializer implements Refreshable, Clearable {
|
|||||||
return instances;
|
return instances;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载实例(不同的容器加载时机不同)
|
||||||
|
*/
|
||||||
private void loadInstances() {
|
private void loadInstances() {
|
||||||
instances = instanceContext.getInstances();
|
instances = instanceContext.getInstances();
|
||||||
log.info("载入 {} 个实例.", instances.size());
|
log.info("载入 {} 个实例.", instances.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 资源释放
|
||||||
|
*/
|
||||||
@SuppressWarnings("SameParameterValue")
|
@SuppressWarnings("SameParameterValue")
|
||||||
private void releaseResource(String resourceName) {
|
private void releaseResource(String resourceName) {
|
||||||
ClassLoader classLoader = SystemConstants.class.getClassLoader();
|
ClassLoader classLoader = SystemConstants.class.getClassLoader();
|
||||||
@@ -149,6 +156,9 @@ public final class SystemInitializer implements Refreshable, Clearable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 运行实例
|
||||||
|
*/
|
||||||
private void runInstances() {
|
private void runInstances() {
|
||||||
Assert.notNull(scheduledThreadPoolExecutor);
|
Assert.notNull(scheduledThreadPoolExecutor);
|
||||||
Assert.notNull(instances);
|
Assert.notNull(instances);
|
||||||
@@ -167,9 +177,15 @@ public final class SystemInitializer implements Refreshable, Clearable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化线程池
|
||||||
|
*
|
||||||
|
* @param coreSize 线程池核心线程数量
|
||||||
|
*/
|
||||||
private void initThreadPool(int coreSize) {
|
private void initThreadPool(int coreSize) {
|
||||||
Assert.isLargerThan(coreSize, 1);
|
Assert.isLargerThan(coreSize, 1);
|
||||||
scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(coreSize, new TaskThreadFactory());
|
scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(coreSize, ThreadFactoryBuilder.builder()
|
||||||
|
.ofNamePattern("ddns-task-%s"));
|
||||||
|
|
||||||
// 初始化一个线程保活
|
// 初始化一个线程保活
|
||||||
scheduledThreadPoolExecutor.submit(() -> {});
|
scheduledThreadPoolExecutor.submit(() -> {});
|
||||||
@@ -179,26 +195,38 @@ public final class SystemInitializer implements Refreshable, Clearable {
|
|||||||
InstanceContextHolder.setAdditional("stopping");
|
InstanceContextHolder.setAdditional("stopping");
|
||||||
log.info("程序正在关闭中, 可能需要一定时间.");
|
log.info("程序正在关闭中, 可能需要一定时间.");
|
||||||
scheduledThreadPoolExecutor.shutdown();
|
scheduledThreadPoolExecutor.shutdown();
|
||||||
|
scheduledProvider.close();
|
||||||
log.info("已关闭.");
|
log.info("已关闭.");
|
||||||
InstanceContextHolder.clearAdditional();
|
InstanceContextHolder.clearAdditional();
|
||||||
}, "DDNS-ShutDownHook"));
|
}, "DDNS-ShutDownHook"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化定时获取IP地址的任务
|
||||||
|
*/
|
||||||
private void initIpTask() {
|
private void initIpTask() {
|
||||||
scheduledProvider = new ScheduledProvider(getInternalProvider(),
|
scheduledProvider = new ScheduledProvider(getInternalProvider(),
|
||||||
configuration.getLong(KEY_TASK_REFRESH_INTERVAL_IP, 300L));
|
configuration.getLong(KEY_TASK_REFRESH_INTERVAL_IP, 300L));
|
||||||
|
|
||||||
scheduledProvider.whenUpdate(ip -> {
|
scheduledProvider.whenUpdate(ip -> {
|
||||||
NetworkContextHolder.setIpAddress(ip);
|
NetworkContextHolder.setIpAddress(ip);
|
||||||
log.info("本机最新公网IP地址 => {}", ip);
|
log.debug("本机最新公网IP地址 => {}", ip);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取内置的IP供应器(获取IP地址的方式)
|
||||||
|
* <p>
|
||||||
|
* 根据配置文件中的定义{@link ConfigurationKeys#KEY_IP_PROVIDER_TYPE}, 默认为{@link com.serliunx.ddns.support.ipprovider.IpApiProvider}
|
||||||
|
*/
|
||||||
private Provider getInternalProvider() {
|
private Provider getInternalProvider() {
|
||||||
return configuration.getEnum(IpProviderType.class, ConfigurationKeys.KEY_IP_PROVIDER_TYPE,
|
return configuration.getEnum(IpProviderType.class, ConfigurationKeys.KEY_IP_PROVIDER_TYPE,
|
||||||
IpProviderType.IP_API).getProvider();
|
IpProviderType.IP_API).getProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭线程池逻辑
|
||||||
|
*/
|
||||||
private void checkAndCloseSafely() {
|
private void checkAndCloseSafely() {
|
||||||
if (scheduledThreadPoolExecutor == null)
|
if (scheduledThreadPoolExecutor == null)
|
||||||
return;
|
return;
|
||||||
@@ -219,4 +247,11 @@ public final class SystemInitializer implements Refreshable, Clearable {
|
|||||||
runningInstances.clear();
|
runningInstances.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取定时IP供应器
|
||||||
|
*/
|
||||||
|
public ScheduledProvider getScheduledProvider() {
|
||||||
|
return scheduledProvider;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package com.serliunx.ddns.support.command;
|
||||||
|
|
||||||
|
import com.serliunx.ddns.ManagerLite;
|
||||||
|
import org.jline.reader.Candidate;
|
||||||
|
import org.jline.reader.LineReader;
|
||||||
|
import org.jline.reader.ParsedLine;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static com.serliunx.ddns.support.ConsoleStyleHelper.coloredPrintf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指令的抽象实现
|
||||||
|
* <li> 实现公共逻辑及定义具体逻辑
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
||||||
|
* @version 1.0.4
|
||||||
|
* @since 2025/1/15
|
||||||
|
*/
|
||||||
|
public abstract class AbstractCommand implements Command {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final List<Command> subCommands;
|
||||||
|
private final String description;
|
||||||
|
private final String usage;
|
||||||
|
|
||||||
|
protected final Logger log = ManagerLite.getLogger();
|
||||||
|
|
||||||
|
public AbstractCommand(String name, List<Command> subCommands, String description, String usage) {
|
||||||
|
this.name = name;
|
||||||
|
this.subCommands = subCommands;
|
||||||
|
this.description = description;
|
||||||
|
this.usage = usage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Command> getSubCommands() {
|
||||||
|
return subCommands;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void addSubCommand(Command command) {
|
||||||
|
subCommands.add(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUsage() {
|
||||||
|
return usage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指令逻辑默认实现: 调用子命令
|
||||||
|
*
|
||||||
|
* @param args 当前指令参数
|
||||||
|
* @return 成功执行返回真, 否则返回假. (目前没影响)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(String[] args) {
|
||||||
|
if (!hasArgs(args) ||
|
||||||
|
args.length < 1) {
|
||||||
|
System.out.println();
|
||||||
|
coloredPrintf("&2用法 =>&r &6%s", getUsage());
|
||||||
|
System.out.println();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String subCommand = args[0];
|
||||||
|
List<Command> subCommands = getSubCommands();
|
||||||
|
for (Command command : subCommands) {
|
||||||
|
if (command.getName().equalsIgnoreCase(subCommand)) {
|
||||||
|
return command.onCommand(CommandDispatcher.splitArgs(args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getArgs() {
|
||||||
|
if (subCommands == null ||
|
||||||
|
subCommands.isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return subCommands.stream()
|
||||||
|
.map(Command::getName)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onComplete(LineReader reader, ParsedLine line, int index, List<Candidate> candidates) {
|
||||||
|
if (index < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String currentWord = line.word();
|
||||||
|
// 补全子命令
|
||||||
|
final List<Command> subCommands = getSubCommands();
|
||||||
|
if (index == 1) {
|
||||||
|
subCommands.forEach(c -> {
|
||||||
|
if (c.getName().startsWith(currentWord)) {
|
||||||
|
candidates.add(new Candidate(c.getName()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else { // 交给子命令补全
|
||||||
|
for (Command c : subCommands) {
|
||||||
|
if (c.getName().equals(line.words().get(1))) {
|
||||||
|
c.onComplete(reader, line, index, candidates);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean hasArgs(String[] args) {
|
||||||
|
return args.length > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
76
src/main/java/com/serliunx/ddns/support/command/Command.java
Normal file
76
src/main/java/com/serliunx/ddns/support/command/Command.java
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package com.serliunx.ddns.support.command;
|
||||||
|
|
||||||
|
import org.jline.reader.Candidate;
|
||||||
|
import org.jline.reader.LineReader;
|
||||||
|
import org.jline.reader.ParsedLine;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指令接口定义
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
||||||
|
* @version 1.0.4
|
||||||
|
* @since 2025/1/15
|
||||||
|
*/
|
||||||
|
public interface Command {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指令执行逻辑
|
||||||
|
*
|
||||||
|
* @param args 当前指令参数
|
||||||
|
* @return 成功执行返回真, 否则返回假. (目前没影响)
|
||||||
|
*/
|
||||||
|
boolean onCommand(String[] args);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指令名称
|
||||||
|
*/
|
||||||
|
String getName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取子命令
|
||||||
|
* <li> 例: cmd c1 c2, 此时 c1为cmd的子命令
|
||||||
|
*
|
||||||
|
* @return 子命令
|
||||||
|
*/
|
||||||
|
List<Command> getSubCommands();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加子命令
|
||||||
|
*
|
||||||
|
* @param command 子命令
|
||||||
|
*/
|
||||||
|
void addSubCommand(Command command);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取该指令的描述
|
||||||
|
*/
|
||||||
|
String getDescription();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取该指令的用法
|
||||||
|
*/
|
||||||
|
String getUsage();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取参数列表
|
||||||
|
*
|
||||||
|
* @return 参数
|
||||||
|
*/
|
||||||
|
default List<String> getArgs() {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 命令参数补全
|
||||||
|
*
|
||||||
|
* @param reader Jline的LineReader{@link LineReader}
|
||||||
|
* @param line 当前命令行的内容
|
||||||
|
* @param candidates 候选参数列表
|
||||||
|
*/
|
||||||
|
default void onComplete(LineReader reader, ParsedLine line, int index, List<Candidate> candidates) {
|
||||||
|
// do nothing by default.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.serliunx.ddns.support.command;
|
||||||
|
|
||||||
|
import org.jline.reader.Candidate;
|
||||||
|
import org.jline.reader.Completer;
|
||||||
|
import org.jline.reader.LineReader;
|
||||||
|
import org.jline.reader.ParsedLine;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Jline 命令补全器
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
||||||
|
* @version 1.0.4
|
||||||
|
* @since 2025/2/4
|
||||||
|
*/
|
||||||
|
public class CommandCompleter implements Completer {
|
||||||
|
|
||||||
|
private final CommandDispatcher commandDispatcher;
|
||||||
|
|
||||||
|
public CommandCompleter(CommandDispatcher commandDispatcher) {
|
||||||
|
this.commandDispatcher = commandDispatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void complete(LineReader reader, ParsedLine line, List<Candidate> candidates) {
|
||||||
|
final String currentWord = line.word();
|
||||||
|
Map<String, Command> commands = commandDispatcher.getCommands();
|
||||||
|
if (commands == null ||
|
||||||
|
commands.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第一个参数补全所有指令
|
||||||
|
if (line.wordIndex() == 0) {
|
||||||
|
commands.keySet().forEach(k -> {
|
||||||
|
if (k.startsWith(currentWord)) {
|
||||||
|
candidates.add(new Candidate(k));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else { // 第二个及以后交由具体的指令进行补全逻辑
|
||||||
|
final Command command = commands.get(line.words().get(0));
|
||||||
|
if (command == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
command.onComplete(reader, line, line.wordIndex(), candidates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package com.serliunx.ddns.support.command;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static com.serliunx.ddns.support.ConsoleStyleHelper.coloredPrintf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指令调度器
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
||||||
|
* @version 1.0.4
|
||||||
|
* @since 2025/1/15
|
||||||
|
*/
|
||||||
|
public final class CommandDispatcher {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(CommandDispatcher.class);
|
||||||
|
private static final CommandDispatcher INSTANCE = new CommandDispatcher();
|
||||||
|
|
||||||
|
// private-ctor
|
||||||
|
private CommandDispatcher() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最顶层指令缓存
|
||||||
|
*/
|
||||||
|
private final Map<String, Command> commands = new LinkedHashMap<>(128);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有已注册的指令
|
||||||
|
*
|
||||||
|
* @return 已注册的指令
|
||||||
|
*/
|
||||||
|
public Map<String, Command> getCommands() {
|
||||||
|
return commands;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指令注册
|
||||||
|
*
|
||||||
|
* @param command 指令
|
||||||
|
*/
|
||||||
|
public synchronized void register(Command command) {
|
||||||
|
commands.put(command.getName(), command);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指令反注册
|
||||||
|
*
|
||||||
|
* @param command 指令
|
||||||
|
*/
|
||||||
|
public synchronized void unregister(Command command) {
|
||||||
|
commands.remove(command.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理输入的指令
|
||||||
|
*
|
||||||
|
* @param input 指令
|
||||||
|
*/
|
||||||
|
public void onCommand(String input) {
|
||||||
|
if (input == null ||
|
||||||
|
input.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] args = input.split(" ");
|
||||||
|
String cmd = args[0];
|
||||||
|
|
||||||
|
Command command = commands.get(cmd);
|
||||||
|
if (command == null) {
|
||||||
|
System.out.println();
|
||||||
|
coloredPrintf("&1未知指令&r: &2%s&r, &1请输入 &3help&r &1查看帮助!", cmd);
|
||||||
|
System.out.println();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!command.onCommand(splitArgs(args))) {
|
||||||
|
coloredPrintf("&1指令执行出现了错误:&r &5%s", Arrays.toString(args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分割指令参数
|
||||||
|
* <li> cmd x1 x2 => x1 x2
|
||||||
|
*
|
||||||
|
* @param args 参数
|
||||||
|
* @return 去除指令本身的参数部分
|
||||||
|
*/
|
||||||
|
public static String[] splitArgs(String[] args) {
|
||||||
|
String[] newArgs = new String[args.length - 1];
|
||||||
|
System.arraycopy(args, 1, newArgs, 0, args.length - 1);
|
||||||
|
return newArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取实例
|
||||||
|
*/
|
||||||
|
public static CommandDispatcher getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
package com.serliunx.ddns.support.command.target;
|
||||||
|
|
||||||
|
import com.serliunx.ddns.support.command.AbstractCommand;
|
||||||
|
import com.serliunx.ddns.support.command.Command;
|
||||||
|
import com.serliunx.ddns.support.command.CommandDispatcher;
|
||||||
|
import org.jline.reader.Candidate;
|
||||||
|
import org.jline.reader.LineReader;
|
||||||
|
import org.jline.reader.ParsedLine;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static com.serliunx.ddns.support.ConsoleStyleHelper.coloredPrintf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指令: help
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
||||||
|
* @version 1.0.4
|
||||||
|
* @since 2025/1/15
|
||||||
|
*/
|
||||||
|
public class HelpCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
public HelpCommand() {
|
||||||
|
super("help", null, "查看帮助信息", "help <指令>");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(String[] args) {
|
||||||
|
final Map<String, Command> commands = getAllCommands();
|
||||||
|
|
||||||
|
if (hasArgs(args)) {
|
||||||
|
final String cmd = args[0];
|
||||||
|
final Command command = commands.get(cmd);
|
||||||
|
System.out.println();
|
||||||
|
if (command == null) {
|
||||||
|
coloredPrintf("&1无法找到指令 %s 的相关信息, 请使用 help 查看可用的指令及帮助!%n", cmd);
|
||||||
|
} else {
|
||||||
|
List<Command> subCommands = command.getSubCommands();
|
||||||
|
if (subCommands == null ||
|
||||||
|
subCommands.isEmpty()) {
|
||||||
|
coloredPrintf("&2%s&r - &6%s&r - &5%s%n", cmd, command.getDescription(), command.getUsage());
|
||||||
|
} else {
|
||||||
|
subCommands.forEach(c -> {
|
||||||
|
coloredPrintf("&2%s&r - &6%s&r - &5%s%n", c.getName(), c.getDescription(), c.getUsage());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println();
|
||||||
|
} else {
|
||||||
|
printCommandDetails(commands);
|
||||||
|
coloredPrintf("&6&l使用 help <指令> 来查看更详细的帮助信息.");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getArgs() {
|
||||||
|
final Map<String, Command> commands = getAllCommands();
|
||||||
|
if (commands == null ||
|
||||||
|
commands.isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
return new ArrayList<>(commands.keySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onComplete(LineReader reader, ParsedLine line, int index, List<Candidate> candidates) {
|
||||||
|
final Map<String, Command> commands = getAllCommands();
|
||||||
|
if (commands == null ||
|
||||||
|
commands.isEmpty() || index < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String currentWord = line.word();
|
||||||
|
|
||||||
|
if (index != 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
commands.keySet().forEach(k -> {
|
||||||
|
if (k.startsWith(currentWord) &&
|
||||||
|
!k.equals("help")) {
|
||||||
|
candidates.add(new Candidate(k));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有指令
|
||||||
|
*/
|
||||||
|
private Map<String, Command> getAllCommands() {
|
||||||
|
return CommandDispatcher.getInstance().getCommands();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 输出指令详细信息, 包括子命令及参数信息.
|
||||||
|
*
|
||||||
|
* @param commands 指令集合
|
||||||
|
*/
|
||||||
|
private void printCommandDetails(final Map<String, Command> commands) {
|
||||||
|
if (commands == null || commands.isEmpty())
|
||||||
|
return;
|
||||||
|
System.out.println();
|
||||||
|
System.out.println();
|
||||||
|
commands.forEach((k, v) -> {
|
||||||
|
coloredPrintf("&2%s&r - &6%s&r", k, v.getDescription());
|
||||||
|
coloredPrintf("\t&5用法:&r &3%s", v.getUsage());
|
||||||
|
final List<Command> subCommands = v.getSubCommands();
|
||||||
|
if (subCommands == null || subCommands.isEmpty()) {
|
||||||
|
coloredPrintf("\t&5参数:&r 无");
|
||||||
|
} else {
|
||||||
|
coloredPrintf("\t&5参数:");
|
||||||
|
subCommands.forEach(c -> {
|
||||||
|
coloredPrintf("\t&2%s&r - &6%s&r", c.getName(), c.getDescription());
|
||||||
|
coloredPrintf("\t\t&5用法:&r &3%s", c.getUsage());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
System.out.println();
|
||||||
|
});
|
||||||
|
System.out.println();
|
||||||
|
System.out.println();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package com.serliunx.ddns.support.command.target;
|
||||||
|
|
||||||
|
import com.serliunx.ddns.config.Configuration;
|
||||||
|
import com.serliunx.ddns.support.SystemInitializer;
|
||||||
|
import com.serliunx.ddns.support.command.AbstractCommand;
|
||||||
|
import com.serliunx.ddns.support.ipprovider.ScheduledProvider;
|
||||||
|
|
||||||
|
import static com.serliunx.ddns.constant.ConfigurationKeys.KEY_TASK_REFRESH_INTERVAL_IP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指令: reload
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
||||||
|
* @version 1.0.4
|
||||||
|
* @since 2025/1/16
|
||||||
|
*/
|
||||||
|
public class ReloadCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置信息
|
||||||
|
*/
|
||||||
|
private final Configuration configuration;
|
||||||
|
/**
|
||||||
|
* 系统初始化组件
|
||||||
|
*/
|
||||||
|
private final SystemInitializer systemInitializer;
|
||||||
|
|
||||||
|
public ReloadCommand(Configuration configuration, SystemInitializer systemInitializer) {
|
||||||
|
super("reload", null, "重新载入配置文件.", "reload");
|
||||||
|
this.configuration = configuration;
|
||||||
|
this.systemInitializer = systemInitializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(String[] args) {
|
||||||
|
log.info("正在重新载入配置文件...");
|
||||||
|
if (configuration == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
long oldIpInterval = getIpInterval();
|
||||||
|
configuration.refresh();
|
||||||
|
|
||||||
|
// 更新定时查询IP任务
|
||||||
|
triggerScheduledProvider(oldIpInterval);
|
||||||
|
|
||||||
|
log.info("配置文件已重新载入!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取更新周期
|
||||||
|
*/
|
||||||
|
private long getIpInterval() {
|
||||||
|
return configuration.getLong(KEY_TASK_REFRESH_INTERVAL_IP, 300L);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新定时查询IP任务
|
||||||
|
*/
|
||||||
|
private void triggerScheduledProvider(long oldIpInterval) {
|
||||||
|
final ScheduledProvider scheduledProvider = systemInitializer.getScheduledProvider();
|
||||||
|
final long newIpInterval = getIpInterval();
|
||||||
|
if (scheduledProvider != null &&
|
||||||
|
oldIpInterval != newIpInterval) {
|
||||||
|
scheduledProvider.changeTimePeriod(newIpInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.serliunx.ddns.support.command.target;
|
||||||
|
|
||||||
|
import com.serliunx.ddns.support.command.AbstractCommand;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指令: stop
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
||||||
|
* @version 1.0.4
|
||||||
|
* @since 2025/2/4
|
||||||
|
*/
|
||||||
|
public final class StopCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
public StopCommand() {
|
||||||
|
super("stop", null, "退出程序", "stop");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(String[] args) {
|
||||||
|
System.exit(0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.serliunx.ddns.support.command.target.config;
|
||||||
|
|
||||||
|
import com.serliunx.ddns.config.Configuration;
|
||||||
|
import com.serliunx.ddns.support.command.AbstractCommand;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指令: config
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
||||||
|
* @version 1.0.4
|
||||||
|
* @since 2025/1/22
|
||||||
|
*/
|
||||||
|
public class ConfigCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
public ConfigCommand(Configuration configuration) {
|
||||||
|
super("config", new ArrayList<>(), "调整配置信息", "config <get/set/...>");
|
||||||
|
// 子命令: set
|
||||||
|
addSubCommand(new ConfigSetCommand(configuration));
|
||||||
|
// 子命令: get
|
||||||
|
addSubCommand(new ConfigGetCommand(configuration));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package com.serliunx.ddns.support.command.target.config;
|
||||||
|
|
||||||
|
import com.serliunx.ddns.config.Configuration;
|
||||||
|
import org.jline.reader.Candidate;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* config 指令相关工具方法
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
||||||
|
* @version 1.0.4
|
||||||
|
* @since 2025/2/4
|
||||||
|
*/
|
||||||
|
final class ConfigCommandHelper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有配置键, 作为参数返回
|
||||||
|
*
|
||||||
|
* @param configuration 配置信息
|
||||||
|
* @return 配置键集合
|
||||||
|
*/
|
||||||
|
static List<String> getArgs(Configuration configuration) {
|
||||||
|
final Map<String, String> allKeyAndValue;
|
||||||
|
if (configuration == null ||
|
||||||
|
(allKeyAndValue = configuration.getAllKeyAndValue()) == null) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
return new ArrayList<>(allKeyAndValue.keySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 补全配置键
|
||||||
|
*
|
||||||
|
* @param configuration 配置键
|
||||||
|
* @param currentWord 当前输入内容
|
||||||
|
* @param candidates 候选参数列表
|
||||||
|
*/
|
||||||
|
static void completeConfigKeys(Configuration configuration, String currentWord, List<Candidate> candidates) {
|
||||||
|
final Map<String, String> allKeyAndValue;
|
||||||
|
if (configuration == null ||
|
||||||
|
(allKeyAndValue = configuration.getAllKeyAndValue()) == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
allKeyAndValue.keySet().forEach(k -> {
|
||||||
|
if (k.startsWith(currentWord)) {
|
||||||
|
candidates.add(new Candidate(k));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package com.serliunx.ddns.support.command.target.config;
|
||||||
|
|
||||||
|
import com.serliunx.ddns.config.Configuration;
|
||||||
|
import com.serliunx.ddns.support.command.AbstractCommand;
|
||||||
|
import org.jline.reader.Candidate;
|
||||||
|
import org.jline.reader.LineReader;
|
||||||
|
import org.jline.reader.ParsedLine;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.serliunx.ddns.support.ConsoleStyleHelper.coloredPrintf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指令: config get
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
||||||
|
* @version 1.0.4
|
||||||
|
* @since 2025/2/4
|
||||||
|
*/
|
||||||
|
public final class ConfigGetCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置信息
|
||||||
|
*/
|
||||||
|
private final Configuration configuration;
|
||||||
|
|
||||||
|
public ConfigGetCommand(Configuration configuration) {
|
||||||
|
super("get", null, "获取指定配置项的值", "config get <配置项>");
|
||||||
|
this.configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(String[] args) {
|
||||||
|
if (!hasArgs(args) ||
|
||||||
|
args.length < 1) {
|
||||||
|
System.out.println();
|
||||||
|
coloredPrintf("&2用法 =>&r &6%s", getUsage());
|
||||||
|
System.out.println();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
System.out.println(configuration.getString(args[0]));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getArgs() {
|
||||||
|
return ConfigCommandHelper.getArgs(configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onComplete(LineReader reader, ParsedLine line, int index, List<Candidate> candidates) {
|
||||||
|
if (index == 2)
|
||||||
|
ConfigCommandHelper.completeConfigKeys(configuration, line.word(), candidates);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package com.serliunx.ddns.support.command.target.config;
|
||||||
|
|
||||||
|
import com.serliunx.ddns.config.Configuration;
|
||||||
|
import com.serliunx.ddns.support.command.AbstractCommand;
|
||||||
|
import org.jline.reader.Candidate;
|
||||||
|
import org.jline.reader.LineReader;
|
||||||
|
import org.jline.reader.ParsedLine;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.serliunx.ddns.support.ConsoleStyleHelper.coloredPrintf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指令: config set
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
||||||
|
* @version 1.0.4
|
||||||
|
* @since 2025/2/4
|
||||||
|
*/
|
||||||
|
public final class ConfigSetCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置信息
|
||||||
|
*/
|
||||||
|
private final Configuration configuration;
|
||||||
|
|
||||||
|
public ConfigSetCommand(Configuration configuration) {
|
||||||
|
super("set", null, "设置指定配置项的值", "config set <配置项> <新的值>");
|
||||||
|
this.configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(String[] args) {
|
||||||
|
if (!hasArgs(args) ||
|
||||||
|
args.length < 2) {
|
||||||
|
System.out.println();
|
||||||
|
coloredPrintf("&2用法 =>&r &6%s", getUsage());
|
||||||
|
System.out.println();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
final String target = args[0];
|
||||||
|
final String value = args[1];
|
||||||
|
return configuration.modify(target, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getArgs() {
|
||||||
|
return ConfigCommandHelper.getArgs(configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onComplete(LineReader reader, ParsedLine line, int index, List<Candidate> candidates) {
|
||||||
|
if (index == 2)
|
||||||
|
ConfigCommandHelper.completeConfigKeys(configuration, line.word(), candidates);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.serliunx.ddns.support.command.target.instance;
|
||||||
|
|
||||||
|
import com.serliunx.ddns.support.SystemInitializer;
|
||||||
|
import com.serliunx.ddns.support.command.AbstractCommand;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指令: instance
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
||||||
|
* @version 1.0.4
|
||||||
|
* @since 2025/2/4
|
||||||
|
*/
|
||||||
|
public final class InstanceCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
public InstanceCommand(SystemInitializer systemInitializer) {
|
||||||
|
super("instance", new ArrayList<>(), "实例相关指令", "instance list/add/...");
|
||||||
|
// 子命令: list
|
||||||
|
addSubCommand(new InstanceListCommand(systemInitializer));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package com.serliunx.ddns.support.command.target.instance;
|
||||||
|
|
||||||
|
import com.serliunx.ddns.core.instance.Instance;
|
||||||
|
import com.serliunx.ddns.support.Assert;
|
||||||
|
import com.serliunx.ddns.support.ConsoleStyleHelper;
|
||||||
|
import com.serliunx.ddns.support.SystemInitializer;
|
||||||
|
import com.serliunx.ddns.support.command.AbstractCommand;
|
||||||
|
import org.jline.reader.Candidate;
|
||||||
|
import org.jline.reader.LineReader;
|
||||||
|
import org.jline.reader.ParsedLine;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指令: instance list
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
||||||
|
* @version 1.0.4
|
||||||
|
* @since 2025/2/4
|
||||||
|
*/
|
||||||
|
public final class InstanceListCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
private final SystemInitializer systemInitializer;
|
||||||
|
|
||||||
|
public InstanceListCommand(SystemInitializer systemInitializer) {
|
||||||
|
super("list", new ArrayList<>(), "列出所有实例", "instance list");
|
||||||
|
Assert.notNull(systemInitializer);
|
||||||
|
this.systemInitializer = systemInitializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(String[] args) {
|
||||||
|
Set<Instance> instances = systemInitializer.getInstances();
|
||||||
|
System.out.println();
|
||||||
|
|
||||||
|
instances.forEach(i -> {
|
||||||
|
ConsoleStyleHelper.coloredPrintf("&2%s&r(&3%s&r)", i.getName(), i.getType());
|
||||||
|
});
|
||||||
|
|
||||||
|
System.out.println();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getArgs() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onComplete(LineReader reader, ParsedLine line, int index, List<Candidate> candidates) {
|
||||||
|
//do nothing for list
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.serliunx.ddns.support.ipprovider;
|
||||||
|
|
||||||
|
import com.serliunx.ddns.support.okhttp.HttpClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ip数据提供商 <a href="https://icanhazip.com/">icanhazip</a>
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
||||||
|
* @since 2024/12/6
|
||||||
|
*/
|
||||||
|
public final class IcanhazipProvider extends AbstractProvider {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String doGet() {
|
||||||
|
return HttpClient.httpGet("https://icanhazip.com/");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
package com.serliunx.ddns.support.ipprovider;
|
package com.serliunx.ddns.support.ipprovider;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||||
import com.serliunx.ddns.support.okhttp.HttpClient;
|
import com.serliunx.ddns.support.okhttp.HttpClient;
|
||||||
import com.serliunx.ddns.support.okhttp.IPAddressResponse;
|
import com.serliunx.ddns.support.okhttp.IPAddressResponse;
|
||||||
|
|
||||||
@@ -13,12 +16,22 @@ import com.serliunx.ddns.support.okhttp.IPAddressResponse;
|
|||||||
*/
|
*/
|
||||||
public final class IpApiProvider extends AbstractProvider {
|
public final class IpApiProvider extends AbstractProvider {
|
||||||
|
|
||||||
|
private static final ObjectMapper JSON_MAPPER = new JsonMapper();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String doGet() {
|
protected String doGet() {
|
||||||
IPAddressResponse response = HttpClient.getIPAddress();
|
final String response = HttpClient.httpGet("http://ip-api.com/json");
|
||||||
if (response == null) {
|
if (response == null
|
||||||
|
|| response.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return response.getQuery();
|
|
||||||
|
try {
|
||||||
|
IPAddressResponse ipAddressResponse = JSON_MAPPER.readValue(response, IPAddressResponse.class);
|
||||||
|
|
||||||
|
return ipAddressResponse.getQuery();
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.serliunx.ddns.support.ipprovider;
|
|||||||
|
|
||||||
import com.serliunx.ddns.support.Assert;
|
import com.serliunx.ddns.support.Assert;
|
||||||
import com.serliunx.ddns.support.InstanceContextHolder;
|
import com.serliunx.ddns.support.InstanceContextHolder;
|
||||||
|
import com.serliunx.ddns.support.thread.ThreadFactoryBuilder;
|
||||||
|
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||||
@@ -11,12 +12,13 @@ import java.util.function.Consumer;
|
|||||||
/**
|
/**
|
||||||
* 自动更新的ip供应器
|
* 自动更新的ip供应器
|
||||||
* <li> 异步更新ip, 获取到的ip地址不一定为最新可用的。
|
* <li> 异步更新ip, 获取到的ip地址不一定为最新可用的。
|
||||||
|
* <li> 也可作为简单的任务提交到线程池中执行.
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
||||||
* @version 1.0.3
|
* @version 1.0.3
|
||||||
* @since 2024/11/25
|
* @since 2024/11/25
|
||||||
*/
|
*/
|
||||||
public class ScheduledProvider extends AbstractProvider {
|
public class ScheduledProvider extends AbstractProvider implements AutoCloseable, Runnable {
|
||||||
|
|
||||||
private final Provider internalProvider;
|
private final Provider internalProvider;
|
||||||
|
|
||||||
@@ -37,6 +39,10 @@ public class ScheduledProvider extends AbstractProvider {
|
|||||||
* 处理器
|
* 处理器
|
||||||
*/
|
*/
|
||||||
private Consumer<String> valueConsumer = null;
|
private Consumer<String> valueConsumer = null;
|
||||||
|
/**
|
||||||
|
* 内置缓存
|
||||||
|
*/
|
||||||
|
private volatile String internalCache = null;
|
||||||
|
|
||||||
public ScheduledProvider(Provider internalProvider, long timePeriod) {
|
public ScheduledProvider(Provider internalProvider, long timePeriod) {
|
||||||
Assert.notNull(internalProvider);
|
Assert.notNull(internalProvider);
|
||||||
@@ -50,14 +56,26 @@ public class ScheduledProvider extends AbstractProvider {
|
|||||||
this(internalProvider, 60);
|
this(internalProvider, 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
poolExecutor.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
doAction();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String get() {
|
public String get() {
|
||||||
return cache;
|
return internalCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init() {
|
public void init() {
|
||||||
poolExecutor = new ScheduledThreadPoolExecutor(2);
|
poolExecutor = new ScheduledThreadPoolExecutor(2, ThreadFactoryBuilder.builder()
|
||||||
|
.ofNamePattern("ip-provider-%s")
|
||||||
|
);
|
||||||
// 提交
|
// 提交
|
||||||
submitTask();
|
submitTask();
|
||||||
}
|
}
|
||||||
@@ -95,18 +113,32 @@ public class ScheduledProvider extends AbstractProvider {
|
|||||||
* 提交任务逻辑
|
* 提交任务逻辑
|
||||||
*/
|
*/
|
||||||
private void submitTask() {
|
private void submitTask() {
|
||||||
task = poolExecutor.scheduleAtFixedRate(() -> {
|
task = poolExecutor.scheduleAtFixedRate(this, 0, timePeriod, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行逻辑
|
||||||
|
*/
|
||||||
|
private synchronized void doAction() {
|
||||||
// 打断时, 终止已有的任务. (逻辑上不应该发生)
|
// 打断时, 终止已有的任务. (逻辑上不应该发生)
|
||||||
if (Thread.currentThread().isInterrupted()) {
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
log.debug("上一个ip更新任务已终止.");
|
log.debug("上一个ip更新任务已终止.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
InstanceContextHolder.setAdditional("ip-update");
|
InstanceContextHolder.setAdditional("ip-update");
|
||||||
cache = internalProvider.get();
|
String rawValue = internalProvider.get();
|
||||||
|
if (rawValue == null || rawValue.isEmpty()) {
|
||||||
|
internalCache = null;
|
||||||
|
} else {
|
||||||
|
internalCache = rawValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (internalCache != null) {
|
||||||
|
internalCache = internalCache.trim();
|
||||||
|
}
|
||||||
|
|
||||||
if (valueConsumer != null) {
|
if (valueConsumer != null) {
|
||||||
valueConsumer.accept(cache);
|
valueConsumer.accept(internalCache);
|
||||||
}
|
}
|
||||||
}, 0, timePeriod, TimeUnit.SECONDS);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.serliunx.ddns.support.log;
|
||||||
|
|
||||||
|
import ch.qos.logback.classic.PatternLayout;
|
||||||
|
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||||
|
import ch.qos.logback.core.AppenderBase;
|
||||||
|
import ch.qos.logback.core.Layout;
|
||||||
|
import org.jline.reader.LineReader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 适配JLine的控制台日志输出
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
||||||
|
* @since 2025/1/15
|
||||||
|
*/
|
||||||
|
public final class JLineAdaptAppender extends AppenderBase<ILoggingEvent> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JLine的输入读取
|
||||||
|
*/
|
||||||
|
private static LineReader lineReader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式控制
|
||||||
|
*/
|
||||||
|
private Layout<ILoggingEvent> layout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所配置的格式
|
||||||
|
*/
|
||||||
|
private String pattern;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
super.start();
|
||||||
|
PatternLayout patternLayout = new PatternLayout();
|
||||||
|
patternLayout.setPattern(pattern);
|
||||||
|
patternLayout.setContext(getContext());
|
||||||
|
patternLayout.start();
|
||||||
|
this.layout = patternLayout;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void append(ILoggingEvent event) {
|
||||||
|
if (lineReader != null) {
|
||||||
|
String formattedLog = layout.doLayout(event);
|
||||||
|
lineReader.printAbove(formattedLog);
|
||||||
|
} else {
|
||||||
|
System.out.print(layout.doLayout(event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置输入读取
|
||||||
|
*
|
||||||
|
* @param lr 读取
|
||||||
|
*/
|
||||||
|
public static void setLineReader(LineReader lr) {
|
||||||
|
lineReader = lr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public void setPattern(String pattern) {
|
||||||
|
this.pattern = pattern;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
package com.serliunx.ddns.support.okhttp;
|
package com.serliunx.ddns.support.okhttp;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import com.fasterxml.jackson.databind.json.JsonMapper;
|
|
||||||
import com.serliunx.ddns.config.Configuration;
|
import com.serliunx.ddns.config.Configuration;
|
||||||
import com.serliunx.ddns.config.ConfigurationKeys;
|
import com.serliunx.ddns.constant.ConfigurationKeys;
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -22,10 +21,9 @@ import java.util.concurrent.TimeUnit;
|
|||||||
*/
|
*/
|
||||||
public final class HttpClient {
|
public final class HttpClient {
|
||||||
|
|
||||||
private static OkHttpClient CLIENT = null;
|
private static OkHttpClient CLIENT;
|
||||||
|
|
||||||
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 int DEFAULT_OVERTIME = 3;
|
private static final int DEFAULT_OVERTIME = 3;
|
||||||
|
|
||||||
private HttpClient() {throw new UnsupportedOperationException();}
|
private HttpClient() {throw new UnsupportedOperationException();}
|
||||||
@@ -39,29 +37,28 @@ public final class HttpClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取本机的ip地址
|
* 发送GET请求
|
||||||
*
|
*
|
||||||
* @return 响应结果
|
* @param url 请求地址
|
||||||
|
* @return 响应
|
||||||
*/
|
*/
|
||||||
public static IPAddressResponse getIPAddress() {
|
public static String httpGet(String url) {
|
||||||
Request request = new Request.Builder()
|
final Request request = new Request.Builder()
|
||||||
.url("http://ip-api.com/json")
|
.url(url)
|
||||||
.get()
|
.get()
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
try (Response response = CLIENT.newCall(request).execute()) {
|
try (Response response = CLIENT.newCall(request).execute()) {
|
||||||
if (!response.isSuccessful() || response.body() == null) {
|
if (!response.isSuccessful()
|
||||||
|
|| response.body() == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
String body = response.body().string();
|
final ResponseBody responseBody = response.body();
|
||||||
if (body.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return JSON_MAPPER.readValue(body, IPAddressResponse.class);
|
return responseBody.string();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("ip地址获取异常:", e);
|
log.error("http 接口异常:", e);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,11 +20,10 @@ import java.util.concurrent.locks.ReentrantLock;
|
|||||||
public final class SQLiteConnector implements Refreshable {
|
public final class SQLiteConnector implements Refreshable {
|
||||||
|
|
||||||
private volatile Connection connection;
|
private volatile Connection connection;
|
||||||
|
private volatile boolean initialized = false;
|
||||||
|
|
||||||
private final Lock initLock = new ReentrantLock();
|
private final Lock initLock = new ReentrantLock();
|
||||||
|
|
||||||
private volatile boolean initialized = false;
|
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(SQLiteConnector.class);
|
private static final Logger log = LoggerFactory.getLogger(SQLiteConnector.class);
|
||||||
private static final SQLiteConnector INSTANCE = new SQLiteConnector();
|
private static final SQLiteConnector INSTANCE = new SQLiteConnector();
|
||||||
|
|
||||||
@@ -53,6 +52,9 @@ public final class SQLiteConnector implements Refreshable {
|
|||||||
log.info("initialing sqlite connection.");
|
log.info("initialing sqlite connection.");
|
||||||
connection = DriverManager.getConnection(SystemConstants.SQLITE_URL);
|
connection = DriverManager.getConnection(SystemConstants.SQLITE_URL);
|
||||||
|
|
||||||
|
// 尝试创建数据库表, 只会执行一次
|
||||||
|
tryCreateTables();
|
||||||
|
|
||||||
initialized = true;
|
initialized = true;
|
||||||
log.info("sqlite connection successfully initialized.");
|
log.info("sqlite connection successfully initialized.");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -63,6 +65,16 @@ public final class SQLiteConnector implements Refreshable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 尝试创建数据库表
|
||||||
|
* <li> 不存在时创建
|
||||||
|
*/
|
||||||
|
private void tryCreateTables() {
|
||||||
|
if (connection == null) {
|
||||||
|
throw new IllegalStateException("sql connection not initialized");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否已经初始化
|
* 是否已经初始化
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package com.serliunx.ddns.support.thread;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.concurrent.ThreadFactory;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 线程工厂构建
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
||||||
|
* @version 1.0.3
|
||||||
|
* @since 2024/12/3
|
||||||
|
*/
|
||||||
|
public final class ThreadFactoryBuilder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实例
|
||||||
|
*/
|
||||||
|
private static final ThreadFactoryBuilder INSTANCE = new ThreadFactoryBuilder();
|
||||||
|
|
||||||
|
private ThreadFactoryBuilder() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取实例
|
||||||
|
*/
|
||||||
|
public static ThreadFactoryBuilder builder() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 线程工厂之模板名称
|
||||||
|
*
|
||||||
|
* @param pattern 名称模板(如: task-util-%s), %s将根据数量递增
|
||||||
|
* @return 线程工厂
|
||||||
|
*/
|
||||||
|
public ThreadFactory ofNamePattern(final String pattern) {
|
||||||
|
return new NamePatternThreadFactory(pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 线程工厂之模板名称
|
||||||
|
*/
|
||||||
|
private static class NamePatternThreadFactory implements ThreadFactory {
|
||||||
|
|
||||||
|
private final AtomicInteger counter = new AtomicInteger(0);
|
||||||
|
|
||||||
|
private final String pattern;
|
||||||
|
|
||||||
|
public NamePatternThreadFactory(String pattern) {
|
||||||
|
this.pattern = pattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Thread newThread(@NotNull Runnable r) {
|
||||||
|
return new Thread(r, String.format(pattern, counter.getAndIncrement()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
package com.serliunx.ddns.thread;
|
|
||||||
|
|
||||||
import com.serliunx.ddns.support.Assert;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.concurrent.ThreadFactory;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 简易的实例活动相关的线程工厂, 仅仅定义了线程的名称规则.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
|
||||||
* @version 1.0.0
|
|
||||||
* @since 2024/5/15
|
|
||||||
*/
|
|
||||||
public class TaskThreadFactory implements ThreadFactory {
|
|
||||||
|
|
||||||
private final AtomicInteger count = new AtomicInteger(0);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Thread newThread(@NotNull Runnable r) {
|
|
||||||
Assert.notNull(r);
|
|
||||||
return new Thread(r, String.format(getNamePattern(), count.getAndIncrement()));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String getNamePattern() {
|
|
||||||
return "ddns-task-%s";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package com.serliunx.ddns.thread;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 同 {@link TaskThreadFactory}, 暂未使用.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
|
||||||
* @version 1.0.0
|
|
||||||
* @since 2024/5/15
|
|
||||||
*/
|
|
||||||
public class UtilThreadFactory extends TaskThreadFactory {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getNamePattern() {
|
|
||||||
return "ddns-util-%s";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,18 +3,15 @@
|
|||||||
<conversionRule conversionWord="instance" converterClass="com.serliunx.ddns.support.log.InstanceNameConverter"/>
|
<conversionRule conversionWord="instance" converterClass="com.serliunx.ddns.support.log.InstanceNameConverter"/>
|
||||||
<conversionRule conversionWord="highlight" converterClass="com.serliunx.ddns.support.log.HighlightingCompositeConverter"/>
|
<conversionRule conversionWord="highlight" converterClass="com.serliunx.ddns.support.log.HighlightingCompositeConverter"/>
|
||||||
|
|
||||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
<appender name="JLINE" class="com.serliunx.ddns.support.log.JLineAdaptAppender">
|
||||||
<encoder>
|
|
||||||
<pattern>
|
<pattern>
|
||||||
%boldGreen(%d{yyyy-MM-dd HH:mm:ss(SSS)}) %cyan([%pid]) %magenta([%15.15thread]) %green([%16.16instance]) %highlight([%-6level]) %boldCyan(%-36logger{32}): %msg%n
|
%boldGreen(%d{yyyy-MM-dd HH:mm:ss(SSS)}) %cyan([%pid]) %magenta([%15.15thread]) %green([%16.16instance]) %highlight([%-6level]) %boldCyan(%-36logger{32}): %msg%n
|
||||||
</pattern>
|
</pattern>
|
||||||
</encoder>
|
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
<logger name="com.serliunx.ddns" level="DEBUG"/>
|
<logger name="com.serliunx.ddns" level="DEBUG"/>
|
||||||
<logger name="feign" level="DEBUG"/>
|
|
||||||
|
|
||||||
<root level="INFO">
|
<root level="INFO">
|
||||||
<appender-ref ref="STDOUT" />
|
<appender-ref ref="JLINE" />
|
||||||
</root>
|
</root>
|
||||||
</configuration>
|
</configuration>
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
package com.serliunx.ddns.test;
|
package com.serliunx.ddns.test;
|
||||||
|
|
||||||
import com.serliunx.ddns.constant.SystemConstants;
|
import com.serliunx.ddns.constant.SystemConstants;
|
||||||
import com.serliunx.ddns.core.Attachment;
|
|
||||||
import com.serliunx.ddns.core.FileAttachment;
|
|
||||||
import com.serliunx.ddns.core.factory.FileInstanceFactory;
|
import com.serliunx.ddns.core.factory.FileInstanceFactory;
|
||||||
import com.serliunx.ddns.core.factory.JsonFileInstanceFactory;
|
|
||||||
import com.serliunx.ddns.core.factory.YamlFileInstanceFactory;
|
import com.serliunx.ddns.core.factory.YamlFileInstanceFactory;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import com.serliunx.ddns.constant.InstanceType;
|
|||||||
import com.serliunx.ddns.constant.SystemConstants;
|
import com.serliunx.ddns.constant.SystemConstants;
|
||||||
import com.serliunx.ddns.core.context.FileInstanceContext;
|
import com.serliunx.ddns.core.context.FileInstanceContext;
|
||||||
import com.serliunx.ddns.core.context.GenericInstanceContext;
|
import com.serliunx.ddns.core.context.GenericInstanceContext;
|
||||||
import com.serliunx.ddns.core.context.MultipleSourceInstanceContext;
|
|
||||||
import com.serliunx.ddns.core.factory.JsonFileInstanceFactory;
|
import com.serliunx.ddns.core.factory.JsonFileInstanceFactory;
|
||||||
import com.serliunx.ddns.core.factory.XmlFileInstanceFactory;
|
import com.serliunx.ddns.core.factory.XmlFileInstanceFactory;
|
||||||
import com.serliunx.ddns.core.factory.YamlFileInstanceFactory;
|
import com.serliunx.ddns.core.factory.YamlFileInstanceFactory;
|
||||||
@@ -33,13 +32,13 @@ public class ContextTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFileContext(){
|
public void testFileContext() {
|
||||||
FileInstanceContext context = new FileInstanceContext();
|
FileInstanceContext context = new FileInstanceContext();
|
||||||
context.getSortedListableInstanceFactories().forEach(System.out::println);
|
context.getSortedListableInstanceFactories().forEach(System.out::println);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEmptyContext(){
|
public void testEmptyContext() {
|
||||||
GenericInstanceContext instanceContext = new GenericInstanceContext(false);
|
GenericInstanceContext instanceContext = new GenericInstanceContext(false);
|
||||||
instanceContext.addListableInstanceFactory(new YamlFileInstanceFactory(SystemConstants.USER_INSTANCE_DIR));
|
instanceContext.addListableInstanceFactory(new YamlFileInstanceFactory(SystemConstants.USER_INSTANCE_DIR));
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,9 @@ package com.serliunx.ddns.test;
|
|||||||
|
|
||||||
import com.serliunx.ddns.constant.InstanceType;
|
import com.serliunx.ddns.constant.InstanceType;
|
||||||
import com.serliunx.ddns.constant.SystemConstants;
|
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.ListableInstanceFactory;
|
||||||
import com.serliunx.ddns.core.factory.YamlFileInstanceFactory;
|
import com.serliunx.ddns.core.factory.YamlFileInstanceFactory;
|
||||||
import com.serliunx.ddns.core.instance.Instance;
|
import com.serliunx.ddns.core.instance.Instance;
|
||||||
import com.serliunx.ddns.support.sqlite.SQLiteConnector;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -30,10 +27,4 @@ public class FactoryTest {
|
|||||||
System.out.println(k + ": " + v);
|
System.out.println(k + ": " + v);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDatabaseFactory() {
|
|
||||||
ListableInstanceFactory factory = new DatabaseInstanceFactory(SQLiteConnector.getInstance());
|
|
||||||
factory.refresh();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.serliunx.ddns.test.config;
|
||||||
|
|
||||||
|
import com.serliunx.ddns.config.CommandLineConfiguration;
|
||||||
|
import com.serliunx.ddns.config.PropertiesConfiguration;
|
||||||
|
import com.serliunx.ddns.constant.SystemConstants;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 命令行配置读取测试
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
||||||
|
* @since 2024/12/24
|
||||||
|
*/
|
||||||
|
public class CmdConfigurationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCmd() {
|
||||||
|
CommandLineConfiguration configuration = new CommandLineConfiguration(new String[]{"-Dtest.env=1",
|
||||||
|
"-Dsystem.cfg.log.onstart=false", "-Dapplication.name=jack"},
|
||||||
|
Collections.singleton(new PropertiesConfiguration(SystemConstants.USER_SETTINGS_PROPERTIES_PATH)));
|
||||||
|
configuration.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,8 @@
|
|||||||
package com.serliunx.ddns.test.support;
|
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;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 供应器测试
|
* 供应器测试
|
||||||
|
* //TODO 暂时移除,待重写单元测试
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
* @author <a href="mailto:serliunx@yeah.net">SerLiunx</a>
|
||||||
* @version 1.0.3
|
* @version 1.0.3
|
||||||
@@ -16,16 +10,4 @@ import java.util.concurrent.TimeUnit;
|
|||||||
*/
|
*/
|
||||||
public class ProviderTest {
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user