添加方法级 Caller 锁
This commit is contained in:
@@ -0,0 +1,54 @@
|
|||||||
|
package quant.rich.emoney.component;
|
||||||
|
|
||||||
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
|
import org.aspectj.lang.annotation.*;
|
||||||
|
import org.aspectj.lang.reflect.MethodSignature;
|
||||||
|
import org.springframework.expression.*;
|
||||||
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||||
|
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import quant.rich.emoney.util.CallerLockUtil;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
@Aspect
|
||||||
|
@Component
|
||||||
|
public class CallerLockAspect {
|
||||||
|
|
||||||
|
private final SpelExpressionParser parser = new SpelExpressionParser();
|
||||||
|
|
||||||
|
@Around("@annotation(com.example.lock.LockByCaller)")
|
||||||
|
public Object around(ProceedingJoinPoint pjp) throws Throwable {
|
||||||
|
MethodSignature signature = (MethodSignature) pjp.getSignature();
|
||||||
|
Method method = signature.getMethod();
|
||||||
|
LockByCaller annotation = method.getAnnotation(LockByCaller.class);
|
||||||
|
|
||||||
|
// 获取 SpEL key(如果有)
|
||||||
|
Object[] args = pjp.getArgs();
|
||||||
|
String[] paramNames = signature.getParameterNames();
|
||||||
|
Object[] extraKey = new Object[0];
|
||||||
|
|
||||||
|
if (!annotation.key().isEmpty()) {
|
||||||
|
EvaluationContext context = new StandardEvaluationContext();
|
||||||
|
for (int i = 0; i < paramNames.length; i++) {
|
||||||
|
context.setVariable(paramNames[i], args[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Expression expression = parser.parseExpression(annotation.key());
|
||||||
|
Object value = expression.getValue(context);
|
||||||
|
extraKey = (value == null) ? new Object[0] : new Object[]{value};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复用工具类的锁逻辑
|
||||||
|
ReentrantLock lock = CallerLockUtil.acquireLock(extraKey);
|
||||||
|
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
return pjp.proceed();
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/main/java/quant/rich/emoney/component/LockByCaller.java
Normal file
20
src/main/java/quant/rich/emoney/component/LockByCaller.java
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package quant.rich.emoney.component;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在方法上添加此注解,可针对调用方加锁,即:<br>
|
||||||
|
* 调用方法为 A 的,多次从 A 调用则加锁,从 B 调用时不受影响<br>
|
||||||
|
* 需要开启 AOP:在任意配置类上增加注解:<i><code>@EnableAspectJAutoProxy</code></i>
|
||||||
|
* @see CallerLockAspect
|
||||||
|
*/
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
public @interface LockByCaller {
|
||||||
|
/**
|
||||||
|
* 可选参数,用于 SpEL 表达式获取 key
|
||||||
|
* 例如:@LockByCaller(key = "#userId")
|
||||||
|
*/
|
||||||
|
String key() default "";
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package quant.rich.emoney.config;
|
|||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
@@ -17,6 +18,7 @@ import quant.rich.emoney.service.ConfigService;
|
|||||||
* @author Doghole
|
* @author Doghole
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
@EnableAspectJAutoProxy
|
||||||
@Configuration
|
@Configuration
|
||||||
@Import(ConfigAutoRegistrar.class)
|
@Import(ConfigAutoRegistrar.class)
|
||||||
public class EmoneyAutoConfig implements WebMvcConfigurer {
|
public class EmoneyAutoConfig implements WebMvcConfigurer {
|
||||||
|
|||||||
@@ -8,11 +8,16 @@ import org.springframework.web.bind.annotation.PostMapping;
|
|||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
import quant.rich.emoney.controller.common.BaseController;
|
import quant.rich.emoney.controller.common.BaseController;
|
||||||
import quant.rich.emoney.entity.config.PlatformConfig;
|
import quant.rich.emoney.entity.config.PlatformConfig;
|
||||||
import quant.rich.emoney.exception.RException;
|
import quant.rich.emoney.exception.RException;
|
||||||
import quant.rich.emoney.pojo.dto.R;
|
import quant.rich.emoney.pojo.dto.R;
|
||||||
|
import quant.rich.emoney.service.AuthService;
|
||||||
import quant.rich.emoney.service.ConfigService;
|
import quant.rich.emoney.service.ConfigService;
|
||||||
|
import quant.rich.emoney.util.EncryptUtils;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
@RequestMapping("/admin/v1")
|
@RequestMapping("/admin/v1")
|
||||||
@@ -24,28 +29,56 @@ public class IndexControllerV1 extends BaseController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
ConfigService configService;
|
ConfigService configService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
AuthService authService;
|
||||||
|
|
||||||
@GetMapping({ "", "/", "/index" })
|
@GetMapping({ "", "/", "/index" })
|
||||||
public String index() {
|
public String index() {
|
||||||
return "admin/v1/index";
|
return "admin/v1/index";
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/changeUserInfo")
|
@GetMapping("/getUserInfo")
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public R<?> changeUserInfo(String newUsername, String oldPassword, String newPassword) {
|
public R<?> getUserInfo() {
|
||||||
if (!platformConfig.getPassword().equals(oldPassword)) {
|
ObjectNode node = new ObjectMapper().valueToTree(platformConfig);
|
||||||
throw RException.badRequest("密码错误");
|
node.remove("password");
|
||||||
}
|
node.remove("isInited");
|
||||||
if (StringUtils.isAllEmpty(newUsername, newPassword)) {
|
return R.ok(node);
|
||||||
throw RException.badRequest("未更改任何信息");
|
|
||||||
}
|
|
||||||
if (StringUtils.isNotEmpty(newUsername)) {
|
|
||||||
platformConfig.setUsername(newUsername);
|
|
||||||
}
|
|
||||||
if (StringUtils.isNotEmpty(newPassword)) {
|
|
||||||
platformConfig.setPassword(newPassword);
|
|
||||||
}
|
|
||||||
configService.saveOrUpdate(platformConfig);
|
|
||||||
return R.ok();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/changeUserInfo")
|
||||||
|
@ResponseBody
|
||||||
|
public R<?> changeUserInfo(
|
||||||
|
String username,
|
||||||
|
String password,
|
||||||
|
String newPassword,
|
||||||
|
String email) {
|
||||||
|
|
||||||
|
if (passwordIsNotEmpty(newPassword)) {
|
||||||
|
if (!platformConfig.getPassword().equals(password)) {
|
||||||
|
throw RException.badRequest("密码错误");
|
||||||
|
}
|
||||||
|
platformConfig.setPassword(newPassword);
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotEmpty(username)) {
|
||||||
|
platformConfig.setUsername(username);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw RException.badRequest("用户名不能为空");
|
||||||
|
}
|
||||||
|
platformConfig.setEmail(email);
|
||||||
|
return R.judge(() -> {
|
||||||
|
if (configService.saveOrUpdate(platformConfig)) {
|
||||||
|
authService.setLogin(username, platformConfig.getPassword());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static final String EMPTY_PASSWORD = EncryptUtils.sha3("", 224);
|
||||||
|
|
||||||
|
static boolean passwordIsNotEmpty(String password) {
|
||||||
|
return StringUtils.isNotEmpty(password) && !password.equalsIgnoreCase(EMPTY_PASSWORD);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package quant.rich.emoney.controller.config;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import quant.rich.emoney.controller.common.BaseController;
|
||||||
|
import quant.rich.emoney.entity.config.ProxyConfig;
|
||||||
|
import quant.rich.emoney.pojo.dto.R;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Controller
|
||||||
|
@RequestMapping("/admin/v1/config/proxy")
|
||||||
|
public class ProxyConfigControllerV1 extends BaseController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ProxyConfig proxyConfig;
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping("/refreshIpThroughProxy")
|
||||||
|
@ResponseBody
|
||||||
|
public R<?> refreshIpThroughProxy() {
|
||||||
|
return R.ok(proxyConfig.refreshIpThroughProxy());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,8 @@ public class PlatformConfig implements IConfig<PlatformConfig> {
|
|||||||
|
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
|
private String email;
|
||||||
|
|
||||||
private Boolean isInited;
|
private Boolean isInited;
|
||||||
|
|
||||||
public PlatformConfig() {
|
public PlatformConfig() {
|
||||||
|
|||||||
@@ -1,27 +1,15 @@
|
|||||||
package quant.rich.emoney.entity.config;
|
package quant.rich.emoney.entity.config;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.Proxy;
|
import java.net.Proxy;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonView;
|
import com.fasterxml.jackson.annotation.JsonView;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
import okhttp3.Request;
|
|
||||||
import okhttp3.Response;
|
|
||||||
import quant.rich.emoney.client.OkHttpClientProvider;
|
|
||||||
import quant.rich.emoney.interceptor.EnumOptionsInterceptor.EnumOptions;
|
import quant.rich.emoney.interceptor.EnumOptionsInterceptor.EnumOptions;
|
||||||
import quant.rich.emoney.interfaces.ConfigInfo;
|
import quant.rich.emoney.interfaces.ConfigInfo;
|
||||||
import quant.rich.emoney.interfaces.IConfig;
|
import quant.rich.emoney.interfaces.IConfig;
|
||||||
import quant.rich.emoney.pojo.dto.IpInfo;
|
import quant.rich.emoney.pojo.dto.IpInfo;
|
||||||
import quant.rich.emoney.util.CallerLockUtil;
|
|
||||||
import quant.rich.emoney.util.GeoIPUtil;
|
import quant.rich.emoney.util.GeoIPUtil;
|
||||||
import quant.rich.emoney.validator.ProxyConfigValid;
|
import quant.rich.emoney.validator.ProxyConfigValid;
|
||||||
|
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ public class PatchOkHttp {
|
|||||||
* <li>在方法体内,使用外部 RestartClassLoader 载入欲重定义的方法类,并在调用 ByteBuddy 时指定该方法类和 ClassLoader。</li>
|
* <li>在方法体内,使用外部 RestartClassLoader 载入欲重定义的方法类,并在调用 ByteBuddy 时指定该方法类和 ClassLoader。</li>
|
||||||
* </ol>
|
* </ol>
|
||||||
* </p>
|
* </p>
|
||||||
|
* <p>进一步地,如果注入很重要,请判断欲重定义类所属 ClassLoader 和外部 ClassLoader 是否一致,如果不一致的,终止程序。</p>
|
||||||
* @see org.springframework.boot.devtools.restart.classloader.RestartClassLoader
|
* @see org.springframework.boot.devtools.restart.classloader.RestartClassLoader
|
||||||
* @see jdk.internal.loader.ClassLoaders
|
* @see jdk.internal.loader.ClassLoaders
|
||||||
*/
|
*/
|
||||||
|
|||||||
70
src/main/java/quant/rich/emoney/pojo/dto/IpInfo.java
Normal file
70
src/main/java/quant/rich/emoney/pojo/dto/IpInfo.java
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package quant.rich.emoney.pojo.dto;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Accessors(chain=true)
|
||||||
|
public class IpInfo {
|
||||||
|
private String ip = "Unknown";
|
||||||
|
private String ipv6;
|
||||||
|
private String country;
|
||||||
|
private String subdivision;
|
||||||
|
private String city;
|
||||||
|
|
||||||
|
public static final IpInfo EMPTY = new IpInfo();
|
||||||
|
|
||||||
|
public IpInfo setIp(String ip) {
|
||||||
|
if (ip != null) this.ip = ip.trim();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IpInfo setIpv6(String ipv6) {
|
||||||
|
if (ipv6 != null) this.ipv6 = ipv6.trim();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("IP: ").append(getIp());
|
||||||
|
if (StringUtils.isNoneBlank(getIpv6())) {
|
||||||
|
sb.append(", ").append("IPv6: ").append(getIpv6());
|
||||||
|
}
|
||||||
|
sb.append(getGeoString());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getString() {
|
||||||
|
return toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGeoString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("(");
|
||||||
|
if (StringUtils.isNotBlank(getCountry())) {
|
||||||
|
// 国家
|
||||||
|
sb.append(getCountry());
|
||||||
|
if (StringUtils.isNotBlank(getSubdivision())) {
|
||||||
|
sb.append(getSubdivision());
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank(getCity()) && !Objects.equals(getCountry(), getCity())) {
|
||||||
|
// 有时候国家和地区与城市一致的,忽略
|
||||||
|
sb.append(getCity());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sb.append("未知位置");
|
||||||
|
}
|
||||||
|
sb.append(")");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return StringUtils.isBlank(getIp());
|
||||||
|
}
|
||||||
|
}
|
||||||
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")));
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/main/resources/META-INF/spring-devtools.properties
Normal file
1
src/main/resources/META-INF/spring-devtools.properties
Normal file
@@ -0,0 +1 @@
|
|||||||
|
restart.include.okhttp3=.*okhttp-.*\.jar
|
||||||
Reference in New Issue
Block a user