feat: 优化设备冷却管理,增加原子设备占用逻辑和过期记录处理

This commit is contained in:
zyh
2025-09-13 10:46:52 +08:00
parent 86140b1294
commit 40479fa38e
16 changed files with 819 additions and 643 deletions

View File

@@ -303,3 +303,4 @@ WHERE created_at < DATE_SUB(NOW(), INTERVAL 30 DAY);
4. 说明当前系统状态
这样可以更快速地定位和解决问题。

View File

@@ -96,3 +96,4 @@ CREATE TABLE `system_monitor` (
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统监控表' ROW_FORMAT = DYNAMIC;
SET FOREIGN_KEY_CHECKS = 1;

View File

@@ -1,6 +1,8 @@
package com.gameplatform.server;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@@ -9,8 +11,13 @@ import org.springframework.scheduling.annotation.EnableScheduling;
@MapperScan("com.gameplatform.server.mapper")
@EnableScheduling
public class GamePlatformServerApplication {
private static final Logger log = LoggerFactory.getLogger(GamePlatformServerApplication.class);
public static void main(String[] args) {
log.info("=== 游戏平台服务器启动中 ===");
log.debug("Debug 日志级别已启用");
SpringApplication.run(GamePlatformServerApplication.class, args);
log.info("=== 游戏平台服务器启动完成 ===");
}
}

View File

@@ -29,7 +29,7 @@ public class AuthController {
@ResponseStatus(HttpStatus.OK)
public Mono<LoginResponse> login(@Valid @RequestBody LoginRequest req) {
// Avoid logging raw usernames at info level
log.debug("/api/auth/login called");
log.info("/api/auth/login called");
return authService.login(req);
}

View File

@@ -15,8 +15,8 @@ import com.gameplatform.server.model.dto.link.UserLinkStatusResponse;
import com.gameplatform.server.model.dto.link.TargetScoreResponse;
import com.gameplatform.server.service.link.LinkGenerationService;
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.link.LinkStatusService;
import io.jsonwebtoken.Claims;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;

View File

@@ -115,4 +115,14 @@ public interface LinkTaskMapper extends BaseMapper<LinkTask> {
* 根据状态查询所有链接任务
*/
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);
}

View File

@@ -61,6 +61,12 @@ public interface MachineCooldownMapper extends BaseMapper<MachineCooldown> {
* 清理指定时间之前的已过期冷却记录
*/
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("offset") int offset);
}

View File

@@ -182,3 +182,4 @@ public class MachineCooldown {
'}';
}
}

View File

@@ -197,3 +197,4 @@ public class GameCompletionLog {
'}';
}
}

View File

@@ -3,7 +3,7 @@ package com.gameplatform.server.service.detection;
import com.gameplatform.server.mapper.agent.LinkTaskMapper;
import com.gameplatform.server.model.entity.agent.LinkTask;
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.history.LinkTaskStatusHistoryMapper;
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 final LinkTaskMapper linkTaskMapper;
private final MachineCooldownService machineCooldownService;
private final MemoryMachineCooldownService machineCooldownService;
private final GameCompletionLogMapper gameCompletionLogMapper;
private final LinkTaskStatusHistoryMapper statusHistoryMapper;
@@ -48,7 +48,7 @@ public class GameCompletionDetectionService {
private final ConcurrentMap<String, LocalDateTime> recentLogins = new ConcurrentHashMap<>();
public GameCompletionDetectionService(LinkTaskMapper linkTaskMapper,
MachineCooldownService machineCooldownService,
MemoryMachineCooldownService machineCooldownService,
GameCompletionLogMapper gameCompletionLogMapper,
LinkTaskStatusHistoryMapper statusHistoryMapper) {
this.linkTaskMapper = linkTaskMapper;
@@ -297,3 +297,4 @@ public class GameCompletionDetectionService {
LOW // 低置信度:不可信的状态变化
}
}

View File

@@ -1,6 +1,6 @@
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.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
@@ -14,9 +14,9 @@ import org.springframework.stereotype.Component;
public class MachineCooldownCleanupTask {
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;
}

View File

@@ -17,7 +17,7 @@ mybatis-plus:
type-aliases-package: com.gameplatform.server.model.entity
configuration:
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:
db-config:
id-type: auto
@@ -37,10 +37,28 @@ management:
logging:
level:
root: info
com.gameplatform.server: info
com.baomidou.mybatisplus: info
org.apache.ibatis: info
com.zaxxer.hikari: info
com.gameplatform.server: debug # 保持整体调试
# 仅保留设备解析最终汇总INFO其余降级
com.gameplatform.server.service.device.DeviceStatusService: 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:
jwt:

View File

@@ -1,7 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<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"/>
<!-- 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">
<file>${LOG_PATH}/audit-status.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
@@ -23,6 +38,26 @@
<appender-ref ref="AUDIT-FILE"/>
</logger>
<!-- Let Spring Boot default console/file config handle others -->
</configuration>
<!-- General rolling file appender to mirror console output -->
<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>

View File

@@ -255,4 +255,25 @@
WHERE status = #{status}
ORDER BY created_at ASC
</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 &lt;&gt; #{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>

View File

@@ -44,10 +44,18 @@
<!-- 批量更新过期的冷却记录状态 -->
<update id="batchUpdateExpiredCooldowns">
UPDATE machine_cooldown
SET status = 'EXPIRED', updated_at = NOW()
WHERE status = 'ACTIVE'
AND cooldown_end_time &lt;= #{currentTime}
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 &lt;= #{currentTime}
AND ex.machine_id IS NULL
</update>
<!-- 手动移除设备的冷却状态 -->
@@ -84,6 +92,19 @@
WHERE status = 'EXPIRED'
AND updated_at &lt; #{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 &lt;= #{currentTime}
) t ON t.machine_id = mc.machine_id
WHERE mc.status = 'EXPIRED'
</delete>
<!-- 获取指定设备的冷却历史记录 -->
<select id="getCooldownHistory" resultMap="MachineCooldownMap">
@@ -94,3 +115,4 @@
</select>
</mapper>