This commit is contained in:
wangli 2026-01-18 21:15:24 +08:00
parent 3e1e1cfc13
commit 5bec08604e
4 changed files with 361 additions and 267 deletions

View File

@ -45,7 +45,7 @@
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<version>1.18.34</version> <version>1.18.30</version>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
@ -99,17 +99,33 @@
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.springframework.boot</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>spring-boot-maven-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration> <configuration>
<excludes> <source>17</source>
<exclude> <target>17</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
</exclude> <version>1.18.30</version>
</excludes> </path>
</annotationProcessorPaths>
</configuration> </configuration>
</plugin> </plugin>
<!-- <plugin>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-maven-plugin</artifactId>-->
<!-- <configuration>-->
<!-- <excludes>-->
<!-- <exclude>-->
<!-- <groupId>org.projectlombok</groupId>-->
<!-- <artifactId>lombok</artifactId>-->
<!-- </exclude>-->
<!-- </excludes>-->
<!-- </configuration>-->
<!-- </plugin>-->
</plugins> </plugins>
</build> </build>

View File

@ -13,6 +13,7 @@ import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.servlet.resource.ResourceUrlProvider;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
@ -25,6 +26,7 @@ import java.util.concurrent.ConcurrentHashMap;
public class BrowserService { public class BrowserService {
private final CookieRepository cookieRepository; private final CookieRepository cookieRepository;
private final ResourceUrlProvider resourceUrlProvider;
private Playwright playwright; private Playwright playwright;
private Browser browser; private Browser browser;
@ -35,8 +37,9 @@ public class BrowserService {
private final Map<String, Object> contextLocks = new ConcurrentHashMap<>(); private final Map<String, Object> contextLocks = new ConcurrentHashMap<>();
@Autowired @Autowired
public BrowserService(CookieRepository cookieRepository) { public BrowserService(CookieRepository cookieRepository, ResourceUrlProvider resourceUrlProvider) {
this.cookieRepository = cookieRepository; this.cookieRepository = cookieRepository;
this.resourceUrlProvider = resourceUrlProvider;
} }
@PostConstruct @PostConstruct
@ -523,6 +526,54 @@ public class BrowserService {
return false; return false;
} }
private boolean attemptQuickLogin(Frame frame) {
boolean containerFound = false;
if (Objects.isNull(frame)) return containerFound;
ElementHandle elementHandle = frame.querySelector("#alibaba-login-box");
if (Objects.isNull(elementHandle)) return containerFound;
Frame quickLoginFrame = elementHandle.contentFrame();
if (Objects.isNull(quickLoginFrame)) return containerFound;
ElementHandle loginButton = quickLoginFrame.querySelector(".fm-button.fm-submit");
if (Objects.isNull(loginButton)) return containerFound;
if (loginButton.isVisible()) {
loginButton.click();
return true;
}
return false;
}
private boolean attemptQuickLoginV2(Frame frame) {
try {
String[] loginButtonSelectors = {".has-login", ".cm-has-login", ".fm-btn", ".fm-button", ".fm-submit"};
boolean containerFound = false;
for (String s : loginButtonSelectors) {
if (frame.querySelector(s) != null && frame.isVisible(s)) {
containerFound = true;
break;
}
}
if (!containerFound) return false;
ElementHandle loginButtonDialog = frame.querySelector(".has-login");
if (loginButtonDialog == null) loginButtonDialog = frame.querySelector(".cm-has-login");
if (loginButtonDialog != null && loginButtonDialog.isVisible()) {
log.info("【Login Task】Detected quick login in frame: {}", frame.url());
ElementHandle loginButton = frame.querySelector(".fm-button");
if (loginButton == null) loginButton = frame.querySelector(".fm-submit");
if (loginButton == null) return false;
loginButton.click();
log.info("【Login Task】quick login success!");
return true;
}
} catch (Exception e) {
log.warn("【Login Task】quick login fail : {}", e.getMessage());
}
return false;
}
/** /**
* 刷新Cookie - 使用持久化浏览器上下文 * 刷新Cookie - 使用持久化浏览器上下文
* Cookie会自动保存到UserData目录类似真实浏览器行为 * Cookie会自动保存到UserData目录类似真实浏览器行为
@ -574,10 +625,18 @@ public class BrowserService {
// 3. 等待页面加载 // 3. 等待页面加载
try { try {
Thread.sleep(3000); Thread.sleep(5000);
} catch (Exception e) { } catch (Exception e) {
} }
// 判断是否有快捷登陆iframe
for (Frame frame : page.frames()) {
if (attemptQuickLogin(frame)) {
break;
}
}
// 4. 重新加载页面以触发Cookie刷新 // 4. 重新加载页面以触发Cookie刷新
log.info("【{}-Cookie Refresh】重新加载页面...", cookieId); log.info("【{}-Cookie Refresh】重新加载页面...", cookieId);
try { try {
@ -618,6 +677,7 @@ public class BrowserService {
// 9. 更新数据库 // 9. 更新数据库
if (!newCookieStr.equals(cookie.getValue())) { if (!newCookieStr.equals(cookie.getValue())) {
cookie.setValue(newCookieStr); cookie.setValue(newCookieStr);
log.debug("【{}】🤖刷新浏览器后获取到的 cookie 为: {}", cookieId, newCookieStr);
cookieRepository.save(cookie); cookieRepository.save(cookie);
log.info("【{}-Cookie Refresh】✅ Cookie已更新并保存到数据库: {}", cookieId, cookieId); log.info("【{}-Cookie Refresh】✅ Cookie已更新并保存到数据库: {}", cookieId, cookieId);
} else { } else {

View File

@ -356,14 +356,14 @@ public class XianyuClient extends TextWebSocketHandler {
/** /**
* WebSocket连接循环 - 对应Python的main方法中的while True循环 * WebSocket连接循环 - 重构版本去除嵌套
* 核心逻辑外层循环保持运行内部单次连接尝试失败后延迟重试
*/ */
private void connectionLoop() { private void connectionLoop() {
while (running.get()) { while (running.get()) {
try { try {
// 检查账号是否启用 // 检查账号是否启用
Optional<Cookie> cookieOpt = cookieRepository.findById(cookieId); if (!isAccountEnabled()) {
if (cookieOpt.isEmpty() || !Boolean.TRUE.equals(cookieOpt.get().getEnabled())) {
log.info("【{}】账号已禁用,停止连接循环", cookieId); log.info("【{}】账号已禁用,停止连接循环", cookieId);
break; break;
} }
@ -372,48 +372,73 @@ public class XianyuClient extends TextWebSocketHandler {
setConnectionState(ConnectionState.CONNECTING, "准备建立WebSocket连接"); setConnectionState(ConnectionState.CONNECTING, "准备建立WebSocket连接");
log.info("【{}】WebSocket目标地址: {}", cookieId, WEBSOCKET_URL); log.info("【{}】WebSocket目标地址: {}", cookieId, WEBSOCKET_URL);
// 创建WebSocket连接 // 单次连接尝试
connectWebSocket(); connectWebSocket();
// 连接成功后等待连接断开 // 连接成功后进入等待循环直到连接断开
// WebSocket会在另一个线程中运行这里需要阻塞等待 waitForDisconnection();
log.info("【{}】WebSocket连接已建立等待连接断开...", cookieId);
while (connected.get() && running.get()) {
try {
Thread.sleep(1000); // 每秒检查一次连接状态
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break;
}
}
log.info("【{}】WebSocket连接已断开", cookieId); log.info("【{}】WebSocket连接已断开", cookieId);
} catch (Exception e) { } catch (Exception e) {
// 统一处理连接错误
handleConnectionError(e); handleConnectionError(e);
} }
// 重连延迟 // 计算并执行重连延迟
if (running.get()) { if (running.get()) {
int retryDelay = calculateRetryDelay(connectionFailures.get()); int retryDelay = calculateRetryDelay(connectionFailures.get());
log.info("【{}】{}秒后尝试重连...", cookieId, retryDelay); log.info("【{}】{}秒后尝试重连...", cookieId, retryDelay);
try { sleepWithInterruptCheck(retryDelay * 1000L);
Thread.sleep(retryDelay * 1000L);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break;
}
} }
} }
log.info("【{}】WebSocket 连接循环已退出", cookieId); log.info("【{}】WebSocket 连接循环已退出", cookieId);
} }
/**
* 检查账号是否启用
*/
private boolean isAccountEnabled() {
try {
Optional<Cookie> cookieOpt = cookieRepository.findById(cookieId);
return cookieOpt.isPresent() && Boolean.TRUE.equals(cookieOpt.get().getEnabled());
} catch (Exception e) {
log.error("【{}】检查账号状态失败", cookieId, e);
return false;
}
}
/** /**
* 创建WebSocket连接 * 等待WebSocket连接断开
*/
private void waitForDisconnection() {
log.info("【{}】WebSocket连接已建立等待连接断开...", cookieId);
while (connected.get() && running.get()) {
sleepWithInterruptCheck(1000);
}
}
/**
* 可中断的Sleep
*/
private void sleepWithInterruptCheck(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
log.warn("【{}】Sleep被中断", cookieId);
}
}
/**
* 创建WebSocket连接 - 重构版本纯粹的单次连接尝试
* 失败直接抛异常 connectionLoop() 统一处理重试
*/ */
private void connectWebSocket() throws Exception { private void connectWebSocket() throws Exception {
log.info("【{}】开始建立WebSocket连接...", cookieId);
// 配置WebSocket容器设置缓冲区大小为10MB解决1009错误消息过大 // 配置WebSocket容器设置缓冲区大小为10MB解决1009错误消息过大
WebSocketContainer container = ContainerProvider.getWebSocketContainer(); WebSocketContainer container = ContainerProvider.getWebSocketContainer();
container.setDefaultMaxTextMessageBufferSize(10 * 1024 * 1024); // 10MB container.setDefaultMaxTextMessageBufferSize(10 * 1024 * 1024); // 10MB
@ -422,9 +447,33 @@ public class XianyuClient extends TextWebSocketHandler {
// 使用配置好的容器创建WebSocket客户端 // 使用配置好的容器创建WebSocket客户端
WebSocketClient client = new StandardWebSocketClient(container); WebSocketClient client = new StandardWebSocketClient(container);
// 准备请求头 - 使用WebSocketHttpHeaders添加所有必要的headers // 准备请求头
WebSocketHttpHeaders headers = new WebSocketHttpHeaders(); WebSocketHttpHeaders headers = buildWebSocketHeaders();
try {
// 发起WebSocket握手
ListenableFuture<WebSocketSession> future =
client.doHandshake(this, headers, URI.create(WEBSOCKET_URL));
// 等待连接完成超时10秒
this.webSocketSession = future.get(10, TimeUnit.SECONDS);
log.info("【{}】WebSocket连接建立成功", cookieId);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new Exception("WebSocket连接被中断", e);
} catch (java.util.concurrent.ExecutionException e) {
throw new Exception("WebSocket连接执行失败: " + e.getMessage(), e);
} catch (java.util.concurrent.TimeoutException e) {
throw new Exception("WebSocket连接超时", e);
}
}
/**
* 构建WebSocket请求头
*/
private WebSocketHttpHeaders buildWebSocketHeaders() {
WebSocketHttpHeaders headers = new WebSocketHttpHeaders();
headers.add("Accept-Encoding", "gzip, deflate, br, zstd"); headers.add("Accept-Encoding", "gzip, deflate, br, zstd");
headers.add("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6"); headers.add("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6");
headers.add("Cache-Control", "no-cache"); headers.add("Cache-Control", "no-cache");
@ -437,24 +486,7 @@ public class XianyuClient extends TextWebSocketHandler {
headers.add("sec-websocket-version", "13"); headers.add("sec-websocket-version", "13");
headers.add("upgrade", "websocket"); headers.add("upgrade", "websocket");
headers.add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0"); headers.add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0");
return headers;
try {
// doHandshake参数: WebSocketHandler, WebSocketHttpHeaders, URI
ListenableFuture<WebSocketSession> future =
client.doHandshake(this, headers, URI.create(WEBSOCKET_URL));
// 等待连接完成
this.webSocketSession = future.get(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new Exception("WebSocket连接被中断", e);
} catch (java.util.concurrent.ExecutionException e) {
throw new Exception("WebSocket连接执行失败: " + e.getMessage(), e);
} catch (java.util.concurrent.TimeoutException e) {
throw new Exception("WebSocket连接超时", e);
}
log.info("【{}】WebSocket连接建立成功", cookieId);
} }
@ -590,7 +622,7 @@ public class XianyuClient extends TextWebSocketHandler {
JSONObject regHeaders = new JSONObject(); JSONObject regHeaders = new JSONObject();
regHeaders.put("cache-header", "app-key token ua wv"); regHeaders.put("cache-header", "app-key token ua wv");
regHeaders.put("app-key",WEBSOCKET_APP_KEY); regHeaders.put("app-key", WEBSOCKET_APP_KEY);
regHeaders.put("token", currentToken); regHeaders.put("token", currentToken);
regHeaders.put("ua", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0"); regHeaders.put("ua", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0");
regHeaders.put("dt", "j"); regHeaders.put("dt", "j");
@ -650,27 +682,12 @@ public class XianyuClient extends TextWebSocketHandler {
/** /**
* 刷新Token - 对应Python的refresh_token()方法 * 刷新Token - 重构版本去除嵌套循环
* 添加自动降级机制Token获取失败时自动刷新Cookie * 策略尝试获取Token失败则刷新Cookie后抛异常由上层决定是否重试
*/ */
private String refreshToken() { private String refreshToken() {
int maxRetries = 3;
int retryCount = 0;
while (retryCount < maxRetries) {
try {
if (retryCount > 0) {
log.info("【{}】Token获取失败第 {} 次重试...", cookieId, retryCount);
// 添加重试延迟避免过快重试导致资源竞争
try {
Thread.sleep(2000 * retryCount); // 指数退避2s, 4s, 6s
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
} else {
log.info("【{}】开始刷新token...", cookieId);
}
lastTokenRefreshStatus = "started"; lastTokenRefreshStatus = "started";
log.info("【{}】开始刷新token...", cookieId);
// 检查是否在消息冷却期内 // 检查是否在消息冷却期内
long currentTime = System.currentTimeMillis(); long currentTime = System.currentTimeMillis();
@ -684,6 +701,39 @@ public class XianyuClient extends TextWebSocketHandler {
} }
// 从数据库重新加载Cookie可能已被浏览器刷新更新 // 从数据库重新加载Cookie可能已被浏览器刷新更新
reloadCookieFromDatabase();
// 尝试获取Token
log.debug("【{}】🤖准备调用官方API获取Token...", cookieId);
String token = attemptGetToken();
log.debug("【{}】🤖调用官方API获取Token结果: {}", cookieId, token != null ? "成功" : "失败");
if (token != null) {
// Token获取成功
this.currentToken = token;
this.lastTokenRefreshTime.set(System.currentTimeMillis());
this.lastMessageReceivedTime.set(0);
log.warn("【{}】✅ Token刷新成功", cookieId);
lastTokenRefreshStatus = "success";
return token;
}
// Token获取失败尝试刷新Cookie然后返回null
// 不在这里重试失败后让connectionLoop处理重连
log.warn("【{}】⚠️ Token获取失败尝试通过浏览器刷新Cookie...", cookieId);
refreshCookieViaBrowser();
// 尝试获取Token
log.debug("【{}】🤖刷新Cookie后调用官方API获取Token...", cookieId);
token = attemptGetToken();
log.debug("【{}】🤖刷新Cookie后调用官方API获取Token结果: {}", cookieId, token != null ? "成功" : "失败");
return token;
}
/**
* 从数据库重新加载Cookie避免重复代码
*/
private void reloadCookieFromDatabase() {
try { try {
Optional<Cookie> cookieOpt = cookieRepository.findById(cookieId); Optional<Cookie> cookieOpt = cookieRepository.findById(cookieId);
if (cookieOpt.isPresent()) { if (cookieOpt.isPresent()) {
@ -698,76 +748,43 @@ public class XianyuClient extends TextWebSocketHandler {
} catch (Exception e) { } catch (Exception e) {
log.warn("【{}】从数据库重新加载cookie失败继续使用当前cookie: {}", cookieId, e.getMessage()); log.warn("【{}】从数据库重新加载cookie失败继续使用当前cookie: {}", cookieId, e.getMessage());
} }
// 尝试获取Token
log.debug("【{}】🤖准备调用官方API获取Token...", cookieId);
String token = attemptGetToken();
log.debug("【{}】🤖准备调用官方API获取Token为: {}", cookieId, token);
if (token != null) {
// Token获取成功
this.currentToken = token;
this.lastTokenRefreshTime.set(System.currentTimeMillis());
this.lastMessageReceivedTime.set(0); // 重置消息接收时间
log.warn("【{}】✅ Token刷新成功", cookieId);
lastTokenRefreshStatus = "success";
return token;
} }
// Token获取失败尝试刷新Cookie /**
log.warn("【{}】⚠️ Token获取失败尝试通过浏览器刷新Cookie...", cookieId); * 通过浏览器刷新Cookie避免重复代码
*/
private void refreshCookieViaBrowser() {
try { try {
Map<String, String> newCookies = browserService.refreshCookies(cookieId); Map<String, String> newCookies = browserService.refreshCookies(cookieId);
if (newCookies != null && !newCookies.isEmpty()) { if (newCookies != null && !newCookies.isEmpty()) {
log.info("【{}】✅ Cookie刷新成功重新加载...", cookieId); log.info("【{}】✅ Cookie刷新成功重新加载...", cookieId);
// 重新加载Cookie
loadCookies(); loadCookies();
retryCount++; return;
// 继续下一轮重试 }
continue;
} else {
log.error("【{}】❌ Cookie刷新失败尝试强制重建持久化上下文", cookieId);
// 强制关闭持久化上下文下次重试时会重新创建
try {
browserService.closePersistentContext(cookieId);
Thread.sleep(3000); // 等待3秒确保资源完全释放
// 再次尝试刷新Cookie // 首次失败尝试重建上下文
log.warn("【{}】Cookie刷新失败尝试强制重建持久化上下文", cookieId);
browserService.closePersistentContext(cookieId);
Thread.sleep(3000); // 等待资源释放
// 再次尝试
newCookies = browserService.refreshCookies(cookieId); newCookies = browserService.refreshCookies(cookieId);
if (newCookies != null && !newCookies.isEmpty()) { if (newCookies != null && !newCookies.isEmpty()) {
log.info("【{}】重建上下文后Cookie刷新成功", cookieId); log.info("【{}】重建上下文后Cookie刷新成功", cookieId);
loadCookies(); loadCookies();
retryCount++; } else {
continue; log.error("【{}】❌ Cookie刷新最终失败", cookieId);
}
} catch (Exception retryEx) {
log.error("【{}】重建上下文后仍然失败: {}", cookieId, retryEx.getMessage());
}
log.error("【{}】❌ Cookie刷新最终失败无法继续", cookieId);
break;
} }
lastTokenRefreshStatus = "success";
} catch (Exception e) { } catch (Exception e) {
log.error("【{}】❌ Cookie刷新异常: {}", cookieId, e.getMessage()); log.error("【{}】❌ Cookie刷新异常: {}", cookieId, e.getMessage());
// 异常时也尝试关闭上下文
try { try {
browserService.closePersistentContext(cookieId); browserService.closePersistentContext(cookieId);
} catch (Exception ignored) { } catch (Exception ignored) {
} }
break;
}
} catch (Exception e) {
log.error("【{}】Token刷新过程异常", cookieId, e);
break;
}
}
log.error("【{}】❌ Token刷新最终失败已重试 {} 次", cookieId, retryCount);
lastTokenRefreshStatus = "failed"; lastTokenRefreshStatus = "failed";
return null; }
} }
/** /**
@ -811,9 +828,10 @@ public class XianyuClient extends TextWebSocketHandler {
log.info("【{}】API端点: {}", cookieId, url); log.info("【{}】API端点: {}", cookieId, url);
log.info("【{}】timestamp: {}", cookieId, timestamp); log.info("【{}】timestamp: {}", cookieId, timestamp);
log.info("【{}】sign: {}", cookieId, sign); log.info("【{}】sign: {}", cookieId, sign);
log.info("【{}】cookies: {}", cookieId, cookiesStr);
// 发送POST请求 // 发送POST请求
HttpRequest request = cn.hutool.http.HttpRequest.post(url); HttpRequest request = HttpRequest.post(url);
request.form("data", dataVal); request.form("data", dataVal);
params.forEach((k, v) -> request.form(k, v.toString())); params.forEach((k, v) -> request.form(k, v.toString()));
request.header("cookie", cookiesStr); request.header("cookie", cookiesStr);
@ -848,7 +866,7 @@ public class XianyuClient extends TextWebSocketHandler {
JSONObject data = resJson.getJSONObject("data"); JSONObject data = resJson.getJSONObject("data");
if (data.containsKey("accessToken")) { if (data.containsKey("accessToken")) {
String newToken = data.getString("accessToken"); String newToken = data.getString("accessToken");
log.info("【{}】获取到accessToken", cookieId); log.info("【{}】获取到accessToken: {}", cookieId, newToken);
return newToken; return newToken;
} }
} }
@ -2480,7 +2498,7 @@ public class XianyuClient extends TextWebSocketHandler {
/** /**
* 处理消息主逻辑 - 完整版阶段1基础消息处理 * 处理消息主逻辑 - 完整版阶段1基础消息处理
* 对应Python的handle_message()方法 * 对应Python的handle_message()方法
* * <p>
* 阶段1包含 * 阶段1包含
* 1. 检查账号状态 * 1. 检查账号状态
* 2. 发送ACK确认 * 2. 发送ACK确认

View File

@ -67,7 +67,7 @@ public class CaptchaHandler {
dragSlider(sliderElement, distance, cookieId); dragSlider(sliderElement, distance, cookieId);
// 检查是否成功 // 检查是否成功
Thread.sleep(10000); Thread.sleep(5000);
boolean success = checkSuccess(verificationUrl, cookieId); boolean success = checkSuccess(verificationUrl, cookieId);
if (success) { if (success) {
@ -249,7 +249,7 @@ public class CaptchaHandler {
double randomX1 = 200 + Math.random() * 400; // 200-600px范围 double randomX1 = 200 + Math.random() * 400; // 200-600px范围
double randomY1 = 100 + Math.random() * 200; // 100-300px范围 double randomY1 = 100 + Math.random() * 200; // 100-300px范围
page.mouse().move(randomX1, randomY1); page.mouse().move(randomX1, randomY1);
page.mouse().click(randomX1, randomY1); page.mouse().click(randomX1, randomY1, new Mouse.ClickOptions().setClickCount(1));
Thread.sleep(100 + (long)(Math.random() * 200)); Thread.sleep(100 + (long)(Math.random() * 200));
// 2. 再移动到接近滑块的位置但不是精确位置 // 2. 再移动到接近滑块的位置但不是精确位置