feat: 删除设备冷却相关的实体、映射和服务,优化代码结构
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
package com.gameplatform.server.controller.admin;
|
||||
|
||||
import com.gameplatform.server.model.dto.cooldown.CooldownStatsResponse;
|
||||
import com.gameplatform.server.model.dto.cooldown.MachineCooldownView;
|
||||
import com.gameplatform.server.service.cooldown.MemoryMachineCooldownService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/cooldown")
|
||||
@Tag(name = "机器冷却", description = "查询与管理内存中的机器冷却状态")
|
||||
public class CooldownController {
|
||||
|
||||
private final MemoryMachineCooldownService machineCooldownService;
|
||||
|
||||
public CooldownController(MemoryMachineCooldownService machineCooldownService) {
|
||||
this.machineCooldownService = machineCooldownService;
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "列出冷却记录", description = "获取当前内存中的机器冷却记录")
|
||||
public ResponseEntity<List<MachineCooldownView>> listCooldowns(
|
||||
@Parameter(description = "仅返回仍在冷却中的记录")
|
||||
@RequestParam(name = "activeOnly", required = false, defaultValue = "true") boolean activeOnly
|
||||
) {
|
||||
List<MachineCooldownView> list = machineCooldownService.listAllCooldowns(activeOnly);
|
||||
return ResponseEntity.ok(list);
|
||||
}
|
||||
|
||||
@GetMapping("/stats")
|
||||
@Operation(summary = "冷却统计", description = "获取冷却总数、活跃数与总剩余分钟数")
|
||||
public ResponseEntity<CooldownStatsResponse> getStats() {
|
||||
MemoryMachineCooldownService.CooldownStats stats = machineCooldownService.getCooldownStats();
|
||||
CooldownStatsResponse body = new CooldownStatsResponse(true,
|
||||
stats.getTotalDevices(),
|
||||
stats.getActiveDevices(),
|
||||
stats.getTotalRemainingMinutes());
|
||||
return ResponseEntity.ok(body);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
package com.gameplatform.server.mapper.cooldown;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.gameplatform.server.model.entity.cooldown.MachineCooldown;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 设备冷却状态Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface MachineCooldownMapper extends BaseMapper<MachineCooldown> {
|
||||
|
||||
/**
|
||||
* 根据设备ID查找活跃的冷却记录
|
||||
*/
|
||||
MachineCooldown findActiveCooldownByMachineId(@Param("machineId") String machineId);
|
||||
|
||||
/**
|
||||
* 根据设备ID和状态查找冷却记录
|
||||
*/
|
||||
List<MachineCooldown> findByMachineIdAndStatus(@Param("machineId") String machineId,
|
||||
@Param("status") String status);
|
||||
|
||||
/**
|
||||
* 查找已过期但状态仍为ACTIVE的冷却记录
|
||||
*/
|
||||
List<MachineCooldown> findExpiredActiveCooldowns(@Param("currentTime") LocalDateTime currentTime,
|
||||
@Param("limit") int limit);
|
||||
|
||||
/**
|
||||
* 批量更新过期的冷却记录状态
|
||||
*/
|
||||
int batchUpdateExpiredCooldowns(@Param("currentTime") LocalDateTime currentTime);
|
||||
|
||||
/**
|
||||
* 手动移除设备的冷却状态
|
||||
*/
|
||||
int removeMachineCooldown(@Param("machineId") String machineId);
|
||||
|
||||
/**
|
||||
* 根据链接任务ID查找冷却记录
|
||||
*/
|
||||
List<MachineCooldown> findByLinkTaskId(@Param("linkTaskId") Long linkTaskId);
|
||||
|
||||
/**
|
||||
* 统计活跃的冷却记录数量
|
||||
*/
|
||||
long countActiveCooldowns();
|
||||
|
||||
/**
|
||||
* 统计指定时间范围内的冷却记录数量
|
||||
*/
|
||||
long countCooldownsByTimeRange(@Param("startTime") LocalDateTime startTime,
|
||||
@Param("endTime") LocalDateTime endTime);
|
||||
|
||||
/**
|
||||
* 清理指定时间之前的已过期冷却记录
|
||||
*/
|
||||
int cleanupExpiredCooldowns(@Param("beforeTime") LocalDateTime beforeTime);
|
||||
|
||||
/**
|
||||
* 删除将要过期(ACTIVE→EXPIRED)的设备,已存在的 EXPIRED 记录,避免唯一键 (machine_id,status) 冲突。
|
||||
* 使用当前时间参数筛选出需要过期的设备列表。
|
||||
*/
|
||||
int deleteExistingExpiredForMachinesToExpire(@Param("currentTime") LocalDateTime currentTime);
|
||||
|
||||
/**
|
||||
* 获取指定设备的冷却历史记录
|
||||
*/
|
||||
List<MachineCooldown> getCooldownHistory(@Param("machineId") String machineId,
|
||||
@Param("limit") int limit,
|
||||
@Param("offset") int offset);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.gameplatform.server.model.dto.cooldown;
|
||||
|
||||
/**
|
||||
* 冷却统计响应
|
||||
*/
|
||||
public class CooldownStatsResponse {
|
||||
private boolean success;
|
||||
private int totalDevices;
|
||||
private int activeDevices;
|
||||
private long totalRemainingMinutes;
|
||||
|
||||
public CooldownStatsResponse() {}
|
||||
|
||||
public CooldownStatsResponse(boolean success, int totalDevices, int activeDevices, long totalRemainingMinutes) {
|
||||
this.success = success;
|
||||
this.totalDevices = totalDevices;
|
||||
this.activeDevices = activeDevices;
|
||||
this.totalRemainingMinutes = totalRemainingMinutes;
|
||||
}
|
||||
|
||||
public boolean isSuccess() { return success; }
|
||||
public void setSuccess(boolean success) { this.success = success; }
|
||||
|
||||
public int getTotalDevices() { return totalDevices; }
|
||||
public void setTotalDevices(int totalDevices) { this.totalDevices = totalDevices; }
|
||||
|
||||
public int getActiveDevices() { return activeDevices; }
|
||||
public void setActiveDevices(int activeDevices) { this.activeDevices = activeDevices; }
|
||||
|
||||
public long getTotalRemainingMinutes() { return totalRemainingMinutes; }
|
||||
public void setTotalRemainingMinutes(long totalRemainingMinutes) { this.totalRemainingMinutes = totalRemainingMinutes; }
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.gameplatform.server.model.dto.cooldown;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 内存冷却视图对象
|
||||
*/
|
||||
public class MachineCooldownView {
|
||||
private String machineId;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime cooldownStartTime;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime cooldownEndTime;
|
||||
|
||||
private String reason;
|
||||
private Long linkTaskId;
|
||||
private long remainingMinutes;
|
||||
private boolean active;
|
||||
|
||||
public MachineCooldownView() {}
|
||||
|
||||
public MachineCooldownView(String machineId,
|
||||
LocalDateTime cooldownStartTime,
|
||||
LocalDateTime cooldownEndTime,
|
||||
String reason,
|
||||
Long linkTaskId,
|
||||
long remainingMinutes,
|
||||
boolean active) {
|
||||
this.machineId = machineId;
|
||||
this.cooldownStartTime = cooldownStartTime;
|
||||
this.cooldownEndTime = cooldownEndTime;
|
||||
this.reason = reason;
|
||||
this.linkTaskId = linkTaskId;
|
||||
this.remainingMinutes = remainingMinutes;
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
public String getMachineId() { return machineId; }
|
||||
public void setMachineId(String machineId) { this.machineId = machineId; }
|
||||
|
||||
public LocalDateTime getCooldownStartTime() { return cooldownStartTime; }
|
||||
public void setCooldownStartTime(LocalDateTime cooldownStartTime) { this.cooldownStartTime = cooldownStartTime; }
|
||||
|
||||
public LocalDateTime getCooldownEndTime() { return cooldownEndTime; }
|
||||
public void setCooldownEndTime(LocalDateTime cooldownEndTime) { this.cooldownEndTime = cooldownEndTime; }
|
||||
|
||||
public String getReason() { return reason; }
|
||||
public void setReason(String reason) { this.reason = reason; }
|
||||
|
||||
public Long getLinkTaskId() { return linkTaskId; }
|
||||
public void setLinkTaskId(Long linkTaskId) { this.linkTaskId = linkTaskId; }
|
||||
|
||||
public long getRemainingMinutes() { return remainingMinutes; }
|
||||
public void setRemainingMinutes(long remainingMinutes) { this.remainingMinutes = remainingMinutes; }
|
||||
|
||||
public boolean isActive() { return active; }
|
||||
public void setActive(boolean active) { this.active = active; }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
package com.gameplatform.server.model.entity.cooldown;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 设备冷却状态实体
|
||||
*/
|
||||
@TableName("machine_cooldown")
|
||||
public class MachineCooldown {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 设备ID
|
||||
*/
|
||||
private String machineId;
|
||||
|
||||
/**
|
||||
* 冷却开始时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime cooldownStartTime;
|
||||
|
||||
/**
|
||||
* 冷却结束时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime cooldownEndTime;
|
||||
|
||||
/**
|
||||
* 冷却原因
|
||||
*/
|
||||
private String reason;
|
||||
|
||||
/**
|
||||
* 关联的链接任务ID
|
||||
*/
|
||||
private Long linkTaskId;
|
||||
|
||||
/**
|
||||
* 冷却状态:ACTIVE-活跃, EXPIRED-已过期, MANUALLY_REMOVED-手动移除
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
// 构造函数
|
||||
public MachineCooldown() {}
|
||||
|
||||
public MachineCooldown(String machineId, LocalDateTime cooldownStartTime,
|
||||
LocalDateTime cooldownEndTime, String reason, Long linkTaskId) {
|
||||
this.machineId = machineId;
|
||||
this.cooldownStartTime = cooldownStartTime;
|
||||
this.cooldownEndTime = cooldownEndTime;
|
||||
this.reason = reason;
|
||||
this.linkTaskId = linkTaskId;
|
||||
this.status = "ACTIVE";
|
||||
this.createdAt = LocalDateTime.now();
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
// Getter and Setter methods
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getMachineId() {
|
||||
return machineId;
|
||||
}
|
||||
|
||||
public void setMachineId(String machineId) {
|
||||
this.machineId = machineId;
|
||||
}
|
||||
|
||||
public LocalDateTime getCooldownStartTime() {
|
||||
return cooldownStartTime;
|
||||
}
|
||||
|
||||
public void setCooldownStartTime(LocalDateTime cooldownStartTime) {
|
||||
this.cooldownStartTime = cooldownStartTime;
|
||||
}
|
||||
|
||||
public LocalDateTime getCooldownEndTime() {
|
||||
return cooldownEndTime;
|
||||
}
|
||||
|
||||
public void setCooldownEndTime(LocalDateTime cooldownEndTime) {
|
||||
this.cooldownEndTime = cooldownEndTime;
|
||||
}
|
||||
|
||||
public String getReason() {
|
||||
return reason;
|
||||
}
|
||||
|
||||
public void setReason(String reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public Long getLinkTaskId() {
|
||||
return linkTaskId;
|
||||
}
|
||||
|
||||
public void setLinkTaskId(Long linkTaskId) {
|
||||
this.linkTaskId = linkTaskId;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(LocalDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public LocalDateTime getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(LocalDateTime updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查冷却是否仍然有效
|
||||
*/
|
||||
public boolean isActive() {
|
||||
return "ACTIVE".equals(status) &&
|
||||
cooldownEndTime != null &&
|
||||
LocalDateTime.now().isBefore(cooldownEndTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取剩余冷却时间(分钟)
|
||||
*/
|
||||
public long getRemainingMinutes() {
|
||||
if (!isActive()) {
|
||||
return 0;
|
||||
}
|
||||
return java.time.Duration.between(LocalDateTime.now(), cooldownEndTime).toMinutes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MachineCooldown{" +
|
||||
"id=" + id +
|
||||
", machineId='" + machineId + '\'' +
|
||||
", cooldownStartTime=" + cooldownStartTime +
|
||||
", cooldownEndTime=" + cooldownEndTime +
|
||||
", reason='" + reason + '\'' +
|
||||
", linkTaskId=" + linkTaskId +
|
||||
", status='" + status + '\'' +
|
||||
", createdAt=" + createdAt +
|
||||
", updatedAt=" + updatedAt +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,298 +0,0 @@
|
||||
package com.gameplatform.server.service.cooldown;
|
||||
|
||||
import com.gameplatform.server.mapper.cooldown.MachineCooldownMapper;
|
||||
import com.gameplatform.server.model.entity.cooldown.MachineCooldown;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/**
|
||||
* 机器冷却服务 - 支持数据库持久化
|
||||
* 实现同一台机器在10分钟内不会重复调用的机制
|
||||
*
|
||||
* 优化点:
|
||||
* 1. 数据库持久化,服务重启不丢失状态
|
||||
* 2. 双重缓存机制,提高查询性能
|
||||
* 3. 分布式锁支持,避免并发问题
|
||||
*/
|
||||
@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> machineCooldownCache = new ConcurrentHashMap<>();
|
||||
|
||||
private final MachineCooldownMapper machineCooldownMapper;
|
||||
|
||||
public MachineCooldownService(MachineCooldownMapper machineCooldownMapper) {
|
||||
this.machineCooldownMapper = machineCooldownMapper;
|
||||
// 启动时加载活跃的冷却记录到缓存
|
||||
loadActiveCooldownsToCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动时加载活跃的冷却记录到缓存
|
||||
*/
|
||||
private void loadActiveCooldownsToCache() {
|
||||
try {
|
||||
List<MachineCooldown> activeCooldowns = machineCooldownMapper.findExpiredActiveCooldowns(
|
||||
LocalDateTime.now().plusMinutes(COOLDOWN_MINUTES), 1000);
|
||||
|
||||
for (MachineCooldown cooldown : activeCooldowns) {
|
||||
if (cooldown.isActive()) {
|
||||
machineCooldownCache.put(cooldown.getMachineId(), cooldown.getCooldownStartTime());
|
||||
}
|
||||
}
|
||||
|
||||
log.info("加载了 {} 个活跃冷却记录到缓存", activeCooldowns.size());
|
||||
} catch (Exception e) {
|
||||
log.error("加载冷却记录到缓存失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查机器是否在冷却期内(优先从数据库查询确保准确性)
|
||||
* @param machineId 机器ID
|
||||
* @return true表示在冷却期内,false表示可以操作
|
||||
*/
|
||||
public boolean isMachineInCooldown(String machineId) {
|
||||
if (machineId == null || machineId.trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// 首先查询数据库获取最准确的状态
|
||||
MachineCooldown activeCooldown = machineCooldownMapper.findActiveCooldownByMachineId(machineId);
|
||||
|
||||
if (activeCooldown == null || !activeCooldown.isActive()) {
|
||||
// 数据库中没有活跃冷却记录,清理缓存
|
||||
machineCooldownCache.remove(machineId);
|
||||
log.debug("机器{}没有活跃冷却记录,允许操作", machineId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 更新缓存
|
||||
machineCooldownCache.put(machineId, activeCooldown.getCooldownStartTime());
|
||||
|
||||
long remainingMinutes = activeCooldown.getRemainingMinutes();
|
||||
log.info("机器{}在冷却期内,剩余冷却时间:{}分钟,原因:{}",
|
||||
machineId, remainingMinutes + 1, activeCooldown.getReason());
|
||||
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("检查机器{}冷却状态时发生异常,默认允许操作", machineId, e);
|
||||
// 发生异常时,回退到缓存检查
|
||||
return isMachineInCooldownFromCache(machineId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从缓存检查机器冷却状态(备用方法)
|
||||
*/
|
||||
private boolean isMachineInCooldownFromCache(String machineId) {
|
||||
LocalDateTime lastOperationTime = machineCooldownCache.get(machineId);
|
||||
if (lastOperationTime == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
LocalDateTime cooldownExpireTime = lastOperationTime.plusMinutes(COOLDOWN_MINUTES);
|
||||
boolean inCooldown = now.isBefore(cooldownExpireTime);
|
||||
|
||||
if (!inCooldown) {
|
||||
// 冷却已过期,清理缓存
|
||||
machineCooldownCache.remove(machineId);
|
||||
}
|
||||
|
||||
return inCooldown;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将机器加入冷却队列(支持关联链接任务)
|
||||
* @param machineId 机器ID
|
||||
* @param reason 加入冷却的原因
|
||||
*/
|
||||
@Transactional
|
||||
public void addMachineToCooldown(String machineId, String reason) {
|
||||
try {
|
||||
addMachineToCooldown(machineId, reason, null);
|
||||
} catch (RuntimeException ex) {
|
||||
// 当上层已完成占用(或并发占用导致唯一键冲突)时,吞掉异常以便上层逻辑继续
|
||||
log.warn("添加冷却记录失败或已存在,machineId={},原因={}(忽略)", machineId, ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将机器加入冷却队列
|
||||
* @param machineId 机器ID
|
||||
* @param reason 加入冷却的原因
|
||||
* @param linkTaskId 关联的链接任务ID(可选)
|
||||
*/
|
||||
@Transactional
|
||||
public void addMachineToCooldown(String machineId, String reason, Long linkTaskId) {
|
||||
if (machineId == null || machineId.trim().isEmpty()) {
|
||||
log.warn("尝试添加空的机器ID到冷却队列");
|
||||
return;
|
||||
}
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
LocalDateTime cooldownEndTime = now.plusMinutes(COOLDOWN_MINUTES);
|
||||
|
||||
try {
|
||||
// 先移除该设备现有的活跃冷却记录(若存在非并发场景下的残留)
|
||||
machineCooldownMapper.removeMachineCooldown(machineId);
|
||||
|
||||
// 创建新的冷却记录(依赖数据库唯一约束防止并发重复占用)
|
||||
MachineCooldown cooldown = new MachineCooldown(
|
||||
machineId, now, cooldownEndTime, reason, linkTaskId);
|
||||
machineCooldownMapper.insert(cooldown);
|
||||
|
||||
// 更新缓存
|
||||
machineCooldownCache.put(machineId, now);
|
||||
|
||||
log.info("机器{}已加入冷却队列,原因:{},冷却时间:{}分钟,冷却结束时间:{},关联任务:{}",
|
||||
machineId, reason, COOLDOWN_MINUTES, cooldownEndTime, linkTaskId);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("将机器{}加入冷却队列失败:{}", machineId, e.getMessage(), e);
|
||||
// 抛出异常,交由上层流程回滚/重试,避免同一设备被并发占用
|
||||
throw new RuntimeException("设备正忙或分配冲突,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取机器剩余冷却时间(分钟)
|
||||
* @param machineId 机器ID
|
||||
* @return 剩余冷却时间,如果不在冷却期则返回0
|
||||
*/
|
||||
public long getRemainingCooldownMinutes(String machineId) {
|
||||
if (machineId == null || machineId.trim().isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {
|
||||
MachineCooldown activeCooldown = machineCooldownMapper.findActiveCooldownByMachineId(machineId);
|
||||
if (activeCooldown != null && activeCooldown.isActive()) {
|
||||
return activeCooldown.getRemainingMinutes();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("查询机器{}剩余冷却时间失败,回退到缓存查询", machineId, e);
|
||||
// 回退到缓存查询
|
||||
LocalDateTime lastOperationTime = machineCooldownCache.get(machineId);
|
||||
if (lastOperationTime != null) {
|
||||
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
|
||||
*/
|
||||
@Transactional
|
||||
public void removeMachineFromCooldown(String machineId) {
|
||||
if (machineId == null || machineId.trim().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
int updated = machineCooldownMapper.removeMachineCooldown(machineId);
|
||||
if (updated > 0) {
|
||||
log.info("已手动移除机器{}的冷却状态", machineId);
|
||||
} else {
|
||||
log.debug("机器{}不在数据库冷却队列中", machineId);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("手动移除机器{}冷却状态失败", machineId, e);
|
||||
}
|
||||
|
||||
// 清理缓存
|
||||
LocalDateTime removedTime = machineCooldownCache.remove(machineId);
|
||||
if (removedTime != null) {
|
||||
log.debug("已从缓存中移除机器{}的冷却状态", machineId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期的冷却记录(可定期调用以释放内存)
|
||||
*/
|
||||
@Transactional
|
||||
public void cleanupExpiredCooldowns() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
try {
|
||||
// 批量更新数据库中过期的冷却记录
|
||||
int dbCleanedCount = machineCooldownMapper.batchUpdateExpiredCooldowns(now);
|
||||
|
||||
// 清理缓存中过期的记录
|
||||
int cacheCleanedCount = 0;
|
||||
var iterator = machineCooldownCache.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
var entry = iterator.next();
|
||||
String machineId = entry.getKey();
|
||||
LocalDateTime lastOperationTime = entry.getValue();
|
||||
LocalDateTime cooldownExpireTime = lastOperationTime.plusMinutes(COOLDOWN_MINUTES);
|
||||
|
||||
if (now.isAfter(cooldownExpireTime)) {
|
||||
iterator.remove();
|
||||
cacheCleanedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (dbCleanedCount > 0 || cacheCleanedCount > 0) {
|
||||
log.info("清理过期冷却记录完成:数据库{}个,缓存{}个", dbCleanedCount, cacheCleanedCount);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("清理过期冷却记录失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前冷却队列的大小
|
||||
* @return 冷却队列中的机器数量
|
||||
*/
|
||||
public int getCooldownQueueSize() {
|
||||
try {
|
||||
return (int) machineCooldownMapper.countActiveCooldowns();
|
||||
} catch (Exception e) {
|
||||
log.warn("查询活跃冷却记录数量失败,返回缓存大小", e);
|
||||
return machineCooldownCache.size();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取机器的冷却历史记录
|
||||
* @param machineId 机器ID
|
||||
* @param limit 返回记录数量限制
|
||||
* @return 冷却历史记录列表
|
||||
*/
|
||||
public List<MachineCooldown> getCooldownHistory(String machineId, int limit) {
|
||||
if (machineId == null || machineId.trim().isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
try {
|
||||
return machineCooldownMapper.getCooldownHistory(machineId, limit, 0);
|
||||
} catch (Exception e) {
|
||||
log.error("查询机器{}冷却历史失败", machineId, e);
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,13 @@ package com.gameplatform.server.service.cooldown;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.gameplatform.server.model.dto.cooldown.MachineCooldownView;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
@@ -226,6 +231,41 @@ public class MemoryMachineCooldownService {
|
||||
return cooldownMap.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列出内存中的所有冷却记录
|
||||
* @param activeOnly 是否仅返回仍在冷却中的记录
|
||||
*/
|
||||
public List<MachineCooldownView> listAllCooldowns(boolean activeOnly) {
|
||||
// 主动清理一次,避免返回过期项
|
||||
cleanupExpiredCooldowns();
|
||||
|
||||
List<MachineCooldownView> result = new ArrayList<>();
|
||||
for (Map.Entry<String, CooldownInfo> entry : cooldownMap.entrySet()) {
|
||||
String machineId = entry.getKey();
|
||||
CooldownInfo info = entry.getValue();
|
||||
boolean active = info.isActive();
|
||||
if (activeOnly && !active) {
|
||||
continue;
|
||||
}
|
||||
|
||||
long remaining = active ? info.getRemainingMinutes() : 0L;
|
||||
result.add(new MachineCooldownView(
|
||||
machineId,
|
||||
info.getCooldownStartTime(),
|
||||
info.getCooldownEndTime(),
|
||||
info.getReason(),
|
||||
info.getLinkTaskId(),
|
||||
remaining,
|
||||
active
|
||||
));
|
||||
}
|
||||
|
||||
// 便于排查:按结束时间升序排列
|
||||
result.sort(Comparator.comparing(MachineCooldownView::getCooldownEndTime,
|
||||
Comparator.nullsLast(Comparator.naturalOrder())));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期的冷却记录
|
||||
*/
|
||||
|
||||
@@ -88,7 +88,7 @@ script:
|
||||
|
||||
# 服务器配置
|
||||
app:
|
||||
base-url: "https://2.uzi0.cc" # 生产环境需要配置为实际域名
|
||||
base-url: "https://uzi1.cn" # 生产环境需要配置为实际域名
|
||||
# base-url: "http://localhost:18080" # 本地测试环境
|
||||
|
||||
link:
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
-- 清理未使用的设备冷却表
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
DROP TABLE IF EXISTS `machine_cooldown`;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.gameplatform.server.mapper.cooldown.MachineCooldownMapper">
|
||||
|
||||
<!-- 结果映射 -->
|
||||
<resultMap id="MachineCooldownMap" type="com.gameplatform.server.model.entity.cooldown.MachineCooldown">
|
||||
<id property="id" column="id" />
|
||||
<result property="machineId" column="machine_id" />
|
||||
<result property="cooldownStartTime" column="cooldown_start_time" />
|
||||
<result property="cooldownEndTime" column="cooldown_end_time" />
|
||||
<result property="reason" column="reason" />
|
||||
<result property="linkTaskId" column="link_task_id" />
|
||||
<result property="status" column="status" />
|
||||
<result property="createdAt" column="created_at" />
|
||||
<result property="updatedAt" column="updated_at" />
|
||||
</resultMap>
|
||||
|
||||
<!-- 根据设备ID查找活跃的冷却记录 -->
|
||||
<select id="findActiveCooldownByMachineId" parameterType="string" resultMap="MachineCooldownMap">
|
||||
SELECT * FROM machine_cooldown
|
||||
WHERE machine_id = #{machineId}
|
||||
AND status = 'ACTIVE'
|
||||
AND cooldown_end_time > NOW()
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<!-- 根据设备ID和状态查找冷却记录 -->
|
||||
<select id="findByMachineIdAndStatus" resultMap="MachineCooldownMap">
|
||||
SELECT * FROM machine_cooldown
|
||||
WHERE machine_id = #{machineId}
|
||||
AND status = #{status}
|
||||
ORDER BY created_at DESC
|
||||
</select>
|
||||
|
||||
<!-- 查找已过期但状态仍为ACTIVE的冷却记录 -->
|
||||
<select id="findExpiredActiveCooldowns" resultMap="MachineCooldownMap">
|
||||
SELECT * FROM machine_cooldown
|
||||
WHERE status = 'ACTIVE'
|
||||
AND cooldown_end_time <= #{currentTime}
|
||||
ORDER BY cooldown_end_time ASC
|
||||
LIMIT #{limit}
|
||||
</select>
|
||||
|
||||
<!-- 批量更新过期的冷却记录状态 -->
|
||||
<update id="batchUpdateExpiredCooldowns">
|
||||
UPDATE machine_cooldown mc
|
||||
LEFT JOIN (
|
||||
SELECT machine_id FROM (
|
||||
SELECT DISTINCT machine_id
|
||||
FROM machine_cooldown
|
||||
WHERE status = 'EXPIRED'
|
||||
) ex0
|
||||
) ex ON ex.machine_id = mc.machine_id
|
||||
SET mc.status = 'EXPIRED', mc.updated_at = NOW()
|
||||
WHERE mc.status = 'ACTIVE'
|
||||
AND mc.cooldown_end_time <= #{currentTime}
|
||||
AND ex.machine_id IS NULL
|
||||
</update>
|
||||
|
||||
<!-- 手动移除设备的冷却状态 -->
|
||||
<update id="removeMachineCooldown">
|
||||
UPDATE machine_cooldown
|
||||
SET status = 'MANUALLY_REMOVED', updated_at = NOW()
|
||||
WHERE machine_id = #{machineId}
|
||||
AND status = 'ACTIVE'
|
||||
</update>
|
||||
|
||||
<!-- 根据链接任务ID查找冷却记录 -->
|
||||
<select id="findByLinkTaskId" parameterType="long" resultMap="MachineCooldownMap">
|
||||
SELECT * FROM machine_cooldown
|
||||
WHERE link_task_id = #{linkTaskId}
|
||||
ORDER BY created_at DESC
|
||||
</select>
|
||||
|
||||
<!-- 统计活跃的冷却记录数量 -->
|
||||
<select id="countActiveCooldowns" resultType="long">
|
||||
SELECT COUNT(*) FROM machine_cooldown
|
||||
WHERE status = 'ACTIVE'
|
||||
AND cooldown_end_time > NOW()
|
||||
</select>
|
||||
|
||||
<!-- 统计指定时间范围内的冷却记录数量 -->
|
||||
<select id="countCooldownsByTimeRange" resultType="long">
|
||||
SELECT COUNT(*) FROM machine_cooldown
|
||||
WHERE created_at BETWEEN #{startTime} AND #{endTime}
|
||||
</select>
|
||||
|
||||
<!-- 清理指定时间之前的已过期冷却记录 -->
|
||||
<delete id="cleanupExpiredCooldowns">
|
||||
DELETE FROM machine_cooldown
|
||||
WHERE status = 'EXPIRED'
|
||||
AND updated_at < #{beforeTime}
|
||||
</delete>
|
||||
|
||||
<!-- 在批量将 ACTIVE 更新为 EXPIRED 前,
|
||||
先删除这些设备已存在的 EXPIRED 记录,避免 (machine_id,status) 唯一键冲突 -->
|
||||
<delete id="deleteExistingExpiredForMachinesToExpire">
|
||||
DELETE mc FROM machine_cooldown mc
|
||||
JOIN (
|
||||
SELECT DISTINCT machine_id
|
||||
FROM machine_cooldown
|
||||
WHERE status = 'ACTIVE'
|
||||
AND cooldown_end_time <= #{currentTime}
|
||||
) t ON t.machine_id = mc.machine_id
|
||||
WHERE mc.status = 'EXPIRED'
|
||||
</delete>
|
||||
|
||||
<!-- 获取指定设备的冷却历史记录 -->
|
||||
<select id="getCooldownHistory" resultMap="MachineCooldownMap">
|
||||
SELECT * FROM machine_cooldown
|
||||
WHERE machine_id = #{machineId}
|
||||
ORDER BY created_at DESC
|
||||
LIMIT #{limit} OFFSET #{offset}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
Reference in New Issue
Block a user