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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import com.gameplatform.server.model.dto.cooldown.MachineCooldownView;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
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.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
@@ -226,6 +231,41 @@ public class MemoryMachineCooldownService {
|
|||||||
return cooldownMap.size();
|
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:
|
app:
|
||||||
base-url: "https://2.uzi0.cc" # 生产环境需要配置为实际域名
|
base-url: "https://uzi1.cn" # 生产环境需要配置为实际域名
|
||||||
# base-url: "http://localhost:18080" # 本地测试环境
|
# base-url: "http://localhost:18080" # 本地测试环境
|
||||||
|
|
||||||
link:
|
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