diff --git a/conf/extra/GeoLite2-City.mmdb b/conf/extra/GeoLite2-City.mmdb
new file mode 100644
index 0000000..177e3ad
Binary files /dev/null and b/conf/extra/GeoLite2-City.mmdb differ
diff --git a/conf/system/emoneyRequest.json b/conf/system/emoneyRequest.json
index 6c7880f..9e6f977 100644
--- a/conf/system/emoneyRequest.json
+++ b/conf/system/emoneyRequest.json
@@ -2,8 +2,8 @@
"isAnonymous" : true,
"username" : "emy1730978",
"password" : "ubVa0vNmD+JJC4171eLYUw==",
- "authorization" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImN0eSI6IkpXVCJ9.eyJ1dWQiOjEwMTQ2MDA5MDcsInVpZCI6Mjg1ODMyMzcsImRpZCI6IjhlMjRiN2U0NjM0NTRlNWE2ODNhODE0ZGRkZDU4MGZkIiwidHlwIjo0LCJhY2MiOiI4ZTI0YjdlNDYzNDU0ZTVhNjgzYTgxNGRkZGQ1ODBmZCIsInN3dCI6MSwibGd0IjoxNzQ2ODgyODc4MTc2LCJuYmYiOjE3NDY4ODI4NzgsImV4cCI6MTc0ODYxMDg3OCwiaWF0IjoxNzQ2ODgyODc4fQ.YDG61MPnRypUC1HDGoRRzTtcx_J_tnAqM6XowXT7Jzk",
- "authorizationUpdateTime" : [ 2025, 5, 10, 21, 14, 38, 649402700 ],
+ "authorization" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImN0eSI6IkpXVCJ9.eyJ1dWQiOjEwMTQ2MDA5MDcsInVpZCI6Mjg1ODMyMzcsImRpZCI6IjhlMjRiN2U0NjM0NTRlNWE2ODNhODE0ZGRkZDU4MGZkIiwidHlwIjo0LCJhY2MiOiI4ZTI0YjdlNDYzNDU0ZTVhNjgzYTgxNGRkZGQ1ODBmZCIsInN3dCI6MSwibGd0IjoxNzQ3MjE2MTIzNDQ2LCJuYmYiOjE3NDcyMTYxMjMsImV4cCI6MTc0ODk0NDEyMywiaWF0IjoxNzQ3MjE2MTIzfQ.fIoy5T41WUmbi7jMlVTXtWY_s8IIhNlO6LDNpXvibEc",
+ "authorizationUpdateTime" : [ 2025, 5, 14, 17, 48, 42, 794676900 ],
"androidId" : "7c135cebb4c94177",
"androidVersion" : "13",
"androidSdkLevel" : "33",
diff --git a/conf/system/platform.json b/conf/system/platform.json
index de1fd6d..4df3bb7 100644
--- a/conf/system/platform.json
+++ b/conf/system/platform.json
@@ -1,5 +1,6 @@
{
"username" : "admin",
"password" : "81667f60a8c11d4c8e9d2e0670ff24667e6c72d49b0b15562525bcbd",
+ "email" : "huocaizhu@gmail.com",
"isInited" : true
}
\ No newline at end of file
diff --git a/conf/system/proxy.json b/conf/system/proxy.json
index edc390d..6c38d7c 100644
--- a/conf/system/proxy.json
+++ b/conf/system/proxy.json
@@ -1,6 +1,6 @@
{
- "proxyType" : "DIRECT",
- "proxyHost" : "",
- "proxyPort" : 0,
+ "proxyType" : "HTTP",
+ "proxyHost" : "127.0.0.1",
+ "proxyPort" : 8888,
"ignoreHttpsVerification" : true
}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index c2810c3..56d04cf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -15,7 +15,11 @@
EmoGrab
Emoney 数据抓取平台
+ UTF-8
+ UTF-8
18
+ 18
+ 18
@@ -51,6 +55,7 @@
org.springframework.boot
spring-boot-devtools
+ runtime
true
@@ -259,19 +264,32 @@
com.fasterxml.jackson.datatype
jackson-datatype-jsr310
-
+
net.bytebuddy
byte-buddy
-
+
net.bytebuddy
byte-buddy-agent
-
+
+
+
+ com.maxmind.geoip2
+ geoip2
+ 4.3.0
+
+
+
+
+ net.cz88
+ czdb-search
+ 1.0.2.7
+
diff --git a/src/main/java/quant/rich/emoney/client/EmoneyClient.java b/src/main/java/quant/rich/emoney/client/EmoneyClient.java
index fda2c12..f2498fc 100644
--- a/src/main/java/quant/rich/emoney/client/EmoneyClient.java
+++ b/src/main/java/quant/rich/emoney/client/EmoneyClient.java
@@ -32,8 +32,8 @@ import okhttp3.OkHttpClient;
public class EmoneyClient implements Cloneable {
private static final String MBS_URL = "https://mbs.emoney.cn/";
- private static final String LOGIN_URL = "https://emapp.emoney.cn/user/auth/login";
- // private static final String LOGIN_URL = "http://localhost:7790/user/auth/login";
+ //private static final String LOGIN_URL = "https://emapp.emoney.cn/user/auth/login";
+ private static final String LOGIN_URL = "http://localhost:7790/user/auth/login";
private static final String LOGIN_X_PROTOCOL_ID = "user%2Fauth%2Flogin";
private static volatile EmoneyRequestConfig emoneyRequestConfig;
diff --git a/src/main/java/quant/rich/emoney/client/OkHttpClientProvider.java b/src/main/java/quant/rich/emoney/client/OkHttpClientProvider.java
index d4b83cc..3e4757f 100644
--- a/src/main/java/quant/rich/emoney/client/OkHttpClientProvider.java
+++ b/src/main/java/quant/rich/emoney/client/OkHttpClientProvider.java
@@ -1,12 +1,17 @@
package quant.rich.emoney.client;
import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.InetAddress;
import java.net.Proxy;
+import java.net.UnknownHostException;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Arrays;
+import java.util.List;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
@@ -16,6 +21,7 @@ import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
+import okhttp3.Dns;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
@@ -30,6 +36,7 @@ import quant.rich.emoney.util.SpringContextHolder;
/**
* OkHttpClient 提供器
* @see quant.rich.emoney.entity.config.ProxyConfig
+ * @see okhttp3.internal.http.BridgeInterceptor
*/
public class OkHttpClientProvider {
@@ -61,7 +68,10 @@ public class OkHttpClientProvider {
*/
public static OkHttpClient getInstance(Consumer builderConsumer) {
ProxyConfig proxyConfig = getProxyConfig();
- return getInstance(proxyConfig.getProxy(), proxyConfig.getIgnoreHttpsVerification(), builderConsumer);
+ return getInstance(
+ proxyConfig.getProxy(),
+ proxyConfig.getIgnoreHttpsVerification(),
+ builderConsumer);
}
/**
@@ -81,7 +91,10 @@ public class OkHttpClientProvider {
* @param builderConsumer 可根据该 consumer 自定义 builder 其他参数,注意 proxy、https 校验等最终仍会根据其他参数覆盖
* @return
*/
- public static OkHttpClient getInstance(Proxy proxy, boolean ignoreHttpsVerification, Consumer builderConsumer) {
+ public static OkHttpClient getInstance(
+ Proxy proxy,
+ boolean ignoreHttpsVerification,
+ Consumer builderConsumer) {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
if (builderConsumer != null) {
builderConsumer.accept(builder);
@@ -184,5 +197,5 @@ public class OkHttpClientProvider {
return response;
}
- }
+ }
}
diff --git a/src/main/java/quant/rich/emoney/entity/config/EmoneyRequestConfig.java b/src/main/java/quant/rich/emoney/entity/config/EmoneyRequestConfig.java
index a5b942e..8124279 100644
--- a/src/main/java/quant/rich/emoney/entity/config/EmoneyRequestConfig.java
+++ b/src/main/java/quant/rich/emoney/entity/config/EmoneyRequestConfig.java
@@ -26,6 +26,7 @@ import quant.rich.emoney.entity.config.DeviceInfoConfig.DeviceInfo;
import quant.rich.emoney.interfaces.ConfigInfo;
import quant.rich.emoney.interfaces.IConfig;
import quant.rich.emoney.patch.okhttp.PatchOkHttp;
+import quant.rich.emoney.patch.okhttp.PatchOkHttpRule;
import quant.rich.emoney.util.EncryptUtils;
import quant.rich.emoney.util.SpringContextHolder;
import quant.rich.emoney.util.TextUtils;
@@ -248,11 +249,11 @@ public class EmoneyRequestConfig implements IConfig {
// 注入 OkHttp
PatchOkHttp.apply(
- r -> r
+ PatchOkHttpRule.when()
.hostEndsWith("emoney.cn")
.or(a -> a.hostContains("emapp"))
.or(b -> b.hasHeaderName("X-Protocol-Id"))
- .overrideIf("User-Agent", getOkHttpUserAgent()));
+ .overrideIf("User-Agent", getOkHttpUserAgent()).build());
}
/**
diff --git a/src/main/java/quant/rich/emoney/entity/config/IndexInfoConfig.java b/src/main/java/quant/rich/emoney/entity/config/IndexInfoConfig.java
index 793921a..e8eb3af 100644
--- a/src/main/java/quant/rich/emoney/entity/config/IndexInfoConfig.java
+++ b/src/main/java/quant/rich/emoney/entity/config/IndexInfoConfig.java
@@ -1,21 +1,8 @@
package quant.rich.emoney.entity.config;
import java.io.IOException;
-import java.net.Proxy;
-import java.security.KeyStore;
-import java.security.SecureRandom;
-import java.security.cert.X509Certificate;
-import java.util.Arrays;
import java.util.concurrent.TimeUnit;
-import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.SSLSocketFactory;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.TrustManagerFactory;
-import javax.net.ssl.X509TrustManager;
-
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
@@ -30,14 +17,10 @@ import lombok.Data;
import lombok.Getter;
import lombok.experimental.Accessors;
import okhttp3.ConnectionPool;
-import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
-import okhttp3.ResponseBody;
-import okio.BufferedSource;
-import okio.GzipSource;
-import okio.Okio;
+import quant.rich.emoney.client.OkHttpClientProvider;
import quant.rich.emoney.interfaces.ConfigInfo;
import quant.rich.emoney.interfaces.IConfig;
@@ -77,145 +60,25 @@ public class IndexInfoConfig implements IConfig {
}
public String getOnlineConfigByUrl(String url) throws IOException {
- Request request = new Request.Builder()
- .url(url)
- .header("User-Agent", emoneyRequestConfig.getOkHttpUserAgent())
- .header("Accept-Encoding", "gzip")
- .header("Connection", "Keep-Alive")
- .header("Cache-Control", "no-cache")
- .get()
- // Host 不能在 OkHttp 中直接设置(由 URL 控制)
- .build();
-
- // 发出请求
- Response backendResponse = getInstance().newCall(request).execute();
- if (backendResponse.body() != null) {
- // 将内容复制给前端
- return
- new String(backendResponse.body().bytes(), "UTF-8");
- } else {
- return "";
+ synchronized (this) {
+ Request request = new Request.Builder()
+ .url(url)
+ .header("Cache-Control", "no-cache")
+ .get()
+ .build();
+
+ // 发出请求
+ Response backendResponse = OkHttpClientProvider.getInstance().newCall(request).execute();
+ if (backendResponse.body() != null) {
+ // 将内容复制给前端
+ return
+ new String(backendResponse.body().bytes(), "UTF-8");
+ } else {
+ return "";
+ }
}
}
- private static OkHttpClient okHttpClient;
-
public static ConnectionPool connectionPool = new ConnectionPool(10, 5, TimeUnit.MINUTES);
- public static OkHttpClient getInstance() {
- if (okHttpClient == null) { //加同步安全
- synchronized (OkHttpClient.class) {
- if (okHttpClient == null) { //okhttp可以缓存数据....指定缓存路径
- okHttpClient = new OkHttpClient.Builder()//构建器
- .proxy(Proxy.NO_PROXY) //来屏蔽系统代理
- .connectionPool(connectionPool)
- .sslSocketFactory(getSSLSocketFactory(), getX509TrustManager())
- .hostnameVerifier(getHostnameVerifier())
- .connectTimeout(600, TimeUnit.SECONDS)//连接超时
- .writeTimeout(600, TimeUnit.SECONDS)//写入超时
- .readTimeout(600, TimeUnit.SECONDS)//读取超时
- .addNetworkInterceptor(new GzipResponseInterceptor())
- .build();
- okHttpClient.dispatcher().setMaxRequestsPerHost(200);
- okHttpClient.dispatcher().setMaxRequests(200);
- }
- }
- }
- return okHttpClient;
- }
-
- /**
- * description 忽略https证书验证
- */
- private static HostnameVerifier getHostnameVerifier() {
- HostnameVerifier hostnameVerifier = new HostnameVerifier() {
- @Override
- public boolean verify(String s, SSLSession sslSession) {
- return true;
- }
- };
- return hostnameVerifier;
- }
- /**
- * description 忽略https证书验证
- */
- private static SSLSocketFactory getSSLSocketFactory() {
- try {
- SSLContext sslContext = SSLContext.getInstance("SSL");
- sslContext.init(null, getTrustManager(), new SecureRandom());
- return sslContext.getSocketFactory();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- private static X509TrustManager getX509TrustManager() {
- X509TrustManager trustManager = null;
- try {
- TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
- trustManagerFactory.init((KeyStore) null);
- TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
- if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
- throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
- }
- trustManager = (X509TrustManager) trustManagers[0];
- } catch (Exception e) {
- e.printStackTrace();
- }
-
- return trustManager;
- }
-
- private static TrustManager[] getTrustManager() {
- TrustManager[] trustAllCerts = new TrustManager[]{
- new X509TrustManager() {
- @Override
- public void checkClientTrusted(X509Certificate[] chain, String authType) {}
-
- @Override
- public void checkServerTrusted(X509Certificate[] chain, String authType) {}
-
- @Override
- public X509Certificate[] getAcceptedIssuers() {
- return new X509Certificate[]{};
- }
- }
- };
- return trustAllCerts;
- }
-
- public static class GzipResponseInterceptor implements Interceptor {
- @Override
- public Response intercept(Chain chain) throws IOException {
- Request request = chain.request();
- Response response = chain.proceed(request);
-
- // 只有服务器返回了 gzip 编码才处理
- if ("gzip".equalsIgnoreCase(response.header("Content-Encoding"))) {
- // 原始响应体
- ResponseBody body = response.body();
- if (body == null) return response;
-
- // 用 GzipSource 包装原始流,并缓冲
- GzipSource gzippedResponseBody = new GzipSource(body.source());
- BufferedSource unzippedSource = Okio.buffer(gzippedResponseBody);
-
- // 构造一个新的 ResponseBody,不再带 Content-Encoding/Length
- ResponseBody newBody = ResponseBody.create(
- unzippedSource,
- body.contentType(),
- -1L
- );
-
- // 去掉 Content-Encoding/Length,让后续调用 body().string() 时拿到解压后的内容
- return response.newBuilder()
- .removeHeader("Content-Encoding")
- .removeHeader("Content-Length")
- .body(newBody)
- .build();
- }
-
- return response;
- }
- }
}
diff --git a/src/main/java/quant/rich/emoney/entity/config/ProxyConfig.java b/src/main/java/quant/rich/emoney/entity/config/ProxyConfig.java
index 82bb8cb..1ffa053 100644
--- a/src/main/java/quant/rich/emoney/entity/config/ProxyConfig.java
+++ b/src/main/java/quant/rich/emoney/entity/config/ProxyConfig.java
@@ -4,8 +4,11 @@ import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
-import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonView;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.Data;
import lombok.experimental.Accessors;
@@ -17,6 +20,9 @@ import quant.rich.emoney.client.OkHttpClientProvider;
import quant.rich.emoney.interceptor.EnumOptionsInterceptor.EnumOptions;
import quant.rich.emoney.interfaces.ConfigInfo;
import quant.rich.emoney.interfaces.IConfig;
+import quant.rich.emoney.pojo.dto.IpInfo;
+import quant.rich.emoney.util.CallerLockUtil;
+import quant.rich.emoney.util.GeoIPUtil;
import quant.rich.emoney.validator.ProxyConfigValid;
/**
@@ -33,25 +39,39 @@ public class ProxyConfig implements IConfig {
* 代理类型
*/
@EnumOptions("ProxyTypeEnum")
+ @JsonView(IConfig.Views.Persistence.class)
private Proxy.Type proxyType = Proxy.Type.DIRECT;
/**
* 代理主机
*/
+ @JsonView(IConfig.Views.Persistence.class)
private String proxyHost = "";
/**
* 代理端口
*/
+ @JsonView(IConfig.Views.Persistence.class)
private Integer proxyPort = 1;
/**
* 是否忽略 HTTPS 证书校验
*/
+ @JsonView(IConfig.Views.Persistence.class)
private Boolean ignoreHttpsVerification = false;
+ /**
+ * 通过代理后的 IP,不做存储,只做呈现
+ */
+ private IpInfo ipInfo;
+
public void afterBeanInit() {
-
+ //refreshIpThroughProxy();
+ }
+
+ public synchronized IpInfo refreshIpThroughProxy() {
+ ipInfo = GeoIPUtil.getIpInfoThroughProxy(this);
+ return ipInfo;
}
@@ -61,7 +81,6 @@ public class ProxyConfig implements IConfig {
* 根据配置获取代理
* @return
*/
- @JsonIgnore
public Proxy getProxy() {
if (getProxyType() != null && getProxyType() != Proxy.Type.DIRECT) {
return new Proxy(getProxyType(),
@@ -70,53 +89,18 @@ public class ProxyConfig implements IConfig {
return Proxy.NO_PROXY;
}
- /**
- * 测试给定的 HTTP 代理是否有效,并验证是否真的走了代理。
- * @param proxy 代理
- * @param ignoreHttpsVerification 是否忽略 https 证书验证
- * @return 是否有效
- */
- public static boolean isProxyEffective(Proxy proxy, boolean ignoreHttpsVerification) {
- synchronized (ProxyConfig.class) {
-
- // OkHttp 客户端配置
- OkHttpClient client = OkHttpClientProvider.getInstance(
- proxy, ignoreHttpsVerification,
- builder -> builder
- .connectTimeout(10, TimeUnit.SECONDS)
- .readTimeout(10, TimeUnit.SECONDS));
-
- // 使用 httpbin.org/ip 获取当前请求的公网 IP
- Request request = new Request.Builder()
- .url("https://httpbin.org/ip")
- .header("User-Agent", "ProxyVerifier")
- .build();
-
- try (Response response = client.newCall(request).execute()) {
- if (!response.isSuccessful()) {
- System.out.println("Request failed with code: " + response.code());
- return false;
- }
-
- String responseBody = response.body().string();
- System.out.println("Response from proxy: " + responseBody);
-
- // 可在此根据 IP 做进一步验证,例如是否与本地 IP 不同
- return true;
- } catch (IOException e) {
- System.out.println("Proxy error: " + e.getMessage());
- return false;
- }
- }
- }
-
public static void main(String[] args) {
String proxyIp = "127.0.0.1";
int proxyPort = 7897;
+
+ ProxyConfig proxyConfig = new ProxyConfig();
+ proxyConfig.setProxyHost(proxyIp).setProxyPort(proxyPort)
+ .setProxyType(Proxy.Type.SOCKS)
+ .setIgnoreHttpsVerification(false);
- boolean result = isProxyEffective(
- new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(proxyIp, proxyPort)), true);
- System.out.println("Proxy is usable: " + result);
+ IpInfo result = GeoIPUtil.getIpInfoThroughProxy(proxyConfig);
+ System.out.println("Proxy is usable with through-proxy ip: " + result);
}
+
}
diff --git a/src/main/java/quant/rich/emoney/patch/okhttp/PatchOkHttp.java b/src/main/java/quant/rich/emoney/patch/okhttp/PatchOkHttp.java
index 497b5af..d35b71a 100644
--- a/src/main/java/quant/rich/emoney/patch/okhttp/PatchOkHttp.java
+++ b/src/main/java/quant/rich/emoney/patch/okhttp/PatchOkHttp.java
@@ -2,11 +2,16 @@ package quant.rich.emoney.patch.okhttp;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.agent.ByteBuddyAgent;
+import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import okhttp3.Request;
+import quant.rich.emoney.util.SpringContextHolder;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import static net.bytebuddy.matcher.ElementMatchers.*;
+import java.lang.instrument.Instrumentation;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -14,12 +19,21 @@ import java.util.function.Consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.context.ConfigurableApplicationContext;
public class PatchOkHttp {
private static final Logger log = LoggerFactory.getLogger(PatchOkHttp.class);
private static final List rules = new CopyOnWriteArrayList<>();
+
private static boolean isHooked = false;
+ public static void apply(PatchOkHttpRule rule) {
+ rules.add(rule);
+ // log.debug("apply() running in classloader {}", PatchOkHttp.class.getClassLoader());
+ if (!isHooked) hook();
+ }
+
public static void apply(PatchOkHttpRule.Builder builder) {
rules.add(builder.build());
if (!isHooked) hook();
@@ -33,6 +47,7 @@ public class PatchOkHttp {
}
public static void match(RequestContext ctx, String currentHeader, Consumer consumer) {
+ // log.debug("match() running in classloader {}", PatchOkHttp.class.getClassLoader());
for (PatchOkHttpRule rule : PatchOkHttp.rules) {
if (rule.matches(ctx)) {
rule.apply(ctx, currentHeader, consumer);
@@ -40,17 +55,46 @@ public class PatchOkHttp {
}
}
+ /**
+ * 通过指定 Advice 重定义指定类的指定方法
+ * 此处为:okhttp3.Request$Builder.header(String, String)
+ *
+ * 注意:必须保证欲重定义的类的方法的 ClassLoader 和调用该方法的 ClassLoader
+ * 一致,否则会导致两者的静态类的静态变量不一致,尤其是项目引入了 spring-boot-devtool
+ * 时,一般调用该方法的类由 RestartClassLoader 加载,而 okhttp 包由于是 jar 包,一般交由默认
+ * ClassLoaders 进行加载,这就导致 PatchOkHttp 在两个 ClassLoader 中不一致,实际注入方法内 PatchOkHttp.rules
+ * 为空,无论如何都无法命中设定的规则。
+ * 解决方法:
+ * - 在 src/main/resources 下建立 META-INF 目录,新建
+ * spring-devtools.properties,并在其内增加一行“restart.include.okhttp3=.*okhttp-.*\.jar”,指定由
+ * RestartClassLoader 加载 okhttp-*.jar 包;
+ * - 在 application.yml 中,增加需要额外扫描的路径,即设置参数“spring.devtools.restart.additional-pathes:
+ * lib/”,否则即便是进行了第一步,也不会生效;
+ * - 在方法体内,使用外部 RestartClassLoader 载入欲重定义的方法类,并在调用 ByteBuddy 时指定该方法类和 ClassLoader。
+ *
+ *
+ * @see org.springframework.boot.devtools.restart.classloader.RestartClassLoader
+ * @see jdk.internal.loader.ClassLoaders
+ */
private static void hook() {
try {
- ByteBuddyAgent.install();
- new ByteBuddy()
- .redefine(Class.forName("okhttp3.Request$Builder"))
- .visit(Advice.to(HeaderInterceptor.class)
- .on(named("header").and(takesArguments(String.class, String.class))))
- .make()
- .load(Class.forName("okhttp3.Request$Builder").getClassLoader(),
- ClassReloadingStrategy.fromInstalledAgent());
+ ByteBuddyAgent.install();
+
+ ClassLoader appLoader = PatchOkHttp.class.getClassLoader();
+ Class> builderClass = appLoader.loadClass("okhttp3.Request$Builder");
+
+
+ if (builderClass.getClassLoader() != appLoader) {
+ int exitCode = SpringApplication.exit(
+ SpringContextHolder.getBean(ConfigurableApplicationContext.class), () -> 0);
+ System.exit(exitCode);
+ }
+
+ new ByteBuddy().redefine(builderClass)
+ .visit(Advice.to(HeaderInterceptor.class)
+ .on(named("header").and(takesArguments(String.class, String.class))))
+ .make().load(appLoader, ClassReloadingStrategy.fromInstalledAgent());
isHooked = true;
@@ -64,21 +108,17 @@ public class PatchOkHttp {
public static final Logger log = LoggerFactory.getLogger(HeaderInterceptor.class);
- @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
+ @Advice.OnMethodEnter(
+ skipOn = Advice.OnNonDefaultValue.class)
public static boolean intercept(
@Advice.This Request.Builder builder,
@Advice.Argument(0) String name,
@Advice.Argument(value = 1, readOnly = false) String value,
@Advice.Local("call") Callable superCall
) throws Exception {
-
- // print调试
- log.debug("[intercept] name = {}, value = {}", name, value);
-
+
try {
- // 获取 headers map
-
-
+ // 获取上下文
RequestContext ctx = new RequestContext(builder);
String[] modified = new String[]{value};
diff --git a/src/main/java/quant/rich/emoney/patch/okhttp/PatchOkHttpRule.java b/src/main/java/quant/rich/emoney/patch/okhttp/PatchOkHttpRule.java
index 98c9a76..8be923f 100644
--- a/src/main/java/quant/rich/emoney/patch/okhttp/PatchOkHttpRule.java
+++ b/src/main/java/quant/rich/emoney/patch/okhttp/PatchOkHttpRule.java
@@ -4,6 +4,11 @@ import java.util.*;
import java.util.function.*;
import java.util.regex.Pattern;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import quant.rich.emoney.patch.okhttp.PatchOkHttp.HeaderInterceptor;
+
public class PatchOkHttpRule {
private final Predicate condition;
private final List actions;
@@ -37,6 +42,7 @@ public class PatchOkHttpRule {
}
public static class Builder {
+ public static final Logger log = LoggerFactory.getLogger(HeaderInterceptor.class);
private Predicate condition;
private final List actions = new ArrayList<>();
@@ -51,6 +57,10 @@ public class PatchOkHttpRule {
public Builder hasHeaderName(String name) {
return and(ctx -> ctx.headers.containsKey(name));
}
+
+ public Builder hasNotHeaderName(String name) {
+ return not(r -> r.and(ctx -> ctx.headers.containsKey(name)));
+ }
public Builder hasHeaderValueMatch(String name, String regex) {
Pattern pattern = Pattern.compile(regex);
@@ -138,6 +148,8 @@ public class PatchOkHttpRule {
public Builder overrideIf(String headerName, String value) {
actions.add((ctx, curr, setter) -> {
if (curr.equalsIgnoreCase(headerName)) {
+ log.debug("matches and applying - host: {}, currHeader {}, targetHeader {}, value: {}, classLoader: {}", ctx.host, curr, headerName,
+ value, this.getClass().getClassLoader());
setter.accept(value);
}
});
diff --git a/src/main/java/quant/rich/emoney/validator/ProxyConfigValidator.java b/src/main/java/quant/rich/emoney/validator/ProxyConfigValidator.java
index 3563f69..81038d6 100644
--- a/src/main/java/quant/rich/emoney/validator/ProxyConfigValidator.java
+++ b/src/main/java/quant/rich/emoney/validator/ProxyConfigValidator.java
@@ -8,6 +8,7 @@ import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import quant.rich.emoney.entity.config.ProxyConfig;
import quant.rich.emoney.interfaces.IConfig;
+import quant.rich.emoney.pojo.dto.IpInfo;
public class ProxyConfigValidator implements IValidator, ConstraintValidator> {
@@ -25,11 +26,8 @@ public class ProxyConfigValidator implements IValidator, ConstraintValidator.layui-nav-img {
+ transition: ease-in-out .4s;
+ z-index: 1;
+ position: relative;
+ opacity: 0.9;
+}
+.nav-user-info:hover>.layui-nav-img {
+ opacity: 1;
+}
+.nav-user-info::before {
+ transition: ease-in-out .4s;
+ content: "";
+ position: absolute;
+ top: 16px;
+ width: 30px; height: 30px;
+ border-radius: 50%;
+ filter: blur(6px);
+ background: conic-gradient(
+ from 0deg,
+ #ff0080, #ff8c00, #ffff00, #00ff80,
+ #00f6ff, #8000ff, #ff0080
+ );
+ z-index: 0;
+ animation: spin 4s linear infinite;
+}
+.nav-user-info:hover::before {
+ filter: blur(10px) brightness(1.5);
+}
+@keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
.manage-body {
padding: 80px 18% 0
}
@@ -333,7 +365,13 @@ ul.start-list span {
border: none;
padding: 0
}
-
+.layui-nav {
+ background-color: rgb(241 241 241 / 60%);
+ backdrop-filter: blur(4.2px);
+}
+.layui-nav .layui-nav-item a {
+ color: black !important;
+}
/* 覆盖样式:blockquote */
blockquote.layui-elem-quote {
border-radius: 5px;
@@ -395,4 +433,13 @@ blockquote.layui-elem-quote {
}
.bottom-anchor-before.submit-fixed {
height: 54px
+}
+fieldset>legend {
+ font-weight: 500
+}
+fieldset>legend:before {
+ content: '#';
+ font-weight: bold;
+ margin-right: .25em;
+ color: rgb(22, 183, 119)
}
\ No newline at end of file
diff --git a/src/main/resources/webpage/admin/v1/config/emoneyRequest/index.html b/src/main/resources/webpage/admin/v1/config/emoneyRequest/index.html
index 9ef94da..41d39b1 100644
--- a/src/main/resources/webpage/admin/v1/config/emoneyRequest/index.html
+++ b/src/main/resources/webpage/admin/v1/config/emoneyRequest/index.html
@@ -13,24 +13,7 @@
益盟操盘手请求头配置,请小心更改。部分参数需要通过模拟的 APP 版本抓包确认。
-