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> findByStatus(@Param("status") String status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调试用:模糊查询链接编号(用于排查"链接不存在"问题)
|
||||||
|
*/
|
||||||
|
List<LinkTask> findByCodeNoLike(@Param("codeNo") String codeNo);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 原子方式占用设备:仅当该设备当前未被 USING/LOGGED_IN 占用时,
|
* 原子方式占用设备:仅当该设备当前未被 USING/LOGGED_IN 占用时,
|
||||||
* 才将指定任务更新为 USING 并写入设备与时间字段。
|
* 才将指定任务更新为 USING 并写入设备与时间字段。
|
||||||
|
|||||||
@@ -446,14 +446,16 @@ public Mono<BatchDeleteResponse> batchDeleteLinks(List<String> codeNos, Long age
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户端获取链接状态(支持 linkId 或 codeNo 参数,带自动刷新逻辑)
|
* 用户端获取链接状态(支持 linkId 或 codeNo 参数,带自动刷新逻辑)
|
||||||
|
* 修复并发问题:避免重复查询数据库,确保线程安全
|
||||||
*/
|
*/
|
||||||
public Mono<UserLinkStatusResponse> getUserLinkStatus(Long linkId, String codeNo) {
|
public Mono<UserLinkStatusResponse> getUserLinkStatus(Long linkId, String codeNo) {
|
||||||
return Mono.fromCallable(() -> {
|
return Mono.fromCallable(() -> {
|
||||||
log.info("=== 开始处理用户端链接状态查询 ===");
|
log.info("=== 开始处理用户端链接状态查询 ===");
|
||||||
log.info("linkId: {}, codeNo: {}", linkId, codeNo);
|
log.info("linkId: {}, codeNo: {}", linkId, codeNo);
|
||||||
|
log.info("当前线程: {}", Thread.currentThread().getName());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. 查询链接任务
|
// 1. 查询链接任务(只查询一次)
|
||||||
LinkTask linkTask = null;
|
LinkTask linkTask = null;
|
||||||
if (linkId != null) {
|
if (linkId != null) {
|
||||||
linkTask = linkTaskMapper.findById(linkId);
|
linkTask = linkTaskMapper.findById(linkId);
|
||||||
@@ -464,49 +466,143 @@ public Mono<UserLinkStatusResponse> getUserLinkStatus(Long linkId, String codeNo
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (linkTask == null) {
|
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("链接不存在");
|
throw new IllegalArgumentException("链接不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("查询到链接任务: id={}, codeNo={}, status={}, needRefresh={}",
|
log.info("查询到链接任务: id={}, codeNo={}, status={}, needRefresh={}",
|
||||||
linkTask.getId(), linkTask.getCodeNo(), linkTask.getStatus(), linkTask.getNeedRefresh());
|
linkTask.getId(), linkTask.getCodeNo(), linkTask.getStatus(), linkTask.getNeedRefresh());
|
||||||
|
|
||||||
return linkTask;
|
// 2. 直接在同一个线程中处理所有逻辑,避免线程切换导致的数据不一致
|
||||||
|
return processUserLinkStatusInSameThread(linkTask);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("=== 用户端链接状态查询失败 ===");
|
log.error("=== 用户端链接状态查询失败 ===");
|
||||||
log.error("错误详情: {}", e.getMessage(), e);
|
log.error("错误详情: {}", e.getMessage(), e);
|
||||||
throw 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());
|
.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);
|
log.info("linkId: {}, codeNo: {}", linkId, codeNo);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -948,27 +1044,71 @@ private UserLinkStatusResponse doGetUserLinkStatus(Long linkId, String codeNo) {
|
|||||||
*/
|
*/
|
||||||
private Mono<Object> validatePollLoginRequest(String code) {
|
private Mono<Object> validatePollLoginRequest(String code) {
|
||||||
return Mono.fromCallable(() -> {
|
return Mono.fromCallable(() -> {
|
||||||
|
log.info("=== 开始验证轮询上号请求 ===");
|
||||||
|
log.info("原始请求参数 code: [{}]", code);
|
||||||
|
|
||||||
// 验证code参数
|
// 验证code参数
|
||||||
if (code == null || code.trim().isEmpty()) {
|
if (code == null || code.trim().isEmpty()) {
|
||||||
|
log.error("参数验证失败: code为null或空字符串 code=[{}]", code);
|
||||||
throw new IllegalArgumentException("参数错误:code不能为空");
|
throw new IllegalArgumentException("参数错误:code不能为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取链接任务
|
String trimmedCode = code.trim();
|
||||||
LinkTask linkTask = linkTaskMapper.findByCodeNo(code);
|
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) {
|
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("链接不存在");
|
throw new IllegalArgumentException("链接不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("找到链接任务: id={}, status={}, codeNo={}",
|
log.info("=== 成功找到链接任务 ===");
|
||||||
linkTask.getId(), linkTask.getStatus(), linkTask.getCodeNo());
|
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状态才能轮询
|
// 检查链接状态,只有USING状态才能轮询
|
||||||
if (!"USING".equals(linkTask.getStatus())) {
|
if (!"USING".equals(linkTask.getStatus())) {
|
||||||
log.warn("链接状态不是USING,当前状态 {}", linkTask.getStatus());
|
log.warn("链接状态验证失败: 期望=USING, 实际={}", linkTask.getStatus());
|
||||||
return new PollLoginResponse(false, linkTask.getStatus());
|
return new PollLoginResponse(false, linkTask.getStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.info("链接状态验证通过: status=USING");
|
||||||
return linkTask;
|
return linkTask;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("=== 数据库查询异常 ===");
|
||||||
|
log.error("查询条件: code_no = '{}'", trimmedCode);
|
||||||
|
log.error("异常详情: {}", e.getMessage(), e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}).subscribeOn(Schedulers.boundedElastic());
|
}).subscribeOn(Schedulers.boundedElastic());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -279,6 +279,15 @@
|
|||||||
ORDER BY created_at ASC
|
ORDER BY created_at ASC
|
||||||
</select>
|
</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 id="reserveDeviceIfFree">
|
||||||
UPDATE link_task lt
|
UPDATE link_task lt
|
||||||
|
|||||||
Reference in New Issue
Block a user