feat: 更新数据库结构和链接任务逻辑
主要修改: 1. 更新`game.sql`文件,添加`system_config`表并调整多个表的`ENGINE`和`AUTO_INCREMENT`设置。 2. 在`LinkTask`实体中新增`completedPoints`字段,更新状态字段以包含`COMPLETED`状态。 3. 在`LinkTaskMapper`中新增根据设备ID和状态查询链接任务的方法。 4. 在`LinkStatusService`中更新状态描述映射,增加对`COMPLETED`状态的处理。 5. 在`DeviceStatusService`和`ScriptClient`中新增解析设备状态的方法,支持检查设备是否完成游戏。 技术细节: - 通过数据库结构的更新,增强了系统的配置管理和链接任务的状态处理能力。 - 新增的功能支持更灵活的设备状态监控和任务管理。
This commit is contained in:
29
docs/database_migration_add_completed_points.sql
Normal file
29
docs/database_migration_add_completed_points.sql
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
-- 数据库迁移脚本:添加完成时点数字段和COMPLETED状态
|
||||||
|
-- 执行时间:2025-08-27
|
||||||
|
|
||||||
|
-- 1. 添加 completed_points 字段
|
||||||
|
ALTER TABLE `link_task`
|
||||||
|
ADD COLUMN `completed_points` int UNSIGNED NULL DEFAULT NULL COMMENT '完成时的点数'
|
||||||
|
AFTER `first_region_select_at`;
|
||||||
|
|
||||||
|
-- 2. 修改 status 枚举,添加 COMPLETED 状态
|
||||||
|
ALTER TABLE `link_task`
|
||||||
|
MODIFY COLUMN `status` enum('NEW','USING','LOGGED_IN','COMPLETED','REFUNDED','EXPIRED')
|
||||||
|
CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'NEW';
|
||||||
|
|
||||||
|
-- 3. 验证修改结果
|
||||||
|
SELECT
|
||||||
|
COLUMN_NAME,
|
||||||
|
DATA_TYPE,
|
||||||
|
COLUMN_TYPE,
|
||||||
|
IS_NULLABLE,
|
||||||
|
COLUMN_DEFAULT,
|
||||||
|
COLUMN_COMMENT
|
||||||
|
FROM INFORMATION_SCHEMA.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = DATABASE()
|
||||||
|
AND TABLE_NAME = 'link_task'
|
||||||
|
AND COLUMN_NAME IN ('status', 'completed_points')
|
||||||
|
ORDER BY ORDINAL_POSITION;
|
||||||
|
|
||||||
|
-- 4. 检查表结构
|
||||||
|
SHOW CREATE TABLE `link_task`;
|
||||||
20
docs/database_migration_add_completed_status.sql
Normal file
20
docs/database_migration_add_completed_status.sql
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
-- 数据库迁移脚本:添加 COMPLETED 状态到 link_task 表
|
||||||
|
-- 执行时间:请在维护窗口期间执行
|
||||||
|
-- 影响:修改 link_task 表的 status 字段枚举值
|
||||||
|
|
||||||
|
-- 修改 link_task 表的 status 字段,添加 'COMPLETED' 状态
|
||||||
|
-- 'COMPLETED' 状态表示用户正常完成了游戏任务
|
||||||
|
ALTER TABLE `link_task`
|
||||||
|
MODIFY COLUMN `status` enum('NEW','USING','LOGGED_IN','COMPLETED','REFUNDED','EXPIRED')
|
||||||
|
CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'NEW'
|
||||||
|
COMMENT '任务状态: NEW=新建, USING=使用中, LOGGED_IN=已登录, COMPLETED=正常完成, REFUNDED=已退款, EXPIRED=已过期';
|
||||||
|
|
||||||
|
-- 验证修改结果
|
||||||
|
-- 查看表结构确认枚举值已更新
|
||||||
|
DESCRIBE `link_task`;
|
||||||
|
|
||||||
|
-- 查看当前各状态的统计
|
||||||
|
SELECT status, COUNT(*) as count
|
||||||
|
FROM `link_task`
|
||||||
|
GROUP BY status
|
||||||
|
ORDER BY status;
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
Target Server Version : 80043 (8.0.43)
|
Target Server Version : 80043 (8.0.43)
|
||||||
File Encoding : 65001
|
File Encoding : 65001
|
||||||
|
|
||||||
Date: 24/08/2025 19:08:50
|
Date: 27/08/2025 15:45:17
|
||||||
*/
|
*/
|
||||||
|
|
||||||
SET NAMES utf8mb4;
|
SET NAMES utf8mb4;
|
||||||
@@ -37,7 +37,7 @@ CREATE TABLE `agent_points_tx` (
|
|||||||
INDEX `fk_apx_operator`(`operator_id` ASC) USING BTREE,
|
INDEX `fk_apx_operator`(`operator_id` ASC) USING BTREE,
|
||||||
CONSTRAINT `fk_apx_account` FOREIGN KEY (`account_id`) REFERENCES `user_account` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
|
CONSTRAINT `fk_apx_account` FOREIGN KEY (`account_id`) REFERENCES `user_account` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
|
||||||
CONSTRAINT `fk_apx_operator` FOREIGN KEY (`operator_id`) REFERENCES `user_account` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
|
CONSTRAINT `fk_apx_operator` FOREIGN KEY (`operator_id`) REFERENCES `user_account` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
|
||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- Table structure for announcement
|
-- Table structure for announcement
|
||||||
@@ -52,7 +52,7 @@ CREATE TABLE `announcement` (
|
|||||||
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
`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),
|
`updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||||
PRIMARY KEY (`id`) USING BTREE
|
PRIMARY KEY (`id`) USING BTREE
|
||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- Table structure for link_batch
|
-- Table structure for link_batch
|
||||||
@@ -63,8 +63,6 @@ CREATE TABLE `link_batch` (
|
|||||||
`agent_id` bigint UNSIGNED NOT NULL,
|
`agent_id` bigint UNSIGNED NOT NULL,
|
||||||
`quantity` int UNSIGNED NOT NULL,
|
`quantity` int UNSIGNED NOT NULL,
|
||||||
`times` int UNSIGNED NOT NULL,
|
`times` int UNSIGNED NOT NULL,
|
||||||
`batch_size` int UNSIGNED NOT NULL,
|
|
||||||
`deduct_points` bigint UNSIGNED NOT NULL,
|
|
||||||
`operator_id` bigint UNSIGNED NULL DEFAULT NULL,
|
`operator_id` bigint UNSIGNED NULL DEFAULT NULL,
|
||||||
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
PRIMARY KEY (`id`) USING BTREE,
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
@@ -72,10 +70,9 @@ CREATE TABLE `link_batch` (
|
|||||||
INDEX `fk_lb_operator`(`operator_id` ASC) USING BTREE,
|
INDEX `fk_lb_operator`(`operator_id` ASC) USING BTREE,
|
||||||
CONSTRAINT `fk_lb_agent` FOREIGN KEY (`agent_id`) REFERENCES `user_account` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
|
CONSTRAINT `fk_lb_agent` FOREIGN KEY (`agent_id`) REFERENCES `user_account` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
|
||||||
CONSTRAINT `fk_lb_operator` FOREIGN KEY (`operator_id`) REFERENCES `user_account` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
|
CONSTRAINT `fk_lb_operator` FOREIGN KEY (`operator_id`) REFERENCES `user_account` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
|
||||||
CONSTRAINT `chk_lb_batch_pos` CHECK (`batch_size` > 0),
|
|
||||||
CONSTRAINT `chk_lb_quantity_pos` CHECK (`quantity` > 0),
|
CONSTRAINT `chk_lb_quantity_pos` CHECK (`quantity` > 0),
|
||||||
CONSTRAINT `chk_lb_times_pos` CHECK (`times` > 0)
|
CONSTRAINT `chk_lb_times_pos` CHECK (`times` > 0)
|
||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- Table structure for link_task
|
-- Table structure for link_task
|
||||||
@@ -88,7 +85,7 @@ CREATE TABLE `link_task` (
|
|||||||
`code_no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
|
`code_no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
|
||||||
`token_hash` char(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
|
`token_hash` char(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
|
||||||
`expire_at` datetime(3) NOT NULL,
|
`expire_at` datetime(3) NOT NULL,
|
||||||
`status` enum('NEW','USING','LOGGED_IN','REFUNDED','EXPIRED') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'NEW',
|
`status` enum('NEW','USING','LOGGED_IN','COMPLETED','REFUNDED','EXPIRED') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'NEW',
|
||||||
`region` enum('Q','V') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
`region` enum('Q','V') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||||
`machine_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
`machine_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||||
`login_at` datetime(3) NULL DEFAULT NULL,
|
`login_at` datetime(3) NULL DEFAULT NULL,
|
||||||
@@ -96,6 +93,12 @@ CREATE TABLE `link_task` (
|
|||||||
`revoked_at` datetime(3) NULL DEFAULT NULL,
|
`revoked_at` datetime(3) NULL DEFAULT NULL,
|
||||||
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
`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),
|
`updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||||
|
`need_refresh` tinyint(1) NULL DEFAULT 0 COMMENT '是否需要刷新(0否,1是)',
|
||||||
|
`refresh_time` datetime(3) NULL DEFAULT NULL COMMENT '刷新时间',
|
||||||
|
`qr_created_at` datetime(3) NULL DEFAULT NULL COMMENT '二维码创建时间',
|
||||||
|
`qr_expire_at` datetime(3) NULL DEFAULT NULL COMMENT '二维码过期时间',
|
||||||
|
`first_region_select_at` datetime(3) NULL DEFAULT NULL COMMENT '首次选区时间',
|
||||||
|
`completed_points` int UNSIGNED NULL DEFAULT NULL COMMENT '完成时的点数',
|
||||||
PRIMARY KEY (`id`) USING BTREE,
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
UNIQUE INDEX `uk_code_no`(`code_no` ASC) USING BTREE,
|
UNIQUE INDEX `uk_code_no`(`code_no` ASC) USING BTREE,
|
||||||
UNIQUE INDEX `uk_token_hash`(`token_hash` ASC) USING BTREE,
|
UNIQUE INDEX `uk_token_hash`(`token_hash` ASC) USING BTREE,
|
||||||
@@ -103,9 +106,12 @@ CREATE TABLE `link_task` (
|
|||||||
INDEX `idx_expire_at`(`expire_at` ASC) USING BTREE,
|
INDEX `idx_expire_at`(`expire_at` ASC) USING BTREE,
|
||||||
INDEX `idx_created_at`(`created_at` ASC) USING BTREE,
|
INDEX `idx_created_at`(`created_at` ASC) USING BTREE,
|
||||||
INDEX `fk_lt_batch`(`batch_id` ASC) USING BTREE,
|
INDEX `fk_lt_batch`(`batch_id` ASC) USING BTREE,
|
||||||
|
INDEX `idx_need_refresh`(`need_refresh` ASC) USING BTREE,
|
||||||
|
INDEX `idx_qr_expire`(`qr_expire_at` ASC) USING BTREE,
|
||||||
|
INDEX `idx_first_region_select`(`first_region_select_at` ASC) USING BTREE,
|
||||||
CONSTRAINT `fk_lt_agent` FOREIGN KEY (`agent_id`) REFERENCES `user_account` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
|
CONSTRAINT `fk_lt_agent` FOREIGN KEY (`agent_id`) REFERENCES `user_account` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
|
||||||
CONSTRAINT `fk_lt_batch` FOREIGN KEY (`batch_id`) REFERENCES `link_batch` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
|
CONSTRAINT `fk_lt_batch` FOREIGN KEY (`batch_id`) REFERENCES `link_batch` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
|
||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
) ENGINE = InnoDB AUTO_INCREMENT = 34 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- Table structure for operation_log
|
-- Table structure for operation_log
|
||||||
@@ -124,7 +130,25 @@ CREATE TABLE `operation_log` (
|
|||||||
PRIMARY KEY (`id`) USING BTREE,
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
INDEX `idx_log_code_time`(`code_no` ASC, `created_at` ASC) USING BTREE,
|
INDEX `idx_log_code_time`(`code_no` ASC, `created_at` ASC) USING BTREE,
|
||||||
INDEX `idx_log_time`(`created_at` ASC) USING BTREE
|
INDEX `idx_log_time`(`created_at` ASC) USING BTREE
|
||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for system_config
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `system_config`;
|
||||||
|
CREATE TABLE `system_config` (
|
||||||
|
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
`config_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '配置键',
|
||||||
|
`config_value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '配置值',
|
||||||
|
`config_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'STRING' COMMENT '配置类型:STRING, INTEGER, BOOLEAN, JSON',
|
||||||
|
`description` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '配置描述',
|
||||||
|
`is_system` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否系统配置(1是,0否)',
|
||||||
|
`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_config_key`(`config_key` ASC) USING BTREE,
|
||||||
|
INDEX `idx_config_type`(`config_type` ASC) USING BTREE
|
||||||
|
) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- Table structure for user_account
|
-- Table structure for user_account
|
||||||
@@ -144,33 +168,4 @@ CREATE TABLE `user_account` (
|
|||||||
CONSTRAINT `chk_points_nonneg` CHECK (`points_balance` >= 0)
|
CONSTRAINT `chk_points_nonneg` CHECK (`points_balance` >= 0)
|
||||||
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
-- ----------------------------
|
|
||||||
-- Table structure for system_config
|
|
||||||
-- ----------------------------
|
|
||||||
DROP TABLE IF EXISTS `system_config`;
|
|
||||||
CREATE TABLE `system_config` (
|
|
||||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
||||||
`config_key` varchar(100) NOT NULL COMMENT '配置键',
|
|
||||||
`config_value` text NOT NULL COMMENT '配置值',
|
|
||||||
`config_type` varchar(50) NOT NULL DEFAULT 'STRING' COMMENT '配置类型:STRING, INTEGER, BOOLEAN, JSON',
|
|
||||||
`description` varchar(500) NULL DEFAULT NULL COMMENT '配置描述',
|
|
||||||
`is_system` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否系统配置(1是,0否)',
|
|
||||||
`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_config_key`(`config_key` ASC) USING BTREE,
|
|
||||||
INDEX `idx_config_type`(`config_type` ASC) USING BTREE
|
|
||||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
|
||||||
|
|
||||||
-- 插入默认配置
|
|
||||||
INSERT INTO `system_config` (`config_key`, `config_value`, `config_type`, `description`, `is_system`) VALUES
|
|
||||||
('link.default_quantity', '50', 'INTEGER', '链接生成默认奖励点数', 1),
|
|
||||||
('link.refresh_interval', '300', 'INTEGER', '链接刷新间隔(秒)', 1),
|
|
||||||
('link.qr_expire_time', '600', 'INTEGER', '二维码过期时间(秒)', 1),
|
|
||||||
('link.max_times_per_batch', '100', 'INTEGER', '每批次最大刷奖励次数', 1),
|
|
||||||
('link.min_quantity', '10', 'INTEGER', '最小奖励点数', 1),
|
|
||||||
('link.max_quantity', '1000', 'INTEGER', '最大奖励点数', 1),
|
|
||||||
('script.server_url', 'http://36.138.184.60:12345', 'STRING', '脚本服务器地址', 1),
|
|
||||||
('script.qr_path_template', '/{machineId}/二维码.png', 'STRING', '二维码图片路径模板', 1);
|
|
||||||
|
|
||||||
SET FOREIGN_KEY_CHECKS = 1;
|
SET FOREIGN_KEY_CHECKS = 1;
|
||||||
|
|||||||
176
docs/game_completion_detection_implementation.md
Normal file
176
docs/game_completion_detection_implementation.md
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
# 游戏完成检测实现文档
|
||||||
|
|
||||||
|
## 📋 功能概述
|
||||||
|
|
||||||
|
实现了两种方式来自动检测游戏是否完成,并在检测到完成时自动更新链接状态为 `COMPLETED`,同时记录完成时的点数。
|
||||||
|
|
||||||
|
## 🔧 实现方案
|
||||||
|
|
||||||
|
### **方案1: 定时检查空闲设备**
|
||||||
|
- **触发方式**: 每1分钟自动执行
|
||||||
|
- **检查逻辑**:
|
||||||
|
1. 获取所有空闲设备列表
|
||||||
|
2. 对每个空闲设备,查找是否有 `LOGGED_IN` 状态的链接任务使用该设备
|
||||||
|
3. 如果找到,则将该链接任务标记为 `COMPLETED`,并记录当前点数
|
||||||
|
|
||||||
|
### **方案2: 选区请求时检查**
|
||||||
|
- **触发方式**: 用户发起选区请求时
|
||||||
|
- **检查逻辑**:
|
||||||
|
1. 在分配新设备给用户前,先检查该设备是否空闲
|
||||||
|
2. 如果设备空闲且有之前的 `LOGGED_IN` 状态链接任务,则标记为完成
|
||||||
|
3. 然后继续正常的选区流程
|
||||||
|
|
||||||
|
## 🗄️ 数据库变更
|
||||||
|
|
||||||
|
### **1. link_task 表结构更新**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 添加完成时点数字段
|
||||||
|
ALTER TABLE `link_task`
|
||||||
|
ADD COLUMN `completed_points` int UNSIGNED NULL DEFAULT NULL COMMENT '完成时的点数'
|
||||||
|
AFTER `first_region_select_at`;
|
||||||
|
|
||||||
|
-- 更新状态枚举,添加 COMPLETED 状态
|
||||||
|
ALTER TABLE `link_task`
|
||||||
|
MODIFY COLUMN `status` enum('NEW','USING','LOGGED_IN','COMPLETED','REFUNDED','EXPIRED')
|
||||||
|
CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'NEW';
|
||||||
|
```
|
||||||
|
|
||||||
|
### **2. 状态流转图**
|
||||||
|
|
||||||
|
```
|
||||||
|
NEW (新建)
|
||||||
|
↓
|
||||||
|
USING (使用中)
|
||||||
|
↓
|
||||||
|
LOGGED_IN (已登录)
|
||||||
|
↓
|
||||||
|
COMPLETED (正常完成) ← 新增状态
|
||||||
|
```
|
||||||
|
|
||||||
|
**终态状态**: `COMPLETED`, `REFUNDED`, `EXPIRED`
|
||||||
|
|
||||||
|
## 💻 代码实现
|
||||||
|
|
||||||
|
### **1. 核心服务类**
|
||||||
|
|
||||||
|
#### **DeviceStatusCheckService**
|
||||||
|
- 负责设备状态检查和链接任务更新逻辑
|
||||||
|
- 解析设备状态响应数据 (f0: 点数, f1: 状态)
|
||||||
|
- 更新相关链接任务为完成状态
|
||||||
|
|
||||||
|
#### **DeviceStatusCheckTask**
|
||||||
|
- 定时任务类,每分钟执行一次
|
||||||
|
- 获取空闲设备列表并逐个检查
|
||||||
|
|
||||||
|
### **2. API 接口更新**
|
||||||
|
|
||||||
|
#### **ScriptClient**
|
||||||
|
```java
|
||||||
|
// 新增方法:获取特定设备状态
|
||||||
|
public Mono<Map<String, Object>> getDeviceStatus(String machineId)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **DeviceStatusService**
|
||||||
|
```java
|
||||||
|
// 新增方法:解析特定设备状态
|
||||||
|
public Map<String, Object> parseDeviceStatusForMachine(String jsonResponse, String machineId)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **LinkTaskMapper**
|
||||||
|
```java
|
||||||
|
// 新增方法:根据设备ID和状态查询链接任务
|
||||||
|
List<LinkTask> findByMachineIdAndStatus(String machineId, String status);
|
||||||
|
```
|
||||||
|
|
||||||
|
### **3. 实体类更新**
|
||||||
|
|
||||||
|
#### **LinkTask**
|
||||||
|
```java
|
||||||
|
@TableField("completed_points")
|
||||||
|
private Integer completedPoints; // 完成时的点数
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 数据处理流程
|
||||||
|
|
||||||
|
### **设备状态数据格式**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"f0": {
|
||||||
|
"val": "900", // 当前点数
|
||||||
|
"time": "2025-08-27 15:49:04"
|
||||||
|
},
|
||||||
|
"f1": {
|
||||||
|
"val": "空闲", // 设备状态
|
||||||
|
"time": "2025-08-27 15:49:01"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### **完成检测逻辑**
|
||||||
|
1. **获取设备状态**: 调用 `readAllMsg?文件名=判断分数` 接口
|
||||||
|
2. **解析状态**: 提取 f0(点数) 和 f1(状态) 信息
|
||||||
|
3. **判断空闲**: 检查 f1.val 是否为 "空闲"
|
||||||
|
4. **查找任务**: 根据 machine_id 和 status='LOGGED_IN' 查询链接任务
|
||||||
|
5. **更新状态**: 设置 status='COMPLETED', completed_points=f0.val
|
||||||
|
|
||||||
|
## 🔧 配置说明
|
||||||
|
|
||||||
|
### **定时任务配置**
|
||||||
|
- **执行频率**: 每60秒 (`@Scheduled(fixedRate = 60000)`)
|
||||||
|
- **启用方式**: 主应用类添加 `@EnableScheduling` 注解
|
||||||
|
|
||||||
|
### **日志记录**
|
||||||
|
- **定时检查**: `DEBUG` 级别,避免日志过多
|
||||||
|
- **状态更新**: `INFO` 级别,记录重要操作
|
||||||
|
- **异常处理**: `WARN/ERROR` 级别,不影响主流程
|
||||||
|
|
||||||
|
## 🚀 部署说明
|
||||||
|
|
||||||
|
### **1. 数据库迁移**
|
||||||
|
```bash
|
||||||
|
# 执行迁移脚本
|
||||||
|
mysql -u username -p database_name < docs/database_migration_add_completed_points.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
### **2. 应用重启**
|
||||||
|
- 新增的定时任务会在应用启动后自动运行
|
||||||
|
- 选区请求的检查逻辑会立即生效
|
||||||
|
|
||||||
|
### **3. 验证方法**
|
||||||
|
1. **查看日志**: 观察定时任务执行日志
|
||||||
|
2. **数据库检查**: 验证 `completed_points` 字段是否正确填充
|
||||||
|
3. **API 测试**: 测试选区请求是否正常工作
|
||||||
|
|
||||||
|
## 📈 监控指标
|
||||||
|
|
||||||
|
### **关键日志**
|
||||||
|
- `=== 开始定时检查空闲设备 ===`
|
||||||
|
- `发现 X 个空闲设备: [设备列表]`
|
||||||
|
- `链接任务 X (代码: X) 已标记为完成,完成点数: X`
|
||||||
|
|
||||||
|
### **异常监控**
|
||||||
|
- 设备状态获取失败
|
||||||
|
- JSON 解析异常
|
||||||
|
- 数据库更新失败
|
||||||
|
|
||||||
|
## 🔄 扩展性
|
||||||
|
|
||||||
|
### **支持更多设备状态**
|
||||||
|
- 可扩展 DeviceStatusInfo 类添加更多状态字段
|
||||||
|
- 支持不同的完成判断条件
|
||||||
|
|
||||||
|
### **自定义检查频率**
|
||||||
|
- 通过配置文件调整定时任务执行频率
|
||||||
|
- 支持不同环境使用不同配置
|
||||||
|
|
||||||
|
### **完成通知**
|
||||||
|
- 可扩展添加完成通知功能 (邮件、短信、webhook等)
|
||||||
|
- 支持完成统计和报表功能
|
||||||
|
|
||||||
|
## ⚠️ 注意事项
|
||||||
|
|
||||||
|
1. **性能考虑**: 定时任务频率不宜过高,避免对系统造成压力
|
||||||
|
2. **异常处理**: 单个设备检查失败不应影响其他设备的检查
|
||||||
|
3. **数据一致性**: 使用事务确保状态更新的原子性
|
||||||
|
4. **日志管理**: 合理设置日志级别,避免日志过多影响性能
|
||||||
@@ -3,9 +3,11 @@ package com.gameplatform.server;
|
|||||||
import org.mybatis.spring.annotation.MapperScan;
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
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;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@MapperScan("com.gameplatform.server.mapper")
|
@MapperScan("com.gameplatform.server.mapper")
|
||||||
|
@EnableScheduling
|
||||||
public class GamePlatformServerApplication {
|
public class GamePlatformServerApplication {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(GamePlatformServerApplication.class, args);
|
SpringApplication.run(GamePlatformServerApplication.class, args);
|
||||||
|
|||||||
@@ -90,4 +90,9 @@ public interface LinkTaskMapper extends BaseMapper<LinkTask> {
|
|||||||
* 根据链接编号列表和代理ID查询链接任务(用于验证权限)
|
* 根据链接编号列表和代理ID查询链接任务(用于验证权限)
|
||||||
*/
|
*/
|
||||||
List<LinkTask> findByCodeNosAndAgentId(@Param("codeNos") List<String> codeNos, @Param("agentId") Long agentId);
|
List<LinkTask> findByCodeNosAndAgentId(@Param("codeNos") List<String> codeNos, @Param("agentId") Long agentId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据设备ID和状态查询链接任务
|
||||||
|
*/
|
||||||
|
List<LinkTask> findByMachineIdAndStatus(@Param("machineId") String machineId, @Param("status") String status);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ public class LinkStatusResponse {
|
|||||||
@Schema(description = "批次ID", example = "123")
|
@Schema(description = "批次ID", example = "123")
|
||||||
private Long batchId;
|
private Long batchId;
|
||||||
|
|
||||||
@Schema(description = "链接状态", example = "NEW", allowableValues = {"NEW", "USING", "LOGGED_IN", "REFUNDED", "EXPIRED"})
|
@Schema(description = "链接状态", example = "NEW", allowableValues = {"NEW", "USING", "LOGGED_IN", "COMPLETED", "REFUNDED", "EXPIRED"})
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
@Schema(description = "链接状态描述", example = "新建")
|
@Schema(description = "链接状态描述", example = "新建")
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
|||||||
@Schema(description = "用户端链接状态响应")
|
@Schema(description = "用户端链接状态响应")
|
||||||
public class UserLinkStatusResponse {
|
public class UserLinkStatusResponse {
|
||||||
|
|
||||||
@Schema(description = "链接状态", example = "NEW", allowableValues = {"NEW", "USING", "LOGGED_IN", "REFUNDED", "EXPIRED"})
|
@Schema(description = "链接状态", example = "NEW", allowableValues = {"NEW", "USING", "LOGGED_IN", "COMPLETED", "REFUNDED", "EXPIRED"})
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
// Getter and Setter
|
// Getter and Setter
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ public class LinkTask {
|
|||||||
@TableField("expire_at")
|
@TableField("expire_at")
|
||||||
private LocalDateTime expireAt;
|
private LocalDateTime expireAt;
|
||||||
|
|
||||||
private String status; // NEW | USING | LOGGED_IN | REFUNDED | EXPIRED
|
private String status; // NEW | USING | LOGGED_IN | COMPLETED | REFUNDED | EXPIRED
|
||||||
|
|
||||||
private String region; // Q | V
|
private String region; // Q | V
|
||||||
|
|
||||||
@@ -61,6 +61,9 @@ public class LinkTask {
|
|||||||
|
|
||||||
@TableField("first_region_select_at")
|
@TableField("first_region_select_at")
|
||||||
private LocalDateTime firstRegionSelectAt;
|
private LocalDateTime firstRegionSelectAt;
|
||||||
|
|
||||||
|
@TableField("completed_points")
|
||||||
|
private Integer completedPoints;
|
||||||
|
|
||||||
public Long getId() { return id; }
|
public Long getId() { return id; }
|
||||||
public void setId(Long id) { this.id = id; }
|
public void setId(Long id) { this.id = id; }
|
||||||
@@ -118,4 +121,7 @@ public class LinkTask {
|
|||||||
|
|
||||||
public LocalDateTime getFirstRegionSelectAt() { return firstRegionSelectAt; }
|
public LocalDateTime getFirstRegionSelectAt() { return firstRegionSelectAt; }
|
||||||
public void setFirstRegionSelectAt(LocalDateTime firstRegionSelectAt) { this.firstRegionSelectAt = firstRegionSelectAt; }
|
public void setFirstRegionSelectAt(LocalDateTime firstRegionSelectAt) { this.firstRegionSelectAt = firstRegionSelectAt; }
|
||||||
|
|
||||||
|
public Integer getCompletedPoints() { return completedPoints; }
|
||||||
|
public void setCompletedPoints(Integer completedPoints) { this.completedPoints = completedPoints; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
package com.gameplatform.server.service.device;
|
||||||
|
|
||||||
|
import com.gameplatform.server.mapper.agent.LinkTaskMapper;
|
||||||
|
import com.gameplatform.server.model.entity.agent.LinkTask;
|
||||||
|
import com.gameplatform.server.service.external.ScriptClient;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class DeviceStatusCheckService {
|
||||||
|
|
||||||
|
private final ScriptClient scriptClient;
|
||||||
|
private final LinkTaskMapper linkTaskMapper;
|
||||||
|
|
||||||
|
public DeviceStatusCheckService(ScriptClient scriptClient, LinkTaskMapper linkTaskMapper) {
|
||||||
|
this.scriptClient = scriptClient;
|
||||||
|
this.linkTaskMapper = linkTaskMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查设备状态并更新相关链接任务
|
||||||
|
* @param machineId 设备ID
|
||||||
|
* @param reason 检查原因(定时检查 或 选区请求)
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public void checkDeviceStatusAndUpdateTasks(String machineId, String reason) {
|
||||||
|
log.info("=== 开始检查设备状态 ===");
|
||||||
|
log.info("设备ID: {}, 检查原因: {}", machineId, reason);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. 获取设备状态
|
||||||
|
Map<String, Object> deviceStatus = scriptClient.getDeviceStatus(machineId).block();
|
||||||
|
if (deviceStatus == null || deviceStatus.isEmpty()) {
|
||||||
|
log.warn("获取设备状态失败,设备ID: {}", machineId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 解析设备状态
|
||||||
|
DeviceStatusInfo statusInfo = parseDeviceStatus(deviceStatus);
|
||||||
|
log.info("设备状态解析结果: {}", statusInfo);
|
||||||
|
|
||||||
|
// 3. 如果设备空闲,检查是否有使用该设备的LOGGED_IN状态链接
|
||||||
|
if (statusInfo.isIdle()) {
|
||||||
|
log.info("设备 {} 处于空闲状态,检查相关链接任务", machineId);
|
||||||
|
updateCompletedTasks(machineId, statusInfo.getPoints());
|
||||||
|
} else {
|
||||||
|
log.info("设备 {} 不是空闲状态: {}", machineId, statusInfo.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("检查设备状态时发生异常,设备ID: {}", machineId, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("=== 设备状态检查完成 ===");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析设备状态响应
|
||||||
|
*/
|
||||||
|
private DeviceStatusInfo parseDeviceStatus(Map<String, Object> deviceStatus) {
|
||||||
|
DeviceStatusInfo info = new DeviceStatusInfo();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 解析 f0 (点数)
|
||||||
|
Object f0Obj = deviceStatus.get("f0");
|
||||||
|
if (f0Obj instanceof Map) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> f0 = (Map<String, Object>) f0Obj;
|
||||||
|
if (f0.get("val") != null) {
|
||||||
|
String pointsStr = f0.get("val").toString();
|
||||||
|
info.setPoints(Integer.parseInt(pointsStr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析 f1 (状态)
|
||||||
|
Object f1Obj = deviceStatus.get("f1");
|
||||||
|
if (f1Obj instanceof Map) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> f1 = (Map<String, Object>) f1Obj;
|
||||||
|
if (f1.get("val") != null) {
|
||||||
|
String status = f1.get("val").toString();
|
||||||
|
info.setStatus(status);
|
||||||
|
info.setIdle("空闲".equals(status));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("解析设备状态时发生异常", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新已完成的任务
|
||||||
|
*/
|
||||||
|
private void updateCompletedTasks(String machineId, Integer points) {
|
||||||
|
// 查找使用该设备且状态为LOGGED_IN的链接任务
|
||||||
|
List<LinkTask> loggedInTasks = linkTaskMapper.findByMachineIdAndStatus(machineId, "LOGGED_IN");
|
||||||
|
|
||||||
|
if (loggedInTasks.isEmpty()) {
|
||||||
|
log.info("设备 {} 没有处于LOGGED_IN状态的链接任务", machineId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("找到 {} 个使用设备 {} 且状态为LOGGED_IN的链接任务", loggedInTasks.size(), machineId);
|
||||||
|
|
||||||
|
// 更新所有相关任务为COMPLETED状态
|
||||||
|
for (LinkTask task : loggedInTasks) {
|
||||||
|
try {
|
||||||
|
task.setStatus("COMPLETED");
|
||||||
|
task.setCompletedPoints(points);
|
||||||
|
task.setUpdatedAt(LocalDateTime.now());
|
||||||
|
|
||||||
|
int updated = linkTaskMapper.updateById(task);
|
||||||
|
if (updated > 0) {
|
||||||
|
log.info("链接任务 {} (代码: {}) 已标记为完成,完成点数: {}",
|
||||||
|
task.getId(), task.getCodeNo(), points);
|
||||||
|
} else {
|
||||||
|
log.warn("更新链接任务 {} 失败", task.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("更新链接任务 {} 时发生异常", task.getId(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备状态信息
|
||||||
|
*/
|
||||||
|
private static class DeviceStatusInfo {
|
||||||
|
private String status;
|
||||||
|
private Integer points;
|
||||||
|
private boolean idle;
|
||||||
|
|
||||||
|
public String getStatus() { return status; }
|
||||||
|
public void setStatus(String status) { this.status = status; }
|
||||||
|
|
||||||
|
public Integer getPoints() { return points; }
|
||||||
|
public void setPoints(Integer points) { this.points = points; }
|
||||||
|
|
||||||
|
public boolean isIdle() { return idle; }
|
||||||
|
public void setIdle(boolean idle) { this.idle = idle; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("DeviceStatusInfo{status='%s', points=%d, idle=%s}",
|
||||||
|
status, points, idle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -129,6 +129,65 @@ public class DeviceStatusService {
|
|||||||
private boolean isDeviceAvailable(String val) {
|
private boolean isDeviceAvailable(String val) {
|
||||||
return "空闲".equals(val);
|
return "空闲".equals(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析特定设备的状态信息(用于检查是否完成游戏)
|
||||||
|
* @param jsonResponse JSON响应字符串
|
||||||
|
* @param machineId 设备ID
|
||||||
|
* @return 包含f0(点数)和f1(状态)的Map
|
||||||
|
*/
|
||||||
|
public Map<String, Object> parseDeviceStatusForMachine(String jsonResponse, String machineId) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
log.debug("解析设备 {} 的状态信息", machineId);
|
||||||
|
|
||||||
|
JsonNode rootNode = objectMapper.readTree(jsonResponse);
|
||||||
|
|
||||||
|
// 查找指定设备的信息
|
||||||
|
JsonNode deviceNode = rootNode.get(machineId);
|
||||||
|
if (deviceNode == null) {
|
||||||
|
log.warn("未找到设备 {} 的状态信息", machineId);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析f0(点数)和f1(状态)等信息
|
||||||
|
String val = deviceNode.get("val").asText();
|
||||||
|
String time = deviceNode.get("time").asText();
|
||||||
|
|
||||||
|
// 构建f0和f1格式的返回数据
|
||||||
|
Map<String, Object> f0Info = new HashMap<>();
|
||||||
|
Map<String, Object> f1Info = new HashMap<>();
|
||||||
|
|
||||||
|
// 假设val包含点数信息,f1包含状态信息
|
||||||
|
// 根据实际API响应格式调整
|
||||||
|
if (val.matches("\\d+")) {
|
||||||
|
// 如果val是数字,则认为是点数
|
||||||
|
f0Info.put("val", val);
|
||||||
|
f0Info.put("time", time);
|
||||||
|
result.put("f0", f0Info);
|
||||||
|
|
||||||
|
// 状态信息需要额外获取,这里先设置默认值
|
||||||
|
f1Info.put("val", "空闲"); // 如果能获取到点数,可能表示空闲
|
||||||
|
f1Info.put("time", time);
|
||||||
|
result.put("f1", f1Info);
|
||||||
|
} else {
|
||||||
|
// 如果val不是数字,可能是状态信息
|
||||||
|
f1Info.put("val", val);
|
||||||
|
f1Info.put("time", time);
|
||||||
|
result.put("f1", f1Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("设备 {} 状态解析完成: {}", machineId, result);
|
||||||
|
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
log.error("解析设备 {} 状态JSON失败: {}", machineId, e.getMessage(), e);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("解析设备 {} 状态时发生异常", machineId, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按系列分组空闲设备
|
* 按系列分组空闲设备
|
||||||
|
|||||||
@@ -280,6 +280,33 @@ public class ScriptClient {
|
|||||||
.doOnSuccess(result -> log.info("保存总次数接口调用成功: url={}, result={}", url, result))
|
.doOnSuccess(result -> log.info("保存总次数接口调用成功: url={}, result={}", url, result))
|
||||||
.doOnError(e -> log.warn("保存总次数接口调用失败: times={}, error={}", times, e.toString()));
|
.doOnError(e -> log.warn("保存总次数接口调用失败: times={}, error={}", times, e.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取特定设备的状态信息(用于检查是否完成游戏)
|
||||||
|
* @param machineId 设备ID
|
||||||
|
* @return 设备状态信息的Map,包含f0(点数)和f1(状态)等信息
|
||||||
|
*/
|
||||||
|
public Mono<java.util.Map<String, Object>> getDeviceStatus(String machineId) {
|
||||||
|
String url = "http://36.138.184.60:1234/yijianwan_netfile/readAllMsg?文件名=判断分数";
|
||||||
|
log.debug("获取设备状态: 设备={}, url={}", machineId, url);
|
||||||
|
|
||||||
|
return webClient.get()
|
||||||
|
.uri(url)
|
||||||
|
.accept(MediaType.APPLICATION_JSON)
|
||||||
|
.retrieve()
|
||||||
|
.bodyToMono(String.class)
|
||||||
|
.timeout(Duration.ofSeconds(10))
|
||||||
|
.map(jsonResponse -> {
|
||||||
|
// 解析JSON响应,提取指定设备的状态信息
|
||||||
|
return deviceStatusService.parseDeviceStatusForMachine(jsonResponse, machineId);
|
||||||
|
})
|
||||||
|
.doOnSuccess(deviceStatus -> {
|
||||||
|
log.debug("获取设备状态成功: 设备={}, 状态={}", machineId, deviceStatus);
|
||||||
|
})
|
||||||
|
.doOnError(e -> {
|
||||||
|
log.warn("获取设备状态失败: 设备={}, 错误={}", machineId, e.toString());
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -190,6 +190,7 @@ public class LinkListService {
|
|||||||
case "NEW" -> "新建";
|
case "NEW" -> "新建";
|
||||||
case "USING" -> "使用中";
|
case "USING" -> "使用中";
|
||||||
case "LOGGED_IN" -> "已登录";
|
case "LOGGED_IN" -> "已登录";
|
||||||
|
case "COMPLETED" -> "已完成";
|
||||||
case "REFUNDED" -> "已退款";
|
case "REFUNDED" -> "已退款";
|
||||||
case "EXPIRED" -> "已过期";
|
case "EXPIRED" -> "已过期";
|
||||||
default -> "未知状态";
|
default -> "未知状态";
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import com.gameplatform.server.model.entity.agent.LinkTask;
|
|||||||
|
|
||||||
import com.gameplatform.server.service.external.ScriptClient;
|
import com.gameplatform.server.service.external.ScriptClient;
|
||||||
import com.gameplatform.server.service.device.DeviceCodeMappingService;
|
import com.gameplatform.server.service.device.DeviceCodeMappingService;
|
||||||
|
import com.gameplatform.server.service.device.DeviceStatusCheckService;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -37,6 +38,7 @@ public class LinkStatusService {
|
|||||||
private final LinkBatchMapper linkBatchMapper;
|
private final LinkBatchMapper linkBatchMapper;
|
||||||
private final ScriptClient scriptClient;
|
private final ScriptClient scriptClient;
|
||||||
private final DeviceCodeMappingService deviceCodeMappingService;
|
private final DeviceCodeMappingService deviceCodeMappingService;
|
||||||
|
private final DeviceStatusCheckService deviceStatusCheckService;
|
||||||
|
|
||||||
|
|
||||||
// 状态描述映射
|
// 状态描述映射
|
||||||
@@ -45,17 +47,19 @@ public class LinkStatusService {
|
|||||||
STATUS_DESC_MAP.put("NEW", "新建");
|
STATUS_DESC_MAP.put("NEW", "新建");
|
||||||
STATUS_DESC_MAP.put("USING", "使用中");
|
STATUS_DESC_MAP.put("USING", "使用中");
|
||||||
STATUS_DESC_MAP.put("LOGGED_IN", "已登录");
|
STATUS_DESC_MAP.put("LOGGED_IN", "已登录");
|
||||||
|
STATUS_DESC_MAP.put("COMPLETED", "已完成");
|
||||||
STATUS_DESC_MAP.put("REFUNDED", "已退款");
|
STATUS_DESC_MAP.put("REFUNDED", "已退款");
|
||||||
STATUS_DESC_MAP.put("EXPIRED", "已过期");
|
STATUS_DESC_MAP.put("EXPIRED", "已过期");
|
||||||
}
|
}
|
||||||
|
|
||||||
public LinkStatusService(LinkTaskMapper linkTaskMapper, LinkBatchMapper linkBatchMapper,
|
public LinkStatusService(LinkTaskMapper linkTaskMapper, LinkBatchMapper linkBatchMapper,
|
||||||
ScriptClient scriptClient, DeviceCodeMappingService deviceCodeMappingService) {
|
ScriptClient scriptClient, DeviceCodeMappingService deviceCodeMappingService,
|
||||||
|
DeviceStatusCheckService deviceStatusCheckService) {
|
||||||
this.linkTaskMapper = linkTaskMapper;
|
this.linkTaskMapper = linkTaskMapper;
|
||||||
this.linkBatchMapper = linkBatchMapper;
|
this.linkBatchMapper = linkBatchMapper;
|
||||||
this.scriptClient = scriptClient;
|
this.scriptClient = scriptClient;
|
||||||
this.deviceCodeMappingService = deviceCodeMappingService;
|
this.deviceCodeMappingService = deviceCodeMappingService;
|
||||||
|
this.deviceStatusCheckService = deviceStatusCheckService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -312,8 +316,8 @@ public class LinkStatusService {
|
|||||||
// 如果未超过10分钟,执行自动刷新
|
// 如果未超过10分钟,执行自动刷新
|
||||||
log.info("链接状态是USING,执行自动刷新");
|
log.info("链接状态是USING,执行自动刷新");
|
||||||
performAutoRefresh(linkTask);
|
performAutoRefresh(linkTask);
|
||||||
} else if ("LOGGED_IN".equals(linkTask.getStatus()) || "REFUNDED".equals(linkTask.getStatus())) {
|
} else if ("LOGGED_IN".equals(linkTask.getStatus()) || "COMPLETED".equals(linkTask.getStatus()) || "REFUNDED".equals(linkTask.getStatus())) {
|
||||||
// 已上号或已退款状态,不需要刷新
|
// 已上号、已完成或已退款状态,不需要刷新
|
||||||
log.info("链接状态为 {},不需要刷新", linkTask.getStatus());
|
log.info("链接状态为 {},不需要刷新", linkTask.getStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,6 +451,15 @@ public class LinkStatusService {
|
|||||||
log.info("从空闲设备列表中选择设备: {}", selectedDevice);
|
log.info("从空闲设备列表中选择设备: {}", selectedDevice);
|
||||||
log.info("设备选择详情: 可用设备总数={}, 选择了第一个设备={}",
|
log.info("设备选择详情: 可用设备总数={}, 选择了第一个设备={}",
|
||||||
deviceStatus.getAvailableDevices().size(), selectedDevice);
|
deviceStatus.getAvailableDevices().size(), selectedDevice);
|
||||||
|
|
||||||
|
// 7.5. 检查该设备是否有之前的LOGGED_IN状态链接任务需要完成
|
||||||
|
try {
|
||||||
|
log.info("检查设备 {} 是否有需要完成的链接任务", selectedDevice);
|
||||||
|
deviceStatusCheckService.checkDeviceStatusAndUpdateTasks(selectedDevice, "选区请求");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("检查设备状态时发生异常,继续选区流程: {}", e.getMessage());
|
||||||
|
// 不影响选区流程,只记录警告日志
|
||||||
|
}
|
||||||
|
|
||||||
// 8. 调用保存总次数接口
|
// 8. 调用保存总次数接口
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package com.gameplatform.server.task;
|
||||||
|
|
||||||
|
import com.gameplatform.server.model.dto.device.DeviceStatusResponse;
|
||||||
|
import com.gameplatform.server.service.device.DeviceStatusCheckService;
|
||||||
|
import com.gameplatform.server.service.external.ScriptClient;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备状态检查定时任务
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class DeviceStatusCheckTask {
|
||||||
|
|
||||||
|
private final ScriptClient scriptClient;
|
||||||
|
private final DeviceStatusCheckService deviceStatusCheckService;
|
||||||
|
|
||||||
|
public DeviceStatusCheckTask(ScriptClient scriptClient, DeviceStatusCheckService deviceStatusCheckService) {
|
||||||
|
this.scriptClient = scriptClient;
|
||||||
|
this.deviceStatusCheckService = deviceStatusCheckService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每分钟检查一次空闲设备,并更新相关链接任务状态
|
||||||
|
*/
|
||||||
|
@Scheduled(fixedRate = 60000) // 每60秒执行一次
|
||||||
|
public void checkIdleDevicesAndUpdateTasks() {
|
||||||
|
log.debug("=== 开始定时检查空闲设备 ===");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. 获取所有设备状态
|
||||||
|
DeviceStatusResponse deviceStatus = scriptClient.checkAvailableDeviceStatus().block();
|
||||||
|
|
||||||
|
if (deviceStatus == null) {
|
||||||
|
log.warn("获取设备状态失败,跳过本次检查");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> availableDevices = deviceStatus.getAvailableDevices();
|
||||||
|
if (availableDevices.isEmpty()) {
|
||||||
|
log.debug("当前没有空闲设备");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("发现 {} 个空闲设备: {}", availableDevices.size(), availableDevices);
|
||||||
|
|
||||||
|
// 2. 对每个空闲设备检查是否有相关的LOGGED_IN状态链接任务
|
||||||
|
for (String deviceId : availableDevices) {
|
||||||
|
try {
|
||||||
|
deviceStatusCheckService.checkDeviceStatusAndUpdateTasks(deviceId, "定时检查");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("检查设备 {} 状态时发生异常", deviceId, e);
|
||||||
|
// 继续检查下一个设备,不因为一个设备出错而中断整个流程
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("定时检查空闲设备时发生异常", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("=== 定时检查空闲设备完成 ===");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,24 +21,25 @@
|
|||||||
<result property="qrCreatedAt" column="qr_created_at" />
|
<result property="qrCreatedAt" column="qr_created_at" />
|
||||||
<result property="qrExpireAt" column="qr_expire_at" />
|
<result property="qrExpireAt" column="qr_expire_at" />
|
||||||
<result property="firstRegionSelectAt" column="first_region_select_at" />
|
<result property="firstRegionSelectAt" column="first_region_select_at" />
|
||||||
|
<result property="completedPoints" column="completed_points" />
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<select id="findById" parameterType="long" resultMap="LinkTaskMap">
|
<select id="findById" parameterType="long" resultMap="LinkTaskMap">
|
||||||
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at
|
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at, completed_points
|
||||||
FROM link_task
|
FROM link_task
|
||||||
WHERE id = #{id}
|
WHERE id = #{id}
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="findByCodeNo" parameterType="string" resultMap="LinkTaskMap">
|
<select id="findByCodeNo" parameterType="string" resultMap="LinkTaskMap">
|
||||||
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at
|
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at, completed_points
|
||||||
FROM link_task
|
FROM link_task
|
||||||
WHERE code_no = #{codeNo}
|
WHERE code_no = #{codeNo}
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="findByTokenHash" parameterType="string" resultMap="LinkTaskMap">
|
<select id="findByTokenHash" parameterType="string" resultMap="LinkTaskMap">
|
||||||
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at
|
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at, completed_points
|
||||||
FROM link_task
|
FROM link_task
|
||||||
WHERE token_hash = #{tokenHash}
|
WHERE token_hash = #{tokenHash}
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
@@ -78,7 +79,7 @@
|
|||||||
</update>
|
</update>
|
||||||
|
|
||||||
<select id="findByAgentId" resultMap="LinkTaskMap">
|
<select id="findByAgentId" resultMap="LinkTaskMap">
|
||||||
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at
|
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at, completed_points
|
||||||
FROM link_task
|
FROM link_task
|
||||||
WHERE agent_id = #{agentId}
|
WHERE agent_id = #{agentId}
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
@@ -90,7 +91,7 @@
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="findByAgentIdAndStatus" resultMap="LinkTaskMap">
|
<select id="findByAgentIdAndStatus" resultMap="LinkTaskMap">
|
||||||
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at
|
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at, completed_points
|
||||||
FROM link_task
|
FROM link_task
|
||||||
WHERE agent_id = #{agentId} AND status = #{status}
|
WHERE agent_id = #{agentId} AND status = #{status}
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
@@ -102,7 +103,7 @@
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="findByBatchId" resultMap="LinkTaskMap">
|
<select id="findByBatchId" resultMap="LinkTaskMap">
|
||||||
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at
|
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at, completed_points
|
||||||
FROM link_task
|
FROM link_task
|
||||||
WHERE batch_id = #{batchId}
|
WHERE batch_id = #{batchId}
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
@@ -114,7 +115,7 @@
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="findExpiredTasks" resultMap="LinkTaskMap">
|
<select id="findExpiredTasks" resultMap="LinkTaskMap">
|
||||||
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at
|
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at, completed_points
|
||||||
FROM link_task
|
FROM link_task
|
||||||
WHERE expire_at <= #{expireTime} AND status IN ('NEW', 'USING')
|
WHERE expire_at <= #{expireTime} AND status IN ('NEW', 'USING')
|
||||||
ORDER BY expire_at ASC
|
ORDER BY expire_at ASC
|
||||||
@@ -122,7 +123,7 @@
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="findLinkTasksWithConditions" resultMap="LinkTaskMap">
|
<select id="findLinkTasksWithConditions" resultMap="LinkTaskMap">
|
||||||
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at
|
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at, completed_points
|
||||||
FROM link_task
|
FROM link_task
|
||||||
<where>
|
<where>
|
||||||
agent_id = #{agentId}
|
agent_id = #{agentId}
|
||||||
@@ -199,7 +200,7 @@
|
|||||||
</delete>
|
</delete>
|
||||||
|
|
||||||
<select id="findByCodeNosAndAgentId" resultMap="LinkTaskMap">
|
<select id="findByCodeNosAndAgentId" resultMap="LinkTaskMap">
|
||||||
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at
|
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at, completed_points, completed_points
|
||||||
FROM link_task
|
FROM link_task
|
||||||
WHERE agent_id = #{agentId}
|
WHERE agent_id = #{agentId}
|
||||||
AND code_no IN
|
AND code_no IN
|
||||||
@@ -207,4 +208,10 @@
|
|||||||
#{codeNo}
|
#{codeNo}
|
||||||
</foreach>
|
</foreach>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="findByMachineIdAndStatus" resultMap="LinkTaskMap">
|
||||||
|
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at, completed_points, completed_points
|
||||||
|
FROM link_task
|
||||||
|
WHERE machine_id = #{machineId} AND status = #{status}
|
||||||
|
</select>
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
Reference in New Issue
Block a user