新增(移动)一些应该放在“管理”形成列表管理,而非放在“设置”形成单一配置的内容
This commit is contained in:
@@ -5,6 +5,7 @@ import java.lang.StackWalker;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
@@ -37,6 +38,44 @@ public class CallerLockUtil {
|
||||
return new WeakReference<>(l);
|
||||
}).get();
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ 方式三:尝试获取锁并运行,支持超时,失败后不阻塞
|
||||
* @return true 表示成功执行,false 表示未获得锁
|
||||
*/
|
||||
public static boolean tryRunWithCallerLock(Runnable task, long timeoutMs, Object... extraKeys) {
|
||||
ReentrantLock lock = acquireLock(extraKeys);
|
||||
boolean locked = false;
|
||||
try {
|
||||
locked = lock.tryLock(timeoutMs, TimeUnit.MILLISECONDS);
|
||||
if (locked) {
|
||||
task.run();
|
||||
}
|
||||
return locked;
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
} finally {
|
||||
if (locked) lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ 非阻塞获取锁,超时失败返回 null 或抛异常
|
||||
*/
|
||||
public static <T> Optional<T> tryCallWithCallerLock(Callable<T> task, long timeoutMs, Object... extraKeys) {
|
||||
ReentrantLock lock = acquireLock(extraKeys);
|
||||
boolean locked = false;
|
||||
try {
|
||||
locked = lock.tryLock(timeoutMs, TimeUnit.MILLISECONDS);
|
||||
if (!locked) return Optional.empty();
|
||||
return Optional.of(task.call());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
if (locked) lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造调用者方法 + 附加参数为 key
|
||||
|
||||
@@ -38,6 +38,16 @@ public class EncryptUtils {
|
||||
private static final String EM_SIGN_MESS_2 = "994fec3c512f2f7756fd5e4403147f01";
|
||||
private static final String SLASH = "/";
|
||||
private static final String COLON = ":";
|
||||
private static final String EMPTY_SHA3_224 = sha3("", 224);
|
||||
|
||||
/**
|
||||
* 判断密码是否空字符串,或经过 SHA_224 加密过的空字符串
|
||||
* @param password
|
||||
* @return
|
||||
*/
|
||||
public static boolean passwordIsNotEmpty(String password) {
|
||||
return StringUtils.isNotEmpty(password) && !password.equalsIgnoreCase(EMPTY_SHA3_224);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密用于 Emoney 登录的密码
|
||||
|
||||
@@ -13,12 +13,12 @@ import quant.rich.emoney.client.OkHttpClientProvider;
|
||||
import quant.rich.emoney.entity.config.ProxyConfig;
|
||||
import quant.rich.emoney.pojo.dto.IpInfo;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Proxy;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
|
||||
@Slf4j
|
||||
public class GeoIPUtil {
|
||||
@@ -28,16 +28,16 @@ public class GeoIPUtil {
|
||||
|
||||
static {
|
||||
try {
|
||||
cityReader = new DatabaseReader.Builder(new File("./conf/extra/GeoLite2-City.mmdb")).build();
|
||||
ClassPathResource geoLite2CityResource = new ClassPathResource("/conf/extra/GeoLite2-City.mmdb");
|
||||
cityReader = new DatabaseReader.Builder(geoLite2CityResource.getInputStream()).build();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("IP 地址库初始化失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Async
|
||||
public static IpInfo getIpInfoThroughProxy(ProxyConfig proxyConfig) {
|
||||
ReentrantLock lock = CallerLockUtil.acquireLock();
|
||||
lock.lock();
|
||||
try {
|
||||
return CallerLockUtil.tryCallWithCallerLock(() -> {
|
||||
Proxy proxy = proxyConfig.getProxy();
|
||||
boolean ignoreHttpsVerification = proxyConfig.getIgnoreHttpsVerification();
|
||||
// OkHttp 客户端配置
|
||||
@@ -82,10 +82,7 @@ public class GeoIPUtil {
|
||||
log.warn("Proxy ipv6 error {}", e.getMessage());
|
||||
}
|
||||
return queryIpInfoGeoLite(ipInfo);
|
||||
}
|
||||
finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}, 100, proxyConfig).orElse(IpInfo.EMPTY);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
111
src/main/java/quant/rich/emoney/util/SmartResourceResolver.java
Normal file
111
src/main/java/quant/rich/emoney/util/SmartResourceResolver.java
Normal file
@@ -0,0 +1,111 @@
|
||||
package quant.rich.emoney.util;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.*;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class SmartResourceResolver {
|
||||
|
||||
private static RunningFrom runningFrom;
|
||||
|
||||
static {
|
||||
if (isRunningFromJar()) {
|
||||
runningFrom = RunningFrom.JAR;
|
||||
}
|
||||
else if (isRunningFromWar()) {
|
||||
runningFrom = RunningFrom.WAR;
|
||||
}
|
||||
else {
|
||||
runningFrom = RunningFrom.IDE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
获取资源
|
||||
<p>
|
||||
<ul>
|
||||
<li>JAR
|
||||
<ul>
|
||||
<li>优先以 jar 文件所在目录为基准,寻找相对路径外部文件</li>
|
||||
<li>当外部文件不存在时,读取 classpath,即 jar 内部资源文件</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>WAR 只获取 classpath 文件,即 /WEB-INF/classes/ 下文件</li>
|
||||
<li>IDE 只获取源文件,即 src/main/resources/ 下文件</li>
|
||||
</ul></p>
|
||||
* @param relativePath 相对路径
|
||||
* @param writable 是否一定可写
|
||||
* @return
|
||||
*/
|
||||
public static InputStream loadResource(String relativePath) {
|
||||
try {
|
||||
Path externalPath = resolveExternalPath(relativePath);
|
||||
|
||||
if (externalPath != null && Files.exists(externalPath)) {
|
||||
log.debug("从外部文件系统加载资源: {}", externalPath);
|
||||
return Files.newInputStream(externalPath);
|
||||
}
|
||||
|
||||
// 否则回退到 classpath(JAR、WAR、IDE)
|
||||
InputStream in = SmartResourceResolver.class.getClassLoader().getResourceAsStream(relativePath);
|
||||
if (in != null) {
|
||||
log.debug("从 classpath 内部加载资源: {}", relativePath);
|
||||
return in;
|
||||
}
|
||||
|
||||
throw new FileNotFoundException("无法找到资源: " + relativePath);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("读取资源失败: " + relativePath, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void saveText(String relativePath, String content) throws IOException {
|
||||
Path outputPath = resolveExternalPath(relativePath);
|
||||
Files.createDirectories(outputPath.getParent()); // 确保目录存在
|
||||
Files.writeString(outputPath, content, StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
log.debug("写入外部资源文件成功: {}", outputPath);
|
||||
}
|
||||
|
||||
private static Path resolveExternalPath(String relativePath) {
|
||||
try {
|
||||
Path basePath;
|
||||
if (runningFrom == RunningFrom.JAR) {
|
||||
basePath = Paths.get(SmartResourceResolver.class.getProtectionDomain()
|
||||
.getCodeSource().getLocation().toURI()).getParent();
|
||||
return basePath.resolve(relativePath).normalize();
|
||||
} else if (runningFrom == RunningFrom.WAR) {
|
||||
basePath = Paths.get(SmartResourceResolver.class.getProtectionDomain()
|
||||
.getCodeSource().getLocation().toURI()); // e.g., WEB-INF/classes/
|
||||
return basePath.resolve(relativePath).normalize();
|
||||
} else {
|
||||
// IDE 环境:返回 src/main/resources 下真实文件
|
||||
return Paths.get("src/main/resources", relativePath).normalize();
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isRunningFromJar() {
|
||||
String path = SmartResourceResolver.class.getResource(
|
||||
SmartResourceResolver.class.getSimpleName() + ".class").toString();
|
||||
return path.startsWith("jar:");
|
||||
}
|
||||
|
||||
private static boolean isRunningFromWar() {
|
||||
String path = SmartResourceResolver.class.getResource(
|
||||
SmartResourceResolver.class.getSimpleName() + ".class").toString();
|
||||
return path.contains("/WEB-INF/classes/");
|
||||
}
|
||||
|
||||
private static enum RunningFrom {
|
||||
JAR,
|
||||
WAR,
|
||||
IDE
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user