feat: 添加设备冷却逻辑,过滤冷却期内设备并处理冷却队列

This commit is contained in:
zyh
2025-08-30 16:09:34 +08:00
parent abe1447e0c
commit b69d48b12f
3 changed files with 228 additions and 5 deletions

View File

@@ -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<String, LocalDateTime> 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();
}
}

View File

@@ -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<String> availableDevices = deviceStatus.getAvailableDevices();
List<String> 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("重复选区: 检查首次选区时效性并复用设备");
// 检查首次选区是否已过期

View File

@@ -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);
}
}
}