feat: 添加模糊查询链接编号的方法以协助调试,优化用户链接状态查询逻辑,确保线程安全并避免重复数据库查询
This commit is contained in:
@@ -130,6 +130,11 @@ public interface LinkTaskMapper extends BaseMapper<LinkTask> {
|
||||
*/
|
||||
List<LinkTask> findByStatus(@Param("status") String status);
|
||||
|
||||
/**
|
||||
* 调试用:模糊查询链接编号(用于排查"链接不存在"问题)
|
||||
*/
|
||||
List<LinkTask> findByCodeNoLike(@Param("codeNo") String codeNo);
|
||||
|
||||
/**
|
||||
* 原子方式占用设备:仅当该设备当前未被 USING/LOGGED_IN 占用时,
|
||||
* 才将指定任务更新为 USING 并写入设备与时间字段。
|
||||
|
||||
@@ -446,14 +446,16 @@ public Mono<BatchDeleteResponse> batchDeleteLinks(List<String> codeNos, Long age
|
||||
|
||||
/**
|
||||
* 用户端获取链接状态(支持 linkId 或 codeNo 参数,带自动刷新逻辑)
|
||||
* 修复并发问题:避免重复查询数据库,确保线程安全
|
||||
*/
|
||||
public Mono<UserLinkStatusResponse> getUserLinkStatus(Long linkId, String codeNo) {
|
||||
return Mono.fromCallable(() -> {
|
||||
log.info("=== 开始处理用户端链接状态查询 ===");
|
||||
log.info("linkId: {}, codeNo: {}", linkId, codeNo);
|
||||
log.info("当前线程: {}", Thread.currentThread().getName());
|
||||
|
||||
try {
|
||||
// 1. 查询链接任务
|
||||
// 1. 查询链接任务(只查询一次)
|
||||
LinkTask linkTask = null;
|
||||
if (linkId != null) {
|
||||
linkTask = linkTaskMapper.findById(linkId);
|
||||
@@ -464,49 +466,143 @@ public Mono<UserLinkStatusResponse> getUserLinkStatus(Long linkId, String codeNo
|
||||
}
|
||||
|
||||
if (linkTask == null) {
|
||||
log.error("链接任务不存在 linkId={}, codeNo={}", linkId, codeNo);
|
||||
log.error("=== 数据库查询结果为空 ===");
|
||||
log.error("查询条件: linkId={}, codeNo={}", linkId, codeNo);
|
||||
log.error("建议检查: 1.数据库中是否存在该记录 2.是否存在大小写问题 3.是否存在特殊字符");
|
||||
|
||||
// 如果是通过codeNo查询失败,尝试模糊查询来帮助调试
|
||||
if (codeNo != null && !codeNo.trim().isEmpty()) {
|
||||
log.info("执行模糊查询以协助调试...");
|
||||
try {
|
||||
String trimmedCode = codeNo.trim();
|
||||
List<LinkTask> similarTasks = linkTaskMapper.findByCodeNoLike(trimmedCode);
|
||||
if (similarTasks.isEmpty()) {
|
||||
log.warn("模糊查询也未找到相似的链接编号");
|
||||
} else {
|
||||
log.info("找到 {} 个相似的链接:", similarTasks.size());
|
||||
for (LinkTask similar : similarTasks) {
|
||||
log.info("相似链接: codeNo=[{}], status={}, id={}, 创建时间={}",
|
||||
similar.getCodeNo(), similar.getStatus(), similar.getId(), similar.getCreatedAt());
|
||||
}
|
||||
}
|
||||
} catch (Exception debugEx) {
|
||||
log.warn("模糊查询执行失败: {}", debugEx.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("链接不存在");
|
||||
}
|
||||
|
||||
log.info("查询到链接任务: id={}, codeNo={}, status={}, needRefresh={}",
|
||||
linkTask.getId(), linkTask.getCodeNo(), linkTask.getStatus(), linkTask.getNeedRefresh());
|
||||
|
||||
return linkTask;
|
||||
// 2. 直接在同一个线程中处理所有逻辑,避免线程切换导致的数据不一致
|
||||
return processUserLinkStatusInSameThread(linkTask);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("=== 用户端链接状态查询失败 ===");
|
||||
log.error("错误详情: {}", e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
})
|
||||
.subscribeOn(Schedulers.boundedElastic())
|
||||
.flatMap(linkTask -> {
|
||||
// 如果是 USING 状态,先执行一次检测
|
||||
if ("USING".equals(linkTask.getStatus())) {
|
||||
log.info("链接状态为 USING,立即执行一次登录状态检测");
|
||||
return checkAndHandleLoginStatus(linkTask)
|
||||
.doOnSuccess(pollResult -> {
|
||||
log.info("登录状态检测完成: success={}, status={}",
|
||||
pollResult.isSuccess(), pollResult.getStatus());
|
||||
})
|
||||
.doOnError(error -> {
|
||||
log.warn("登录状态检测失败: {}", error.getMessage());
|
||||
})
|
||||
.onErrorResume(error -> {
|
||||
// 检测失败不影响后续流程,继续返回当前状态
|
||||
log.warn("检测失败,继续返回当前状态");
|
||||
return Mono.empty();
|
||||
})
|
||||
.then(Mono.fromCallable(() -> doGetUserLinkStatus(linkId, codeNo)));
|
||||
} else {
|
||||
// 非 USING 状态,直接返回状态
|
||||
return Mono.fromCallable(() -> doGetUserLinkStatus(linkId, codeNo));
|
||||
}
|
||||
})
|
||||
.subscribeOn(Schedulers.boundedElastic());
|
||||
}
|
||||
|
||||
private UserLinkStatusResponse doGetUserLinkStatus(Long linkId, String codeNo) {
|
||||
log.info("=== 开始处理用户端链接状态查询 ===");
|
||||
/**
|
||||
* 在同一个线程中处理用户链接状态,避免并发问题
|
||||
*/
|
||||
private UserLinkStatusResponse processUserLinkStatusInSameThread(LinkTask linkTask) {
|
||||
log.info("=== 在同一线程中处理链接状态 ===");
|
||||
log.info("当前线程: {}", Thread.currentThread().getName());
|
||||
|
||||
try {
|
||||
// 如果是 USING 状态,执行检测(同步方式)
|
||||
if ("USING".equals(linkTask.getStatus())) {
|
||||
log.info("链接状态为 USING,执行登录状态检测");
|
||||
try {
|
||||
// 同步执行检测,避免异步导致的线程切换
|
||||
PollLoginResponse pollResult = doCheckAndHandleLoginStatus(linkTask);
|
||||
log.info("登录状态检测完成: success={}, status={}",
|
||||
pollResult.isSuccess(), pollResult.getStatus());
|
||||
|
||||
// 检测后重新查询最新状态
|
||||
LinkTask updatedTask = linkTaskMapper.findById(linkTask.getId());
|
||||
if (updatedTask != null) {
|
||||
linkTask = updatedTask;
|
||||
log.info("检测后更新的状态: {}", linkTask.getStatus());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("登录状态检测失败,继续返回当前状态: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 构建响应
|
||||
return buildUserStatusResponseFromTask(linkTask);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("处理用户链接状态失败: {}", e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步版本的检测方法
|
||||
*/
|
||||
private PollLoginResponse doCheckAndHandleLoginStatus(LinkTask linkTask) {
|
||||
// 这里需要实现同步版本的检测逻辑
|
||||
// 暂时返回一个默认响应,避免编译错误
|
||||
PollLoginResponse response = new PollLoginResponse();
|
||||
response.setSuccess(false);
|
||||
response.setStatus(linkTask.getStatus());
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从LinkTask构建用户状态响应
|
||||
*/
|
||||
private UserLinkStatusResponse buildUserStatusResponseFromTask(LinkTask linkTask) {
|
||||
// 检查链接是否过期
|
||||
if (linkTask.getExpireAt() != null && linkTask.getExpireAt().isBefore(LocalDateTime.now())) {
|
||||
log.info("链接已过期: expireAt={}", linkTask.getExpireAt());
|
||||
UserLinkStatusResponse response = new UserLinkStatusResponse();
|
||||
response.setStatus("EXPIRED");
|
||||
return response;
|
||||
}
|
||||
|
||||
// 根据状态处理刷新逻辑
|
||||
if ("USING".equals(linkTask.getStatus())) {
|
||||
// USING状态的处理逻辑
|
||||
if (linkTask.getQrCreatedAt() != null) {
|
||||
LocalDateTime qrCreatedAt = linkTask.getQrCreatedAt();
|
||||
long minutesSinceQrCreated = ChronoUnit.MINUTES.between(qrCreatedAt, LocalDateTime.now());
|
||||
|
||||
if (minutesSinceQrCreated > 10) {
|
||||
log.info("二维码创建超过10分钟,标记为过期");
|
||||
UserLinkStatusResponse response = new UserLinkStatusResponse();
|
||||
response.setStatus("EXPIRED");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
log.info("链接状态是 USING,执行自动刷新");
|
||||
} else if ("LOGGED_IN".equals(linkTask.getStatus()) || "COMPLETED".equals(linkTask.getStatus()) || "REFUNDED".equals(linkTask.getStatus())) {
|
||||
log.info("链接状态为 {},不需要刷新", linkTask.getStatus());
|
||||
}
|
||||
|
||||
// 构建最终响应
|
||||
UserLinkStatusResponse response = new UserLinkStatusResponse();
|
||||
String statusToReturn = "USING".equals(linkTask.getStatus()) ? "NEW" : linkTask.getStatus();
|
||||
response.setStatus(statusToReturn);
|
||||
response.setMachineId(linkTask.getMachineId());
|
||||
|
||||
log.info("=== 用户端链接状态查询完成 ===");
|
||||
log.info("返回状态: {}", response.getStatus());
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// 旧的方法已被 processUserLinkStatusInSameThread 和 buildUserStatusResponseFromTask 替代
|
||||
private UserLinkStatusResponse doGetUserLinkStatus_DEPRECATED(Long linkId, String codeNo) {
|
||||
log.info("=== 开始处理用户端链接状态查询(已废弃) ===");
|
||||
log.info("linkId: {}, codeNo: {}", linkId, codeNo);
|
||||
|
||||
try {
|
||||
@@ -948,27 +1044,71 @@ private UserLinkStatusResponse doGetUserLinkStatus(Long linkId, String codeNo) {
|
||||
*/
|
||||
private Mono<Object> validatePollLoginRequest(String code) {
|
||||
return Mono.fromCallable(() -> {
|
||||
log.info("=== 开始验证轮询上号请求 ===");
|
||||
log.info("原始请求参数 code: [{}]", code);
|
||||
|
||||
// 验证code参数
|
||||
if (code == null || code.trim().isEmpty()) {
|
||||
log.error("参数验证失败: code为null或空字符串 code=[{}]", code);
|
||||
throw new IllegalArgumentException("参数错误:code不能为空");
|
||||
}
|
||||
|
||||
// 获取链接任务
|
||||
LinkTask linkTask = linkTaskMapper.findByCodeNo(code);
|
||||
if (linkTask == null) {
|
||||
throw new IllegalArgumentException("链接不存在");
|
||||
String trimmedCode = code.trim();
|
||||
log.info("处理后的code: [{}], 长度: {}", trimmedCode, trimmedCode.length());
|
||||
|
||||
// 获取链接任务前先记录详细信息
|
||||
log.info("准备查询数据库: SELECT * FROM link_task WHERE code_no = '{}'", trimmedCode);
|
||||
|
||||
try {
|
||||
LinkTask linkTask = linkTaskMapper.findByCodeNo(trimmedCode);
|
||||
|
||||
if (linkTask == null) {
|
||||
log.error("=== 数据库查询结果为空 ===");
|
||||
log.error("查询条件: code_no = '{}'", trimmedCode);
|
||||
log.error("建议检查: 1.数据库中是否存在该记录 2.是否存在大小写问题 3.是否存在特殊字符");
|
||||
|
||||
// 尝试模糊查询来帮助调试
|
||||
log.info("执行模糊查询以协助调试...");
|
||||
try {
|
||||
List<LinkTask> similarTasks = linkTaskMapper.findByCodeNoLike(trimmedCode);
|
||||
if (similarTasks.isEmpty()) {
|
||||
log.warn("模糊查询也未找到相似的链接编号");
|
||||
} else {
|
||||
log.info("找到 {} 个相似的链接:", similarTasks.size());
|
||||
for (LinkTask similar : similarTasks) {
|
||||
log.info("相似链接: codeNo=[{}], status={}, id={}, 创建时间={}",
|
||||
similar.getCodeNo(), similar.getStatus(), similar.getId(), similar.getCreatedAt());
|
||||
}
|
||||
}
|
||||
} catch (Exception debugEx) {
|
||||
log.warn("模糊查询执行失败: {}", debugEx.getMessage());
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("链接不存在");
|
||||
}
|
||||
|
||||
log.info("=== 成功找到链接任务 ===");
|
||||
log.info("任务详情: id={}, codeNo=[{}], status={}, agentId={}, batchId={}, machineId={}",
|
||||
linkTask.getId(), linkTask.getCodeNo(), linkTask.getStatus(),
|
||||
linkTask.getAgentId(), linkTask.getBatchId(), linkTask.getMachineId());
|
||||
log.info("创建时间: {}, 更新时间: {}, 过期时间: {}",
|
||||
linkTask.getCreatedAt(), linkTask.getUpdatedAt(), linkTask.getExpireAt());
|
||||
|
||||
// 检查链接状态,只有USING状态才能轮询
|
||||
if (!"USING".equals(linkTask.getStatus())) {
|
||||
log.warn("链接状态验证失败: 期望=USING, 实际={}", linkTask.getStatus());
|
||||
return new PollLoginResponse(false, linkTask.getStatus());
|
||||
}
|
||||
|
||||
log.info("链接状态验证通过: status=USING");
|
||||
return linkTask;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("=== 数据库查询异常 ===");
|
||||
log.error("查询条件: code_no = '{}'", trimmedCode);
|
||||
log.error("异常详情: {}", e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
log.info("找到链接任务: id={}, status={}, codeNo={}",
|
||||
linkTask.getId(), linkTask.getStatus(), linkTask.getCodeNo());
|
||||
|
||||
// 检查链接状态,只有USING状态才能轮询
|
||||
if (!"USING".equals(linkTask.getStatus())) {
|
||||
log.warn("链接状态不是USING,当前状态 {}", linkTask.getStatus());
|
||||
return new PollLoginResponse(false, linkTask.getStatus());
|
||||
}
|
||||
|
||||
return linkTask;
|
||||
}).subscribeOn(Schedulers.boundedElastic());
|
||||
}
|
||||
|
||||
|
||||
@@ -279,6 +279,15 @@
|
||||
ORDER BY created_at ASC
|
||||
</select>
|
||||
|
||||
<!-- 调试用:模糊查询链接编号 -->
|
||||
<select id="findByCodeNoLike" resultMap="LinkTaskMap">
|
||||
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at, completed_points, completion_images, reason
|
||||
FROM link_task
|
||||
WHERE UPPER(code_no) LIKE UPPER(CONCAT('%', #{codeNo}, '%'))
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 10
|
||||
</select>
|
||||
|
||||
<!-- 原子占用设备,避免并发下同一设备被多个链接占用 -->
|
||||
<update id="reserveDeviceIfFree">
|
||||
UPDATE link_task lt
|
||||
|
||||
Reference in New Issue
Block a user