fix 重启恢复任务

This commit is contained in:
wells
2026-08-05 20:38:23 +08:00
parent e5f60b7b30
commit 96207c03b9
4 changed files with 205 additions and 91 deletions
+157 -91
View File
@@ -1,11 +1,13 @@
package com.tailbet.job; package com.tailbet.job;
import com.tailbet.model.RoundTask;
import com.tailbet.model.entity.GameRound; import com.tailbet.model.entity.GameRound;
import com.tailbet.model.entity.RedPacket; import com.tailbet.model.entity.RedPacket;
import com.tailbet.model.entity.User; import com.tailbet.model.entity.User;
import com.tailbet.mapper.DrawRecordMapper; import com.tailbet.mapper.DrawRecordMapper;
import com.tailbet.mapper.DigitWhiteMapper; import com.tailbet.mapper.DigitWhiteMapper;
import com.tailbet.mapper.GameRoundMapper; import com.tailbet.mapper.GameRoundMapper;
import com.tailbet.mapper.PendingTaskMapper;
import com.tailbet.mapper.RedPacketMapper; import com.tailbet.mapper.RedPacketMapper;
import com.tailbet.mapper.RedPacketRecvMapper; import com.tailbet.mapper.RedPacketRecvMapper;
import com.tailbet.mapper.SysConfigMapper; import com.tailbet.mapper.SysConfigMapper;
@@ -14,6 +16,8 @@ import com.tailbet.model.entity.DigitWhite;
import com.tailbet.model.entity.DrawRecord; import com.tailbet.model.entity.DrawRecord;
import com.tailbet.model.entity.RedPacketRecv; import com.tailbet.model.entity.RedPacketRecv;
import com.tailbet.model.entity.SysConfig; import com.tailbet.model.entity.SysConfig;
import com.tailbet.model.entity.PendingTask;
import com.tailbet.model.PendingTaskInfo;
import com.tailbet.openim.OpenIMClient; import com.tailbet.openim.OpenIMClient;
import com.tailbet.service.IGameService; import com.tailbet.service.IGameService;
import com.tailbet.service.IRedPacketService; import com.tailbet.service.IRedPacketService;
@@ -46,6 +50,7 @@ public class GameJob {
private final SysConfigMapper sysConfigMapper; private final SysConfigMapper sysConfigMapper;
private final UserMapper userMapper; private final UserMapper userMapper;
private final DigitWhiteMapper digitWhiteMapper; private final DigitWhiteMapper digitWhiteMapper;
private final PendingTaskMapper pendingTaskMapper;
private final IGameService gameService; private final IGameService gameService;
private final IRedPacketService redPacketService; private final IRedPacketService redPacketService;
private final IFakeUserService fakeUserService; private final IFakeUserService fakeUserService;
@@ -58,24 +63,106 @@ public class GameJob {
private final Map<String, String> configCache = new ConcurrentHashMap<>(); private final Map<String, String> configCache = new ConcurrentHashMap<>();
// 延迟任务队列 // 延迟任务队列
private final Map<String, PendingTask> pendingTasks = new ConcurrentHashMap<>(); private final Map<String, PendingTaskInfo> pendingTasks = new ConcurrentHashMap<>();
// 任务ID生成器 // 任务ID生成器 (格式: {type}_{groupId}_{自增序号})
private java.util.concurrent.atomic.AtomicLong taskIdGenerator = new java.util.concurrent.atomic.AtomicLong(1); private java.util.concurrent.atomic.AtomicLong taskIdGenerator = new java.util.concurrent.atomic.AtomicLong(1);
/** /**
* 启动时加载配置缓存 * 启动时加载配置缓存并恢复内存状态
*/ */
@jakarta.annotation.PostConstruct @jakarta.annotation.PostConstruct
public void loadConfig() { public void loadConfig() {
try { try {
// 加载配置缓存
List<SysConfig> configs = sysConfigMapper.selectList(null); List<SysConfig> configs = sysConfigMapper.selectList(null);
for (SysConfig config : configs) { for (SysConfig config : configs) {
configCache.put(config.getCfgKey(), config.getCfgValue()); configCache.put(config.getCfgKey(), config.getCfgValue());
} }
log.info("配置缓存加载完成,共 {} 条配置", configs.size()); log.info("配置缓存加载完成,共 {} 条配置", configs.size());
// 恢复进行中的局状态
recoverRoundTasks();
// 恢复延迟任务队列
recoverPendingTasks();
} catch (Exception e) { } catch (Exception e) {
log.error("配置缓存加载失败: {}", e.getMessage()); log.error("启动初始化失败: {}", e.getMessage(), e);
}
}
/**
* 从数据库恢复进行中的局状态
*/
private void recoverRoundTasks() {
try {
List<GameRound> ongoingRounds = gameRoundMapper.selectList(
new LambdaQueryWrapper<GameRound>()
.eq(GameRound::getStatus, 0));
if (ongoingRounds.isEmpty()) {
log.info("无进行中的局,无需恢复");
return;
}
int bettingInterval = getConfigInt("betting_interval", 60);
for (GameRound round : ongoingRounds) {
RoundTask task = new RoundTask();
task.setRoundId(round.getId());
task.setGroupId(round.getGroupId());
// 优先使用数据库中的endBetTime,否则根据startTime计算
if (round.getEndBetTime() != null) {
task.setEndBetTime(round.getEndBetTime());
} else if (round.getStartTime() != null) {
task.setEndBetTime(round.getStartTime().plusSeconds(bettingInterval));
} else {
// 兜底:使用配置的下注时长
task.setEndBetTime(LocalDateTime.now().plusSeconds(bettingInterval));
}
roundTasks.put(round.getId(), task);
}
log.info("局状态恢复完成,共恢复 {} 个进行中的局", ongoingRounds.size());
} catch (Exception e) {
log.error("恢复局状态失败: {}", e.getMessage(), e);
}
}
/**
* 从数据库恢复延迟任务队列
*/
private void recoverPendingTasks() {
try {
List<PendingTask> dbTasks = pendingTaskMapper.selectList(
new LambdaQueryWrapper<PendingTask>()
.gt(PendingTask::getExecuteTime, LocalDateTime.now()));
if (dbTasks == null || dbTasks.isEmpty()) {
log.info("无待执行的延迟任务,无需恢复");
return;
}
for (PendingTask dbTask : dbTasks) {
if (dbTask.getExecuteTime() == null || !dbTask.getExecuteTime().isAfter(LocalDateTime.now())) {
continue;
}
PendingTaskInfo info = new PendingTaskInfo();
info.setTaskId(dbTask.getTaskId());
info.setType(dbTask.getType());
info.setRoundId(dbTask.getRoundId());
info.setGroupId(dbTask.getGroupId());
info.setDigit(dbTask.getDigit());
info.setExecuteTime(dbTask.getExecuteTime());
pendingTasks.put(info.getTaskId(), info);
}
log.info("延迟任务恢复完成,共恢复 {} 个待执行任务", pendingTasks.size());
} catch (Exception e) {
log.error("恢复延迟任务失败: {}", e.getMessage(), e);
} }
} }
@@ -186,7 +273,7 @@ public class GameJob {
for (GameRound round : ongoingRounds) { for (GameRound round : ongoingRounds) {
// 检查是否超过接注时间 // 检查是否超过接注时间
RoundTask task = roundTasks.get(round.getId()); RoundTask task = roundTasks.get(round.getId());
if (task != null && task.endBetTime.isBefore(LocalDateTime.now())) { if (task != null && task.getEndBetTime().isBefore(LocalDateTime.now())) {
// 自动结束接注 // 自动结束接注
try { try {
gameService.endBet(round.getGroupId(), 0L); // 0L表示系统自动触发 gameService.endBet(round.getGroupId(), 0L); // 0L表示系统自动触发
@@ -212,14 +299,17 @@ public class GameJob {
* 安排开始发红包 * 安排开始发红包
*/ */
public String scheduleStartRp(Long roundId, Long groupId, int delaySeconds) { public String scheduleStartRp(Long roundId, Long groupId, int delaySeconds) {
String taskId = "start_rp_" + taskIdGenerator.getAndIncrement(); String taskId = "start_rp_" + groupId + "_" + taskIdGenerator.getAndIncrement();
PendingTask task = new PendingTask(); PendingTaskInfo task = new PendingTaskInfo();
task.setTaskId(taskId); task.setTaskId(taskId);
task.setType("start_rp"); task.setType("start_rp");
task.setRoundId(roundId); task.setRoundId(roundId);
task.setGroupId(groupId); task.setGroupId(groupId);
task.setExecuteTime(LocalDateTime.now().plusSeconds(delaySeconds)); LocalDateTime executeTime = LocalDateTime.now().plusSeconds(delaySeconds);
task.setExecuteTime(executeTime);
pendingTasks.put(taskId, task); pendingTasks.put(taskId, task);
// 持久化到数据库
saveTaskToDb(taskId, "start_rp", roundId, groupId, null, executeTime);
log.info("安排{}秒后开始发红包: roundId={}", delaySeconds, roundId); log.info("安排{}秒后开始发红包: roundId={}", delaySeconds, roundId);
return taskId; return taskId;
} }
@@ -228,15 +318,18 @@ public class GameJob {
* 安排自动结算 * 安排自动结算
*/ */
public String scheduleSettle(Long roundId, Long groupId, int digit, int delaySeconds) { public String scheduleSettle(Long roundId, Long groupId, int digit, int delaySeconds) {
String taskId = "settle_" + taskIdGenerator.getAndIncrement(); String taskId = "settle_" + groupId + "_" + taskIdGenerator.getAndIncrement();
PendingTask task = new PendingTask(); PendingTaskInfo task = new PendingTaskInfo();
task.setTaskId(taskId); task.setTaskId(taskId);
task.setType("settle"); task.setType("settle");
task.setRoundId(roundId); task.setRoundId(roundId);
task.setGroupId(groupId); task.setGroupId(groupId);
task.setDigit(digit); task.setDigit(digit);
task.setExecuteTime(LocalDateTime.now().plusSeconds(delaySeconds)); LocalDateTime executeTime = LocalDateTime.now().plusSeconds(delaySeconds);
task.setExecuteTime(executeTime);
pendingTasks.put(taskId, task); pendingTasks.put(taskId, task);
// 持久化到数据库
saveTaskToDb(taskId, "settle", roundId, groupId, digit, executeTime);
log.info("安排{}秒后结算: taskId={}, roundId={}, digit={}", delaySeconds, taskId, roundId, digit); log.info("安排{}秒后结算: taskId={}, roundId={}, digit={}", delaySeconds, taskId, roundId, digit);
return taskId; return taskId;
} }
@@ -245,13 +338,16 @@ public class GameJob {
* 安排自动下一期 * 安排自动下一期
*/ */
public String scheduleAutoNextTask(Long groupId, int delaySeconds) { public String scheduleAutoNextTask(Long groupId, int delaySeconds) {
String taskId = "auto_next_" + taskIdGenerator.getAndIncrement(); String taskId = "auto_next_" + groupId + "_" + taskIdGenerator.getAndIncrement();
PendingTask task = new PendingTask(); PendingTaskInfo task = new PendingTaskInfo();
task.setTaskId(taskId); task.setTaskId(taskId);
task.setType("auto_next"); task.setType("auto_next");
task.setGroupId(groupId); task.setGroupId(groupId);
task.setExecuteTime(LocalDateTime.now().plusSeconds(delaySeconds)); LocalDateTime executeTime = LocalDateTime.now().plusSeconds(delaySeconds);
task.setExecuteTime(executeTime);
pendingTasks.put(taskId, task); pendingTasks.put(taskId, task);
// 持久化到数据库
saveTaskToDb(taskId, "auto_next", null, groupId, null, executeTime);
log.info("安排{}秒后自动开始下一期: groupId={}", delaySeconds, groupId); log.info("安排{}秒后自动开始下一期: groupId={}", delaySeconds, groupId);
return taskId; return taskId;
} }
@@ -260,26 +356,60 @@ public class GameJob {
* 安排机器人发包 * 安排机器人发包
*/ */
public String scheduleBotSendRp(Long roundId, Long groupId, int delaySeconds) { public String scheduleBotSendRp(Long roundId, Long groupId, int delaySeconds) {
String taskId = "bot_send_rp_" + taskIdGenerator.getAndIncrement(); String taskId = "bot_send_rp_" + groupId + "_" + taskIdGenerator.getAndIncrement();
PendingTask task = new PendingTask(); PendingTaskInfo task = new PendingTaskInfo();
task.setTaskId(taskId); task.setTaskId(taskId);
task.setType("bot_send_rp"); task.setType("bot_send_rp");
task.setRoundId(roundId); task.setRoundId(roundId);
task.setGroupId(groupId); task.setGroupId(groupId);
task.setExecuteTime(LocalDateTime.now().plusSeconds(delaySeconds)); LocalDateTime executeTime = LocalDateTime.now().plusSeconds(delaySeconds);
task.setExecuteTime(executeTime);
pendingTasks.put(taskId, task); pendingTasks.put(taskId, task);
// 持久化到数据库
saveTaskToDb(taskId, "bot_send_rp", roundId, groupId, null, executeTime);
log.info("安排{}秒后机器人发包: roundId={}", delaySeconds, roundId); log.info("安排{}秒后机器人发包: roundId={}", delaySeconds, roundId);
return taskId; return taskId;
} }
/**
* 保存任务到数据库
*/
private void saveTaskToDb(String taskId, String type, Long roundId, Long groupId, Integer digit, LocalDateTime executeTime) {
try {
PendingTask dbTask = new PendingTask();
dbTask.setTaskId(taskId);
dbTask.setType(type);
dbTask.setRoundId(roundId);
dbTask.setGroupId(groupId);
dbTask.setDigit(digit);
dbTask.setExecuteTime(executeTime);
dbTask.setCreateTime(LocalDateTime.now());
pendingTaskMapper.insert(dbTask);
} catch (Exception e) {
log.error("保存任务到数据库失败: taskId={}, error={}", taskId, e.getMessage());
}
}
/**
* 从数据库删除任务
*/
private void deleteTaskFromDb(String taskId) {
try {
pendingTaskMapper.delete(new LambdaQueryWrapper<PendingTask>()
.eq(PendingTask::getTaskId, taskId));
} catch (Exception e) {
log.error("从数据库删除任务失败: taskId={}, error={}", taskId, e.getMessage());
}
}
/** /**
* 开始游戏后安排定时器 * 开始游戏后安排定时器
*/ */
public void onGameStart(Long roundId, Long groupId, int intervalSeconds) { public void onGameStart(Long roundId, Long groupId, int intervalSeconds) {
RoundTask task = new RoundTask(); RoundTask task = new RoundTask();
task.roundId = roundId; task.setRoundId(roundId);
task.groupId = groupId; task.setGroupId(groupId);
task.endBetTime = LocalDateTime.now().plusSeconds(intervalSeconds); task.setEndBetTime(LocalDateTime.now().plusSeconds(intervalSeconds));
roundTasks.put(roundId, task); roundTasks.put(roundId, task);
// 广播开始下注 // 广播开始下注
@@ -475,75 +605,6 @@ public class GameJob {
return sb.toString().trim(); return sb.toString().trim();
} }
/**
* 局任务信息
*/
private static class RoundTask {
Long roundId;
Long groupId;
LocalDateTime endBetTime;
LocalDateTime startRpTime;
}
/**
* 延迟任务信息
*/
private static class PendingTask {
private String taskId;
private String type;
private Long roundId;
private Long groupId;
private Integer digit;
private LocalDateTime executeTime;
public String getTaskId() {
return taskId;
}
public void setTaskId(String taskId) {
this.taskId = taskId;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Long getRoundId() {
return roundId;
}
public void setRoundId(Long roundId) {
this.roundId = roundId;
}
public Long getGroupId() {
return groupId;
}
public void setGroupId(Long groupId) {
this.groupId = groupId;
}
public Integer getDigit() {
return digit;
}
public void setDigit(Integer digit) {
this.digit = digit;
}
public LocalDateTime getExecuteTime() {
return executeTime;
}
public void setExecuteTime(LocalDateTime executeTime) {
this.executeTime = executeTime;
}
}
/** /**
* 处理延迟任务队列(每秒执行) * 处理延迟任务队列(每秒执行)
@@ -552,9 +613,14 @@ public class GameJob {
public void processPendingTasks() { public void processPendingTasks() {
LocalDateTime now = LocalDateTime.now(); LocalDateTime now = LocalDateTime.now();
pendingTasks.entrySet().removeIf(entry -> { pendingTasks.entrySet().removeIf(entry -> {
PendingTask task = entry.getValue(); PendingTaskInfo task = entry.getValue();
if (task.getExecuteTime() == null) {
deleteTaskFromDb(task.getTaskId());
return true;
}
if (task.getExecuteTime().isBefore(now) || task.getExecuteTime().isEqual(now)) { if (task.getExecuteTime().isBefore(now) || task.getExecuteTime().isEqual(now)) {
executeTask(task); executeTask(task);
deleteTaskFromDb(task.getTaskId());
return true; return true;
} }
return false; return false;
@@ -564,7 +630,7 @@ public class GameJob {
/** /**
* 执行延迟任务 * 执行延迟任务
*/ */
private void executeTask(PendingTask task) { private void executeTask(PendingTaskInfo task) {
try { try {
switch (task.getType()) { switch (task.getType()) {
case "settle": case "settle":
@@ -0,0 +1,17 @@
package com.tailbet.model;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 延迟任务信息(内存缓存用)
*/
@Data
public class PendingTaskInfo {
private String taskId;
private String type;
private Long roundId;
private Long groupId;
private Integer digit;
private LocalDateTime executeTime;
}
@@ -0,0 +1,16 @@
package com.tailbet.model;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 局任务信息
*/
@Data
public class RoundTask {
private Long roundId;
private Long groupId;
private LocalDateTime endBetTime;
private LocalDateTime startRpTime;
}
+15
View File
@@ -229,8 +229,23 @@ ALTER TABLE red_packet ADD COLUMN share_taken_count INT DEFAULT 0 COMMENT '已
-- game_round 表增加第一个红包ID字段 -- game_round 表增加第一个红包ID字段
ALTER TABLE game_round ADD COLUMN first_rp_id BIGINT COMMENT '第一个红包ID'; ALTER TABLE game_round ADD COLUMN first_rp_id BIGINT COMMENT '第一个红包ID';
-- 新增配置项 -- 新增配置项
INSERT INTO sys_config (cfg_key, cfg_value, remark) VALUES INSERT INTO sys_config (cfg_key, cfg_value, remark) VALUES
('bet_window_seconds', '60', '接注窗口时间(秒)'), ('bet_window_seconds', '60', '接注窗口时间(秒)'),
('fake_recv_seconds', '5', '托领取超时时间(秒)'); ('fake_recv_seconds', '5', '托领取超时时间(秒)');
CREATE TABLE `pending_task` (
`id` BIGINT AUTO_INCREMENT PRIMARY KEY,
`task_id` VARCHAR(64) NOT NULL COMMENT '任务ID',
`type` VARCHAR(32) NOT NULL COMMENT 'start_rp/settle/auto_next/bot_send_rp',
`round_id` BIGINT COMMENT '局ID',
`group_id` BIGINT NOT NULL COMMENT '群ID',
`digit` INT COMMENT '开奖尾数(settle任务用)',
`execute_time` DATETIME NOT NULL COMMENT '执行时间',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
INDEX `idx_execute_time` (`execute_time`),
UNIQUE INDEX `idx_task_id` (`task_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;