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

View File

@ -13,6 +13,7 @@ import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.resource.ResourceUrlProvider;
import java.nio.file.Path;
import java.nio.file.Paths;
@ -25,6 +26,7 @@ import java.util.concurrent.ConcurrentHashMap;
public class BrowserService {
private final CookieRepository cookieRepository;
private final ResourceUrlProvider resourceUrlProvider;
private Playwright playwright;
private Browser browser;
@ -35,8 +37,9 @@ public class BrowserService {
private final Map<String, Object> contextLocks = new ConcurrentHashMap<>();
@Autowired
public BrowserService(CookieRepository cookieRepository) {
public BrowserService(CookieRepository cookieRepository, ResourceUrlProvider resourceUrlProvider) {
this.cookieRepository = cookieRepository;
this.resourceUrlProvider = resourceUrlProvider;
}
@PostConstruct
@ -523,6 +526,54 @@ public class BrowserService {
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会自动保存到UserData目录类似真实浏览器行为
@ -574,10 +625,18 @@ public class BrowserService {
// 3. 等待页面加载
try {
Thread.sleep(3000);
Thread.sleep(5000);
} catch (Exception e) {
}
// 判断是否有快捷登陆iframe
for (Frame frame : page.frames()) {
if (attemptQuickLogin(frame)) {
break;
}
}
// 4. 重新加载页面以触发Cookie刷新
log.info("【{}-Cookie Refresh】重新加载页面...", cookieId);
try {
@ -618,6 +677,7 @@ public class BrowserService {
// 9. 更新数据库
if (!newCookieStr.equals(cookie.getValue())) {
cookie.setValue(newCookieStr);
log.debug("【{}】🤖刷新浏览器后获取到的 cookie 为: {}", cookieId, newCookieStr);
cookieRepository.save(cookie);
log.info("【{}-Cookie Refresh】✅ Cookie已更新并保存到数据库: {}", cookieId, cookieId);
} else {
@ -652,7 +712,7 @@ public class BrowserService {
log.info("【QR Login】Verifying cookies for account: {}", accountId);
try (BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setUserAgent("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")
.setUserAgent("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")
// .setViewportSize(1920, 1080)
)) {
@ -850,13 +910,13 @@ public class BrowserService {
if (java.nio.file.Files.isDirectory(path)) {
try (java.util.stream.Stream<java.nio.file.Path> stream = java.nio.file.Files.walk(path)) {
stream.sorted(java.util.Comparator.reverseOrder())
.forEach(p -> {
try {
java.nio.file.Files.delete(p);
} catch (java.io.IOException e) {
log.warn("删除文件失败: {}", p, e);
}
});
.forEach(p -> {
try {
java.nio.file.Files.delete(p);
} catch (java.io.IOException e) {
log.warn("删除文件失败: {}", p, e);
}
});
}
}
}

View File

@ -356,14 +356,14 @@ public class XianyuClient extends TextWebSocketHandler {
/**
* WebSocket连接循环 - 对应Python的main方法中的while True循环
* WebSocket连接循环 - 重构版本去除嵌套
* 核心逻辑外层循环保持运行内部单次连接尝试失败后延迟重试
*/
private void connectionLoop() {
while (running.get()) {
try {
// 检查账号是否启用
Optional<Cookie> cookieOpt = cookieRepository.findById(cookieId);
if (cookieOpt.isEmpty() || !Boolean.TRUE.equals(cookieOpt.get().getEnabled())) {
if (!isAccountEnabled()) {
log.info("【{}】账号已禁用,停止连接循环", cookieId);
break;
}
@ -372,48 +372,73 @@ public class XianyuClient extends TextWebSocketHandler {
setConnectionState(ConnectionState.CONNECTING, "准备建立WebSocket连接");
log.info("【{}】WebSocket目标地址: {}", cookieId, WEBSOCKET_URL);
// 创建WebSocket连接
// 单次连接尝试
connectWebSocket();
// 连接成功后等待连接断开
// WebSocket会在另一个线程中运行这里需要阻塞等待
log.info("【{}】WebSocket连接已建立等待连接断开...", cookieId);
while (connected.get() && running.get()) {
try {
Thread.sleep(1000); // 每秒检查一次连接状态
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break;
}
}
// 连接成功后进入等待循环直到连接断开
waitForDisconnection();
log.info("【{}】WebSocket连接已断开", cookieId);
} catch (Exception e) {
// 统一处理连接错误
handleConnectionError(e);
}
// 重连延迟
// 计算并执行重连延迟
if (running.get()) {
int retryDelay = calculateRetryDelay(connectionFailures.get());
log.info("【{}】{}秒后尝试重连...", cookieId, retryDelay);
try {
Thread.sleep(retryDelay * 1000L);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break;
}
sleepWithInterruptCheck(retryDelay * 1000L);
}
}
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 {
log.info("【{}】开始建立WebSocket连接...", cookieId);
// 配置WebSocket容器设置缓冲区大小为10MB解决1009错误消息过大
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
container.setDefaultMaxTextMessageBufferSize(10 * 1024 * 1024); // 10MB
@ -422,9 +447,33 @@ public class XianyuClient extends TextWebSocketHandler {
// 使用配置好的容器创建WebSocket客户端
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-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");
@ -437,24 +486,7 @@ public class XianyuClient extends TextWebSocketHandler {
headers.add("sec-websocket-version", "13");
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");
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);
return headers;
}
@ -590,7 +622,7 @@ public class XianyuClient extends TextWebSocketHandler {
JSONObject regHeaders = new JSONObject();
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("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");
@ -650,124 +682,109 @@ public class XianyuClient extends TextWebSocketHandler {
/**
* 刷新Token - 对应Python的refresh_token()方法
* 添加自动降级机制Token获取失败时自动刷新Cookie
* 刷新Token - 重构版本去除嵌套循环
* 策略尝试获取Token失败则刷新Cookie后抛异常由上层决定是否重试
*/
private String refreshToken() {
int maxRetries = 3;
int retryCount = 0;
lastTokenRefreshStatus = "started";
log.info("【{}】开始刷新token...", cookieId);
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";
// 检查是否在消息冷却期内
long currentTime = System.currentTimeMillis();
long timeSinceLastMessage = currentTime - lastMessageReceivedTime.get();
if (lastMessageReceivedTime.get() > 0 && timeSinceLastMessage < MESSAGE_COOLDOWN * 1000L) {
long remainingTime = MESSAGE_COOLDOWN * 1000L - timeSinceLastMessage;
log.info("【{}】收到消息后冷却中放弃本次token刷新还需等待 {} 秒",
cookieId, remainingTime / 1000);
lastTokenRefreshStatus = "skipped_cooldown";
return null;
}
// 从数据库重新加载Cookie可能已被浏览器刷新更新
try {
Optional<Cookie> cookieOpt = cookieRepository.findById(cookieId);
if (cookieOpt.isPresent()) {
String newCookiesStr = cookieOpt.get().getValue();
if (!newCookiesStr.equals(this.cookiesStr)) {
log.info("【{}】检测到数据库中的cookie已更新重新加载cookie", cookieId);
this.cookiesStr = newCookiesStr;
this.cookies = parseCookies(this.cookiesStr);
log.warn("【{}】Cookie已从数据库重新加载", cookieId);
}
}
} catch (Exception e) {
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);
try {
Map<String, String> newCookies = browserService.refreshCookies(cookieId);
if (newCookies != null && !newCookies.isEmpty()) {
log.info("【{}】✅ Cookie刷新成功重新加载...", cookieId);
// 重新加载Cookie
loadCookies();
retryCount++;
// 继续下一轮重试
continue;
} else {
log.error("【{}】❌ Cookie刷新失败尝试强制重建持久化上下文", cookieId);
// 强制关闭持久化上下文下次重试时会重新创建
try {
browserService.closePersistentContext(cookieId);
Thread.sleep(3000); // 等待3秒确保资源完全释放
// 再次尝试刷新Cookie
newCookies = browserService.refreshCookies(cookieId);
if (newCookies != null && !newCookies.isEmpty()) {
log.info("【{}】重建上下文后Cookie刷新成功", cookieId);
loadCookies();
retryCount++;
continue;
}
} catch (Exception retryEx) {
log.error("【{}】重建上下文后仍然失败: {}", cookieId, retryEx.getMessage());
}
log.error("【{}】❌ Cookie刷新最终失败无法继续", cookieId);
break;
}
} catch (Exception e) {
log.error("【{}】❌ Cookie刷新异常: {}", cookieId, e.getMessage());
// 异常时也尝试关闭上下文
try {
browserService.closePersistentContext(cookieId);
} catch (Exception ignored) {
}
break;
}
} catch (Exception e) {
log.error("【{}】Token刷新过程异常", cookieId, e);
break;
}
// 检查是否在消息冷却期内
long currentTime = System.currentTimeMillis();
long timeSinceLastMessage = currentTime - lastMessageReceivedTime.get();
if (lastMessageReceivedTime.get() > 0 && timeSinceLastMessage < MESSAGE_COOLDOWN * 1000L) {
long remainingTime = MESSAGE_COOLDOWN * 1000L - timeSinceLastMessage;
log.info("【{}】收到消息后冷却中放弃本次token刷新还需等待 {} 秒",
cookieId, remainingTime / 1000);
lastTokenRefreshStatus = "skipped_cooldown";
return null;
}
log.error("【{}】❌ Token刷新最终失败已重试 {} 次", cookieId, retryCount);
lastTokenRefreshStatus = "failed";
return null;
// 从数据库重新加载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 {
Optional<Cookie> cookieOpt = cookieRepository.findById(cookieId);
if (cookieOpt.isPresent()) {
String newCookiesStr = cookieOpt.get().getValue();
if (!newCookiesStr.equals(this.cookiesStr)) {
log.info("【{}】检测到数据库中的cookie已更新重新加载cookie", cookieId);
this.cookiesStr = newCookiesStr;
this.cookies = parseCookies(this.cookiesStr);
log.warn("【{}】Cookie已从数据库重新加载", cookieId);
}
}
} catch (Exception e) {
log.warn("【{}】从数据库重新加载cookie失败继续使用当前cookie: {}", cookieId, e.getMessage());
}
}
/**
* 通过浏览器刷新Cookie避免重复代码
*/
private void refreshCookieViaBrowser() {
try {
Map<String, String> newCookies = browserService.refreshCookies(cookieId);
if (newCookies != null && !newCookies.isEmpty()) {
log.info("【{}】✅ Cookie刷新成功重新加载...", cookieId);
loadCookies();
return;
}
// 首次失败尝试重建上下文
log.warn("【{}】Cookie刷新失败尝试强制重建持久化上下文", cookieId);
browserService.closePersistentContext(cookieId);
Thread.sleep(3000); // 等待资源释放
// 再次尝试
newCookies = browserService.refreshCookies(cookieId);
if (newCookies != null && !newCookies.isEmpty()) {
log.info("【{}】重建上下文后Cookie刷新成功", cookieId);
loadCookies();
} else {
log.error("【{}】❌ Cookie刷新最终失败", cookieId);
}
lastTokenRefreshStatus = "success";
} catch (Exception e) {
log.error("【{}】❌ Cookie刷新异常: {}", cookieId, e.getMessage());
try {
browserService.closePersistentContext(cookieId);
} catch (Exception ignored) {
}
lastTokenRefreshStatus = "failed";
}
}
/**
@ -811,9 +828,10 @@ public class XianyuClient extends TextWebSocketHandler {
log.info("【{}】API端点: {}", cookieId, url);
log.info("【{}】timestamp: {}", cookieId, timestamp);
log.info("【{}】sign: {}", cookieId, sign);
log.info("【{}】cookies: {}", cookieId, cookiesStr);
// 发送POST请求
HttpRequest request = cn.hutool.http.HttpRequest.post(url);
HttpRequest request = HttpRequest.post(url);
request.form("data", dataVal);
params.forEach((k, v) -> request.form(k, v.toString()));
request.header("cookie", cookiesStr);
@ -848,7 +866,7 @@ public class XianyuClient extends TextWebSocketHandler {
JSONObject data = resJson.getJSONObject("data");
if (data.containsKey("accessToken")) {
String newToken = data.getString("accessToken");
log.info("【{}】获取到accessToken", cookieId);
log.info("【{}】获取到accessToken: {}", cookieId, newToken);
return newToken;
}
}
@ -2480,7 +2498,7 @@ public class XianyuClient extends TextWebSocketHandler {
/**
* 处理消息主逻辑 - 完整版阶段1基础消息处理
* 对应Python的handle_message()方法
*
* <p>
* 阶段1包含
* 1. 检查账号状态
* 2. 发送ACK确认
@ -2538,7 +2556,7 @@ public class XianyuClient extends TextWebSocketHandler {
String orderId = extractOrderId(message);
if (orderId != null) {
String msgTime = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
.format(new java.util.Date());
.format(new java.util.Date());
log.info("[{}] 【{}】✅ 检测到订单ID: {},开始获取订单详情", msgTime, cookieId, orderId);
// 提取用户ID和商品ID用于订单详情获取
@ -2563,7 +2581,7 @@ public class XianyuClient extends TextWebSocketHandler {
try {
log.info("【{}】🔍 完整消息结构: {}", cookieId, message.toJSONString());
String msgTime = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
.format(new java.util.Date());
.format(new java.util.Date());
// 安全地检查订单状态红色提醒
String redReminder = null;
@ -2659,7 +2677,7 @@ public class XianyuClient extends TextWebSocketHandler {
// 格式化消息时间
String msgTime = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
.format(new java.util.Date(createTime));
.format(new java.util.Date(createTime));
// ========== 步骤9: 判断消息方向 ==========
// 对应Python: Line 7561-7568
@ -2675,7 +2693,7 @@ public class XianyuClient extends TextWebSocketHandler {
// ========== 步骤10: 消息通知 ==========
// 对应Python: Line 7569-7582
log.info("[{}] 【收到】用户: {} (ID: {}), 商品({}): {}",
msgTime, sendUserName, sendUserId, itemId, sendMessage);
msgTime, sendUserName, sendUserId, itemId, sendMessage);
// 🔔 立即发送消息通知独立于自动回复功能
// 检查是否为群组消息如果是群组消息则跳过通知
@ -2745,7 +2763,7 @@ public class XianyuClient extends TextWebSocketHandler {
// 对应Python: Line 7664-7669
if (isAutoDeliveryTrigger(sendMessage)) {
log.info("[{}] 【{}】检测到自动发货触发消息,即使在暂停期间也继续处理: {}",
msgTime, cookieId, sendMessage);
msgTime, cookieId, sendMessage);
// 异步处理自动发货
final String finalSendUserName = sendUserName;
@ -2774,7 +2792,7 @@ public class XianyuClient extends TextWebSocketHandler {
if ("我已小刀,待刀成".equals(cardTitle)) {
log.info("[{}] 【{}】【系统】检测到\"我已小刀,待刀成\",即使在暂停期间也继续处理",
msgTime, cookieId);
msgTime, cookieId);
// 检查商品是否属于当前cookies
if (itemId != null && !itemId.startsWith("auto_")) {
@ -2819,7 +2837,7 @@ public class XianyuClient extends TextWebSocketHandler {
return;
} else {
log.info("[{}] 【{}】收到卡片消息,标题: {}", msgTime, cookieId,
cardTitle != null ? cardTitle : "未知");
cardTitle != null ? cardTitle : "未知");
// 不是目标卡片消息继续正常处理流程
}
}
@ -2845,7 +2863,7 @@ public class XianyuClient extends TextWebSocketHandler {
// 4. 延迟后调用processChatMessageReply
log.info("【{}】防抖回复调度已启动: chatId={}, 用户={}, 消息={}",
cookieId, finalChatId, finalSendUserName, finalSendMessage);
cookieId, finalChatId, finalSendUserName, finalSendMessage);
} catch (Exception e) {
log.error("【{}】防抖回复调度失败", cookieId, e);
@ -2870,9 +2888,9 @@ public class XianyuClient extends TextWebSocketHandler {
try {
// 获取同步数据
JSONObject syncData = messageData.getJSONObject("body")
.getJSONObject("syncPushPackage")
.getJSONArray("data")
.getJSONObject(0);
.getJSONObject("syncPushPackage")
.getJSONArray("data")
.getJSONObject(0);
// 检查是否有必要的字段
if (!syncData.containsKey("data")) {
@ -2898,12 +2916,12 @@ public class XianyuClient extends TextWebSocketHandler {
// 处理系统引导消息
if (content.containsKey("sessionArouse")) {
String msgTime = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
.format(new java.util.Date());
.format(new java.util.Date());
log.info("[{}] 【{}】【系统】小闲鱼智能提示(已跳过)", msgTime, cookieId);
return null;
} else if (content.containsKey("contentType")) {
String msgTime = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
.format(new java.util.Date());
.format(new java.util.Date());
log.warn("[{}] 【{}】【系统】其他类型消息(已跳过)", msgTime, cookieId);
return null;
}
@ -2949,7 +2967,7 @@ public class XianyuClient extends TextWebSocketHandler {
if (message1Obj.containsKey("10") && message1Obj.get("10") instanceof JSONObject) {
JSONObject message10 = message1Obj.getJSONObject("10");
return message10.getString("senderUserId") != null ?
message10.getString("senderUserId") : "unknown_user";
message10.getString("senderUserId") : "unknown_user";
}
}
} catch (Exception e) {

View File

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