feat: 优化选区逻辑和日志输出

主要修改:
1. 在LinkController中更新选区请求的日志信息,增强可读性。
2. 在LinkStatusService中重构选区操作的日志记录,增加步骤标识和详细信息,提升调试能力。
3. 移除performAutoRefresh方法,简化链接状态处理逻辑。
4. 在GameInterfaceResponse中新增操作成功字段,提供更清晰的响应数据。

技术细节:
- 通过优化日志输出和选区逻辑,提升了代码的可维护性和用户体验,同时确保了操作的透明性和准确性。
This commit is contained in:
zyh
2025-08-29 15:57:30 +08:00
parent 7499bce07b
commit 058970b95c
4 changed files with 99 additions and 78 deletions

View File

@@ -396,17 +396,20 @@ public Mono<Boolean> deleteLink(@PathVariable("codeNo") String codeNo, Authentic
@PostMapping("/select-region") @PostMapping("/select-region")
@Operation(summary = "选择区域", description = "用户选择游戏区域Q或V选区成功后生成二维码") @Operation(summary = "选择区域", description = "用户选择游戏区域Q或V选区成功后生成二维码")
public Mono<SelectRegionResponse> selectRegion(@Valid @RequestBody SelectRegionRequest request) { public Mono<SelectRegionResponse> selectRegion(@Valid @RequestBody SelectRegionRequest request) {
log.info("=== 开始处理选区请求 ==="); log.info("=== 控制器: 接收选区请求 ===");
log.info("请求参数: code={}, region={}", request.getCode(), request.getRegion()); log.info("HTTP请求参数: code={}, region={}", request.getCode(), request.getRegion());
return linkStatusService.selectRegion(request.getCode(), request.getRegion()) return linkStatusService.selectRegion(request.getCode(), request.getRegion())
.doOnSuccess(response -> { .doOnSuccess(response -> {
log.info("选区请求处理成功: success={}, status={}, region={}", log.info("控制器: 选区请求处理成功");
response.isSuccess(), response.getStatus(), response.getRegion()); log.info("响应数据: success={}, status={}, qrCodeUrl={}, qrDelaySeconds={}, machineId={}",
response.isSuccess(), response.getStatus(), response.getQrCodeUrl(),
response.getQrDelaySeconds(), response.getMecmachineId());
}) })
.doOnError(error -> { .doOnError(error -> {
log.error("选区请求处理失败: code={}, region={}, error={}", log.error("控制器: 选区请求处理失败");
request.getCode(), request.getRegion(), error.getMessage(), error); log.error("错误详情: code={}, region={}, errorType={}, errorMessage={}",
request.getCode(), request.getRegion(), error.getClass().getSimpleName(), error.getMessage(), error);
}); });
} }

View File

@@ -47,6 +47,8 @@ public class GameInterfaceResponse {
@Schema(description = "已经完成的点数") @Schema(description = "已经完成的点数")
private Integer completedPoints; private Integer completedPoints;
@Schema(description = "操作是否成功", example = "true")
public String getCodeNo() { public String getCodeNo() {
return codeNo; return codeNo;
} }

View File

@@ -173,7 +173,7 @@ public class ScriptClient {
/** /**
* 判断刷新接口 - 统一管理刷新判断逻辑 * 判断刷新接口 - 统一管理刷新判断逻辑
*/ */
@RepeatCall(times = 3, description = "判断刷新") @RepeatCall(times = 1, description = "判断刷新")
public Mono<String> refresh(String machineId) { public Mono<String> refresh(String machineId) {
String url = String.format(apiBaseUrl + "/yijianwan_netfile/saveMsg?文件名=判断刷新&%s=刷新", machineId); String url = String.format(apiBaseUrl + "/yijianwan_netfile/saveMsg?文件名=判断刷新&%s=刷新", machineId);
log.info("调用判断刷新接口: {}", url); log.info("调用判断刷新接口: {}", url);

View File

@@ -489,7 +489,7 @@ public class LinkStatusService {
// 如果未超过10分钟执行自动刷新 // 如果未超过10分钟执行自动刷新
log.info("链接状态是USING执行自动刷新"); log.info("链接状态是USING执行自动刷新");
performAutoRefresh(linkTask); // performAutoRefresh(linkTask);
} else if ("LOGGED_IN".equals(linkTask.getStatus()) || "COMPLETED".equals(linkTask.getStatus()) || "REFUNDED".equals(linkTask.getStatus())) { } else if ("LOGGED_IN".equals(linkTask.getStatus()) || "COMPLETED".equals(linkTask.getStatus()) || "REFUNDED".equals(linkTask.getStatus())) {
// 已上号、已完成或已退款状态,不需要刷新 // 已上号、已完成或已退款状态,不需要刷新
log.info("链接状态为 {},不需要刷新", linkTask.getStatus()); log.info("链接状态为 {},不需要刷新", linkTask.getStatus());
@@ -508,26 +508,7 @@ public class LinkStatusService {
} }
} }
/**
* 执行自动刷新逻辑
* 只调用判断刷新接口,不更新数据库状态
*/
private void performAutoRefresh(LinkTask linkTask) {
try {
log.info("开始执行刷新操作");
if (linkTask.getMachineId() == null) {
log.warn("机器ID为空无法执行刷新操作");
return;
}
// 调用判断刷新接口通过ScriptClient统一管理
String refreshResult = scriptClient.refresh(linkTask.getMachineId()).block();
log.info("判断刷新接口调用完成: result={}", refreshResult);
} catch (Exception e) {
log.warn("执行刷新操作失败: {}", e.getMessage());
// 刷新失败不影响后续流程,只记录警告日志
}
}
@@ -554,42 +535,51 @@ public class LinkStatusService {
.subscribeOn(Schedulers.boundedElastic()); .subscribeOn(Schedulers.boundedElastic());
} }
private SelectRegionResponse doSelectRegion(String code, String region) { private SelectRegionResponse doSelectRegion(String code, String region) throws InterruptedException {
log.info("=== 开始选区操作 ==="); log.info("=== 开始选区操作 ===");
log.info("code: {}, region: {}", code, region); log.info("请求参数: code={}, region={}", code, region);
try { try {
// 1. 验证参数 // 1. 验证参数
log.info("步骤1: 开始参数验证");
if (code == null || code.trim().isEmpty()) { if (code == null || code.trim().isEmpty()) {
log.error("参数验证失败: code不能为空");
throw new IllegalArgumentException("code不能为空"); throw new IllegalArgumentException("code不能为空");
} }
if (region == null || (!region.equals("Q") && !region.equals("V"))) { if (region == null || (!region.equals("Q") && !region.equals("V"))) {
log.error("参数验证失败: region只能是Q或V, 当前值={}", region);
throw new IllegalArgumentException("region只能是Q或V"); throw new IllegalArgumentException("region只能是Q或V");
} }
log.info("参数验证通过: code={}, region={}", code.trim(), region);
// 2. 查询链接任务 // 2. 查询链接任务
log.info("步骤2: 开始查询链接任务");
LinkTask linkTask = linkTaskMapper.findByCodeNo(code.trim()); LinkTask linkTask = linkTaskMapper.findByCodeNo(code.trim());
if (linkTask == null) { if (linkTask == null) {
log.error("链接任务不存在: code={}", code); log.error("链接任务查询失败: 未找到对应的链接任务, code={}", code);
throw new IllegalArgumentException("链接不存在"); throw new IllegalArgumentException("链接不存在");
} }
log.info("查询到链接任务: id={}, codeNo={}, status={}, needRefresh={}", log.info("链接任务查询成功: id={}, codeNo={}, status={}, batchId={}, machineId={}, needRefresh={}, firstRegionSelectAt={}",
linkTask.getId(), linkTask.getCodeNo(), linkTask.getStatus(), linkTask.getNeedRefresh()); linkTask.getId(), linkTask.getCodeNo(), linkTask.getStatus(),
linkTask.getBatchId(), linkTask.getMachineId(), linkTask.getNeedRefresh(), linkTask.getFirstRegionSelectAt());
// 查询批次信息获取times参数 // 查询批次信息获取times参数
log.info("步骤3: 开始查询批次信息");
LinkBatch linkBatch = linkBatchMapper.findById(linkTask.getBatchId()); LinkBatch linkBatch = linkBatchMapper.findById(linkTask.getBatchId());
if (linkBatch == null) { if (linkBatch == null) {
log.error("批次信息不存在: batchId={}", linkTask.getBatchId()); log.error("批次信息查询失败: 未找到对应的批次信息, batchId={}", linkTask.getBatchId());
throw new IllegalStateException("批次信息不存在"); throw new IllegalStateException("批次信息不存在");
} }
log.info("查询到批次信息: batchId={}, times={}", linkBatch.getId(), linkBatch.getTimes()); log.info("批次信息查询成功: batchId={}, times={}, quantity={}", linkBatch.getId(), linkBatch.getTimes(), linkBatch.getQuantity());
// 3. 检查链接状态只有NEW或USING状态才能选区 // 4. 检查链接状态只有NEW或USING状态才能选区
log.info("步骤4: 开始检查链接状态");
if (!"NEW".equals(linkTask.getStatus()) && !"USING".equals(linkTask.getStatus())) { if (!"NEW".equals(linkTask.getStatus()) && !"USING".equals(linkTask.getStatus())) {
log.error("链接状态不正确,无法选区: status={}", linkTask.getStatus()); log.error("链接状态检查失败: 当前状态={}, 只允许NEW或USING状态进行选区操作", linkTask.getStatus());
throw new IllegalArgumentException("链接状态不正确,只有新建或使用中状态的链接才能选区"); throw new IllegalArgumentException("链接状态不正确,只有新建或使用中状态的链接才能选区");
} }
log.info("链接状态检查通过: 当前状态={}, 允许进行选区操作", linkTask.getStatus());
// 5. 如果need_refresh=true检查是否已等待10秒 // 5. 如果need_refresh=true检查是否已等待10秒
@@ -603,110 +593,136 @@ public class LinkStatusService {
// return response; // return response;
// } // }
// } // }
// 5. 设备选择逻辑
log.info("步骤5: 开始设备选择逻辑");
String selectedDeviceId; String selectedDeviceId;
if(linkTask.getFirstRegionSelectAt() == null){ if(linkTask.getFirstRegionSelectAt() == null){
log.info("开始检查空闲设备"); log.info("首次选区: 开始检查和分配空闲设备");
DeviceStatusResponse deviceStatus = scriptClient.checkAvailableDeviceStatus().block(); DeviceStatusResponse deviceStatus = scriptClient.checkAvailableDeviceStatus().block();
// 检查是否有空闲设备 // 检查是否有空闲设备
if (deviceStatus.getAvailableCount() == 0) { if (deviceStatus.getAvailableCount() == 0) {
log.warn("当前没有空闲设备,无法选择区域"); log.error("设备分配失败: 当前没有空闲设备可用, 总设备数={}, 空闲设备数={}",
deviceStatus.getTotalDevices(), deviceStatus.getAvailableCount());
throw new RuntimeException("当前没有空闲设备,请稍后再试"); throw new RuntimeException("当前没有空闲设备,请稍后再试");
} }
log.info("空闲设备检查完成: 总设备数={}, 空闲设备数={}, 空闲设备列表={}", log.info("空闲设备检查完成: 总设备数={}, 空闲设备数={}, 空闲设备列表={}",
deviceStatus.getTotalDevices(), deviceStatus.getAvailableCount(), deviceStatus.getAvailableDevices()); deviceStatus.getTotalDevices(), deviceStatus.getAvailableCount(), deviceStatus.getAvailableDevices());
// 7. 选择一个空闲设备 TODO // 选择一个空闲设备
selectedDeviceId = deviceStatus.getAvailableDevices().get(0); // 选择第一个空闲设备 selectedDeviceId = deviceStatus.getAvailableDevices().get(0); // 选择第一个空闲设备
// String selectedDevice = "cc2"; log.info("首次选区设备分配成功: 选中设备={}, 从{}个空闲设备中选择第一个",
log.info("从空闲设备列表中选择设备: {}", selectedDeviceId); selectedDeviceId, deviceStatus.getAvailableDevices().size());
log.info("设备选择详情: 可用设备总数={}, 选择了第一个设备={}",
deviceStatus.getAvailableDevices().size(), selectedDeviceId);
}else{ }else{
log.info("重复选区: 检查首次选区时效性并复用设备");
// 检查首次选区是否已过期 // 检查首次选区是否已过期
LocalDateTime firstSelectTime = linkTask.getFirstRegionSelectAt(); LocalDateTime firstSelectTime = linkTask.getFirstRegionSelectAt();
long expireSeconds = systemConfigService.getFirstRegionExpireSeconds(); long expireSeconds = systemConfigService.getFirstRegionExpireSeconds();
LocalDateTime expireTime = firstSelectTime.plusSeconds(expireSeconds); LocalDateTime expireTime = firstSelectTime.plusSeconds(expireSeconds);
LocalDateTime now = LocalDateTime.now();
if (LocalDateTime.now().isAfter(expireTime)) { log.info("时效性检查: 首次选区时间={}, 过期时间={}, 当前时间={}, 过期秒数={}",
log.warn("链接首次选区已过期: firstSelectTime={}, expireTime={}, now={}", firstSelectTime, expireTime, now, expireSeconds);
firstSelectTime, expireTime, LocalDateTime.now());
if (now.isAfter(expireTime)) {
log.error("链接过期检查失败: 首次选区已过期, firstSelectTime={}, expireTime={}, now={}",
firstSelectTime, expireTime, now);
// 将链接状态设置为过期 // 将链接状态设置为过期
linkTask.setStatus("EXPIRED"); linkTask.setStatus("EXPIRED");
linkTask.setUpdatedAt(LocalDateTime.now()); linkTask.setUpdatedAt(now);
linkTaskMapper.updateById(linkTask); linkTaskMapper.updateById(linkTask);
log.info("链接状态已更新为EXPIRED: linkTaskId={}", linkTask.getId()); log.info("链接状态已更新为EXPIRED: linkTaskId={}, 更新时间={}", linkTask.getId(), now);
throw new RuntimeException("链接已过期,请重新获取"); throw new RuntimeException("链接已过期,请重新获取");
} }
selectedDeviceId = linkTask.getMachineId(); selectedDeviceId = linkTask.getMachineId();
log.info("重复选区设备复用成功: 使用之前分配的设备={}, 首次选区时间={}", selectedDeviceId, firstSelectTime);
log.info("执行设备刷新操作: 设备={}", selectedDeviceId);
scriptClient.refresh(selectedDeviceId).block(); scriptClient.refresh(selectedDeviceId).block();
log.info("链接已选过区且未过期,继续使用之前的设备: {}", selectedDeviceId); log.info("设备刷新完成: 设备={}, 继续使用之前分配的设备", selectedDeviceId);
// 休眠5秒确保设备状态稳定
Thread.sleep(11000);
log.info("等待5秒完成继续使用之前分配的设备: 设备={}", selectedDeviceId);
} }
// 6. 检查空闲设备
log.info("设备选择完成: 最终选中设备={}", selectedDeviceId);
// 7.5. 检查该设备是否有之前的LOGGED_IN状态链接任务需要完成 // 6. 检查该设备是否有之前的LOGGED_IN状态链接任务需要完成
log.info("步骤6: 开始检查设备状态和历史任务");
try { try {
log.info("检查设备 {} 是否有需要完成的链接任务", selectedDeviceId); log.info("检查设备历史任务: 设备={}, 触发原因=选区请求", selectedDeviceId);
deviceStatusCheckService.checkDeviceStatusAndUpdateTasks(selectedDeviceId, "选区请求"); deviceStatusCheckService.checkDeviceStatusAndUpdateTasks(selectedDeviceId, "选区请求");
log.info("设备历史任务检查完成: 设备={}", selectedDeviceId);
} catch (Exception e) { } catch (Exception e) {
log.warn("检查设备状态时发生异常,继续选区流程: {}", e.getMessage()); log.warn("设备历史任务检查异常: 设备={}, 错误={}, 继续选区流程", selectedDeviceId, e.getMessage());
// 不影响选区流程,只记录警告日志 // 不影响选区流程,只记录警告日志
} }
// 8. 调用保存总次数接口 TODO 应该放在登录成功之后 // 7. 调用保存总次数接口
log.info("步骤7: 开始保存总次数到脚本端");
try { try {
scriptClient.saveTotalTimes(selectedDeviceId,linkBatch.getTimes()).block(); log.info("保存总次数: 设备={}, 次数={}", selectedDeviceId, linkBatch.getTimes());
// saveTotalTimes方法已经包含了详细的日志记录 scriptClient.saveTotalTimes(selectedDeviceId, linkBatch.getTimes()).block();
log.info("总次数保存成功: 设备={}, 次数={}", selectedDeviceId, linkBatch.getTimes());
} catch (Exception e) { } catch (Exception e) {
log.warn("保存总次数接口调用失败: {}", e.getMessage()); log.warn("总次数保存失败: 设备={}, 次数={}, 错误={}, 继续后续流程", selectedDeviceId, linkBatch.getTimes(), e.getMessage());
// 不影响后续流程,只记录警告日志 // 不影响后续流程,只记录警告日志
} }
// 9. 为选中的设备创建代理code // 8. 调用脚本端选区,使用选中的设备
// String proxyCode = deviceCodeMappingService.createProxyCode(selectedDeviceId); log.info("步骤8: 开始调用脚本端选区");
// log.info("为设备 {} 创建代理code: {}", selectedDeviceId, proxyCode); log.info("选区请求参数: 设备={}, 区域={}", selectedDeviceId, region);
// 10. 调用脚本端选区,使用选中的设备
log.info("开始调用脚本端选区,设备={}, 区域={}", selectedDeviceId, region);
String selectResult = scriptClient.selectRegion(selectedDeviceId, region).block(); String selectResult = scriptClient.selectRegion(selectedDeviceId, region).block();
log.info("脚本端选区结果: {}", selectResult); log.info("脚本端选区调用成功: 设备={}, 区域={}, 返回结果={}", selectedDeviceId, region, selectResult);
// 11. 等待脚本端生成二维码(这里可以添加轮询逻辑) // 11. 等待脚本端生成二维码(这里可以添加轮询逻辑)
// log.info("等待脚本端生成二维码等待3秒..."); // log.info("等待脚本端生成二维码等待3秒...");
// Thread.sleep(3000); // Thread.sleep(3000);
// 12. 更新数据库状态为USING保存设备信息和代理code // 9. 更新数据库状态为USING保存设备信息
log.info("步骤9: 开始更新数据库状态");
LocalDateTime now = LocalDateTime.now(); LocalDateTime now = LocalDateTime.now();
LocalDateTime qrExpireAt = now.plusSeconds(60); // 60秒后过期
// 记录更新前的状态
log.info("数据库更新前状态: id={}, status={}, region={}, machineId={}, firstRegionSelectAt={}",
linkTask.getId(), linkTask.getStatus(), linkTask.getRegion(), linkTask.getMachineId(), linkTask.getFirstRegionSelectAt());
linkTask.setStatus("USING"); linkTask.setStatus("USING");
linkTask.setRegion(region); linkTask.setRegion(region);
linkTask.setCodeNo(code); // 使用代理code替换原来的codeNo linkTask.setCodeNo(code);
linkTask.setQrCreatedAt(now); linkTask.setQrCreatedAt(now);
linkTask.setQrExpireAt(now.plusSeconds(60)); // 60秒后过期 ToDO linkTask.setQrExpireAt(qrExpireAt);
linkTask.setFirstRegionSelectAt(now); // 记录首次选区时间 if (linkTask.getFirstRegionSelectAt() == null) {
linkTask.setFirstRegionSelectAt(now); // 只在首次选区时记录
log.info("首次选区: 记录首次选区时间={}", now);
}
linkTask.setNeedRefresh(false); linkTask.setNeedRefresh(false);
linkTask.setUpdatedAt(now); linkTask.setUpdatedAt(now);
// 在machineId字段保存真实设备编号便于调试和维护
linkTask.setMachineId(selectedDeviceId); linkTask.setMachineId(selectedDeviceId);
linkTaskMapper.update(linkTask); linkTaskMapper.update(linkTask);
log.info("数据库更新成功: id={}, status=USING, region={}, machineId={}, qrCreatedAt={}, qrExpireAt={}, firstRegionSelectAt={}",
linkTask.getId(), region, selectedDeviceId, now, qrExpireAt, linkTask.getFirstRegionSelectAt());
log.info("链接状态更新成功: status=USING, region={}, proxyCode={}, device={}", region, code, selectedDeviceId); // 10. 构建响应
log.info("步骤10: 开始构建响应数据");
// 13. 构建响应
SelectRegionResponse response = new SelectRegionResponse(true, "选区成功"); SelectRegionResponse response = new SelectRegionResponse(true, "选区成功");
response.setQrCodeUrl(scriptClient.getProxyQrCodeUrl(code)); String qrCodeUrl = scriptClient.getProxyQrCodeUrl(code);
response.setQrCodeUrl(qrCodeUrl);
response.setQrCreatedAt(now); response.setQrCreatedAt(now);
response.setQrExpireAt(linkTask.getQrExpireAt()); response.setQrExpireAt(qrExpireAt);
response.setStatus("USING"); response.setStatus("USING");
// 不返回选区字段response.setRegion(region); response.setQrDelaySeconds(10); // 客户端收到响应后等待5秒再请求二维码
response.setQrDelaySeconds(5); // 客户端收到响应后等待5秒再请求二维码
response.setMecmachineId(selectedDeviceId); // 便于调试和维护 response.setMecmachineId(selectedDeviceId); // 便于调试和维护
log.info("响应构建完成: success=true, status=USING, qrCodeUrl={}, qrDelaySeconds=5, machineId={}",
qrCodeUrl, selectedDeviceId);
log.info("=== 选区操作完成 ==="); log.info("=== 选区操作完成 ===");
log.info("二维码URL: {}", response.getQrCodeUrl());
return response; return response;