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 // 低置信度:不可信的状态变化
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -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