添加方法级 Caller 锁
This commit is contained in:
79
src/main/java/quant/rich/emoney/util/CallerLockUtil.java
Normal file
79
src/main/java/quant/rich/emoney/util/CallerLockUtil.java
Normal file
@@ -0,0 +1,79 @@
|
||||
package quant.rich.emoney.util;
|
||||
|
||||
|
||||
import java.lang.StackWalker;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
public class CallerLockUtil {
|
||||
|
||||
private static final StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
|
||||
private static final ConcurrentMap<LockKey, WeakReference<ReentrantLock>> lockMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* ✅ 方式一:一键加锁执行
|
||||
*/
|
||||
public static void runWithCallerLock(Runnable task, Object... extraKeys) {
|
||||
ReentrantLock lock = acquireLock(extraKeys);
|
||||
lock.lock();
|
||||
try {
|
||||
task.run();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ 方式二:手动获取锁
|
||||
* 用于:lock() + try/finally + unlock()
|
||||
*/
|
||||
public static ReentrantLock acquireLock(Object... extraKeys) {
|
||||
LockKey key = buildLockKey(extraKeys);
|
||||
return lockMap.compute(key, (k, ref) -> {
|
||||
ReentrantLock l = (ref == null || ref.get() == null) ? new ReentrantLock() : ref.get();
|
||||
return new WeakReference<>(l);
|
||||
}).get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造调用者方法 + 附加参数为 key
|
||||
*/
|
||||
private static LockKey buildLockKey(Object... extraKeys) {
|
||||
String caller = walker.walk(frames ->
|
||||
frames.skip(3).findFirst() // skip getStackTrace → buildLockKey → acquireLock → 调用者
|
||||
.map(f -> f.getClassName() + "#" + f.getMethodName())
|
||||
.orElse("unknown")
|
||||
);
|
||||
return new LockKey(caller, extraKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* 复合 Key 类(调用方法 + 参数)
|
||||
*/
|
||||
private static class LockKey {
|
||||
private final String method;
|
||||
private final Object[] extra;
|
||||
private final int hash;
|
||||
|
||||
LockKey(String method, Object[] extra) {
|
||||
this.method = method;
|
||||
this.extra = (extra == null) ? new Object[0] : extra;
|
||||
this.hash = Objects.hash(method, Arrays.deepHashCode(this.extra));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof LockKey)) return false;
|
||||
LockKey other = (LockKey) obj;
|
||||
return method.equals(other.method) && Arrays.deepEquals(this.extra, other.extra);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
127
src/main/java/quant/rich/emoney/util/GeoIPUtil.java
Normal file
127
src/main/java/quant/rich/emoney/util/GeoIPUtil.java
Normal file
@@ -0,0 +1,127 @@
|
||||
package quant.rich.emoney.util;
|
||||
|
||||
import com.maxmind.geoip2.DatabaseReader;
|
||||
import com.maxmind.geoip2.exception.GeoIp2Exception;
|
||||
import com.maxmind.geoip2.model.CityResponse;
|
||||
import com.maxmind.geoip2.record.Subdivision;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
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;
|
||||
|
||||
@Slf4j
|
||||
public class GeoIPUtil {
|
||||
|
||||
private static DatabaseReader cityReader;
|
||||
public static String LOCALE = "zh-CN";
|
||||
|
||||
static {
|
||||
try {
|
||||
cityReader = new DatabaseReader.Builder(new File("./conf/extra/GeoLite2-City.mmdb")).build();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("IP 地址库初始化失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static IpInfo getIpInfoThroughProxy(ProxyConfig proxyConfig) {
|
||||
ReentrantLock lock = CallerLockUtil.acquireLock();
|
||||
lock.lock();
|
||||
try {
|
||||
Proxy proxy = proxyConfig.getProxy();
|
||||
boolean ignoreHttpsVerification = proxyConfig.getIgnoreHttpsVerification();
|
||||
// OkHttp 客户端配置
|
||||
OkHttpClient client = OkHttpClientProvider.getInstance(
|
||||
proxy, ignoreHttpsVerification,
|
||||
builder -> builder
|
||||
.connectTimeout(10, TimeUnit.SECONDS)
|
||||
.readTimeout(10, TimeUnit.SECONDS));
|
||||
|
||||
// 使用 httpbin.org/ip 获取当前请求的公网 IP
|
||||
Request requestIpv4 = new Request.Builder()
|
||||
.url("https://ipv4.icanhazip.com")
|
||||
.build();
|
||||
|
||||
IpInfo ipInfo = new IpInfo();
|
||||
|
||||
try (Response response = client.newCall(requestIpv4).execute()) {
|
||||
if (!response.isSuccessful()) {
|
||||
log.warn("Request ipv4 failed with code: {}", response.code());
|
||||
return IpInfo.EMPTY;
|
||||
}
|
||||
|
||||
String responseBody = response.body().string();
|
||||
log.debug("Response ipv4 from proxy: {}", responseBody.trim());
|
||||
ipInfo.setIp(responseBody);
|
||||
} catch (IOException e) {
|
||||
log.warn("Proxy ipv4 error: {}", e.getMessage());
|
||||
return IpInfo.EMPTY;
|
||||
}
|
||||
Request requestIpv6 = new Request.Builder()
|
||||
.url("https://ipv6.icanhazip.com")
|
||||
.build();
|
||||
try (Response response = client.newCall(requestIpv6).execute()) {
|
||||
if (!response.isSuccessful()) {
|
||||
log.warn("Request ipv6 failed with code: {}", response.code());
|
||||
}
|
||||
|
||||
String responseBody = response.body().string();
|
||||
log.debug("Response ipv6 from proxy: {}", responseBody.trim());
|
||||
ipInfo.setIpv6(responseBody);
|
||||
} catch (IOException e) {
|
||||
log.warn("Proxy ipv6 error {}", e.getMessage());
|
||||
}
|
||||
return queryIpInfoGeoLite(ipInfo);
|
||||
}
|
||||
finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 以 IPv4 为准
|
||||
* @param ipInfo
|
||||
*/
|
||||
public static IpInfo queryIpInfoGeoLite(IpInfo ipInfo) {
|
||||
try {
|
||||
InetAddress ipv4Address = InetAddress.getByName(ipInfo.getIp());
|
||||
CityResponse response = cityReader.city(ipv4Address);
|
||||
|
||||
if (response.getCountry() != null) {
|
||||
String countryStr = response.getCountry().getNames().get(LOCALE);
|
||||
ipInfo.setCountry(countryStr);
|
||||
}
|
||||
|
||||
if (response.getSubdivisions() != null && !response.getSubdivisions().isEmpty()) {
|
||||
// 省
|
||||
Subdivision subdivision = response.getSubdivisions().get(0);
|
||||
if (subdivision != null) {
|
||||
String subdivisionStr = subdivision.getNames().get(LOCALE);
|
||||
ipInfo.setSubdivision(subdivisionStr);
|
||||
}
|
||||
}
|
||||
if (response.getCity() != null) {
|
||||
// 市
|
||||
String cityStr = response.getCity().getNames().get(LOCALE);
|
||||
ipInfo.setCity(cityStr);
|
||||
}
|
||||
} catch (IOException | GeoIp2Exception e) {
|
||||
log.warn("Get city response from GeoLite2-City database failed: {}", e.getMessage());
|
||||
}
|
||||
return ipInfo;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.format("GeoLite2-City %s\r\n", queryIpInfoGeoLite(new IpInfo().setIp("46.3.98.216")));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user