init
This commit is contained in:
parent
3e1e1cfc13
commit
5bec08604e
@ -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>
|
||||||
|
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -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确认
|
||||||
|
|||||||
@ -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. 再移动到接近滑块的位置(但不是精确位置)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user