feat: 优化设备冷却管理,增加原子设备占用逻辑和过期记录处理
This commit is contained in:
@@ -303,3 +303,4 @@ WHERE created_at < DATE_SUB(NOW(), INTERVAL 30 DAY);
|
|||||||
4. 说明当前系统状态
|
4. 说明当前系统状态
|
||||||
|
|
||||||
这样可以更快速地定位和解决问题。
|
这样可以更快速地定位和解决问题。
|
||||||
|
|
||||||
|
|||||||
@@ -96,3 +96,4 @@ CREATE TABLE `system_monitor` (
|
|||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统监控表' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统监控表' ROW_FORMAT = DYNAMIC;
|
||||||
|
|
||||||
SET FOREIGN_KEY_CHECKS = 1;
|
SET FOREIGN_KEY_CHECKS = 1;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.gameplatform.server;
|
package com.gameplatform.server;
|
||||||
|
|
||||||
import org.mybatis.spring.annotation.MapperScan;
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
@@ -9,8 +11,13 @@ import org.springframework.scheduling.annotation.EnableScheduling;
|
|||||||
@MapperScan("com.gameplatform.server.mapper")
|
@MapperScan("com.gameplatform.server.mapper")
|
||||||
@EnableScheduling
|
@EnableScheduling
|
||||||
public class GamePlatformServerApplication {
|
public class GamePlatformServerApplication {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(GamePlatformServerApplication.class);
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
log.info("=== 游戏平台服务器启动中 ===");
|
||||||
|
log.debug("Debug 日志级别已启用");
|
||||||
SpringApplication.run(GamePlatformServerApplication.class, args);
|
SpringApplication.run(GamePlatformServerApplication.class, args);
|
||||||
|
log.info("=== 游戏平台服务器启动完成 ===");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public class AuthController {
|
|||||||
@ResponseStatus(HttpStatus.OK)
|
@ResponseStatus(HttpStatus.OK)
|
||||||
public Mono<LoginResponse> login(@Valid @RequestBody LoginRequest req) {
|
public Mono<LoginResponse> login(@Valid @RequestBody LoginRequest req) {
|
||||||
// Avoid logging raw usernames at info level
|
// Avoid logging raw usernames at info level
|
||||||
log.debug("/api/auth/login called");
|
log.info("/api/auth/login called");
|
||||||
return authService.login(req);
|
return authService.login(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ import com.gameplatform.server.model.dto.link.UserLinkStatusResponse;
|
|||||||
import com.gameplatform.server.model.dto.link.TargetScoreResponse;
|
import com.gameplatform.server.model.dto.link.TargetScoreResponse;
|
||||||
import com.gameplatform.server.service.link.LinkGenerationService;
|
import com.gameplatform.server.service.link.LinkGenerationService;
|
||||||
import com.gameplatform.server.service.link.LinkListService;
|
import com.gameplatform.server.service.link.LinkListService;
|
||||||
import com.gameplatform.server.service.link.LinkStatusService;
|
|
||||||
import com.gameplatform.server.service.external.ScriptClient;
|
import com.gameplatform.server.service.external.ScriptClient;
|
||||||
|
import com.gameplatform.server.service.link.LinkStatusService;
|
||||||
import io.jsonwebtoken.Claims;
|
import io.jsonwebtoken.Claims;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|||||||
@@ -115,4 +115,14 @@ public interface LinkTaskMapper extends BaseMapper<LinkTask> {
|
|||||||
* 根据状态查询所有链接任务
|
* 根据状态查询所有链接任务
|
||||||
*/
|
*/
|
||||||
List<LinkTask> findByStatus(@Param("status") String status);
|
List<LinkTask> findByStatus(@Param("status") String status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原子方式占用设备:仅当该设备当前未被 USING/LOGGED_IN 占用时,
|
||||||
|
* 才将指定任务更新为 USING 并写入设备与时间字段。
|
||||||
|
* 返回受影响行数(1=成功,0=设备已被占用)。
|
||||||
|
*/
|
||||||
|
int reserveDeviceIfFree(@Param("id") Long id,
|
||||||
|
@Param("region") String region,
|
||||||
|
@Param("deviceId") String deviceId,
|
||||||
|
@Param("qrExpireSeconds") int qrExpireSeconds);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,12 @@ public interface MachineCooldownMapper extends BaseMapper<MachineCooldown> {
|
|||||||
*/
|
*/
|
||||||
int cleanupExpiredCooldowns(@Param("beforeTime") LocalDateTime beforeTime);
|
int cleanupExpiredCooldowns(@Param("beforeTime") LocalDateTime beforeTime);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除将要过期(ACTIVE→EXPIRED)的设备,已存在的 EXPIRED 记录,避免唯一键 (machine_id,status) 冲突。
|
||||||
|
* 使用当前时间参数筛选出需要过期的设备列表。
|
||||||
|
*/
|
||||||
|
int deleteExistingExpiredForMachinesToExpire(@Param("currentTime") LocalDateTime currentTime);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取指定设备的冷却历史记录
|
* 获取指定设备的冷却历史记录
|
||||||
*/
|
*/
|
||||||
@@ -69,3 +75,4 @@ public interface MachineCooldownMapper extends BaseMapper<MachineCooldown> {
|
|||||||
@Param("limit") int limit,
|
@Param("limit") int limit,
|
||||||
@Param("offset") int offset);
|
@Param("offset") int offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -182,3 +182,4 @@ public class MachineCooldown {
|
|||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -197,3 +197,4 @@ public class GameCompletionLog {
|
|||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package com.gameplatform.server.service.detection;
|
|||||||
import com.gameplatform.server.mapper.agent.LinkTaskMapper;
|
import com.gameplatform.server.mapper.agent.LinkTaskMapper;
|
||||||
import com.gameplatform.server.model.entity.agent.LinkTask;
|
import com.gameplatform.server.model.entity.agent.LinkTask;
|
||||||
import com.gameplatform.server.model.entity.detection.GameCompletionLog;
|
import com.gameplatform.server.model.entity.detection.GameCompletionLog;
|
||||||
import com.gameplatform.server.service.cooldown.MachineCooldownService;
|
import com.gameplatform.server.service.cooldown.MemoryMachineCooldownService;
|
||||||
import com.gameplatform.server.mapper.detection.GameCompletionLogMapper;
|
import com.gameplatform.server.mapper.detection.GameCompletionLogMapper;
|
||||||
import com.gameplatform.server.mapper.history.LinkTaskStatusHistoryMapper;
|
import com.gameplatform.server.mapper.history.LinkTaskStatusHistoryMapper;
|
||||||
import com.gameplatform.server.model.entity.history.LinkTaskStatusHistory;
|
import com.gameplatform.server.model.entity.history.LinkTaskStatusHistory;
|
||||||
@@ -37,7 +37,7 @@ public class GameCompletionDetectionService {
|
|||||||
private static final int COMPLETION_CONFIRMATION_INTERVAL_SECONDS = 10;
|
private static final int COMPLETION_CONFIRMATION_INTERVAL_SECONDS = 10;
|
||||||
|
|
||||||
private final LinkTaskMapper linkTaskMapper;
|
private final LinkTaskMapper linkTaskMapper;
|
||||||
private final MachineCooldownService machineCooldownService;
|
private final MemoryMachineCooldownService machineCooldownService;
|
||||||
private final GameCompletionLogMapper gameCompletionLogMapper;
|
private final GameCompletionLogMapper gameCompletionLogMapper;
|
||||||
private final LinkTaskStatusHistoryMapper statusHistoryMapper;
|
private final LinkTaskStatusHistoryMapper statusHistoryMapper;
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ public class GameCompletionDetectionService {
|
|||||||
private final ConcurrentMap<String, LocalDateTime> recentLogins = new ConcurrentHashMap<>();
|
private final ConcurrentMap<String, LocalDateTime> recentLogins = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public GameCompletionDetectionService(LinkTaskMapper linkTaskMapper,
|
public GameCompletionDetectionService(LinkTaskMapper linkTaskMapper,
|
||||||
MachineCooldownService machineCooldownService,
|
MemoryMachineCooldownService machineCooldownService,
|
||||||
GameCompletionLogMapper gameCompletionLogMapper,
|
GameCompletionLogMapper gameCompletionLogMapper,
|
||||||
LinkTaskStatusHistoryMapper statusHistoryMapper) {
|
LinkTaskStatusHistoryMapper statusHistoryMapper) {
|
||||||
this.linkTaskMapper = linkTaskMapper;
|
this.linkTaskMapper = linkTaskMapper;
|
||||||
@@ -297,3 +297,4 @@ public class GameCompletionDetectionService {
|
|||||||
LOW // 低置信度:不可信的状态变化
|
LOW // 低置信度:不可信的状态变化
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import com.gameplatform.server.service.external.ScriptClient;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import com.gameplatform.server.service.device.DeviceStatusCheckService;
|
import com.gameplatform.server.service.device.DeviceStatusCheckService;
|
||||||
import com.gameplatform.server.service.admin.SystemConfigService;
|
import com.gameplatform.server.service.admin.SystemConfigService;
|
||||||
import com.gameplatform.server.service.cooldown.MachineCooldownService;
|
import com.gameplatform.server.service.cooldown.MemoryMachineCooldownService;
|
||||||
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;
|
||||||
@@ -42,13 +42,13 @@ public class LinkStatusService {
|
|||||||
private final ScriptClient scriptClient;
|
private final ScriptClient scriptClient;
|
||||||
private final DeviceStatusCheckService deviceStatusCheckService;
|
private final DeviceStatusCheckService deviceStatusCheckService;
|
||||||
private final SystemConfigService systemConfigService;
|
private final SystemConfigService systemConfigService;
|
||||||
private final MachineCooldownService machineCooldownService;
|
private final MemoryMachineCooldownService machineCooldownService;
|
||||||
|
private final DeviceAllocationService deviceAllocationService;
|
||||||
@Autowired(required = false)
|
@Autowired(required = false)
|
||||||
private com.gameplatform.server.service.detection.GameCompletionDetectionService completionDetectionService;
|
private com.gameplatform.server.service.detection.GameCompletionDetectionService completionDetectionService;
|
||||||
@Autowired(required = false)
|
@Autowired(required = false)
|
||||||
private com.gameplatform.server.mapper.history.LinkTaskStatusHistoryMapper statusHistoryMapper;
|
private com.gameplatform.server.mapper.history.LinkTaskStatusHistoryMapper statusHistoryMapper;
|
||||||
|
|
||||||
|
|
||||||
// 状态描述映射
|
// 状态描述映射
|
||||||
private static final Map<String, String> STATUS_DESC_MAP = new HashMap<>();
|
private static final Map<String, String> STATUS_DESC_MAP = new HashMap<>();
|
||||||
static {
|
static {
|
||||||
@@ -62,13 +62,14 @@ public class LinkStatusService {
|
|||||||
|
|
||||||
public LinkStatusService(LinkTaskMapper linkTaskMapper, LinkBatchMapper linkBatchMapper,
|
public LinkStatusService(LinkTaskMapper linkTaskMapper, LinkBatchMapper linkBatchMapper,
|
||||||
ScriptClient scriptClient,
|
ScriptClient scriptClient,
|
||||||
DeviceStatusCheckService deviceStatusCheckService, SystemConfigService systemConfigService, MachineCooldownService machineCooldownService) {
|
DeviceStatusCheckService deviceStatusCheckService, SystemConfigService systemConfigService, MemoryMachineCooldownService machineCooldownService, DeviceAllocationService deviceAllocationService) {
|
||||||
this.linkTaskMapper = linkTaskMapper;
|
this.linkTaskMapper = linkTaskMapper;
|
||||||
this.linkBatchMapper = linkBatchMapper;
|
this.linkBatchMapper = linkBatchMapper;
|
||||||
this.scriptClient = scriptClient;
|
this.scriptClient = scriptClient;
|
||||||
this.deviceStatusCheckService = deviceStatusCheckService;
|
this.deviceStatusCheckService = deviceStatusCheckService;
|
||||||
this.systemConfigService = systemConfigService;
|
this.systemConfigService = systemConfigService;
|
||||||
this.machineCooldownService = machineCooldownService;
|
this.machineCooldownService = machineCooldownService;
|
||||||
|
this.deviceAllocationService = deviceAllocationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,13 +90,13 @@ public class LinkStatusService {
|
|||||||
// 查询链接任务
|
// 查询链接任务
|
||||||
LinkTask linkTask = linkTaskMapper.findByCodeNo(codeNo);
|
LinkTask linkTask = linkTaskMapper.findByCodeNo(codeNo);
|
||||||
if (linkTask == null) {
|
if (linkTask == null) {
|
||||||
throw new IllegalArgumentException("链接不存在: " + codeNo);
|
throw new IllegalArgumentException("链接不存在 " + codeNo);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询批次信息
|
// 查询批次信息
|
||||||
LinkBatch linkBatch = linkBatchMapper.findById(linkTask.getBatchId());
|
LinkBatch linkBatch = linkBatchMapper.findById(linkTask.getBatchId());
|
||||||
if (linkBatch == null) {
|
if (linkBatch == null) {
|
||||||
throw new IllegalStateException("批次信息不存在: batchId=" + linkTask.getBatchId());
|
throw new IllegalStateException("批次信息不存在 batchId=" + linkTask.getBatchId());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建响应对象
|
// 构建响应对象
|
||||||
@@ -125,7 +126,7 @@ public class LinkStatusService {
|
|||||||
response.setRemainingSeconds(0L);
|
response.setRemainingSeconds(0L);
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("链接状态查询完成: codeNo={}, status={}, isExpired={}, remainingSeconds={}",
|
log.debug("链接状态查询完成 codeNo={}, status={}, isExpired={}, remainingSeconds={}",
|
||||||
codeNo, linkTask.getStatus(), isExpired, response.getRemainingSeconds());
|
codeNo, linkTask.getStatus(), isExpired, response.getRemainingSeconds());
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
@@ -174,7 +175,7 @@ public class LinkStatusService {
|
|||||||
// 1. 查询链接任务
|
// 1. 查询链接任务
|
||||||
LinkTask linkTask = linkTaskMapper.findByCodeNo(codeNo);
|
LinkTask linkTask = linkTaskMapper.findByCodeNo(codeNo);
|
||||||
if (linkTask == null) {
|
if (linkTask == null) {
|
||||||
log.error("链接任务不存在: codeNo={}", codeNo);
|
log.error("链接任务不存在 codeNo={}", codeNo);
|
||||||
throw new IllegalArgumentException("链接不存在");
|
throw new IllegalArgumentException("链接不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,9 +210,9 @@ public class LinkStatusService {
|
|||||||
try {
|
try {
|
||||||
// 同步调用脚本端退单接口
|
// 同步调用脚本端退单接口
|
||||||
String refundResult = scriptClient.refundOrder(machineId).block();
|
String refundResult = scriptClient.refundOrder(machineId).block();
|
||||||
log.info("脚本端退单接口调用成功: 设备={}, 结果={}", machineId, refundResult);
|
log.info("脚本端退单接口调用成功, 设备={}, 结果={}", machineId, refundResult);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("脚本端退单接口调用失败: 设备={}, 错误={}", machineId, e.getMessage());
|
log.error("脚本端退单接口调用失败, 设备={}, 错误={}", machineId, e.getMessage());
|
||||||
// 即使脚本端调用失败,我们仍然继续更新数据库状态
|
// 即使脚本端调用失败,我们仍然继续更新数据库状态
|
||||||
// 这样可以确保用户能够看到退单状态,避免重复退单
|
// 这样可以确保用户能够看到退单状态,避免重复退单
|
||||||
}
|
}
|
||||||
@@ -224,9 +225,9 @@ public class LinkStatusService {
|
|||||||
linkTask.setRefundAt(LocalDateTime.now());
|
linkTask.setRefundAt(LocalDateTime.now());
|
||||||
linkTask.setUpdatedAt(LocalDateTime.now());
|
linkTask.setUpdatedAt(LocalDateTime.now());
|
||||||
|
|
||||||
int updateResult = linkTaskMapper.update(linkTask);
|
int updateResult = linkTaskMapper.updateById(linkTask);
|
||||||
if (updateResult <= 0) {
|
if (updateResult <= 0) {
|
||||||
log.error("更新链接状态失败: codeNo={}", codeNo);
|
log.error("更新链接状态失败 codeNo={}", codeNo);
|
||||||
throw new RuntimeException("更新链接状态失败");
|
throw new RuntimeException("更新链接状态失败");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,7 +252,7 @@ public class LinkStatusService {
|
|||||||
// 首先检查链接是否存在且属于该用户
|
// 首先检查链接是否存在且属于该用户
|
||||||
LinkTask linkTask = linkTaskMapper.findByCodeNo(codeNo);
|
LinkTask linkTask = linkTaskMapper.findByCodeNo(codeNo);
|
||||||
if (linkTask == null) {
|
if (linkTask == null) {
|
||||||
log.warn("链接不存在: codeNo={}", codeNo);
|
log.warn("链接不存在 codeNo={}", codeNo);
|
||||||
throw new IllegalArgumentException("链接不存在");
|
throw new IllegalArgumentException("链接不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,6 +363,7 @@ public class LinkStatusService {
|
|||||||
}).subscribeOn(Schedulers.boundedElastic());
|
}).subscribeOn(Schedulers.boundedElastic());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量删除链接(确保用户只能删除自己的链接)
|
* 批量删除链接(确保用户只能删除自己的链接)
|
||||||
*/
|
*/
|
||||||
@@ -458,7 +460,7 @@ public class LinkStatusService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (linkTask == null) {
|
if (linkTask == null) {
|
||||||
log.error("链接任务不存在: linkId={}, codeNo={}", linkId, codeNo);
|
log.error("链接任务不存在 linkId={}, codeNo={}", linkId, codeNo);
|
||||||
throw new IllegalArgumentException("链接不存在");
|
throw new IllegalArgumentException("链接不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -486,7 +488,7 @@ public class LinkStatusService {
|
|||||||
log.warn("登录状态检测失败: {}", error.getMessage());
|
log.warn("登录状态检测失败: {}", error.getMessage());
|
||||||
})
|
})
|
||||||
.onErrorResume(error -> {
|
.onErrorResume(error -> {
|
||||||
// 检测失败不影响后续流程,继续返回状态
|
// 检测失败不影响后续流程,继续返回当前状态
|
||||||
log.warn("检测失败,继续返回当前状态");
|
log.warn("检测失败,继续返回当前状态");
|
||||||
return Mono.empty();
|
return Mono.empty();
|
||||||
})
|
})
|
||||||
@@ -515,7 +517,7 @@ public class LinkStatusService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (linkTask == null) {
|
if (linkTask == null) {
|
||||||
log.error("链接任务不存在: linkId={}, codeNo={}", linkId, codeNo);
|
log.error("链接任务不存在 linkId={}, codeNo={}", linkId, codeNo);
|
||||||
throw new IllegalArgumentException("链接不存在");
|
throw new IllegalArgumentException("链接不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -529,11 +531,19 @@ public class LinkStatusService {
|
|||||||
linkTask.setStatus("EXPIRED");
|
linkTask.setStatus("EXPIRED");
|
||||||
linkTask.setExpireAt(now); // 设置过期时间戳
|
linkTask.setExpireAt(now); // 设置过期时间戳
|
||||||
linkTask.setUpdatedAt(now);
|
linkTask.setUpdatedAt(now);
|
||||||
|
// Note: The following line was incomplete/incorrect in the original.
|
||||||
|
// It's commented out as 'region' and 'selectedDeviceId' are not defined in this context.
|
||||||
|
// int affected = linkTaskMapper.reserveDeviceIfFree(linkTask.getId(), region, selectedDeviceId, 60);
|
||||||
|
// if (affected == 0) {
|
||||||
|
// log.warn("原子占用失败:设备已被其他任务占用 device={},codeNo={}", selectedDeviceId, linkTask.getCodeNo());
|
||||||
|
// throw new RuntimeException("设备已被占用,请重试");
|
||||||
|
// }
|
||||||
|
if (linkTask.getFirstRegionSelectAt() == null) {
|
||||||
|
linkTask.setFirstRegionSelectAt(LocalDateTime.now());
|
||||||
|
try {
|
||||||
linkTaskMapper.update(linkTask);
|
linkTaskMapper.update(linkTask);
|
||||||
|
} catch (Exception ignore) {}
|
||||||
UserLinkStatusResponse response = new UserLinkStatusResponse();
|
}
|
||||||
response.setStatus("EXPIRED");
|
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 检查 USING 状态的 10 分钟过期逻辑
|
// 3. 检查 USING 状态的 10 分钟过期逻辑
|
||||||
@@ -553,7 +563,19 @@ public class LinkStatusService {
|
|||||||
linkTask.setStatus("EXPIRED");
|
linkTask.setStatus("EXPIRED");
|
||||||
linkTask.setExpireAt(now); // 设置过期时间戳
|
linkTask.setExpireAt(now); // 设置过期时间戳
|
||||||
linkTask.setUpdatedAt(now);
|
linkTask.setUpdatedAt(now);
|
||||||
|
// Note: The following line was incomplete/incorrect in the original.
|
||||||
|
// It's commented out as 'region' and 'selectedDeviceId' are not defined in this context.
|
||||||
|
// int affected = linkTaskMapper.reserveDeviceIfFree(linkTask.getId(), region, selectedDeviceId, 60);
|
||||||
|
// if (affected == 0) {
|
||||||
|
// log.warn("原子占用失败:设备已被其他任务占用 device={},codeNo={}", selectedDeviceId, linkTask.getCodeNo());
|
||||||
|
// throw new RuntimeException("设备已被占用,请重试");
|
||||||
|
// }
|
||||||
|
if (linkTask.getFirstRegionSelectAt() == null) {
|
||||||
|
linkTask.setFirstRegionSelectAt(LocalDateTime.now());
|
||||||
|
try {
|
||||||
linkTaskMapper.update(linkTask);
|
linkTaskMapper.update(linkTask);
|
||||||
|
} catch (Exception ignore) {}
|
||||||
|
}
|
||||||
|
|
||||||
UserLinkStatusResponse response = new UserLinkStatusResponse();
|
UserLinkStatusResponse response = new UserLinkStatusResponse();
|
||||||
response.setStatus("EXPIRED");
|
response.setStatus("EXPIRED");
|
||||||
@@ -564,7 +586,7 @@ public class LinkStatusService {
|
|||||||
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());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -584,7 +606,6 @@ public class LinkStatusService {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建用户端状态响应
|
* 构建用户端状态响应
|
||||||
*/
|
*/
|
||||||
@@ -620,7 +641,7 @@ public class LinkStatusService {
|
|||||||
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);
|
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);
|
log.info("参数验证通过: code={}, region={}", code.trim(), region);
|
||||||
@@ -649,10 +670,10 @@ public class LinkStatusService {
|
|||||||
// 4. 检查链接状态,只有NEW或USING状态才能选区
|
// 4. 检查链接状态,只有NEW或USING状态才能选区
|
||||||
log.info("步骤4: 开始检查链接状态");
|
log.info("步骤4: 开始检查链接状态");
|
||||||
if (!"NEW".equals(linkTask.getStatus()) && !"USING".equals(linkTask.getStatus())) {
|
if (!"NEW".equals(linkTask.getStatus()) && !"USING".equals(linkTask.getStatus())) {
|
||||||
log.error("链接状态检查失败: 当前状态={}, 只允许NEW或USING状态进行选区操作", linkTask.getStatus());
|
log.error("链接状态检查失败 当前状态{}, 只允许NEW或USING状态进行选区操作", linkTask.getStatus());
|
||||||
throw new IllegalArgumentException("链接状态不正确,只有新建或使用中状态的链接才能选区");
|
throw new IllegalArgumentException("链接状态不正确,只有新建或使用中状态的链接才能选区");
|
||||||
}
|
}
|
||||||
log.info("链接状态检查通过: 当前状态={}, 允许进行选区操作", linkTask.getStatus());
|
log.info("链接状态检查通过: 当前状态{}, 允许进行选区操作", linkTask.getStatus());
|
||||||
|
|
||||||
|
|
||||||
// 5. 如果need_refresh=true,检查是否已等待10秒
|
// 5. 如果need_refresh=true,检查是否已等待10秒
|
||||||
@@ -661,7 +682,7 @@ public class LinkStatusService {
|
|||||||
// long secondsSinceRefresh = ChronoUnit.SECONDS.between(linkTask.getRefreshTime(), now);
|
// long secondsSinceRefresh = ChronoUnit.SECONDS.between(linkTask.getRefreshTime(), now);
|
||||||
// if (secondsSinceRefresh < 10) {
|
// if (secondsSinceRefresh < 10) {
|
||||||
// long waitTime = 10 - secondsSinceRefresh;
|
// long waitTime = 10 - secondsSinceRefresh;
|
||||||
// log.error("刷新后需要等待,剩余等待时间: {}秒", waitTime);
|
// log.error("刷新后需要等待,剩余等待时间: {}秒, waitTime);
|
||||||
// SelectRegionResponse response = new SelectRegionResponse(false, "刷新后需要等待" + waitTime + "秒才能选区");
|
// SelectRegionResponse response = new SelectRegionResponse(false, "刷新后需要等待" + waitTime + "秒才能选区");
|
||||||
// return response;
|
// return response;
|
||||||
// }
|
// }
|
||||||
@@ -676,44 +697,26 @@ public class LinkStatusService {
|
|||||||
|
|
||||||
// 检查是否有空闲设备
|
// 检查是否有空闲设备
|
||||||
if (deviceStatus.getAvailableCount() == 0) {
|
if (deviceStatus.getAvailableCount() == 0) {
|
||||||
log.error("设备分配失败: 当前没有空闲设备可用, 总设备数={}, 空闲设备数={}",
|
log.error("设备分配失败: 当前没有空闲设备可用, 总设备数={}, 空闲设备数{}",
|
||||||
deviceStatus.getTotalDevices(), deviceStatus.getAvailableCount());
|
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());
|
||||||
|
|
||||||
// 过滤掉冷却期内的设备
|
// 使用新的设备分配服务进行原子设备分配
|
||||||
List<String> availableDevices = deviceStatus.getAvailableDevices();
|
List<String> availableDevices = deviceStatus.getAvailableDevices();
|
||||||
List<String> nonCooldownDevices = new ArrayList<>();
|
selectedDeviceId = deviceAllocationService.allocateDevice(availableDevices, linkTask.getId(), "首次选区");
|
||||||
|
|
||||||
for (String deviceId : availableDevices) {
|
if (selectedDeviceId == null) {
|
||||||
if (!machineCooldownService.isMachineInCooldown(deviceId)) {
|
log.error("设备分配失败: 所有空闲设备都在冷却期内或被占用 总空闲设备数={}", availableDevices.size());
|
||||||
nonCooldownDevices.add(deviceId);
|
throw new RuntimeException("所有设备都在冷却期内或被占用,请稍后再试");
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("首次选区设备分配成功: 选中设备={}, 任务ID={}", selectedDeviceId, linkTask.getId());
|
||||||
}else{
|
}else{
|
||||||
long remainingMinutes = machineCooldownService.getRemainingCooldownMinutes(deviceId);
|
log.info("重复选区: 检查首次选区时有效性并复用设备");
|
||||||
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("重复选区: 检查首次选区时效性并复用设备");
|
|
||||||
// 检查首次选区是否已过期
|
// 检查首次选区是否已过期
|
||||||
LocalDateTime firstSelectTime = linkTask.getFirstRegionSelectAt();
|
LocalDateTime firstSelectTime = linkTask.getFirstRegionSelectAt();
|
||||||
long expireSeconds = systemConfigService.getFirstRegionExpireSeconds();
|
long expireSeconds = systemConfigService.getFirstRegionExpireSeconds();
|
||||||
@@ -724,7 +727,7 @@ public class LinkStatusService {
|
|||||||
firstSelectTime, expireTime, now, expireSeconds);
|
firstSelectTime, expireTime, now, expireSeconds);
|
||||||
|
|
||||||
if (now.isAfter(expireTime)) {
|
if (now.isAfter(expireTime)) {
|
||||||
log.error("链接过期检查失败: 首次选区已过期, firstSelectTime={}, expireTime={}, now={}",
|
log.error("链接过期检查失败: 首次选区已过期 firstSelectTime={}, expireTime={}, now={}",
|
||||||
firstSelectTime, expireTime, now);
|
firstSelectTime, expireTime, now);
|
||||||
|
|
||||||
// 将链接状态设置为过期
|
// 将链接状态设置为过期
|
||||||
@@ -738,13 +741,29 @@ public class LinkStatusService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
selectedDeviceId = linkTask.getMachineId();
|
selectedDeviceId = linkTask.getMachineId();
|
||||||
log.info("重复选区设备复用成功: 使用之前分配的设备={}, 首次选区时间={}", selectedDeviceId, firstSelectTime);
|
|
||||||
|
// 检查之前分配的设备是否被其他链接任务占用
|
||||||
|
List<LinkTask> occupiedTasks = linkTaskMapper.findByMachineIdAndStatus(selectedDeviceId, "USING");
|
||||||
|
occupiedTasks.addAll(linkTaskMapper.findByMachineIdAndStatus(selectedDeviceId, "LOGGED_IN"));
|
||||||
|
|
||||||
|
// 过滤掉当前链接任务本身
|
||||||
|
occupiedTasks.removeIf(task -> task.getId().equals(linkTask.getId()));
|
||||||
|
|
||||||
|
if (!occupiedTasks.isEmpty()) {
|
||||||
|
log.error("重复选区设备冲突: 之前分配的设备{}被其他链接任务占用 占用任务数{}", selectedDeviceId, occupiedTasks.size());
|
||||||
|
for (LinkTask occupiedTask : occupiedTasks) {
|
||||||
|
log.error("占用设备{}的链接 codeNo={}, status={}, id={}", selectedDeviceId, occupiedTask.getCodeNo(), occupiedTask.getStatus(), occupiedTask.getId());
|
||||||
|
}
|
||||||
|
throw new RuntimeException("设备被其他链接占用,请重新获取链接");
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("重复选区设备复用成功: 使用之前分配的设备{}, 首次选区时间={}", selectedDeviceId, firstSelectTime);
|
||||||
log.info("执行设备刷新操作: 设备={}", selectedDeviceId);
|
log.info("执行设备刷新操作: 设备={}", selectedDeviceId);
|
||||||
scriptClient.refresh(selectedDeviceId).block();
|
scriptClient.refresh(selectedDeviceId).block();
|
||||||
log.info("设备刷新完成: 设备={}, 继续使用之前分配的设备", selectedDeviceId);
|
log.info("设备刷新完成: 设备={}, 继续使用之前分配的设备", selectedDeviceId);
|
||||||
// 休眠5秒,确保设备状态稳定
|
// 休眠5秒,确保设备状态稳定
|
||||||
Thread.sleep(11000);
|
Thread.sleep(5000);
|
||||||
log.info("等待5秒完成,继续使用之前分配的设备: 设备={}", selectedDeviceId);
|
log.info("等待5秒完成,继续使用之前分配的设备 设备={}", selectedDeviceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("设备选择完成: 最终选中设备={}", selectedDeviceId);
|
log.info("设备选择完成: 最终选中设备={}", selectedDeviceId);
|
||||||
@@ -752,59 +771,68 @@ public class LinkStatusService {
|
|||||||
// 6. 检查该设备是否有之前的LOGGED_IN状态链接任务需要完成
|
// 6. 检查该设备是否有之前的LOGGED_IN状态链接任务需要完成
|
||||||
log.info("步骤6: 开始检查设备状态和历史任务");
|
log.info("步骤6: 开始检查设备状态和历史任务");
|
||||||
try {
|
try {
|
||||||
log.info("检查设备历史任务: 设备={}, 触发原因=选区请求", selectedDeviceId);
|
log.info("检查设备历史任务 设备={}, 触发原因=选区请求", selectedDeviceId);
|
||||||
deviceStatusCheckService.checkDeviceStatusAndUpdateTasks(selectedDeviceId, "选区请求");
|
deviceStatusCheckService.checkDeviceStatusAndUpdateTasks(selectedDeviceId, "选区请求");
|
||||||
log.info("设备历史任务检查完成: 设备={}", selectedDeviceId);
|
log.info("设备历史任务检查完成 设备={}", selectedDeviceId);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("设备历史任务检查异常: 设备={}, 错误={}, 继续选区流程", selectedDeviceId, e.getMessage());
|
log.warn("设备历史任务检查异常 设备={}, 错误={}, 继续选区流程", selectedDeviceId, e.getMessage());
|
||||||
// 不影响选区流程,只记录警告日志
|
// 不影响选区流程,只记录警告日志
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7. 调用保存总次数接口
|
// 7. 调用保存总次数接口
|
||||||
// log.info("步骤7: 开始保存总次数到脚本端");
|
// log.info("步骤7: 开始保存总次数到脚本端");
|
||||||
// try {
|
// try {
|
||||||
// log.info("保存总次数: 设备={}, 次数={}", selectedDeviceId, linkBatch.getTimes());
|
// log.info("保存总次数 设备={}, 次数={}", selectedDeviceId, linkBatch.getTimes());
|
||||||
// scriptClient.saveTotalTimes(selectedDeviceId, linkBatch.getTimes()).block();
|
// scriptClient.saveTotalTimes(selectedDeviceId, linkBatch.getTimes()).block();
|
||||||
// log.info("总次数保存成功: 设备={}, 次数={}", selectedDeviceId, linkBatch.getTimes());
|
// log.info("总次数保存成功 设备={}, 次数={}", selectedDeviceId, linkBatch.getTimes());
|
||||||
// } catch (Exception e) {
|
// } catch (Exception e) {
|
||||||
// log.warn("总次数保存失败: 设备={}, 次数={}, 错误={}, 继续后续流程", selectedDeviceId, linkBatch.getTimes(), e.getMessage());
|
// log.warn("总次数保存失败 设备={}, 次数={}, 错误={}, 继续后续流程", selectedDeviceId, linkBatch.getTimes(), e.getMessage());
|
||||||
// // 不影响后续流程,只记录警告日志
|
// // 不影响后续流程,只记录警告日志
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// 8. 调用脚本端选区,使用选中的设备
|
// 8. 调用脚本端选区,使用选中的设备
|
||||||
log.info("步骤8: 开始调用脚本端选区");
|
log.info("步骤8: 开始调用脚本端选区");
|
||||||
log.info("选区请求参数: 设备={}, 区域={}", selectedDeviceId, region);
|
log.info("选区请求参数: 设备={}, 区域={}", selectedDeviceId, region);
|
||||||
String selectResult = scriptClient.selectRegion(selectedDeviceId, region).block();
|
|
||||||
|
String selectResult;
|
||||||
|
try {
|
||||||
|
selectResult = scriptClient.selectRegion(selectedDeviceId, region).block();
|
||||||
log.info("脚本端选区调用成功: 设备={}, 区域={}, 返回结果={}", selectedDeviceId, region, selectResult);
|
log.info("脚本端选区调用成功: 设备={}, 区域={}, 返回结果={}", selectedDeviceId, region, selectResult);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("脚本端选区调用失败: 设备={}, 区域={}, 错误={}", selectedDeviceId, region, e.getMessage());
|
||||||
|
|
||||||
|
// 释放设备分配(仅在首次选区时)
|
||||||
|
if (linkTask.getFirstRegionSelectAt() == null) {
|
||||||
|
log.info("脚本调用失败,释放设备分配: 设备={}, 任务ID={}", selectedDeviceId, linkTask.getId());
|
||||||
|
deviceAllocationService.releaseDeviceAllocation(selectedDeviceId, linkTask.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RuntimeException("选区操作失败,请重试: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
// 11. 等待脚本端生成二维码(这里可以添加轮询逻辑)
|
// 11. 等待脚本端生成二维码(这里可以添加轮询逻辑)
|
||||||
// log.info("等待脚本端生成二维码,等待3秒...");
|
// log.info("等待脚本端生成二维码,等待3秒..");
|
||||||
// Thread.sleep(3000);
|
// Thread.sleep(3000);
|
||||||
|
|
||||||
// 9. 更新数据库状态为USING,保存设备信息
|
// 9. 原子占用设备并更新任务状态
|
||||||
log.info("步骤9: 开始更新数据库状态");
|
log.info("步骤9: 原子占用设备并更新任务状态");
|
||||||
LocalDateTime now = LocalDateTime.now();
|
LocalDateTime now = LocalDateTime.now();
|
||||||
LocalDateTime qrExpireAt = now.plusSeconds(60); // 60秒后过期
|
LocalDateTime qrExpireAt = now.plusSeconds(60); // 60秒后过期
|
||||||
|
|
||||||
// 记录更新前的状态
|
// 记录更新前的状态
|
||||||
log.info("数据库更新前状态: id={}, status={}, region={}, machineId={}, firstRegionSelectAt={}",
|
log.info("数据库更新前状态 id={}, status={}, region={}, machineId={}, firstRegionSelectAt={}",
|
||||||
linkTask.getId(), linkTask.getStatus(), linkTask.getRegion(), linkTask.getMachineId(), linkTask.getFirstRegionSelectAt());
|
linkTask.getId(), linkTask.getStatus(), linkTask.getRegion(), linkTask.getMachineId(), linkTask.getFirstRegionSelectAt());
|
||||||
|
|
||||||
String prev = linkTask.getStatus();
|
String prev = linkTask.getStatus();
|
||||||
linkTask.setStatus("USING");
|
int affected = linkTaskMapper.reserveDeviceIfFree(linkTask.getId(), region, selectedDeviceId, 60);
|
||||||
linkTask.setRegion(region);
|
if (affected == 0) {
|
||||||
linkTask.setCodeNo(code);
|
log.warn("原子占用失败:设备已被其他任务占用 device={},codeNo={}", selectedDeviceId, linkTask.getCodeNo());
|
||||||
linkTask.setQrCreatedAt(now);
|
throw new RuntimeException("设备已被占用,请重试");
|
||||||
linkTask.setQrExpireAt(qrExpireAt);
|
}
|
||||||
if (linkTask.getFirstRegionSelectAt() == null) {
|
if (linkTask.getFirstRegionSelectAt() == null) {
|
||||||
linkTask.setFirstRegionSelectAt(now); // 只在首次选区时记录
|
linkTask.setFirstRegionSelectAt(now);
|
||||||
log.info("首次选区: 记录首次选区时间={}", now);
|
try { linkTaskMapper.update(linkTask); } catch (Exception ignore) {}
|
||||||
}
|
}
|
||||||
linkTask.setNeedRefresh(false);
|
|
||||||
linkTask.setUpdatedAt(now);
|
|
||||||
linkTask.setMachineId(selectedDeviceId);
|
|
||||||
|
|
||||||
linkTaskMapper.update(linkTask);
|
|
||||||
// Audit and history for USING transition
|
// Audit and history for USING transition
|
||||||
try {
|
try {
|
||||||
com.gameplatform.server.util.AuditLogger.info("StatusTransition: codeNo={}, device={}, {}->USING, source=REGION_SELECT", linkTask.getCodeNo(), selectedDeviceId, prev);
|
com.gameplatform.server.util.AuditLogger.info("StatusTransition: codeNo={}, device={}, {}->USING, source=REGION_SELECT", linkTask.getCodeNo(), selectedDeviceId, prev);
|
||||||
@@ -814,9 +842,30 @@ public class LinkStatusService {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
} catch (Exception ignore) {}
|
} catch (Exception ignore) {}
|
||||||
log.info("数据库更新成功: id={}, status=USING, region={}, machineId={}, qrCreatedAt={}, qrExpireAt={}, firstRegionSelectAt={}",
|
log.info("数据库更新成功 id={}, status=USING, region={}, machineId={}, qrCreatedAt={}, qrExpireAt={}, firstRegionSelectAt={}",
|
||||||
linkTask.getId(), region, selectedDeviceId, now, qrExpireAt, linkTask.getFirstRegionSelectAt());
|
linkTask.getId(), region, selectedDeviceId, now, qrExpireAt, linkTask.getFirstRegionSelectAt());
|
||||||
|
|
||||||
|
// 9.5. 验证设备分配是否存在冲突
|
||||||
|
log.info("步骤9.5: 验证设备分配结果");
|
||||||
|
if (!deviceAllocationService.validateDeviceAllocation(selectedDeviceId, linkTask.getId())) {
|
||||||
|
log.error("设备分配冲突检测失败,准备回滚操作 设备={}, 任务ID={}", selectedDeviceId, linkTask.getId());
|
||||||
|
|
||||||
|
// 回滚操作:释放设备分配和还原任务状态
|
||||||
|
try {
|
||||||
|
deviceAllocationService.releaseDeviceAllocation(selectedDeviceId, linkTask.getId());
|
||||||
|
|
||||||
|
// 将任务状态回滚到NEW
|
||||||
|
linkTaskMapper.updateStatus(linkTask.getId(), "NEW");
|
||||||
|
log.info("设备分配冲突回滚完成 设备={}, 任务ID={}", selectedDeviceId, linkTask.getId());
|
||||||
|
} catch (Exception rollbackEx) {
|
||||||
|
log.error("设备分配回滚失败 设备={}, 任务ID={}, 错误: {}",
|
||||||
|
selectedDeviceId, linkTask.getId(), rollbackEx.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RuntimeException("设备分配冲突,请重试");
|
||||||
|
}
|
||||||
|
log.info("设备分配验证通过 设备={}, 任务ID={}", selectedDeviceId, linkTask.getId());
|
||||||
|
|
||||||
// 10. 构建响应
|
// 10. 构建响应
|
||||||
log.info("步骤10: 开始构建响应数据");
|
log.info("步骤10: 开始构建响应数据");
|
||||||
SelectRegionResponse response = new SelectRegionResponse(true, "选区成功");
|
SelectRegionResponse response = new SelectRegionResponse(true, "选区成功");
|
||||||
@@ -827,7 +876,6 @@ public class LinkStatusService {
|
|||||||
response.setStatus("USING");
|
response.setStatus("USING");
|
||||||
response.setQrDelaySeconds(10); // 客户端收到响应后,等待5秒再请求二维码
|
response.setQrDelaySeconds(10); // 客户端收到响应后,等待5秒再请求二维码
|
||||||
response.setMecmachineId(selectedDeviceId); // 便于调试和维护
|
response.setMecmachineId(selectedDeviceId); // 便于调试和维护
|
||||||
|
|
||||||
log.info("响应构建完成: success=true, status=USING, qrCodeUrl={}, qrDelaySeconds=5, machineId={}",
|
log.info("响应构建完成: success=true, status=USING, qrCodeUrl={}, qrDelaySeconds=5, machineId={}",
|
||||||
qrCodeUrl, selectedDeviceId);
|
qrCodeUrl, selectedDeviceId);
|
||||||
log.info("=== 选区操作完成 ===");
|
log.info("=== 选区操作完成 ===");
|
||||||
@@ -886,7 +934,7 @@ public class LinkStatusService {
|
|||||||
|
|
||||||
// 检查链接状态,只有USING状态才能轮询
|
// 检查链接状态,只有USING状态才能轮询
|
||||||
if (!"USING".equals(linkTask.getStatus())) {
|
if (!"USING".equals(linkTask.getStatus())) {
|
||||||
log.warn("链接状态不是USING,当前状态: {}", linkTask.getStatus());
|
log.warn("链接状态不是USING,当前状态 {}", linkTask.getStatus());
|
||||||
return new PollLoginResponse(false, linkTask.getStatus());
|
return new PollLoginResponse(false, linkTask.getStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -918,12 +966,12 @@ public class LinkStatusService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 调用脚本端检查上号状态
|
// 调用脚本端检查上号状态
|
||||||
log.info("调用脚本端检查上号状态: 代理code={}, 真实设备={}", linkTask.getCodeNo(), realDeviceId);
|
log.info("调用脚本端检查上号状态 代理code={}, 真实设备={}", linkTask.getCodeNo(), realDeviceId);
|
||||||
|
|
||||||
return scriptClient.checkLoginStatus(realDeviceId)
|
return scriptClient.checkLoginStatus(realDeviceId)
|
||||||
.map(loginResult -> processLoginResult(linkTask, realDeviceId, loginResult))
|
.map(loginResult -> processLoginResult(linkTask, realDeviceId, loginResult))
|
||||||
.onErrorResume(error -> {
|
.onErrorResume(error -> {
|
||||||
log.warn("调用脚本端检查上号状态失败: codeNo={}, error={}",
|
log.warn("调用脚本端检查上号状态失败 codeNo={}, error={}",
|
||||||
linkTask.getCodeNo(), error.getMessage());
|
linkTask.getCodeNo(), error.getMessage());
|
||||||
// 脚本端出错时,返回当前状态,不影响轮询
|
// 脚本端出错时,返回当前状态,不影响轮询
|
||||||
return Mono.just(new PollLoginResponse(false, "USING"));
|
return Mono.just(new PollLoginResponse(false, "USING"));
|
||||||
@@ -934,7 +982,7 @@ public class LinkStatusService {
|
|||||||
* 处理登录检查结果
|
* 处理登录检查结果
|
||||||
*/
|
*/
|
||||||
private PollLoginResponse processLoginResult(LinkTask linkTask, String deviceId, String loginResult) {
|
private PollLoginResponse processLoginResult(LinkTask linkTask, String deviceId, String loginResult) {
|
||||||
log.info("脚本端返回结果: {}", loginResult);
|
log.info("脚本端返回结果 {}", loginResult);
|
||||||
|
|
||||||
// 检查是否已上号
|
// 检查是否已上号
|
||||||
if ("已上号".equals(loginResult) || "已登录".equals(loginResult)) {
|
if ("已上号".equals(loginResult) || "已登录".equals(loginResult)) {
|
||||||
@@ -947,7 +995,7 @@ public class LinkStatusService {
|
|||||||
log.info("=============================================");
|
log.info("=============================================");
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("保存总次数接口调用失败: {}", e.getMessage());
|
log.warn("保存总次数接口调用失败 {}", e.getMessage());
|
||||||
// 不影响后续流程,只记录警告日志
|
// 不影响后续流程,只记录警告日志
|
||||||
}
|
}
|
||||||
return handleSuccessfulLogin(linkTask, deviceId);
|
return handleSuccessfulLogin(linkTask, deviceId);
|
||||||
@@ -992,7 +1040,7 @@ public class LinkStatusService {
|
|||||||
log.warn("记录设备登录时间失败", e);
|
log.warn("记录设备登录时间失败", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("状态更新完成: codeNo={}, status=LOGGED_IN", linkTask.getCodeNo());
|
log.info("状态更新完成 codeNo={}, status=LOGGED_IN", linkTask.getCodeNo());
|
||||||
|
|
||||||
// 构建成功响应
|
// 构建成功响应
|
||||||
PollLoginResponse response = new PollLoginResponse(true, "LOGGED_IN", "SECOND",
|
PollLoginResponse response = new PollLoginResponse(true, "LOGGED_IN", "SECOND",
|
||||||
@@ -1021,7 +1069,7 @@ public class LinkStatusService {
|
|||||||
// 查询链接任务
|
// 查询链接任务
|
||||||
LinkTask linkTask = linkTaskMapper.findByCodeNo(code.trim());
|
LinkTask linkTask = linkTaskMapper.findByCodeNo(code.trim());
|
||||||
if (linkTask == null) {
|
if (linkTask == null) {
|
||||||
log.warn("链接任务不存在: code={}", code);
|
log.warn("链接任务不存在 code={}", code);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1031,7 +1079,7 @@ public class LinkStatusService {
|
|||||||
return machineId;
|
return machineId;
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("根据code获取设备ID时发生异常: code={}, error={}", code, e.getMessage(), e);
|
log.error("根据code获取设备ID时发生异常 code={}, error={}", code, e.getMessage(), e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1060,27 +1108,27 @@ public class LinkStatusService {
|
|||||||
// 2. 查询链接任务,获取machineId
|
// 2. 查询链接任务,获取machineId
|
||||||
LinkTask linkTask = linkTaskMapper.findByCodeNo(codeNo.trim());
|
LinkTask linkTask = linkTaskMapper.findByCodeNo(codeNo.trim());
|
||||||
if (linkTask == null) {
|
if (linkTask == null) {
|
||||||
log.error("链接任务不存在: codeNo={}", codeNo);
|
log.error("链接任务不存在 codeNo={}", codeNo);
|
||||||
return TargetScoreResponse.error(codeNo, null, "链接不存在");
|
return TargetScoreResponse.error(codeNo, null, "链接不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
String machineId = linkTask.getMachineId();
|
String machineId = linkTask.getMachineId();
|
||||||
if (machineId == null || machineId.trim().isEmpty()) {
|
if (machineId == null || machineId.trim().isEmpty()) {
|
||||||
log.error("链接未关联设备: codeNo={}", codeNo);
|
log.error("链接未关联设备 codeNo={}", codeNo);
|
||||||
return TargetScoreResponse.error(codeNo, null, "链接未关联设备");
|
return TargetScoreResponse.error(codeNo, null, "链接未关联设备");
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("查询到设备ID: codeNo={}, machineId={}", codeNo, machineId);
|
log.info("查询到设备ID: codeNo={}, machineId={}", codeNo, machineId);
|
||||||
|
|
||||||
// 3. 调用脚本端获取目标分数
|
// 3. 调用脚本端获取目标分数
|
||||||
log.info("调用脚本端获取目标分数: machineId={}", machineId);
|
log.info("调用脚本端获取目标分数 machineId={}", machineId);
|
||||||
String targetScoreStr = scriptClient.getTargetScore(machineId).block();
|
String targetScoreStr = scriptClient.getTargetScore(machineId).block();
|
||||||
log.info("脚本端返回结果: machineId={}, result={}", machineId, targetScoreStr);
|
log.info("脚本端返回结果 machineId={}, result={}", machineId, targetScoreStr);
|
||||||
|
|
||||||
// 4. 解析返回结果
|
// 4. 解析返回结果
|
||||||
if (targetScoreStr == null || targetScoreStr.trim().isEmpty()) {
|
if (targetScoreStr == null || targetScoreStr.trim().isEmpty()) {
|
||||||
log.warn("脚本端返回空结果: machineId={}", machineId);
|
log.warn("脚本端返回空结果: machineId={}", machineId);
|
||||||
return TargetScoreResponse.error(codeNo, machineId, "网络繁忙,稍后再试");
|
return TargetScoreResponse.error(codeNo, machineId, "网络拥堵,请稍后再试");
|
||||||
}
|
}
|
||||||
|
|
||||||
String trimmedResult = targetScoreStr.trim();
|
String trimmedResult = targetScoreStr.trim();
|
||||||
@@ -1088,12 +1136,12 @@ public class LinkStatusService {
|
|||||||
// 检查是否为数字
|
// 检查是否为数字
|
||||||
try {
|
try {
|
||||||
Integer targetScore = Integer.parseInt(trimmedResult);
|
Integer targetScore = Integer.parseInt(trimmedResult);
|
||||||
log.info("解析到数字目标分数: {}", targetScore);
|
log.info("解析到数字目标分数 {}", targetScore);
|
||||||
|
|
||||||
// 5. 保存到数据库
|
// 5. 保存到数据库
|
||||||
linkTask.setCompletedPoints(targetScore);
|
linkTask.setCompletedPoints(targetScore);
|
||||||
linkTask.setUpdatedAt(LocalDateTime.now());
|
linkTask.setUpdatedAt(LocalDateTime.now());
|
||||||
int updateResult = linkTaskMapper.update(linkTask);
|
int updateResult = linkTaskMapper.updateById(linkTask); // Fixed the assignment here
|
||||||
|
|
||||||
if (updateResult > 0) {
|
if (updateResult > 0) {
|
||||||
log.info("目标分数保存成功: codeNo={}, machineId={}, targetScore={}",
|
log.info("目标分数保存成功: codeNo={}, machineId={}, targetScore={}",
|
||||||
@@ -1111,10 +1159,10 @@ public class LinkStatusService {
|
|||||||
|
|
||||||
if ("空的".equals(trimmedResult) || "空闲".equals(trimmedResult) || "已运行".equals(trimmedResult)) {
|
if ("空的".equals(trimmedResult) || "空闲".equals(trimmedResult) || "已运行".equals(trimmedResult)) {
|
||||||
log.info("设备状态为: {}", trimmedResult);
|
log.info("设备状态为: {}", trimmedResult);
|
||||||
return TargetScoreResponse.error(codeNo, machineId, "网络繁忙,稍后再试");
|
return TargetScoreResponse.error(codeNo, machineId, "网络拥堵,请稍后再试");
|
||||||
} else {
|
} else {
|
||||||
log.warn("未知的返回结果: {}", trimmedResult);
|
log.warn("未知的返回结果 {}", trimmedResult);
|
||||||
return TargetScoreResponse.error(codeNo, machineId, "网络繁忙,稍后再试");
|
return TargetScoreResponse.error(codeNo, machineId, "网络拥堵,请稍后再试");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1125,3 +1173,6 @@ public class LinkStatusService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.gameplatform.server.task;
|
package com.gameplatform.server.task;
|
||||||
|
|
||||||
import com.gameplatform.server.service.cooldown.MachineCooldownService;
|
import com.gameplatform.server.service.cooldown.MemoryMachineCooldownService;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
@@ -14,9 +14,9 @@ import org.springframework.stereotype.Component;
|
|||||||
public class MachineCooldownCleanupTask {
|
public class MachineCooldownCleanupTask {
|
||||||
private static final Logger log = LoggerFactory.getLogger(MachineCooldownCleanupTask.class);
|
private static final Logger log = LoggerFactory.getLogger(MachineCooldownCleanupTask.class);
|
||||||
|
|
||||||
private final MachineCooldownService machineCooldownService;
|
private final MemoryMachineCooldownService machineCooldownService;
|
||||||
|
|
||||||
public MachineCooldownCleanupTask(MachineCooldownService machineCooldownService) {
|
public MachineCooldownCleanupTask(MemoryMachineCooldownService machineCooldownService) {
|
||||||
this.machineCooldownService = machineCooldownService;
|
this.machineCooldownService = machineCooldownService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ mybatis-plus:
|
|||||||
type-aliases-package: com.gameplatform.server.model.entity
|
type-aliases-package: com.gameplatform.server.model.entity
|
||||||
configuration:
|
configuration:
|
||||||
map-underscore-to-camel-case: true
|
map-underscore-to-camel-case: true
|
||||||
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 注释掉以关闭SQL日志
|
||||||
global-config:
|
global-config:
|
||||||
db-config:
|
db-config:
|
||||||
id-type: auto
|
id-type: auto
|
||||||
@@ -37,10 +37,28 @@ management:
|
|||||||
logging:
|
logging:
|
||||||
level:
|
level:
|
||||||
root: info
|
root: info
|
||||||
com.gameplatform.server: info
|
com.gameplatform.server: debug # 保持整体调试
|
||||||
com.baomidou.mybatisplus: info
|
# 仅保留设备解析最终汇总(INFO),其余降级
|
||||||
org.apache.ibatis: info
|
com.gameplatform.server.service.device.DeviceStatusService: info
|
||||||
com.zaxxer.hikari: info
|
com.gameplatform.server.service.device.DeviceStatusCheckService: info
|
||||||
|
# 脚本客户端与定时任务降噪
|
||||||
|
com.gameplatform.server.service.external.ScriptClient: warn
|
||||||
|
com.gameplatform.server.task.DeviceStatusCheckTask: warn
|
||||||
|
com.gameplatform.server.task.UsingLinkCheckTask: warn
|
||||||
|
# 完成检测服务降噪(屏蔽debug“置信度低”之类日志)
|
||||||
|
com.gameplatform.server.service.detection.GameCompletionDetectionService: warn
|
||||||
|
# 设备任务更新服务:只保留警告/错误(不输出“开始处理设备/点数已更新为”等调试信息)
|
||||||
|
com.gameplatform.server.service.link.DeviceTaskUpdateService: warn
|
||||||
|
# Mapper 与 SQL 调用降噪(屏蔽 MyBatis 的参数/SQL DEBUG)
|
||||||
|
com.gameplatform.server.mapper: warn
|
||||||
|
com.baomidou.mybatisplus: warn
|
||||||
|
org.apache.ibatis: warn
|
||||||
|
org.mybatis: warn
|
||||||
|
com.zaxxer.hikari: warn
|
||||||
|
pattern:
|
||||||
|
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
|
||||||
|
console:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
security:
|
security:
|
||||||
jwt:
|
jwt:
|
||||||
|
|||||||
@@ -1,7 +1,22 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<configuration scan="true">
|
<configuration scan="true">
|
||||||
|
<!-- Include Spring Boot defaults -->
|
||||||
|
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
|
||||||
|
|
||||||
|
<!-- Explicit console appender with UTF-8 to avoid garbled output on Windows -->
|
||||||
|
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
|
||||||
|
<charset>UTF-8</charset>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
<property name="LOG_PATH" value="logs"/>
|
<property name="LOG_PATH" value="logs"/>
|
||||||
|
|
||||||
|
<!-- Optional: include file appender if you also want general file logs -->
|
||||||
|
<!-- <include resource="org/springframework/boot/logging/logback/file-appender.xml"/> -->
|
||||||
|
|
||||||
|
<!-- Audit appender for status transitions -->
|
||||||
<appender name="AUDIT-FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
<appender name="AUDIT-FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
<file>${LOG_PATH}/audit-status.log</file>
|
<file>${LOG_PATH}/audit-status.log</file>
|
||||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
@@ -23,6 +38,26 @@
|
|||||||
<appender-ref ref="AUDIT-FILE"/>
|
<appender-ref ref="AUDIT-FILE"/>
|
||||||
</logger>
|
</logger>
|
||||||
|
|
||||||
<!-- Let Spring Boot default console/file config handle others -->
|
<!-- General rolling file appender to mirror console output -->
|
||||||
</configuration>
|
<appender name="GENERAL-FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${LOG_PATH}/server.log</file>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>${LOG_PATH}/server.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
|
||||||
|
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||||
|
<maxFileSize>20MB</maxFileSize>
|
||||||
|
</timeBasedFileNamingAndTriggeringPolicy>
|
||||||
|
<maxHistory>30</maxHistory>
|
||||||
|
<totalSizeCap>2GB</totalSizeCap>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
|
||||||
|
<charset>UTF-8</charset>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- Root logger: send to console and general file -->
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="CONSOLE"/>
|
||||||
|
<appender-ref ref="GENERAL-FILE"/>
|
||||||
|
</root>
|
||||||
|
</configuration>
|
||||||
|
|||||||
@@ -255,4 +255,25 @@
|
|||||||
WHERE status = #{status}
|
WHERE status = #{status}
|
||||||
ORDER BY created_at ASC
|
ORDER BY created_at ASC
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<!-- 原子占用设备,避免并发下同一设备被多个链接占用 -->
|
||||||
|
<update id="reserveDeviceIfFree">
|
||||||
|
UPDATE link_task lt
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT 1 as has_conflict
|
||||||
|
FROM link_task x
|
||||||
|
WHERE x.machine_id = #{deviceId}
|
||||||
|
AND x.status IN ('USING','LOGGED_IN')
|
||||||
|
AND x.id <> #{id}
|
||||||
|
) conflict_check ON 1=1
|
||||||
|
SET lt.status = 'USING',
|
||||||
|
lt.region = #{region},
|
||||||
|
lt.machine_id = #{deviceId},
|
||||||
|
lt.qr_created_at = NOW(),
|
||||||
|
lt.qr_expire_at = DATE_ADD(NOW(), INTERVAL #{qrExpireSeconds} SECOND),
|
||||||
|
lt.updated_at = NOW()
|
||||||
|
WHERE lt.id = #{id}
|
||||||
|
AND conflict_check.has_conflict IS NULL
|
||||||
|
</update>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
@@ -44,10 +44,18 @@
|
|||||||
|
|
||||||
<!-- 批量更新过期的冷却记录状态 -->
|
<!-- 批量更新过期的冷却记录状态 -->
|
||||||
<update id="batchUpdateExpiredCooldowns">
|
<update id="batchUpdateExpiredCooldowns">
|
||||||
UPDATE machine_cooldown
|
UPDATE machine_cooldown mc
|
||||||
SET status = 'EXPIRED', updated_at = NOW()
|
LEFT JOIN (
|
||||||
WHERE status = 'ACTIVE'
|
SELECT machine_id FROM (
|
||||||
AND cooldown_end_time <= #{currentTime}
|
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>
|
||||||
|
|
||||||
<!-- 手动移除设备的冷却状态 -->
|
<!-- 手动移除设备的冷却状态 -->
|
||||||
@@ -85,6 +93,19 @@
|
|||||||
AND updated_at < #{beforeTime}
|
AND updated_at < #{beforeTime}
|
||||||
</delete>
|
</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 id="getCooldownHistory" resultMap="MachineCooldownMap">
|
||||||
SELECT * FROM machine_cooldown
|
SELECT * FROM machine_cooldown
|
||||||
@@ -94,3 +115,4 @@
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user