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 + * 为空,无论如何都无法命中设定的规则。

+ *

解决方法:

    + *
  1. 在 src/main/resources 下建立 META-INF 目录,新建 + * spring-devtools.properties,并在其内增加一行“restart.include.okhttp3=.*okhttp-.*\.jar”,指定由 + * RestartClassLoader 加载 okhttp-*.jar 包;
  2. + *
  3. 在 application.yml 中,增加需要额外扫描的路径,即设置参数“spring.devtools.restart.additional-pathes: + * lib/”,否则即便是进行了第一步,也不会生效;
  4. + *
  5. 在方法体内,使用外部 RestartClassLoader 载入欲重定义的方法类,并在调用 ByteBuddy 时指定该方法类和 ClassLoader。
  6. + *
+ *

+ * @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 版本抓包确认。
-
- 代理配置 -
- - -
- -
- -
-
- -
+
鉴权配置
@@ -67,7 +50,7 @@
-
+
应用配置
@@ -79,7 +62,7 @@
- +
默认 5.8.1 @@ -95,7 +78,7 @@
-
+
设备配置
@@ -113,7 +96,7 @@
- +
对应 build.prop 中 Build.MODEL,用于 @@ -126,7 +109,7 @@
- +
对应 build.prop 中 Build.FINGERPRINT,用于 @@ -139,19 +122,19 @@
- +
用于
  • 益盟登录接口 softwareType = softwareType
- 一般和设备有关,需要更改请刷新 + Mobile 或 Pad,一般和设备有关,需要更改请刷新
- +
用于 WebView User-Agent,需要更改请刷新 diff --git a/src/main/resources/webpage/admin/v1/config/proxy/index.html b/src/main/resources/webpage/admin/v1/config/proxy/index.html index 1a2ed35..51c893e 100644 --- a/src/main/resources/webpage/admin/v1/config/proxy/index.html +++ b/src/main/resources/webpage/admin/v1/config/proxy/index.html @@ -27,17 +27,13 @@
-
代理地址 -
- -
非匿名登录时的密码 -
+
@@ -50,10 +46,13 @@ th:checked="${@proxyConfig.ignoreHttpsVerification}" name="ignoreHttpsVerification" lay-verify="required"/>
+
+ 出于安全考虑,当且仅当使用 Fiddler/BurpSuite 调试时才建议启用 +
-
+
@@ -79,12 +79,6 @@
-
- -
- -
-
@@ -115,6 +109,37 @@