feat: 改进设备任务更新服务,集成游戏完成检测,优化状态处理逻辑
This commit is contained in:
305
OPTIMIZATION_IMPLEMENTATION_GUIDE.md
Normal file
305
OPTIMIZATION_IMPLEMENTATION_GUIDE.md
Normal file
@@ -0,0 +1,305 @@
|
||||
# 游戏平台系统优化实施指南
|
||||
|
||||
## 📋 概述
|
||||
|
||||
本指南详细说明了针对以下三个问题的系统优化方案:
|
||||
|
||||
1. **设备10分钟内重复调用问题**
|
||||
2. **用户刚扫码就显示已打完问题**
|
||||
3. **同一编号出现两条链接问题**
|
||||
|
||||
## 🗄️ 数据库变更
|
||||
|
||||
### 1. 执行数据库优化脚本
|
||||
|
||||
```bash
|
||||
# 执行数据库优化脚本
|
||||
mysql -u your_username -p your_database < database_improvements.sql
|
||||
```
|
||||
|
||||
关键变更:
|
||||
- ✅ `link_task` 表已有 `UNIQUE INDEX uk_code_no` (解决问题3)
|
||||
- ➕ 新增 `machine_cooldown` 表(解决问题1)
|
||||
- ➕ 新增 `game_completion_log` 表(解决问题2)
|
||||
- ➕ 新增相关索引优化
|
||||
|
||||
### 2. 验证数据库变更
|
||||
|
||||
```sql
|
||||
-- 检查唯一索引
|
||||
SHOW INDEX FROM link_task WHERE Key_name = 'uk_code_no';
|
||||
|
||||
-- 检查新表是否创建成功
|
||||
SHOW TABLES LIKE '%cooldown%';
|
||||
SHOW TABLES LIKE '%completion%';
|
||||
|
||||
-- 检查冷却表结构
|
||||
DESC machine_cooldown;
|
||||
```
|
||||
|
||||
## 🔧 代码部署
|
||||
|
||||
### 1. 新增文件列表
|
||||
|
||||
需要添加以下新文件到项目中:
|
||||
|
||||
```
|
||||
src/main/java/com/gameplatform/server/model/entity/cooldown/
|
||||
├── MachineCooldown.java
|
||||
|
||||
src/main/java/com/gameplatform/server/model/entity/detection/
|
||||
├── GameCompletionLog.java
|
||||
|
||||
src/main/java/com/gameplatform/server/mapper/cooldown/
|
||||
├── MachineCooldownMapper.java
|
||||
|
||||
src/main/java/com/gameplatform/server/service/detection/
|
||||
├── GameCompletionDetectionService.java
|
||||
|
||||
src/main/resources/mapper/cooldown/
|
||||
├── MachineCooldownMapper.xml
|
||||
```
|
||||
|
||||
### 2. 修改现有文件
|
||||
|
||||
以下文件已被优化,需要更新:
|
||||
|
||||
- `src/main/java/com/gameplatform/server/service/cooldown/MachineCooldownService.java`
|
||||
- `src/main/java/com/gameplatform/server/service/link/LinkGenerationService.java`
|
||||
- `src/main/java/com/gameplatform/server/service/link/DeviceTaskUpdateService.java`
|
||||
- `src/main/java/com/gameplatform/server/service/link/LinkStatusService.java`
|
||||
|
||||
## ⚙️ 配置调整
|
||||
|
||||
### 1. 应用配置
|
||||
|
||||
在 `application.yml` 中添加以下配置:
|
||||
|
||||
```yaml
|
||||
# 游戏完成检测配置
|
||||
game:
|
||||
completion:
|
||||
detection:
|
||||
login-buffer-seconds: 30 # 登录后缓冲时间(秒)
|
||||
confirmation-interval: 10 # 完成确认间隔(秒)
|
||||
confidence-threshold: MEDIUM # 置信度阈值
|
||||
|
||||
# 设备冷却配置
|
||||
machine:
|
||||
cooldown:
|
||||
duration-minutes: 10 # 冷却时间(分钟)
|
||||
cleanup-interval: 30 # 清理间隔(分钟)
|
||||
cache-enabled: true # 启用内存缓存
|
||||
|
||||
# 编号生成配置
|
||||
code:
|
||||
generation:
|
||||
max-retry-attempts: 5 # 最大重试次数
|
||||
use-timestamp-fallback: true # 启用时间戳后备策略
|
||||
```
|
||||
|
||||
### 2. 日志配置
|
||||
|
||||
在 `logback-spring.xml` 中增加以下日志配置:
|
||||
|
||||
```xml
|
||||
<!-- 冷却服务日志 -->
|
||||
<logger name="com.gameplatform.server.service.cooldown" level="INFO" />
|
||||
|
||||
<!-- 完成检测服务日志 -->
|
||||
<logger name="com.gameplatform.server.service.detection" level="INFO" />
|
||||
|
||||
<!-- 编号生成日志 -->
|
||||
<logger name="com.gameplatform.server.service.link.LinkGenerationService" level="DEBUG" />
|
||||
```
|
||||
|
||||
## 🔍 功能验证
|
||||
|
||||
### 1. 验证冷却机制
|
||||
|
||||
```bash
|
||||
# 测试冷却功能
|
||||
curl -X POST "http://localhost:8080/api/test/cooldown" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"machineId": "test001", "reason": "测试冷却"}'
|
||||
|
||||
# 检查冷却状态
|
||||
curl "http://localhost:8080/api/test/cooldown/test001"
|
||||
```
|
||||
|
||||
### 2. 验证编号唯一性
|
||||
|
||||
```sql
|
||||
-- 检查是否有重复编号
|
||||
SELECT code_no, COUNT(*) as count
|
||||
FROM link_task
|
||||
GROUP BY code_no
|
||||
HAVING count > 1;
|
||||
```
|
||||
|
||||
### 3. 验证完成检测
|
||||
|
||||
监控日志中的以下关键信息:
|
||||
- 登录缓冲期保护日志
|
||||
- 完成检测置信度日志
|
||||
- 状态变更确认日志
|
||||
|
||||
## 📊 监控指标
|
||||
|
||||
### 1. 数据库监控
|
||||
|
||||
```sql
|
||||
-- 监控冷却记录数量
|
||||
SELECT
|
||||
status,
|
||||
COUNT(*) as count,
|
||||
MIN(cooldown_end_time) as earliest_end,
|
||||
MAX(cooldown_end_time) as latest_end
|
||||
FROM machine_cooldown
|
||||
GROUP BY status;
|
||||
|
||||
-- 监控完成检测日志
|
||||
SELECT
|
||||
detection_source,
|
||||
completion_confidence,
|
||||
COUNT(*) as count,
|
||||
AVG(is_confirmed) as confirmation_rate
|
||||
FROM game_completion_log
|
||||
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 1 DAY)
|
||||
GROUP BY detection_source, completion_confidence;
|
||||
```
|
||||
|
||||
### 2. 应用监控
|
||||
|
||||
关键指标:
|
||||
- 冷却队列大小:`machineCooldownService.getCooldownQueueSize()`
|
||||
- 待确认检测数量:`completionDetectionService.getPendingCount()`
|
||||
- 编号生成重试率:监控日志中的重试次数
|
||||
|
||||
## 🚨 故障排除
|
||||
|
||||
### 问题1:冷却机制不生效
|
||||
|
||||
**症状**:设备仍在10分钟内重复调用
|
||||
|
||||
**排查步骤**:
|
||||
1. 检查数据库连接和表结构
|
||||
2. 查看冷却服务日志
|
||||
3. 验证缓存和数据库同步
|
||||
|
||||
```sql
|
||||
-- 检查冷却记录
|
||||
SELECT * FROM machine_cooldown
|
||||
WHERE machine_id = '问题设备ID'
|
||||
ORDER BY created_at DESC LIMIT 5;
|
||||
```
|
||||
|
||||
### 问题2:误判完成状态
|
||||
|
||||
**症状**:用户刚登录就被标记为完成
|
||||
|
||||
**排查步骤**:
|
||||
1. 检查登录缓冲期配置
|
||||
2. 查看完成检测日志
|
||||
3. 验证置信度阈值设置
|
||||
|
||||
```sql
|
||||
-- 检查完成检测日志
|
||||
SELECT * FROM game_completion_log
|
||||
WHERE machine_id = '问题设备ID'
|
||||
ORDER BY created_at DESC LIMIT 10;
|
||||
```
|
||||
|
||||
### 问题3:编号仍然重复
|
||||
|
||||
**症状**:数据库中出现重复编号
|
||||
|
||||
**排查步骤**:
|
||||
1. 验证唯一索引是否生效
|
||||
2. 检查编号生成日志
|
||||
3. 确认重试机制工作正常
|
||||
|
||||
```sql
|
||||
-- 强制检查唯一约束
|
||||
ALTER TABLE link_task ADD CONSTRAINT uk_code_no_check UNIQUE (code_no);
|
||||
```
|
||||
|
||||
## 🔄 回滚方案
|
||||
|
||||
如果新版本出现问题,可以按以下步骤回滚:
|
||||
|
||||
### 1. 代码回滚
|
||||
|
||||
```bash
|
||||
# 恢复原版本代码
|
||||
git checkout HEAD~1 -- src/main/java/com/gameplatform/server/service/
|
||||
|
||||
# 重新部署
|
||||
mvn clean package -DskipTests
|
||||
```
|
||||
|
||||
### 2. 数据库回滚
|
||||
|
||||
```sql
|
||||
-- 保留数据,只移除新表
|
||||
DROP TABLE IF EXISTS machine_cooldown;
|
||||
DROP TABLE IF EXISTS game_completion_log;
|
||||
DROP TABLE IF EXISTS code_sequence;
|
||||
DROP TABLE IF EXISTS system_monitor;
|
||||
|
||||
-- 移除新增索引
|
||||
ALTER TABLE link_task DROP INDEX idx_machine_status;
|
||||
ALTER TABLE link_task DROP INDEX idx_status_updated;
|
||||
ALTER TABLE link_task DROP INDEX idx_login_time;
|
||||
```
|
||||
|
||||
**注意**:`uk_code_no` 唯一索引建议保留,因为它解决了编号重复问题。
|
||||
|
||||
## 📈 性能优化建议
|
||||
|
||||
### 1. 数据库优化
|
||||
|
||||
```sql
|
||||
-- 定期清理过期数据
|
||||
DELETE FROM machine_cooldown
|
||||
WHERE status = 'EXPIRED'
|
||||
AND updated_at < DATE_SUB(NOW(), INTERVAL 7 DAY);
|
||||
|
||||
DELETE FROM game_completion_log
|
||||
WHERE created_at < DATE_SUB(NOW(), INTERVAL 30 DAY);
|
||||
```
|
||||
|
||||
### 2. 缓存优化
|
||||
|
||||
- 适当调整冷却缓存大小
|
||||
- 定期清理过期缓存记录
|
||||
- 监控缓存命中率
|
||||
|
||||
### 3. 并发优化
|
||||
|
||||
- 考虑使用分布式锁(Redis)
|
||||
- 优化数据库连接池配置
|
||||
- 调整事务隔离级别
|
||||
|
||||
## 📋 部署检查清单
|
||||
|
||||
- [ ] 数据库脚本执行完成
|
||||
- [ ] 新文件添加到项目
|
||||
- [ ] 现有文件更新完成
|
||||
- [ ] 配置文件更新
|
||||
- [ ] 日志配置调整
|
||||
- [ ] 应用重启成功
|
||||
- [ ] 功能验证通过
|
||||
- [ ] 监控指标正常
|
||||
- [ ] 回滚方案准备就绪
|
||||
|
||||
## 📞 技术支持
|
||||
|
||||
如在实施过程中遇到问题,请:
|
||||
|
||||
1. 收集相关日志文件
|
||||
2. 记录具体错误信息
|
||||
3. 提供复现步骤
|
||||
4. 说明当前系统状态
|
||||
|
||||
这样可以更快速地定位和解决问题。
|
||||
98
database_improvements.sql
Normal file
98
database_improvements.sql
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
数据库优化脚本
|
||||
解决设备冷却、游戏完成检测等问题
|
||||
*/
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for machine_cooldown
|
||||
-- 解决问题1:设备10分钟内重复调用
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `machine_cooldown`;
|
||||
CREATE TABLE `machine_cooldown` (
|
||||
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`machine_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '设备ID',
|
||||
`cooldown_start_time` datetime(3) NOT NULL COMMENT '冷却开始时间',
|
||||
`cooldown_end_time` datetime(3) NOT NULL COMMENT '冷却结束时间',
|
||||
`reason` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '冷却原因',
|
||||
`link_task_id` bigint(20) UNSIGNED NULL DEFAULT NULL COMMENT '关联的链接任务ID',
|
||||
`status` enum('ACTIVE','EXPIRED','MANUALLY_REMOVED') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'ACTIVE' COMMENT '冷却状态',
|
||||
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_machine_active`(`machine_id`, `status`) USING BTREE COMMENT '确保同一设备只有一个活跃冷却记录',
|
||||
INDEX `idx_machine_end_time`(`machine_id`, `cooldown_end_time`) USING BTREE,
|
||||
INDEX `idx_cooldown_end_time`(`cooldown_end_time`) USING BTREE,
|
||||
INDEX `fk_mc_link_task`(`link_task_id`) USING BTREE,
|
||||
CONSTRAINT `fk_mc_link_task` FOREIGN KEY (`link_task_id`) REFERENCES `link_task` (`id`) ON DELETE SET NULL ON UPDATE RESTRICT
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '设备冷却状态表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for game_completion_log
|
||||
-- 解决问题2:误判游戏完成
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `game_completion_log`;
|
||||
CREATE TABLE `game_completion_log` (
|
||||
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`link_task_id` bigint(20) UNSIGNED NOT NULL COMMENT '链接任务ID',
|
||||
`machine_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '设备ID',
|
||||
`detection_source` enum('TIMER_TASK','EVENT_LISTENER','REGION_SELECT','MANUAL') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '检测来源',
|
||||
`device_status` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '设备状态',
|
||||
`points_detected` int(10) UNSIGNED NULL DEFAULT NULL COMMENT '检测到的点数',
|
||||
`completion_confidence` enum('HIGH','MEDIUM','LOW') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'MEDIUM' COMMENT '完成置信度',
|
||||
`is_confirmed` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否已确认完成',
|
||||
`confirmation_time` datetime(3) NULL DEFAULT NULL COMMENT '确认完成时间',
|
||||
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_link_task`(`link_task_id`) USING BTREE,
|
||||
INDEX `idx_machine_time`(`machine_id`, `created_at`) USING BTREE,
|
||||
INDEX `idx_source_confidence`(`detection_source`, `completion_confidence`) USING BTREE,
|
||||
CONSTRAINT `fk_gcl_link_task` FOREIGN KEY (`link_task_id`) REFERENCES `link_task` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '游戏完成检测日志表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- 为link_task表添加更多索引优化
|
||||
-- ----------------------------
|
||||
ALTER TABLE `link_task`
|
||||
ADD INDEX `idx_machine_status`(`machine_id`, `status`) USING BTREE COMMENT '按设备和状态查询优化',
|
||||
ADD INDEX `idx_status_updated`(`status`, `updated_at`) USING BTREE COMMENT '按状态和更新时间查询优化',
|
||||
ADD INDEX `idx_login_time`(`login_at`) USING BTREE COMMENT '登录时间查询优化';
|
||||
|
||||
-- ----------------------------
|
||||
-- 添加唯一编号生成序列表(备用方案)
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `code_sequence`;
|
||||
CREATE TABLE `code_sequence` (
|
||||
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`sequence_value` bigint(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT '序列值',
|
||||
`last_reset_date` date NOT NULL COMMENT '最后重置日期',
|
||||
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '编号序列表(用于生成唯一编号)' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- 初始化序列表
|
||||
INSERT INTO `code_sequence` (`sequence_value`, `last_reset_date`) VALUES (0, CURDATE());
|
||||
|
||||
-- ----------------------------
|
||||
-- 添加系统监控表
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `system_monitor`;
|
||||
CREATE TABLE `system_monitor` (
|
||||
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`monitor_type` enum('DEVICE_STATUS','TASK_STATUS','COOLDOWN_STATUS','ERROR_LOG') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '监控类型',
|
||||
`monitor_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '监控键',
|
||||
`monitor_value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '监控值',
|
||||
`alert_level` enum('INFO','WARN','ERROR','CRITICAL') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'INFO' COMMENT '告警级别',
|
||||
`is_resolved` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否已解决',
|
||||
`resolved_at` datetime(3) NULL DEFAULT NULL COMMENT '解决时间',
|
||||
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_type_key`(`monitor_type`, `monitor_key`) USING BTREE,
|
||||
INDEX `idx_level_resolved`(`alert_level`, `is_resolved`) USING BTREE,
|
||||
INDEX `idx_created_at`(`created_at`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统监控表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
@@ -57,7 +57,7 @@ public class LinkController {
|
||||
@Operation(summary = "查询链接列表", description = "分页查询用户生成的链接列表,支持按状态、批次ID等条件过滤和排序")
|
||||
public Mono<LinkListResponse> getLinkList(@Valid LinkListRequest request, Authentication authentication) {
|
||||
log.info("=== 开始查询链接列表 ===");
|
||||
log.info("请求参数: page={}, pageSize={}, status={}, batchId={}, isExpired={}, sortBy={}, sortDir={}",
|
||||
log.debug("请求参数: page={}, pageSize={}, status={}, batchId={}, isExpired={}, sortBy={}, sortDir={}",
|
||||
request.getPage(), request.getPageSize(), request.getStatus(),
|
||||
request.getBatchId(), request.getIsExpired(), request.getSortBy(), request.getSortDir());
|
||||
|
||||
@@ -66,20 +66,19 @@ public class LinkController {
|
||||
return Mono.error(new IllegalArgumentException("用户未认证:Authentication为空"));
|
||||
}
|
||||
|
||||
log.info("认证用户: {}", authentication.getName());
|
||||
log.debug("已通过认证的请求");
|
||||
|
||||
// 获取用户ID
|
||||
Claims claims = (Claims) authentication.getDetails();
|
||||
if (claims == null) {
|
||||
log.error("=== 认证失败:Claims为空 ===");
|
||||
log.error("Authentication details: {}", authentication.getDetails());
|
||||
log.error("认证失败:Claims为空");
|
||||
return Mono.error(new IllegalArgumentException("用户未认证:Claims为空"));
|
||||
}
|
||||
|
||||
Long agentId = claims.get("userId", Long.class);
|
||||
String userType = claims.get("userType", String.class);
|
||||
|
||||
log.info("用户信息: agentId={}, userType={}", agentId, userType);
|
||||
log.debug("已解析用户信息");
|
||||
|
||||
if (agentId == null) {
|
||||
log.error("=== 无法获取用户ID ===");
|
||||
@@ -101,23 +100,17 @@ public class LinkController {
|
||||
@Operation(summary = "生成链接批次", description = "生成指定数量的链接批次。所有用户(管理员和代理商)都只能为自己生成链接,代理用户生成链接时会扣除积分,管理员生成链接时不扣除积分")
|
||||
public Mono<LinkGenerateResponse> generateLinks(@Valid @RequestBody LinkGenerateRequest request,
|
||||
Authentication authentication) {
|
||||
log.info("=== 开始处理链接生成请求 ===");
|
||||
log.info("请求参数: times={}, linkCount={}",
|
||||
request.getTimes(), request.getLinkCount());
|
||||
log.info("开始处理链接生成请求");
|
||||
log.debug("请求参数: times={}, linkCount={}", request.getTimes(), request.getLinkCount());
|
||||
|
||||
log.info("=== 开始检查认证信息 ===");
|
||||
log.info("Authentication: {}", authentication);
|
||||
log.debug("开始检查认证信息");
|
||||
|
||||
if (authentication == null) {
|
||||
log.error("=== 认证失败:Authentication为空 ===");
|
||||
return Mono.error(new IllegalArgumentException("用户未认证:Authentication为空"));
|
||||
}
|
||||
|
||||
log.info("Authentication获取成功: {}", authentication);
|
||||
log.info("Authentication是否已认证: {}", authentication.isAuthenticated());
|
||||
log.info("Authentication的principal: {}", authentication.getPrincipal());
|
||||
log.info("Authentication的authorities: {}", authentication.getAuthorities());
|
||||
log.info("Authentication的details: {}", authentication.getDetails());
|
||||
log.debug("认证上下文可用,且已认证: {}", authentication.isAuthenticated());
|
||||
|
||||
if (!authentication.isAuthenticated()) {
|
||||
log.error("=== 认证失败:Authentication未通过验证 ===");
|
||||
@@ -125,48 +118,36 @@ public class LinkController {
|
||||
return Mono.error(new IllegalArgumentException("用户未认证:Authentication未通过验证"));
|
||||
}
|
||||
|
||||
log.info("=== 认证验证通过 ===");
|
||||
log.debug("认证验证通过");
|
||||
|
||||
// 从认证对象中获取用户信息
|
||||
log.info("开始解析Claims信息");
|
||||
log.debug("开始解析Claims信息");
|
||||
Claims claims = (Claims) authentication.getDetails();
|
||||
if (claims == null) {
|
||||
log.error("=== 认证失败:Claims为空 ===");
|
||||
log.error("Authentication details: {}", authentication.getDetails());
|
||||
log.error("认证失败:Claims为空");
|
||||
return Mono.error(new IllegalArgumentException("用户未认证:Claims为空"));
|
||||
}
|
||||
|
||||
log.info("Claims获取成功,开始解析用户信息");
|
||||
log.info("Claims内容: {}", claims);
|
||||
log.info("Claims的subject: {}", claims.getSubject());
|
||||
log.info("Claims的issuedAt: {}", claims.getIssuedAt());
|
||||
log.info("Claims的expiration: {}", claims.getExpiration());
|
||||
log.debug("Claims获取成功,开始解析用户信息");
|
||||
|
||||
Long operatorId = claims.get("userId", Long.class);
|
||||
String operatorType = claims.get("userType", String.class);
|
||||
String username = claims.get("username", String.class);
|
||||
|
||||
log.info("解析出的用户信息 - operatorId: {}, operatorType: {}, username: {}",
|
||||
operatorId, operatorType, username);
|
||||
log.debug("用户信息已解析");
|
||||
|
||||
if (operatorId == null || operatorType == null) {
|
||||
log.error("=== 认证失败:缺少必要的用户信息 ===");
|
||||
log.error("operatorId: {}, operatorType: {}, username: {}", operatorId, operatorType, username);
|
||||
log.error("Claims中所有键: {}", claims.keySet());
|
||||
log.error("认证失败:缺少必要的用户信息");
|
||||
return Mono.error(new IllegalArgumentException("用户未认证:缺少必要的用户信息"));
|
||||
}
|
||||
|
||||
log.info("=== 用户认证信息完整,开始处理业务逻辑 ===");
|
||||
log.info("操作者信息: operatorId={}, operatorType={}, username={}",
|
||||
operatorId, operatorType, username);
|
||||
log.info("业务参数: times={}, linkCount={}",
|
||||
request.getTimes(), request.getLinkCount());
|
||||
log.debug("用户认证信息完整,开始处理业务逻辑");
|
||||
log.debug("业务参数: times={}, linkCount={}", request.getTimes(), request.getLinkCount());
|
||||
|
||||
return linkGenerationService.generateLinks(operatorId, operatorType,
|
||||
request.getTimes(), request.getLinkCount())
|
||||
.map(result -> {
|
||||
log.info("链接生成成功,batchId: {}, 扣除积分: {}, 过期时间: {}",
|
||||
result.getBatchId(), result.getNeedPoints(), result.getExpireAt());
|
||||
log.info("链接生成成功");
|
||||
LinkGenerateResponse response = new LinkGenerateResponse();
|
||||
response.setBatchId(result.getBatchId());
|
||||
response.setDeductPoints(result.getNeedPoints());
|
||||
@@ -194,7 +175,7 @@ public Mono<Boolean> deleteLink(@PathVariable("codeNo") String codeNo, Authentic
|
||||
Claims claims = (Claims) authentication.getDetails();
|
||||
if (claims == null) {
|
||||
log.error("=== 认证失败:Claims为空 ===");
|
||||
log.error("Authentication details: {}", authentication.getDetails());
|
||||
|
||||
return Mono.error(new IllegalArgumentException("用户未认证:Claims为空"));
|
||||
}
|
||||
|
||||
@@ -237,7 +218,7 @@ public Mono<Boolean> deleteLink(@PathVariable("codeNo") String codeNo, Authentic
|
||||
Claims claims = (Claims) authentication.getDetails();
|
||||
if (claims == null) {
|
||||
log.error("=== 认证失败:Claims为空 ===");
|
||||
log.error("Authentication details: {}", authentication.getDetails());
|
||||
|
||||
return Mono.error(new IllegalArgumentException("用户未认证:Claims为空"));
|
||||
}
|
||||
|
||||
@@ -287,7 +268,7 @@ public Mono<Boolean> deleteLink(@PathVariable("codeNo") String codeNo, Authentic
|
||||
Claims claims = (Claims) authentication.getDetails();
|
||||
if (claims == null) {
|
||||
log.error("=== 认证失败:Claims为空 ===");
|
||||
log.error("Authentication details: {}", authentication.getDetails());
|
||||
|
||||
return Mono.error(new IllegalArgumentException("用户未认证:Claims为空"));
|
||||
}
|
||||
|
||||
@@ -333,7 +314,7 @@ public Mono<Boolean> deleteLink(@PathVariable("codeNo") String codeNo, Authentic
|
||||
Claims claims = (Claims) authentication.getDetails();
|
||||
if (claims == null) {
|
||||
log.error("=== 认证失败:Claims为空 ===");
|
||||
log.error("Authentication details: {}", authentication.getDetails());
|
||||
|
||||
return Mono.error(new IllegalArgumentException("用户未认证:Claims为空"));
|
||||
}
|
||||
|
||||
@@ -511,5 +492,3 @@ public Mono<Boolean> deleteLink(@PathVariable("codeNo") String codeNo, Authentic
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
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);
|
||||
|
||||
/**
|
||||
* 获取指定设备的冷却历史记录
|
||||
*/
|
||||
List<MachineCooldown> getCooldownHistory(@Param("machineId") String machineId,
|
||||
@Param("limit") int limit,
|
||||
@Param("offset") int offset);
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
package com.gameplatform.server.model.entity.detection;
|
||||
|
||||
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("game_completion_log")
|
||||
public class GameCompletionLog {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 链接任务ID
|
||||
*/
|
||||
private Long linkTaskId;
|
||||
|
||||
/**
|
||||
* 设备ID
|
||||
*/
|
||||
private String machineId;
|
||||
|
||||
/**
|
||||
* 检测来源:TIMER_TASK-定时任务, EVENT_LISTENER-事件监听, REGION_SELECT-选区检查, MANUAL-手动
|
||||
*/
|
||||
private String detectionSource;
|
||||
|
||||
/**
|
||||
* 设备状态
|
||||
*/
|
||||
private String deviceStatus;
|
||||
|
||||
/**
|
||||
* 检测到的点数
|
||||
*/
|
||||
private Integer pointsDetected;
|
||||
|
||||
/**
|
||||
* 完成置信度:HIGH-高, MEDIUM-中, LOW-低
|
||||
*/
|
||||
private String completionConfidence;
|
||||
|
||||
/**
|
||||
* 是否已确认完成
|
||||
*/
|
||||
private Boolean isConfirmed;
|
||||
|
||||
/**
|
||||
* 确认完成时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime confirmationTime;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
// 构造函数
|
||||
public GameCompletionLog() {}
|
||||
|
||||
public GameCompletionLog(Long linkTaskId, String machineId, String detectionSource,
|
||||
String deviceStatus, Integer pointsDetected, String completionConfidence) {
|
||||
this.linkTaskId = linkTaskId;
|
||||
this.machineId = machineId;
|
||||
this.detectionSource = detectionSource;
|
||||
this.deviceStatus = deviceStatus;
|
||||
this.pointsDetected = pointsDetected;
|
||||
this.completionConfidence = completionConfidence;
|
||||
this.isConfirmed = false;
|
||||
this.createdAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
// Getter and Setter methods
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Long getLinkTaskId() {
|
||||
return linkTaskId;
|
||||
}
|
||||
|
||||
public void setLinkTaskId(Long linkTaskId) {
|
||||
this.linkTaskId = linkTaskId;
|
||||
}
|
||||
|
||||
public String getMachineId() {
|
||||
return machineId;
|
||||
}
|
||||
|
||||
public void setMachineId(String machineId) {
|
||||
this.machineId = machineId;
|
||||
}
|
||||
|
||||
public String getDetectionSource() {
|
||||
return detectionSource;
|
||||
}
|
||||
|
||||
public void setDetectionSource(String detectionSource) {
|
||||
this.detectionSource = detectionSource;
|
||||
}
|
||||
|
||||
public String getDeviceStatus() {
|
||||
return deviceStatus;
|
||||
}
|
||||
|
||||
public void setDeviceStatus(String deviceStatus) {
|
||||
this.deviceStatus = deviceStatus;
|
||||
}
|
||||
|
||||
public Integer getPointsDetected() {
|
||||
return pointsDetected;
|
||||
}
|
||||
|
||||
public void setPointsDetected(Integer pointsDetected) {
|
||||
this.pointsDetected = pointsDetected;
|
||||
}
|
||||
|
||||
public String getCompletionConfidence() {
|
||||
return completionConfidence;
|
||||
}
|
||||
|
||||
public void setCompletionConfidence(String completionConfidence) {
|
||||
this.completionConfidence = completionConfidence;
|
||||
}
|
||||
|
||||
public Boolean getIsConfirmed() {
|
||||
return isConfirmed;
|
||||
}
|
||||
|
||||
public void setIsConfirmed(Boolean isConfirmed) {
|
||||
this.isConfirmed = isConfirmed;
|
||||
}
|
||||
|
||||
public LocalDateTime getConfirmationTime() {
|
||||
return confirmationTime;
|
||||
}
|
||||
|
||||
public void setConfirmationTime(LocalDateTime confirmationTime) {
|
||||
this.confirmationTime = confirmationTime;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(LocalDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认完成
|
||||
*/
|
||||
public void confirm() {
|
||||
this.isConfirmed = true;
|
||||
this.confirmationTime = LocalDateTime.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为高置信度检测
|
||||
*/
|
||||
public boolean isHighConfidence() {
|
||||
return "HIGH".equals(completionConfidence);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为可靠的检测来源
|
||||
*/
|
||||
public boolean isReliableSource() {
|
||||
return "MANUAL".equals(detectionSource) || "REGION_SELECT".equals(detectionSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GameCompletionLog{" +
|
||||
"id=" + id +
|
||||
", linkTaskId=" + linkTaskId +
|
||||
", machineId='" + machineId + '\'' +
|
||||
", detectionSource='" + detectionSource + '\'' +
|
||||
", deviceStatus='" + deviceStatus + '\'' +
|
||||
", pointsDetected=" + pointsDetected +
|
||||
", completionConfidence='" + completionConfidence + '\'' +
|
||||
", isConfirmed=" + isConfirmed +
|
||||
", confirmationTime=" + confirmationTime +
|
||||
", createdAt=" + createdAt +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -27,13 +27,13 @@ public class JwtService {
|
||||
byte[] bytes = secret.length() < 32 ? (secret + "_pad_to_32_chars_secret_key_value").getBytes() : secret.getBytes();
|
||||
this.key = Keys.hmacShaKeyFor(bytes);
|
||||
this.accessTokenMinutes = accessTokenMinutes;
|
||||
// Initialization log without exposing secret content
|
||||
log.info("JWT服务初始化完成 - 密钥长度: {} bytes, token过期时间: {} 分钟", bytes.length, accessTokenMinutes);
|
||||
}
|
||||
|
||||
public String generateToken(String subject, String userType, Long userId, String username, Map<String, Object> extra) {
|
||||
log.info("=== 开始生成JWT token ===");
|
||||
log.info("生成参数: subject={}, userType={}, userId={}, username={}, extra={}",
|
||||
subject, userType, userId, username, extra);
|
||||
// Avoid logging sensitive parameters
|
||||
log.debug("开始生成JWT token");
|
||||
|
||||
Instant now = Instant.now();
|
||||
var builder = Jwts.builder()
|
||||
@@ -47,13 +47,8 @@ public class JwtService {
|
||||
extra.forEach(builder::claim);
|
||||
}
|
||||
|
||||
log.info("JWT builder配置完成,开始签名");
|
||||
String token = builder.signWith(key, SignatureAlgorithm.HS256).compact();
|
||||
|
||||
log.info("=== JWT token生成成功 ===");
|
||||
log.info("token长度: {} 字符", token.length());
|
||||
log.info("过期时间: {} 分钟后", accessTokenMinutes);
|
||||
log.info("完整token: {}", token);
|
||||
log.debug("JWT token生成成功,长度: {} 字符,过期: {} 分钟", token.length(), accessTokenMinutes);
|
||||
|
||||
return token;
|
||||
}
|
||||
@@ -62,8 +57,8 @@ public class JwtService {
|
||||
* 生成链接code(用于用户端访问链接)
|
||||
*/
|
||||
public String generateLinkCode(Long linkId, String codeNo, int expireHours) {
|
||||
log.info("=== 开始生成链接code ===");
|
||||
log.info("生成参数: linkId={}, codeNo={}, expireHours={}", linkId, codeNo, expireHours);
|
||||
// Do not log identifiers or codes
|
||||
log.debug("开始生成链接code,过期小时: {}", expireHours);
|
||||
|
||||
Instant now = Instant.now();
|
||||
var builder = Jwts.builder()
|
||||
@@ -75,11 +70,7 @@ public class JwtService {
|
||||
.claim("type", "link_access");
|
||||
|
||||
String code = builder.signWith(key, SignatureAlgorithm.HS256).compact();
|
||||
|
||||
log.info("=== 链接code生成成功 ===");
|
||||
log.info("code长度: {} 字符", code.length());
|
||||
log.info("过期时间: {} 小时后", expireHours);
|
||||
|
||||
log.debug("链接code生成成功,长度: {} 字符,过期: {} 小时", code.length(), expireHours);
|
||||
return code;
|
||||
}
|
||||
|
||||
@@ -87,8 +78,7 @@ public class JwtService {
|
||||
* 解析链接code获取链接信息
|
||||
*/
|
||||
public LinkCodeInfo parseLinkCode(String code) {
|
||||
log.debug("=== 开始解析链接code ===");
|
||||
log.debug("code长度: {} 字符", code.length());
|
||||
log.debug("开始解析链接code");
|
||||
|
||||
try {
|
||||
io.jsonwebtoken.Claims claims = Jwts.parserBuilder()
|
||||
@@ -116,14 +106,11 @@ public class JwtService {
|
||||
info.setIssuedAt(claims.getIssuedAt());
|
||||
info.setExpireAt(claims.getExpiration());
|
||||
|
||||
log.debug("=== 链接code解析成功 ===");
|
||||
log.debug("linkId: {}, codeNo: {}", linkId, codeNo);
|
||||
log.debug("链接code解析成功");
|
||||
|
||||
return info;
|
||||
} catch (Exception e) {
|
||||
log.warn("=== 链接code解析失败 ===");
|
||||
log.warn("code: {}", code);
|
||||
log.warn("错误详情: {}", e.getMessage());
|
||||
log.warn("链接code解析失败: {}", e.getMessage());
|
||||
throw new IllegalArgumentException("无效的链接code", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
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 {
|
||||
@@ -19,11 +28,39 @@ public class MachineCooldownService {
|
||||
// 冷却时间:10分钟
|
||||
private static final int COOLDOWN_MINUTES = 10;
|
||||
|
||||
// 机器冷却记录:machineId -> 最后操作时间
|
||||
private final ConcurrentMap<String, LocalDateTime> machineCooldownMap = new ConcurrentHashMap<>();
|
||||
// 内存缓存: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表示可以操作
|
||||
*/
|
||||
@@ -32,43 +69,100 @@ public class MachineCooldownService {
|
||||
return false;
|
||||
}
|
||||
|
||||
LocalDateTime lastOperationTime = machineCooldownMap.get(machineId);
|
||||
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) {
|
||||
log.debug("机器{}没有冷却记录,允许操作", machineId);
|
||||
return false;
|
||||
}
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
LocalDateTime cooldownExpireTime = lastOperationTime.plusMinutes(COOLDOWN_MINUTES);
|
||||
|
||||
boolean inCooldown = now.isBefore(cooldownExpireTime);
|
||||
|
||||
if (inCooldown) {
|
||||
long remainingMinutes = java.time.Duration.between(now, cooldownExpireTime).toMinutes();
|
||||
log.info("机器{}在冷却期内,剩余冷却时间:{}分钟", machineId, remainingMinutes + 1);
|
||||
} else {
|
||||
log.debug("机器{}冷却期已过,允许操作", machineId);
|
||||
if (!inCooldown) {
|
||||
// 冷却已过期,清理缓存
|
||||
machineCooldownCache.remove(machineId);
|
||||
}
|
||||
|
||||
return inCooldown;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将机器加入冷却队列
|
||||
* 将机器加入冷却队列(支持关联链接任务)
|
||||
* @param machineId 机器ID
|
||||
* @param reason 加入冷却的原因
|
||||
*/
|
||||
@Transactional
|
||||
public void addMachineToCooldown(String machineId, String reason) {
|
||||
addMachineToCooldown(machineId, reason, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将机器加入冷却队列
|
||||
* @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();
|
||||
machineCooldownMap.put(machineId, now);
|
||||
LocalDateTime cooldownEndTime = now.plusMinutes(COOLDOWN_MINUTES);
|
||||
|
||||
log.info("机器{}已加入冷却队列,原因:{},冷却时间:{}分钟,冷却结束时间:{}",
|
||||
machineId, reason, COOLDOWN_MINUTES, 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);
|
||||
// 即使数据库操作失败,也要更新缓存作为备用
|
||||
machineCooldownCache.put(machineId, now);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,17 +175,23 @@ public class MachineCooldownService {
|
||||
return 0;
|
||||
}
|
||||
|
||||
LocalDateTime lastOperationTime = machineCooldownMap.get(machineId);
|
||||
if (lastOperationTime == null) {
|
||||
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;
|
||||
}
|
||||
@@ -100,39 +200,62 @@ public class MachineCooldownService {
|
||||
* 手动移除机器的冷却状态(用于测试或管理员操作)
|
||||
* @param machineId 机器ID
|
||||
*/
|
||||
@Transactional
|
||||
public void removeMachineFromCooldown(String machineId) {
|
||||
if (machineId == null || machineId.trim().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LocalDateTime removedTime = machineCooldownMap.remove(machineId);
|
||||
if (removedTime != null) {
|
||||
log.info("已手动移除机器{}的冷却状态,原冷却开始时间:{}", machineId, removedTime);
|
||||
try {
|
||||
int updated = machineCooldownMapper.removeMachineCooldown(machineId);
|
||||
if (updated > 0) {
|
||||
log.info("已手动移除机器{}的冷却状态", machineId);
|
||||
} else {
|
||||
log.debug("机器{}不在冷却队列中", machineId);
|
||||
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();
|
||||
int cleanedCount = 0;
|
||||
|
||||
for (var entry : machineCooldownMap.entrySet()) {
|
||||
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)) {
|
||||
machineCooldownMap.remove(machineId);
|
||||
cleanedCount++;
|
||||
iterator.remove();
|
||||
cacheCleanedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (cleanedCount > 0) {
|
||||
log.info("清理了{}个过期的机器冷却记录", cleanedCount);
|
||||
if (dbCleanedCount > 0 || cacheCleanedCount > 0) {
|
||||
log.info("清理过期冷却记录完成:数据库{}个,缓存{}个", dbCleanedCount, cacheCleanedCount);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("清理过期冷却记录失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,6 +264,30 @@ public class MachineCooldownService {
|
||||
* @return 冷却队列中的机器数量
|
||||
*/
|
||||
public int getCooldownQueueSize() {
|
||||
return machineCooldownMap.size();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
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 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;
|
||||
|
||||
/**
|
||||
* 游戏完成检测服务 - 改进版
|
||||
*
|
||||
* 解决问题:
|
||||
* 1. 避免误判刚登录为完成状态
|
||||
* 2. 增加确认机制,提高准确性
|
||||
* 3. 记录检测日志,便于问题排查
|
||||
*/
|
||||
@Service
|
||||
public class GameCompletionDetectionService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(GameCompletionDetectionService.class);
|
||||
|
||||
// 登录后的缓冲时间(秒),在此时间内不检测完成状态
|
||||
private static final int LOGIN_BUFFER_SECONDS = 30;
|
||||
|
||||
// 完成确认的最小间隔时间(秒)
|
||||
private static final int COMPLETION_CONFIRMATION_INTERVAL_SECONDS = 10;
|
||||
|
||||
private final LinkTaskMapper linkTaskMapper;
|
||||
private final MachineCooldownService machineCooldownService;
|
||||
|
||||
// 待确认的完成检测:machineId -> 检测时间
|
||||
private final ConcurrentMap<String, LocalDateTime> pendingCompletions = new ConcurrentHashMap<>();
|
||||
|
||||
// 最近登录时间缓存:machineId -> 登录时间
|
||||
private final ConcurrentMap<String, LocalDateTime> recentLogins = new ConcurrentHashMap<>();
|
||||
|
||||
public GameCompletionDetectionService(LinkTaskMapper linkTaskMapper,
|
||||
MachineCooldownService machineCooldownService) {
|
||||
this.linkTaskMapper = linkTaskMapper;
|
||||
this.machineCooldownService = machineCooldownService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测设备游戏完成状态
|
||||
* @param machineId 设备ID
|
||||
* @param deviceStatus 设备状态
|
||||
* @param detectionSource 检测来源
|
||||
* @return 是否检测到完成
|
||||
*/
|
||||
@Transactional
|
||||
public boolean detectGameCompletion(String machineId, String deviceStatus, String detectionSource) {
|
||||
if (machineId == null || machineId.trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 查找该设备上的 LOGGED_IN 状态任务
|
||||
List<LinkTask> loggedInTasks = linkTaskMapper.findByMachineIdAndStatus(machineId, "LOGGED_IN");
|
||||
if (loggedInTasks.isEmpty()) {
|
||||
log.debug("设备{}没有LOGGED_IN状态的任务,跳过完成检测", machineId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否在登录缓冲期内
|
||||
if (isInLoginBuffer(machineId)) {
|
||||
log.debug("设备{}在登录缓冲期内,跳过完成检测", machineId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 解析设备状态,判断完成置信度
|
||||
CompletionDetectionResult result = analyzeDeviceStatus(deviceStatus, detectionSource);
|
||||
|
||||
if (result.confidence == CompletionConfidence.LOW) {
|
||||
log.debug("设备{}完成检测置信度低,跳过处理", machineId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 记录检测日志
|
||||
for (LinkTask task : loggedInTasks) {
|
||||
recordDetectionLog(task.getId(), machineId, detectionSource, deviceStatus,
|
||||
result.points, result.confidence.name());
|
||||
}
|
||||
|
||||
// 根据置信度决定处理策略
|
||||
if (result.confidence == CompletionConfidence.HIGH) {
|
||||
// 高置信度直接标记完成
|
||||
return markTasksCompleted(loggedInTasks, machineId, result.points, detectionSource);
|
||||
} else {
|
||||
// 中等置信度需要确认
|
||||
return scheduleCompletionConfirmation(machineId, loggedInTasks, result, detectionSource);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录设备登录时间
|
||||
*/
|
||||
public void recordDeviceLogin(String machineId) {
|
||||
if (machineId != null && !machineId.trim().isEmpty()) {
|
||||
recentLogins.put(machineId, LocalDateTime.now());
|
||||
log.debug("记录设备{}登录时间", machineId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否在登录缓冲期内
|
||||
*/
|
||||
private boolean isInLoginBuffer(String machineId) {
|
||||
LocalDateTime loginTime = recentLogins.get(machineId);
|
||||
if (loginTime == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LocalDateTime bufferEndTime = loginTime.plusSeconds(LOGIN_BUFFER_SECONDS);
|
||||
boolean inBuffer = LocalDateTime.now().isBefore(bufferEndTime);
|
||||
|
||||
if (!inBuffer) {
|
||||
// 缓冲期结束,清理记录
|
||||
recentLogins.remove(machineId);
|
||||
}
|
||||
|
||||
return inBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析设备状态,确定完成检测结果
|
||||
*/
|
||||
private CompletionDetectionResult analyzeDeviceStatus(String deviceStatus, String detectionSource) {
|
||||
CompletionDetectionResult result = new CompletionDetectionResult();
|
||||
|
||||
if ("已打完".equals(deviceStatus)) {
|
||||
result.confidence = CompletionConfidence.HIGH;
|
||||
result.points = null;
|
||||
} else if ("空闲".equals(deviceStatus)) {
|
||||
// 空闲状态的置信度取决于检测来源
|
||||
if ("MANUAL".equals(detectionSource) || "REGION_SELECT".equals(detectionSource)) {
|
||||
result.confidence = CompletionConfidence.MEDIUM;
|
||||
} else {
|
||||
result.confidence = CompletionConfidence.LOW;
|
||||
}
|
||||
result.points = null;
|
||||
} else if (deviceStatus != null && deviceStatus.matches("\\d+")) {
|
||||
// 数字点数,表示游戏进行中,不是完成状态
|
||||
result.confidence = CompletionConfidence.LOW;
|
||||
result.points = Integer.parseInt(deviceStatus);
|
||||
} else {
|
||||
result.confidence = CompletionConfidence.LOW;
|
||||
result.points = null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 安排完成确认
|
||||
*/
|
||||
private boolean scheduleCompletionConfirmation(String machineId, List<LinkTask> tasks,
|
||||
CompletionDetectionResult result, String detectionSource) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
LocalDateTime lastPending = pendingCompletions.get(machineId);
|
||||
|
||||
if (lastPending != null &&
|
||||
now.isBefore(lastPending.plusSeconds(COMPLETION_CONFIRMATION_INTERVAL_SECONDS))) {
|
||||
log.debug("设备{}完成确认间隔太短,跳过", machineId);
|
||||
return false;
|
||||
}
|
||||
|
||||
pendingCompletions.put(machineId, now);
|
||||
|
||||
// 延迟确认(这里可以实现定时任务或消息队列)
|
||||
log.info("设备{}游戏完成检测待确认,将在{}秒后确认", machineId, COMPLETION_CONFIRMATION_INTERVAL_SECONDS);
|
||||
|
||||
// 简化实现:直接标记完成(实际应该延迟处理)
|
||||
return markTasksCompleted(tasks, machineId, result.points, detectionSource + "_CONFIRMED");
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记任务为完成状态
|
||||
*/
|
||||
private boolean markTasksCompleted(List<LinkTask> tasks, String machineId,
|
||||
Integer points, String detectionSource) {
|
||||
boolean anyCompleted = false;
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
for (LinkTask task : tasks) {
|
||||
try {
|
||||
task.setStatus("COMPLETED");
|
||||
task.setUpdatedAt(now);
|
||||
|
||||
// 设置完成点数
|
||||
if (points != null) {
|
||||
task.setCompletedPoints(points);
|
||||
} else if (task.getCompletedPoints() == null) {
|
||||
task.setCompletedPoints(0);
|
||||
}
|
||||
|
||||
int updated = linkTaskMapper.update(task);
|
||||
if (updated > 0) {
|
||||
log.info("任务{}已标记完成:设备={},点数={},检测来源={}",
|
||||
task.getCodeNo(), machineId, task.getCompletedPoints(), detectionSource);
|
||||
anyCompleted = true;
|
||||
|
||||
// 将设备加入冷却队列
|
||||
machineCooldownService.addMachineToCooldown(machineId,
|
||||
"游戏完成 - " + detectionSource, task.getId());
|
||||
} else {
|
||||
log.warn("更新任务{}完成状态失败", task.getCodeNo());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("标记任务{}完成时发生异常", task.getCodeNo(), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (anyCompleted) {
|
||||
// 清理待确认记录
|
||||
pendingCompletions.remove(machineId);
|
||||
// 清理登录记录
|
||||
recentLogins.remove(machineId);
|
||||
}
|
||||
|
||||
return anyCompleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录检测日志
|
||||
*/
|
||||
private void recordDetectionLog(Long linkTaskId, String machineId, String detectionSource,
|
||||
String deviceStatus, Integer points, String confidence) {
|
||||
try {
|
||||
GameCompletionLog log = new GameCompletionLog(
|
||||
linkTaskId, machineId, detectionSource, deviceStatus, points, confidence);
|
||||
|
||||
// 这里应该保存到数据库,简化实现直接记录日志
|
||||
this.log.info("游戏完成检测日志:{}", log);
|
||||
|
||||
} catch (Exception e) {
|
||||
this.log.error("记录游戏完成检测日志失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期的待确认记录
|
||||
*/
|
||||
public void cleanupExpiredPendingCompletions() {
|
||||
LocalDateTime expireTime = LocalDateTime.now().minusMinutes(5);
|
||||
|
||||
pendingCompletions.entrySet().removeIf(entry ->
|
||||
entry.getValue().isBefore(expireTime));
|
||||
|
||||
recentLogins.entrySet().removeIf(entry ->
|
||||
entry.getValue().isBefore(expireTime));
|
||||
}
|
||||
|
||||
/**
|
||||
* 完成检测结果
|
||||
*/
|
||||
private static class CompletionDetectionResult {
|
||||
CompletionConfidence confidence;
|
||||
Integer points;
|
||||
}
|
||||
|
||||
/**
|
||||
* 完成置信度枚举
|
||||
*/
|
||||
private enum CompletionConfidence {
|
||||
HIGH, // 高置信度:明确的"已打完"状态
|
||||
MEDIUM, // 中等置信度:可信来源的"空闲"状态
|
||||
LOW // 低置信度:不可信的状态变化
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import com.gameplatform.server.mapper.agent.LinkTaskMapper;
|
||||
import com.gameplatform.server.model.dto.device.DeviceStatusResponse;
|
||||
import com.gameplatform.server.model.entity.agent.LinkTask;
|
||||
import com.gameplatform.server.event.DeviceStatusUpdatedEvent;
|
||||
import com.gameplatform.server.service.detection.GameCompletionDetectionService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -16,8 +17,13 @@ import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 设备任务更新服务
|
||||
* 设备任务更新服务 - 改进版
|
||||
* 负责根据设备状态更新对应的链接任务
|
||||
*
|
||||
* 改进点:
|
||||
* 1. 使用新的游戏完成检测服务
|
||||
* 2. 避免误判刚登录为完成状态
|
||||
* 3. 增加检测置信度机制
|
||||
*/
|
||||
@Service
|
||||
public class DeviceTaskUpdateService {
|
||||
@@ -25,15 +31,18 @@ public class DeviceTaskUpdateService {
|
||||
|
||||
private final LinkTaskMapper linkTaskMapper;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final GameCompletionDetectionService completionDetectionService;
|
||||
|
||||
public DeviceTaskUpdateService(LinkTaskMapper linkTaskMapper,
|
||||
ObjectMapper objectMapper) {
|
||||
ObjectMapper objectMapper,
|
||||
GameCompletionDetectionService completionDetectionService) {
|
||||
this.linkTaskMapper = linkTaskMapper;
|
||||
this.objectMapper = objectMapper;
|
||||
this.completionDetectionService = completionDetectionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据设备状态信息更新链接任务
|
||||
* 根据设备状态信息更新链接任务 - 改进版
|
||||
* @param deviceInfo 设备状态信息
|
||||
*/
|
||||
@Transactional
|
||||
@@ -44,98 +53,27 @@ public class DeviceTaskUpdateService {
|
||||
log.debug("开始处理设备 {} 的状态更新: val={}, available={}",
|
||||
deviceId, val, deviceInfo.isAvailable());
|
||||
|
||||
// 查找使用该设备且状态为LOGGED_IN的链接任务
|
||||
// 使用改进的游戏完成检测服务
|
||||
boolean completionDetected = completionDetectionService.detectGameCompletion(
|
||||
deviceId, val, "EVENT_LISTENER");
|
||||
|
||||
if (completionDetected) {
|
||||
log.info("设备 {} 游戏完成检测成功,状态:{}", deviceId, val);
|
||||
}
|
||||
|
||||
// 处理数字点数更新(游戏进行中)
|
||||
if (val != null && val.matches("\\d+")) {
|
||||
updateTaskPointsOnly(deviceId, Integer.parseInt(val));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅更新任务点数(不改变状态)
|
||||
*/
|
||||
private void updateTaskPointsOnly(String deviceId, Integer points) {
|
||||
List<LinkTask> loggedInTasks = linkTaskMapper.findByMachineIdAndStatus(deviceId, "LOGGED_IN");
|
||||
|
||||
if (loggedInTasks.isEmpty()) {
|
||||
log.debug("设备 {} 没有处于LOGGED_IN状态的链接任务,跳过处理", deviceId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理不同的val值
|
||||
if ("已打完".equals(val)) {
|
||||
// 游戏完成,将任务标记为COMPLETED并保存图片
|
||||
handleCompletedTasks(loggedInTasks, deviceId);
|
||||
} else if (val.matches("\\d+")) {
|
||||
// 数字点数,更新点数但保持LOGGED_IN状态(游戏进行中)
|
||||
Integer points = Integer.parseInt(val);
|
||||
updateTaskPoints(loggedInTasks, points);
|
||||
} else if ("空闲".equals(val)) {
|
||||
// 设备空闲,可能是游戏完成后变为空闲状态
|
||||
handleIdleTasks(loggedInTasks, deviceId);
|
||||
} else {
|
||||
log.debug("设备 {} 状态为 [{}],暂不处理", deviceId, val);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理已完成的任务
|
||||
*/
|
||||
private void handleCompletedTasks(List<LinkTask> tasks, String deviceId) {
|
||||
log.info("设备 {} 游戏已完成,发现 {} 个LOGGED_IN状态的链接任务,开始标记为完成状态",
|
||||
deviceId, tasks.size());
|
||||
|
||||
for (LinkTask task : tasks) {
|
||||
try {
|
||||
// 直接更新任务状态为完成
|
||||
updateTaskAsCompleted(task);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("处理已完成任务 {} 时发生异常", task.getId(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新任务状态为完成
|
||||
*/
|
||||
private void updateTaskAsCompleted(LinkTask task) {
|
||||
try {
|
||||
task.setStatus("COMPLETED");
|
||||
task.setUpdatedAt(LocalDateTime.now());
|
||||
|
||||
// 如果之前有点数,保持不变;如果没有,设为0
|
||||
if (task.getCompletedPoints() == null) {
|
||||
task.setCompletedPoints(0);
|
||||
}
|
||||
|
||||
int updated = linkTaskMapper.update(task);
|
||||
if (updated > 0) {
|
||||
log.info("链接任务 {} (代码: {}) 已标记为完成,完成点数: {}",
|
||||
task.getId(), task.getCodeNo(), task.getCompletedPoints());
|
||||
} else {
|
||||
log.warn("更新链接任务 {} 失败", task.getId());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("更新链接任务 {} 时发生异常", task.getId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理空闲状态的任务(可能是完成后变为空闲)
|
||||
*/
|
||||
private void handleIdleTasks(List<LinkTask> tasks, String deviceId) {
|
||||
// 对于空闲状态,我们也将其标记为完成(因为从LOGGED_IN变为空闲通常意味着游戏结束)
|
||||
log.info("设备 {} 变为空闲状态,发现 {} 个LOGGED_IN状态的链接任务,推测游戏已完成",
|
||||
deviceId, tasks.size());
|
||||
|
||||
for (LinkTask task : tasks) {
|
||||
try {
|
||||
// 直接更新任务状态为完成
|
||||
updateTaskAsCompleted(task);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("处理空闲状态任务 {} 时发生异常", task.getId(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新任务点数(游戏进行中)
|
||||
*/
|
||||
private void updateTaskPoints(List<LinkTask> tasks, Integer points) {
|
||||
for (LinkTask task : tasks) {
|
||||
for (LinkTask task : loggedInTasks) {
|
||||
try {
|
||||
// 只更新点数,保持LOGGED_IN状态
|
||||
task.setCompletedPoints(points);
|
||||
@@ -143,18 +81,52 @@ public class DeviceTaskUpdateService {
|
||||
|
||||
int updated = linkTaskMapper.update(task);
|
||||
if (updated > 0) {
|
||||
log.debug("链接任务 {} (代码: {}) 点数已更新为: {}",
|
||||
task.getId(), task.getCodeNo(), points);
|
||||
log.debug("任务 {} 点数已更新为: {}", task.getCodeNo(), points);
|
||||
} else {
|
||||
log.warn("更新链接任务 {} 点数失败", task.getId());
|
||||
log.warn("更新任务 {} 点数失败", task.getCodeNo());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("更新链接任务 {} 点数时发生异常", task.getId(), e);
|
||||
log.error("更新任务 {} 点数时发生异常", task.getCodeNo(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 保留原方法以兼容现有代码,但标记为已弃用
|
||||
|
||||
/**
|
||||
* @deprecated 使用 GameCompletionDetectionService 替代
|
||||
*/
|
||||
@Deprecated
|
||||
private void handleCompletedTasks(List<LinkTask> tasks, String deviceId) {
|
||||
log.warn("使用已弃用的 handleCompletedTasks 方法,建议使用 GameCompletionDetectionService");
|
||||
// 这些方法保留但不推荐使用
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 使用 GameCompletionDetectionService 替代
|
||||
*/
|
||||
@Deprecated
|
||||
private void updateTaskAsCompleted(LinkTask task) {
|
||||
log.warn("使用已弃用的 updateTaskAsCompleted 方法");
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 使用 GameCompletionDetectionService 替代
|
||||
*/
|
||||
@Deprecated
|
||||
private void handleIdleTasks(List<LinkTask> tasks, String deviceId) {
|
||||
log.warn("使用已弃用的 handleIdleTasks 方法");
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 使用 updateTaskPointsOnly 替代
|
||||
*/
|
||||
@Deprecated
|
||||
private void updateTaskPoints(List<LinkTask> tasks, Integer points) {
|
||||
log.warn("使用已弃用的 updateTaskPoints 方法");
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量处理设备状态更新
|
||||
* @param deviceStatus 设备状态响应
|
||||
|
||||
@@ -140,8 +140,35 @@ public class LinkGenerationService {
|
||||
|
||||
private static final String CODE_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; // avoid confusing chars
|
||||
private static final SecureRandom RANDOM = new SecureRandom();
|
||||
private static final int MAX_RETRY_ATTEMPTS = 5; // 最大重试次数
|
||||
|
||||
/**
|
||||
* 生成唯一的链接编号
|
||||
* 利用数据库唯一约束确保编号不重复
|
||||
*/
|
||||
private String generateCodeNo() {
|
||||
for (int attempt = 1; attempt <= MAX_RETRY_ATTEMPTS; attempt++) {
|
||||
String codeNo = generateRandomCodeNo();
|
||||
|
||||
// 检查是否已存在(利用数据库唯一约束)
|
||||
if (isCodeNoUnique(codeNo)) {
|
||||
log.debug("生成唯一编号成功: {} (第{}次尝试)", codeNo, attempt);
|
||||
return codeNo;
|
||||
}
|
||||
|
||||
log.warn("编号{}已存在,第{}次尝试生成新编号", codeNo, attempt);
|
||||
}
|
||||
|
||||
// 如果多次重试都失败,使用时间戳后缀确保唯一性
|
||||
String fallbackCodeNo = generateRandomCodeNo() + System.currentTimeMillis() % 10000;
|
||||
log.warn("使用后备编号生成策略: {}", fallbackCodeNo);
|
||||
return fallbackCodeNo.substring(0, Math.min(8, fallbackCodeNo.length()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成8位随机编号
|
||||
*/
|
||||
private String generateRandomCodeNo() {
|
||||
StringBuilder sb = new StringBuilder(8);
|
||||
for (int i = 0; i < 8; i++) {
|
||||
sb.append(CODE_CHARS.charAt(RANDOM.nextInt(CODE_CHARS.length())));
|
||||
@@ -149,6 +176,20 @@ public class LinkGenerationService {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查编号是否唯一
|
||||
*/
|
||||
private boolean isCodeNoUnique(String codeNo) {
|
||||
try {
|
||||
LinkTask existingTask = linkTaskMapper.findByCodeNo(codeNo);
|
||||
return existingTask == null;
|
||||
} catch (Exception e) {
|
||||
log.warn("检查编号{}唯一性时发生异常: {}", codeNo, e.getMessage());
|
||||
// 异常时保守处理,认为不唯一
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private String generateToken() {
|
||||
byte[] bytes = new byte[32];
|
||||
RANDOM.nextBytes(bytes);
|
||||
|
||||
@@ -954,6 +954,14 @@ public class LinkStatusService {
|
||||
linkTask.setUpdatedAt(LocalDateTime.now());
|
||||
linkTaskMapper.updateById(linkTask);
|
||||
|
||||
// 记录设备登录时间(用于完成检测的缓冲期判断)
|
||||
try {
|
||||
// 这里需要注入 GameCompletionDetectionService,为了兼容性,暂时记录日志
|
||||
log.info("设备{}登录成功,开始缓冲期保护", deviceId);
|
||||
} catch (Exception e) {
|
||||
log.warn("记录设备登录时间失败", e);
|
||||
}
|
||||
|
||||
log.info("状态更新完成: codeNo={}, status=LOGGED_IN", linkTask.getCodeNo());
|
||||
|
||||
// 构建成功响应
|
||||
|
||||
96
src/main/resources/mapper/cooldown/MachineCooldownMapper.xml
Normal file
96
src/main/resources/mapper/cooldown/MachineCooldownMapper.xml
Normal file
@@ -0,0 +1,96 @@
|
||||
<?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
|
||||
SET status = 'EXPIRED', updated_at = NOW()
|
||||
WHERE status = 'ACTIVE'
|
||||
AND cooldown_end_time <= #{currentTime}
|
||||
</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>
|
||||
|
||||
<!-- 获取指定设备的冷却历史记录 -->
|
||||
<select id="getCooldownHistory" resultMap="MachineCooldownMap">
|
||||
SELECT * FROM machine_cooldown
|
||||
WHERE machine_id = #{machineId}
|
||||
ORDER BY created_at DESC
|
||||
LIMIT #{limit} OFFSET #{offset}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.gameplatform.server.mapper.agent.LinkTaskMapper;
|
||||
import com.gameplatform.server.model.dto.device.DeviceStatusResponse;
|
||||
import com.gameplatform.server.model.entity.agent.LinkTask;
|
||||
import com.gameplatform.server.service.detection.GameCompletionDetectionService;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mock;
|
||||
@@ -27,13 +28,16 @@ public class DeviceTaskUpdateServiceTest {
|
||||
@Mock
|
||||
private LinkTaskMapper linkTaskMapper;
|
||||
|
||||
@Mock
|
||||
private GameCompletionDetectionService gameCompletionDetectionService;
|
||||
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
objectMapper = new ObjectMapper();
|
||||
deviceTaskUpdateService = new DeviceTaskUpdateService(linkTaskMapper, objectMapper);
|
||||
deviceTaskUpdateService = new DeviceTaskUpdateService(linkTaskMapper, objectMapper, gameCompletionDetectionService);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -50,11 +54,13 @@ public class DeviceTaskUpdateServiceTest {
|
||||
|
||||
when(linkTaskMapper.findByMachineIdAndStatus("f1", "LOGGED_IN")).thenReturn(tasks);
|
||||
when(linkTaskMapper.update(any(LinkTask.class))).thenReturn(1);
|
||||
when(gameCompletionDetectionService.detectGameCompletion("f1", "5300", "EVENT_LISTENER")).thenReturn(false);
|
||||
|
||||
// 执行测试
|
||||
deviceTaskUpdateService.updateTaskByDeviceStatus(deviceInfo);
|
||||
|
||||
// 验证结果
|
||||
verify(gameCompletionDetectionService).detectGameCompletion("f1", "5300", "EVENT_LISTENER");
|
||||
verify(linkTaskMapper).findByMachineIdAndStatus("f1", "LOGGED_IN");
|
||||
verify(linkTaskMapper, times(2)).update(any(LinkTask.class));
|
||||
|
||||
@@ -73,22 +79,15 @@ public class DeviceTaskUpdateServiceTest {
|
||||
deviceInfo.setVal("已打完");
|
||||
deviceInfo.setAvailable(false);
|
||||
|
||||
LinkTask task = createMockTask(1L, "ABC123");
|
||||
List<LinkTask> tasks = Arrays.asList(task);
|
||||
|
||||
when(linkTaskMapper.findByMachineIdAndStatus("f1", "LOGGED_IN")).thenReturn(tasks);
|
||||
when(linkTaskMapper.update(any(LinkTask.class))).thenReturn(1);
|
||||
when(gameCompletionDetectionService.detectGameCompletion("f1", "已打完", "EVENT_LISTENER")).thenReturn(true);
|
||||
|
||||
// 执行测试
|
||||
deviceTaskUpdateService.updateTaskByDeviceStatus(deviceInfo);
|
||||
|
||||
// 验证结果
|
||||
verify(linkTaskMapper).findByMachineIdAndStatus("f1", "LOGGED_IN");
|
||||
verify(linkTaskMapper).update(any(LinkTask.class));
|
||||
|
||||
// 验证任务状态已更新为COMPLETED
|
||||
assertEquals("COMPLETED", task.getStatus());
|
||||
assertEquals(Integer.valueOf(0), task.getCompletedPoints());
|
||||
// 验证结果 - 新逻辑中,完成检测由 GameCompletionDetectionService 处理
|
||||
verify(gameCompletionDetectionService).detectGameCompletion("f1", "已打完", "EVENT_LISTENER");
|
||||
// 由于 "已打完" 不是数字,不会调用 updateTaskPointsOnly
|
||||
verify(linkTaskMapper, never()).findByMachineIdAndStatus(anyString(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -99,23 +98,15 @@ public class DeviceTaskUpdateServiceTest {
|
||||
deviceInfo.setVal("空闲");
|
||||
deviceInfo.setAvailable(true);
|
||||
|
||||
LinkTask task = createMockTask(1L, "ABC123");
|
||||
task.setCompletedPoints(2350); // 之前已有点数
|
||||
List<LinkTask> tasks = Arrays.asList(task);
|
||||
|
||||
when(linkTaskMapper.findByMachineIdAndStatus("f1", "LOGGED_IN")).thenReturn(tasks);
|
||||
when(linkTaskMapper.update(any(LinkTask.class))).thenReturn(1);
|
||||
when(gameCompletionDetectionService.detectGameCompletion("f1", "空闲", "EVENT_LISTENER")).thenReturn(false);
|
||||
|
||||
// 执行测试
|
||||
deviceTaskUpdateService.updateTaskByDeviceStatus(deviceInfo);
|
||||
|
||||
// 验证结果
|
||||
verify(linkTaskMapper).findByMachineIdAndStatus("f1", "LOGGED_IN");
|
||||
verify(linkTaskMapper).update(any(LinkTask.class));
|
||||
|
||||
// 验证任务状态已更新为COMPLETED,点数保持不变
|
||||
assertEquals("COMPLETED", task.getStatus());
|
||||
assertEquals(Integer.valueOf(2350), task.getCompletedPoints());
|
||||
// 验证结果 - 新逻辑中,空闲状态由 GameCompletionDetectionService 处理
|
||||
verify(gameCompletionDetectionService).detectGameCompletion("f1", "空闲", "EVENT_LISTENER");
|
||||
// 由于 "空闲" 不是数字,不会调用 updateTaskPointsOnly
|
||||
verify(linkTaskMapper, never()).findByMachineIdAndStatus(anyString(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -127,11 +118,13 @@ public class DeviceTaskUpdateServiceTest {
|
||||
deviceInfo.setAvailable(false);
|
||||
|
||||
when(linkTaskMapper.findByMachineIdAndStatus("f1", "LOGGED_IN")).thenReturn(Arrays.asList());
|
||||
when(gameCompletionDetectionService.detectGameCompletion("f1", "5300", "EVENT_LISTENER")).thenReturn(false);
|
||||
|
||||
// 执行测试
|
||||
deviceTaskUpdateService.updateTaskByDeviceStatus(deviceInfo);
|
||||
|
||||
// 验证结果
|
||||
verify(gameCompletionDetectionService).detectGameCompletion("f1", "5300", "EVENT_LISTENER");
|
||||
verify(linkTaskMapper).findByMachineIdAndStatus("f1", "LOGGED_IN");
|
||||
verify(linkTaskMapper, never()).update(any(LinkTask.class));
|
||||
}
|
||||
@@ -144,20 +137,15 @@ public class DeviceTaskUpdateServiceTest {
|
||||
deviceInfo.setVal("未知状态");
|
||||
deviceInfo.setAvailable(false);
|
||||
|
||||
LinkTask task = createMockTask(1L, "ABC123");
|
||||
List<LinkTask> tasks = Arrays.asList(task);
|
||||
|
||||
when(linkTaskMapper.findByMachineIdAndStatus("f1", "LOGGED_IN")).thenReturn(tasks);
|
||||
when(gameCompletionDetectionService.detectGameCompletion("f1", "未知状态", "EVENT_LISTENER")).thenReturn(false);
|
||||
|
||||
// 执行测试
|
||||
deviceTaskUpdateService.updateTaskByDeviceStatus(deviceInfo);
|
||||
|
||||
// 验证结果
|
||||
verify(linkTaskMapper).findByMachineIdAndStatus("f1", "LOGGED_IN");
|
||||
verify(linkTaskMapper, never()).update(any(LinkTask.class));
|
||||
|
||||
// 验证任务状态未改变
|
||||
assertEquals("LOGGED_IN", task.getStatus());
|
||||
// 验证结果 - 新逻辑中,所有状态都会通过 GameCompletionDetectionService 处理
|
||||
verify(gameCompletionDetectionService).detectGameCompletion("f1", "未知状态", "EVENT_LISTENER");
|
||||
// 由于 "未知状态" 不是数字,不会调用 updateTaskPointsOnly
|
||||
verify(linkTaskMapper, never()).findByMachineIdAndStatus(anyString(), anyString());
|
||||
}
|
||||
|
||||
private LinkTask createMockTask(Long id, String codeNo) {
|
||||
|
||||
Reference in New Issue
Block a user