From b69d48b12f3a3ad83927fc762192f745ed48468b Mon Sep 17 00:00:00 2001 From: zyh Date: Sat, 30 Aug 2025 16:09:34 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E5=86=B7=E5=8D=B4=E9=80=BB=E8=BE=91=EF=BC=8C=E8=BF=87=E6=BB=A4?= =?UTF-8?q?=E5=86=B7=E5=8D=B4=E6=9C=9F=E5=86=85=E8=AE=BE=E5=A4=87=E5=B9=B6?= =?UTF-8?q?=E5=A4=84=E7=90=86=E5=86=B7=E5=8D=B4=E9=98=9F=E5=88=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cooldown/MachineCooldownService.java | 146 ++++++++++++++++++ .../service/link/LinkStatusService.java | 44 +++++- .../task/MachineCooldownCleanupTask.java | 43 ++++++ 3 files changed, 228 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/gameplatform/server/service/cooldown/MachineCooldownService.java create mode 100644 src/main/java/com/gameplatform/server/task/MachineCooldownCleanupTask.java diff --git a/src/main/java/com/gameplatform/server/service/cooldown/MachineCooldownService.java b/src/main/java/com/gameplatform/server/service/cooldown/MachineCooldownService.java new file mode 100644 index 0000000..3bed8f8 --- /dev/null +++ b/src/main/java/com/gameplatform/server/service/cooldown/MachineCooldownService.java @@ -0,0 +1,146 @@ +package com.gameplatform.server.service.cooldown; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * 机器冷却服务 + * 实现同一台机器在10分钟内不会重复调用的机制 + */ +@Service +public class MachineCooldownService { + private static final Logger log = LoggerFactory.getLogger(MachineCooldownService.class); + + // 冷却时间:10分钟 + private static final int COOLDOWN_MINUTES = 10; + + // 机器冷却记录:machineId -> 最后操作时间 + private final ConcurrentMap machineCooldownMap = new ConcurrentHashMap<>(); + + /** + * 检查机器是否在冷却期内 + * @param machineId 机器ID + * @return true表示在冷却期内,false表示可以操作 + */ + public boolean isMachineInCooldown(String machineId) { + if (machineId == null || machineId.trim().isEmpty()) { + return false; + } + + LocalDateTime lastOperationTime = machineCooldownMap.get(machineId); + if (lastOperationTime == null) { + log.debug("机器{}没有冷却记录,允许操作", machineId); + return false; + } + + LocalDateTime now = LocalDateTime.now(); + LocalDateTime cooldownExpireTime = lastOperationTime.plusMinutes(COOLDOWN_MINUTES); + + boolean inCooldown = now.isBefore(cooldownExpireTime); + + if (inCooldown) { + long remainingMinutes = java.time.Duration.between(now, cooldownExpireTime).toMinutes(); + log.info("机器{}在冷却期内,剩余冷却时间:{}分钟", machineId, remainingMinutes + 1); + } else { + log.debug("机器{}冷却期已过,允许操作", machineId); + } + + return inCooldown; + } + + /** + * 将机器加入冷却队列 + * @param machineId 机器ID + * @param reason 加入冷却的原因 + */ + public void addMachineToCooldown(String machineId, String reason) { + if (machineId == null || machineId.trim().isEmpty()) { + log.warn("尝试添加空的机器ID到冷却队列"); + return; + } + + LocalDateTime now = LocalDateTime.now(); + machineCooldownMap.put(machineId, now); + + log.info("机器{}已加入冷却队列,原因:{},冷却时间:{}分钟,冷却结束时间:{}", + machineId, reason, COOLDOWN_MINUTES, now.plusMinutes(COOLDOWN_MINUTES)); + } + + /** + * 获取机器剩余冷却时间(分钟) + * @param machineId 机器ID + * @return 剩余冷却时间,如果不在冷却期则返回0 + */ + public long getRemainingCooldownMinutes(String machineId) { + if (machineId == null || machineId.trim().isEmpty()) { + return 0; + } + + LocalDateTime lastOperationTime = machineCooldownMap.get(machineId); + if (lastOperationTime == null) { + return 0; + } + + LocalDateTime now = LocalDateTime.now(); + LocalDateTime cooldownExpireTime = lastOperationTime.plusMinutes(COOLDOWN_MINUTES); + + if (now.isBefore(cooldownExpireTime)) { + return java.time.Duration.between(now, cooldownExpireTime).toMinutes() + 1; + } + + return 0; + } + + /** + * 手动移除机器的冷却状态(用于测试或管理员操作) + * @param machineId 机器ID + */ + public void removeMachineFromCooldown(String machineId) { + if (machineId == null || machineId.trim().isEmpty()) { + return; + } + + LocalDateTime removedTime = machineCooldownMap.remove(machineId); + if (removedTime != null) { + log.info("已手动移除机器{}的冷却状态,原冷却开始时间:{}", machineId, removedTime); + } else { + log.debug("机器{}不在冷却队列中", machineId); + } + } + + /** + * 清理过期的冷却记录(可定期调用以释放内存) + */ + public void cleanupExpiredCooldowns() { + LocalDateTime now = LocalDateTime.now(); + int cleanedCount = 0; + + for (var entry : machineCooldownMap.entrySet()) { + String machineId = entry.getKey(); + LocalDateTime lastOperationTime = entry.getValue(); + LocalDateTime cooldownExpireTime = lastOperationTime.plusMinutes(COOLDOWN_MINUTES); + + if (now.isAfter(cooldownExpireTime)) { + machineCooldownMap.remove(machineId); + cleanedCount++; + } + } + + if (cleanedCount > 0) { + log.info("清理了{}个过期的机器冷却记录", cleanedCount); + } + } + + /** + * 获取当前冷却队列的大小 + * @return 冷却队列中的机器数量 + */ + public int getCooldownQueueSize() { + return machineCooldownMap.size(); + } +} \ No newline at end of file diff --git a/src/main/java/com/gameplatform/server/service/link/LinkStatusService.java b/src/main/java/com/gameplatform/server/service/link/LinkStatusService.java index 1e2b364..0cf59b4 100644 --- a/src/main/java/com/gameplatform/server/service/link/LinkStatusService.java +++ b/src/main/java/com/gameplatform/server/service/link/LinkStatusService.java @@ -15,6 +15,7 @@ import com.gameplatform.server.model.entity.agent.LinkTask; import com.gameplatform.server.service.external.ScriptClient; import com.gameplatform.server.service.device.DeviceStatusCheckService; import com.gameplatform.server.service.admin.SystemConfigService; +import com.gameplatform.server.service.cooldown.MachineCooldownService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @@ -40,6 +41,7 @@ public class LinkStatusService { private final ScriptClient scriptClient; private final DeviceStatusCheckService deviceStatusCheckService; private final SystemConfigService systemConfigService; + private final MachineCooldownService machineCooldownService; // 状态描述映射 @@ -55,12 +57,13 @@ public class LinkStatusService { public LinkStatusService(LinkTaskMapper linkTaskMapper, LinkBatchMapper linkBatchMapper, ScriptClient scriptClient, - DeviceStatusCheckService deviceStatusCheckService, SystemConfigService systemConfigService) { + DeviceStatusCheckService deviceStatusCheckService, SystemConfigService systemConfigService, MachineCooldownService machineCooldownService) { this.linkTaskMapper = linkTaskMapper; this.linkBatchMapper = linkBatchMapper; this.scriptClient = scriptClient; this.deviceStatusCheckService = deviceStatusCheckService; this.systemConfigService = systemConfigService; + this.machineCooldownService = machineCooldownService; } /** @@ -481,6 +484,13 @@ public class LinkStatusService { if (linkTask.getQrCreatedAt() != null && linkTask.getQrCreatedAt().isBefore(LocalDateTime.now().minusMinutes(10))) { log.warn("选择设备已超过10分钟未登录,链接过期: qrCreatedAt={}", linkTask.getQrCreatedAt()); + + // 将设备加入冷却队列 - 因为10分钟内没有成功登录 + if (linkTask.getMachineId() != null) { + machineCooldownService.addMachineToCooldown(linkTask.getMachineId(), "10分钟内未登录成功"); + log.info("设备{}因10分钟内未登录成功被加入冷却队列", linkTask.getMachineId()); + } + LocalDateTime now = LocalDateTime.now(); linkTask.setStatus("EXPIRED"); linkTask.setExpireAt(now); // 设置过期时间戳 @@ -616,10 +626,34 @@ public class LinkStatusService { log.info("空闲设备检查完成: 总设备数={}, 空闲设备数={}, 空闲设备列表={}", deviceStatus.getTotalDevices(), deviceStatus.getAvailableCount(), deviceStatus.getAvailableDevices()); - // 选择一个空闲设备 - selectedDeviceId = deviceStatus.getAvailableDevices().get(0); // 选择第一个空闲设备 - log.info("首次选区设备分配成功: 选中设备={}, 从{}个空闲设备中选择第一个", - selectedDeviceId, deviceStatus.getAvailableDevices().size()); + // 过滤掉冷却期内的设备 + List availableDevices = deviceStatus.getAvailableDevices(); + List nonCooldownDevices = new ArrayList<>(); + + for (String deviceId : availableDevices) { + if (!machineCooldownService.isMachineInCooldown(deviceId)) { + nonCooldownDevices.add(deviceId); + } else { + long remainingMinutes = machineCooldownService.getRemainingCooldownMinutes(deviceId); + log.info("设备{}在冷却期内,剩余时间{}分钟,跳过选择", deviceId, remainingMinutes); + } + } + + if (nonCooldownDevices.isEmpty()) { + log.error("设备分配失败: 所有空闲设备都在冷却期内, 总空闲设备数={}", availableDevices.size()); + throw new RuntimeException("所有设备都在冷却期内,请稍后再试"); + } + + log.info("冷却期过滤完成: 原空闲设备数={}, 可用设备数={}, 可用设备列表={}", + availableDevices.size(), nonCooldownDevices.size(), nonCooldownDevices); + + // 选择一个非冷却期设备 + selectedDeviceId = nonCooldownDevices.get(0); // 选择第一个非冷却期设备 + log.info("首次选区设备分配成功: 选中设备={}, 从{}个非冷却期设备中选择第一个", + selectedDeviceId, nonCooldownDevices.size()); + + // 将选中的设备加入冷却队列 + machineCooldownService.addMachineToCooldown(selectedDeviceId, "首次选区"); }else{ log.info("重复选区: 检查首次选区时效性并复用设备"); // 检查首次选区是否已过期 diff --git a/src/main/java/com/gameplatform/server/task/MachineCooldownCleanupTask.java b/src/main/java/com/gameplatform/server/task/MachineCooldownCleanupTask.java new file mode 100644 index 0000000..da54abf --- /dev/null +++ b/src/main/java/com/gameplatform/server/task/MachineCooldownCleanupTask.java @@ -0,0 +1,43 @@ +package com.gameplatform.server.task; + +import com.gameplatform.server.service.cooldown.MachineCooldownService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +/** + * 机器冷却清理任务 + * 定期清理过期的机器冷却记录以释放内存 + */ +@Component +public class MachineCooldownCleanupTask { + private static final Logger log = LoggerFactory.getLogger(MachineCooldownCleanupTask.class); + + private final MachineCooldownService machineCooldownService; + + public MachineCooldownCleanupTask(MachineCooldownService machineCooldownService) { + this.machineCooldownService = machineCooldownService; + } + + /** + * 每30分钟清理一次过期的冷却记录 + */ + @Scheduled(fixedRate = 30 * 60 * 1000) // 30分钟 + public void cleanupExpiredCooldowns() { + try { + int sizeBefore = machineCooldownService.getCooldownQueueSize(); + machineCooldownService.cleanupExpiredCooldowns(); + int sizeAfter = machineCooldownService.getCooldownQueueSize(); + + if (sizeBefore != sizeAfter) { + log.info("机器冷却记录清理完成: 清理前{}个,清理后{}个,清理了{}个过期记录", + sizeBefore, sizeAfter, sizeBefore - sizeAfter); + } else { + log.debug("机器冷却记录清理完成: 无过期记录需要清理,当前记录数{}", sizeAfter); + } + } catch (Exception e) { + log.error("机器冷却记录清理失败: {}", e.getMessage(), e); + } + } +} \ No newline at end of file