694 lines
25 KiB
Java
694 lines
25 KiB
Java
package com.tailbet.job;
|
|
|
|
import com.tailbet.model.RoundTask;
|
|
import com.tailbet.model.entity.GameRound;
|
|
import com.tailbet.model.entity.RedPacket;
|
|
import com.tailbet.model.entity.User;
|
|
import com.tailbet.mapper.DrawRecordMapper;
|
|
import com.tailbet.mapper.DigitWhiteMapper;
|
|
import com.tailbet.mapper.GameRoundMapper;
|
|
import com.tailbet.mapper.PendingTaskMapper;
|
|
import com.tailbet.mapper.RedPacketMapper;
|
|
import com.tailbet.mapper.RedPacketRecvMapper;
|
|
import com.tailbet.mapper.SysConfigMapper;
|
|
import com.tailbet.mapper.UserMapper;
|
|
import com.tailbet.model.entity.DigitWhite;
|
|
import com.tailbet.model.entity.DrawRecord;
|
|
import com.tailbet.model.entity.RedPacketRecv;
|
|
import com.tailbet.model.entity.SysConfig;
|
|
import com.tailbet.model.entity.PendingTask;
|
|
import com.tailbet.model.PendingTaskInfo;
|
|
import com.tailbet.openim.OpenIMApiClient;
|
|
import com.tailbet.service.IGameService;
|
|
import com.tailbet.service.IRedPacketService;
|
|
import com.tailbet.service.IFakeUserService;
|
|
import com.tailbet.util.RedisLockUtil;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
import lombok.RequiredArgsConstructor;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import org.springframework.scheduling.annotation.Scheduled;
|
|
import org.springframework.stereotype.Component;
|
|
|
|
import java.math.BigDecimal;
|
|
import java.time.LocalDateTime;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
/**
|
|
* 游戏定时任务
|
|
*/
|
|
@Slf4j
|
|
@Component
|
|
@RequiredArgsConstructor
|
|
public class GameJob {
|
|
|
|
private final GameRoundMapper gameRoundMapper;
|
|
private final RedPacketMapper redPacketMapper;
|
|
private final RedPacketRecvMapper redPacketRecvMapper;
|
|
private final DrawRecordMapper drawRecordMapper;
|
|
private final SysConfigMapper sysConfigMapper;
|
|
private final UserMapper userMapper;
|
|
private final DigitWhiteMapper digitWhiteMapper;
|
|
private final PendingTaskMapper pendingTaskMapper;
|
|
private final IGameService gameService;
|
|
private final IRedPacketService redPacketService;
|
|
private final IFakeUserService fakeUserService;
|
|
private final OpenIMApiClient openIMApiClient;
|
|
private final com.tailbet.config.OpenIMProperties openIMProperties;
|
|
|
|
// 局状态监控(使用内存缓存,实际生产应使用Redis)
|
|
private final Map<Long, RoundTask> roundTasks = new ConcurrentHashMap<>();
|
|
|
|
// 配置缓存
|
|
private final Map<String, String> configCache = new ConcurrentHashMap<>();
|
|
|
|
// 延迟任务队列
|
|
private final Map<String, PendingTaskInfo> pendingTasks = new ConcurrentHashMap<>();
|
|
|
|
// 任务ID生成器 (格式: {type}_{groupId}_{自增序号})
|
|
private java.util.concurrent.atomic.AtomicLong taskIdGenerator = new java.util.concurrent.atomic.AtomicLong(1);
|
|
|
|
/**
|
|
* 启动时加载配置缓存并恢复内存状态
|
|
*/
|
|
@jakarta.annotation.PostConstruct
|
|
public void loadConfig() {
|
|
try {
|
|
// 加载配置缓存
|
|
List<SysConfig> configs = sysConfigMapper.selectList(null);
|
|
for (SysConfig config : configs) {
|
|
configCache.put(config.getCfgKey(), config.getCfgValue());
|
|
}
|
|
log.info("配置缓存加载完成,共 {} 条配置", configs.size());
|
|
|
|
// 恢复进行中的局状态
|
|
recoverRoundTasks();
|
|
|
|
// 恢复延迟任务队列
|
|
recoverPendingTasks();
|
|
|
|
} catch (Exception e) {
|
|
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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取配置值
|
|
*/
|
|
private String getConfig(String key) {
|
|
return configCache.get(key);
|
|
}
|
|
|
|
/**
|
|
* 获取整型配置值
|
|
*/
|
|
public int getConfigInt(String key, int defaultValue) {
|
|
String value = configCache.get(key);
|
|
if (value == null) return defaultValue;
|
|
try {
|
|
return Integer.parseInt(value);
|
|
} catch (NumberFormatException e) {
|
|
return defaultValue;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 检查红包是否需要托领取(每3秒执行)
|
|
*/
|
|
@Scheduled(fixedDelay = 3000)
|
|
public void checkRedPacketForFakeReceive() {
|
|
// 查找进行中的游戏红包
|
|
List<RedPacket> ongoingRps = redPacketMapper.selectList(new LambdaQueryWrapper<RedPacket>()
|
|
.eq(RedPacket::getStatus, 0)
|
|
.eq(RedPacket::getType, IRedPacketService.TYPE_GAME)
|
|
.isNotNull(RedPacket::getRoundId));
|
|
|
|
for (RedPacket rp : ongoingRps) {
|
|
LocalDateTime createTime = rp.getCreateTime();
|
|
// 发出超过配置时间未领完,让托领取
|
|
int fakeRecvTimeout = getConfigInt("fake_recv_seconds", 5);
|
|
if (createTime.plusSeconds(fakeRecvTimeout).isBefore(LocalDateTime.now())) {
|
|
// 查找未领取的用户,随机选一个托领取
|
|
try {
|
|
fakeUserReceiveRemaining(rp);
|
|
} catch (Exception e) {
|
|
log.error("托领取红包失败: rpId={}, error={}", rp.getId(), e.getMessage());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 假人托领取剩余份额(循环领取直到红包领完)
|
|
*/
|
|
private void fakeUserReceiveRemaining(RedPacket rp) {
|
|
List<User> fakes = fakeUserService.getFakeUsers();
|
|
if (fakes.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
// 循环领取直到红包领完
|
|
while (true) {
|
|
RedPacket rpCheck = redPacketMapper.selectById(rp.getId());
|
|
if (rpCheck == null || rpCheck.getStatus() != 0) {
|
|
// 红包已领完或不存在
|
|
break;
|
|
}
|
|
|
|
// 查找还未领取的假人
|
|
User fakeToUse = null;
|
|
for (User fake : fakes) {
|
|
// 检查该假人是否已领取过本红包
|
|
var received = redPacketRecvMapper.selectList(
|
|
new LambdaQueryWrapper<RedPacketRecv>()
|
|
.eq(RedPacketRecv::getRpId, rp.getId())
|
|
.eq(RedPacketRecv::getUserId, fake.getId()));
|
|
if (received.isEmpty()) {
|
|
fakeToUse = fake;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (fakeToUse == null) {
|
|
// 所有假人都已领取过,无法继续
|
|
log.warn("所有假人都已领取过本红包,无法继续: rpId={}", rp.getId());
|
|
break;
|
|
}
|
|
|
|
final User fakeUser = fakeToUse;
|
|
try {
|
|
// 使用分布式锁防止多领
|
|
RedisLockUtil.lock("rp:receive:" + rp.getId(),
|
|
() -> redPacketService.receiveRp(rp.getId(), fakeUser.getId()));
|
|
log.info("假人{}领取红包{}剩余份额", fakeToUse.getId(), rp.getId());
|
|
|
|
} catch (Exception e) {
|
|
log.error("假人领取红包失败: rpId={}, fakeId={}, error={}", rp.getId(), fakeUser.getId(), e.getMessage());
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 检查局超时(每10秒执行)
|
|
*/
|
|
@Scheduled(fixedDelay = 10000)
|
|
public void checkRoundTimeout() {
|
|
List<GameRound> ongoingRounds = gameRoundMapper.selectList(new LambdaQueryWrapper<GameRound>()
|
|
.eq(GameRound::getStatus, 0));
|
|
|
|
for (GameRound round : ongoingRounds) {
|
|
// 检查是否超过接注时间
|
|
RoundTask task = roundTasks.get(round.getId());
|
|
if (task != null && task.getEndBetTime().isBefore(LocalDateTime.now())) {
|
|
// 自动结束接注
|
|
try {
|
|
gameService.endBet(round.getGroupId(), 0L); // 0L表示系统自动触发
|
|
// 触发开始发红包,延迟时间从配置读取
|
|
int sendRpDelay = getConfigInt("send_rp_delay", 20);
|
|
scheduleStartRp(round.getId(), round.getGroupId(), sendRpDelay);
|
|
} catch (Exception e) {
|
|
log.error("自动结束接注失败: roundId={}, error={}", round.getId(), e.getMessage());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 每日凌晨重置活跃假人池
|
|
*/
|
|
@Scheduled(cron = "0 0 0 * * ?")
|
|
public void resetDailyFakeUsers() {
|
|
fakeUserService.resetDailyActiveFakes();
|
|
}
|
|
|
|
/**
|
|
* 安排开始发红包
|
|
*/
|
|
public String scheduleStartRp(Long roundId, Long groupId, int delaySeconds) {
|
|
String taskId = "start_rp_" + groupId + "_" + taskIdGenerator.getAndIncrement();
|
|
PendingTaskInfo task = new PendingTaskInfo();
|
|
task.setTaskId(taskId);
|
|
task.setType("start_rp");
|
|
task.setRoundId(roundId);
|
|
task.setGroupId(groupId);
|
|
LocalDateTime executeTime = LocalDateTime.now().plusSeconds(delaySeconds);
|
|
task.setExecuteTime(executeTime);
|
|
pendingTasks.put(taskId, task);
|
|
// 持久化到数据库
|
|
saveTaskToDb(taskId, "start_rp", roundId, groupId, null, executeTime);
|
|
log.info("安排{}秒后开始发红包: roundId={}", delaySeconds, roundId);
|
|
return taskId;
|
|
}
|
|
|
|
/**
|
|
* 安排自动结算
|
|
*/
|
|
public String scheduleSettle(Long roundId, Long groupId, int digit, int delaySeconds) {
|
|
String taskId = "settle_" + groupId + "_" + taskIdGenerator.getAndIncrement();
|
|
PendingTaskInfo task = new PendingTaskInfo();
|
|
task.setTaskId(taskId);
|
|
task.setType("settle");
|
|
task.setRoundId(roundId);
|
|
task.setGroupId(groupId);
|
|
task.setDigit(digit);
|
|
LocalDateTime executeTime = LocalDateTime.now().plusSeconds(delaySeconds);
|
|
task.setExecuteTime(executeTime);
|
|
pendingTasks.put(taskId, task);
|
|
// 持久化到数据库
|
|
saveTaskToDb(taskId, "settle", roundId, groupId, digit, executeTime);
|
|
log.info("安排{}秒后结算: taskId={}, roundId={}, digit={}", delaySeconds, taskId, roundId, digit);
|
|
return taskId;
|
|
}
|
|
|
|
/**
|
|
* 安排自动下一期
|
|
*/
|
|
public String scheduleAutoNextTask(Long groupId, int delaySeconds) {
|
|
String taskId = "auto_next_" + groupId + "_" + taskIdGenerator.getAndIncrement();
|
|
PendingTaskInfo task = new PendingTaskInfo();
|
|
task.setTaskId(taskId);
|
|
task.setType("auto_next");
|
|
task.setGroupId(groupId);
|
|
LocalDateTime executeTime = LocalDateTime.now().plusSeconds(delaySeconds);
|
|
task.setExecuteTime(executeTime);
|
|
pendingTasks.put(taskId, task);
|
|
// 持久化到数据库
|
|
saveTaskToDb(taskId, "auto_next", null, groupId, null, executeTime);
|
|
log.info("安排{}秒后自动开始下一期: groupId={}", delaySeconds, groupId);
|
|
return taskId;
|
|
}
|
|
|
|
/**
|
|
* 安排机器人发包
|
|
*/
|
|
public String scheduleBotSendRp(Long roundId, Long groupId, int delaySeconds) {
|
|
String taskId = "bot_send_rp_" + groupId + "_" + taskIdGenerator.getAndIncrement();
|
|
PendingTaskInfo task = new PendingTaskInfo();
|
|
task.setTaskId(taskId);
|
|
task.setType("bot_send_rp");
|
|
task.setRoundId(roundId);
|
|
task.setGroupId(groupId);
|
|
LocalDateTime executeTime = LocalDateTime.now().plusSeconds(delaySeconds);
|
|
task.setExecuteTime(executeTime);
|
|
pendingTasks.put(taskId, task);
|
|
// 持久化到数据库
|
|
saveTaskToDb(taskId, "bot_send_rp", roundId, groupId, null, executeTime);
|
|
log.info("安排{}秒后机器人发包: roundId={}", delaySeconds, roundId);
|
|
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) {
|
|
RoundTask task = new RoundTask();
|
|
task.setRoundId(roundId);
|
|
task.setGroupId(groupId);
|
|
task.setEndBetTime(LocalDateTime.now().plusSeconds(intervalSeconds));
|
|
roundTasks.put(roundId, task);
|
|
|
|
// 广播开始下注
|
|
GameRound round = gameRoundMapper.selectById(roundId);
|
|
if (round != null) {
|
|
broadcastStartBet(groupId, round.getRoundNo());
|
|
}
|
|
|
|
log.info("游戏开始: roundId={}, {}秒后结束接注", roundId, intervalSeconds);
|
|
}
|
|
|
|
/**
|
|
* 结束接注后安排发红包
|
|
*/
|
|
public void onEndBet(Long roundId, Long groupId) {
|
|
// 移除结束接注定时器
|
|
roundTasks.remove(roundId);
|
|
|
|
// 广播结束下注
|
|
GameRound round = gameRoundMapper.selectById(roundId);
|
|
if (round != null) {
|
|
broadcastEndBet(groupId, round.getRoundNo());
|
|
}
|
|
|
|
// 通知白名单用户尾数试算完成
|
|
notifyWhiteUsers(roundId);
|
|
|
|
// 安排延迟后开始发红包
|
|
int sendRpDelay = getConfigInt("send_rp_delay", 20);
|
|
scheduleStartRp(roundId, groupId, sendRpDelay);
|
|
}
|
|
|
|
/**
|
|
* 开始发红包
|
|
*/
|
|
public void onStartRp(Long roundId, Long groupId) {
|
|
GameRound round = gameRoundMapper.selectById(roundId);
|
|
if (round == null) {
|
|
return;
|
|
}
|
|
|
|
// 广播开始发红包
|
|
broadcastStartRp(groupId, round.getRoundNo());
|
|
|
|
// 安排超时后如果没人发包则机器人自动发
|
|
int autoRpTimeout = getConfigInt("auto_rp_timeout", 10);
|
|
scheduleBotSendRp(roundId, groupId, autoRpTimeout);
|
|
}
|
|
|
|
/**
|
|
* 机器人自动发包
|
|
*/
|
|
public void botSendRp(Long roundId, Long groupId) {
|
|
GameRound round = gameRoundMapper.selectById(roundId);
|
|
if (round == null || round.getTargetDigit() == null) {
|
|
return;
|
|
}
|
|
|
|
// 查找机器人账号
|
|
User bot = userMapper.selectOne(new LambdaQueryWrapper<User>()
|
|
.eq(User::getIsBot, 1)
|
|
.last("LIMIT 1"));
|
|
|
|
if (bot == null) {
|
|
log.error("未找到机器人账号");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// 机器人发1元3个包
|
|
RedPacket rp = redPacketService.sendRp(bot.getId(), groupId,
|
|
new BigDecimal("1"), 3, roundId);
|
|
|
|
// 红包尾数控制: 需要在发红包时预计算各份额金额
|
|
// 确保手气王金额的尾数等于目标尾数
|
|
// 注: 当前实现中红包金额随机分配,实际生产需要调用RedPacketService控制尾数
|
|
log.info("机器人自动发包: roundId={}, rpId={}, targetDigit={}",
|
|
roundId, rp.getId(), round != null ? round.getTargetDigit() : "null");
|
|
} catch (Exception e) {
|
|
log.error("机器人发包失败: roundId={}, error={}", roundId, e.getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 开奖结算
|
|
*/
|
|
public void onSettle(Long roundId, Long groupId, int digit) {
|
|
try {
|
|
gameService.settle(roundId, digit);
|
|
|
|
// 广播开奖结果
|
|
GameRound round = gameRoundMapper.selectById(roundId);
|
|
if (round != null) {
|
|
broadcastResult(groupId, digit, round.getRoundNo());
|
|
}
|
|
|
|
// 检查是否自动开启下一期
|
|
GameRound roundAfterSettle = gameRoundMapper.selectById(roundId);
|
|
if (roundAfterSettle != null && roundAfterSettle.getAutoNext() == 1) {
|
|
// 5秒后自动开始下一期
|
|
scheduleAutoNextTask(groupId, 5);
|
|
}
|
|
|
|
// 清理任务
|
|
roundTasks.remove(roundId);
|
|
|
|
log.info("局结算完成: roundId={}, digit={}", roundId, digit);
|
|
} catch (Exception e) {
|
|
log.error("结算失败: roundId={}, error={}", roundId, e.getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 通知白名单用户(尾数试算完成)
|
|
*/
|
|
private void notifyWhiteUsers(Long roundId) {
|
|
List<DigitWhite> whites = digitWhiteMapper.selectList(new LambdaQueryWrapper<>());
|
|
GameRound round = gameRoundMapper.selectById(roundId);
|
|
if (round == null) return;
|
|
|
|
for (DigitWhite white : whites) {
|
|
User user = userMapper.selectById(white.getUserId());
|
|
if (user != null) {
|
|
// 通过OpenIM发送通知
|
|
openIMApiClient.sendMsg(
|
|
openIMProperties.getAdminUserId(),
|
|
String.valueOf(user.getId()),
|
|
1, // sessionType 1=单聊
|
|
101, // contentType 101=文本
|
|
java.util.Map.of("content", "尾数试算完成,请前往修改尾数。期号: " + round.getRoundNo())
|
|
);
|
|
log.info("通知白名单用户尾数试算完成: userId={}, roundId={}", user.getId(), roundId);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 广播开始下注
|
|
*/
|
|
private void broadcastStartBet(Long groupId, String roundNo) {
|
|
// 注意: 实际生产中需要通过Group表映射获取OpenIM群ID
|
|
openIMApiClient.sendMsg(
|
|
openIMProperties.getAdminUserId(),
|
|
String.valueOf(groupId),
|
|
3, // sessionType 3=群聊
|
|
101, // contentType 101=文本
|
|
java.util.Map.of("content", roundNo + "期开始下注")
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 广播结束下注
|
|
*/
|
|
private void broadcastEndBet(Long groupId, String roundNo) {
|
|
openIMApiClient.sendMsg(
|
|
openIMProperties.getAdminUserId(),
|
|
String.valueOf(groupId),
|
|
3, // sessionType 3=群聊
|
|
101, // contentType 101=文本
|
|
java.util.Map.of("content", roundNo + "期结束下注")
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 广播开始发红包
|
|
*/
|
|
private void broadcastStartRp(Long groupId, String roundNo) {
|
|
openIMApiClient.sendMsg(
|
|
openIMProperties.getAdminUserId(),
|
|
String.valueOf(groupId),
|
|
3, // sessionType 3=群聊
|
|
101, // contentType 101=文本
|
|
java.util.Map.of("content", roundNo + "期开始发红包")
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 广播开奖结果
|
|
*/
|
|
private void broadcastResult(Long groupId, int digit, String roundNo) {
|
|
String playWinDesc = gameService.getPlayWinDesc(digit);
|
|
String message = roundNo + "期开奖\n" +
|
|
"尾数: " + digit + "\n" +
|
|
"中奖玩法: " + playWinDesc;
|
|
|
|
// 查询近30期历史
|
|
String history = getRecentHistory(groupId);
|
|
if (history != null && !history.isEmpty()) {
|
|
message += "\n\n近30期:\n" + history;
|
|
}
|
|
|
|
openIMApiClient.sendMsg(
|
|
openIMProperties.getAdminUserId(),
|
|
String.valueOf(groupId),
|
|
3, // sessionType 3=群聊
|
|
101, // contentType 101=文本
|
|
java.util.Map.of("content", message)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 获取近30期历史开奖记录
|
|
*/
|
|
private String getRecentHistory(Long groupId) {
|
|
List<DrawRecord> records = drawRecordMapper.selectList(
|
|
new LambdaQueryWrapper<DrawRecord>()
|
|
.eq(DrawRecord::getGroupId, groupId)
|
|
.orderByDesc(DrawRecord::getCreateTime)
|
|
.last("LIMIT 30"));
|
|
|
|
if (records == null || records.isEmpty()) {
|
|
return "";
|
|
}
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
for (DrawRecord record : records) {
|
|
sb.append(record.getRoundNo()).append(":").append(record.getDigit()).append("\n");
|
|
}
|
|
return sb.toString().trim();
|
|
}
|
|
|
|
|
|
/**
|
|
* 处理延迟任务队列(每秒执行)
|
|
*/
|
|
@Scheduled(fixedDelay = 1000)
|
|
public void processPendingTasks() {
|
|
LocalDateTime now = LocalDateTime.now();
|
|
pendingTasks.entrySet().removeIf(entry -> {
|
|
PendingTaskInfo task = entry.getValue();
|
|
if (task.getExecuteTime() == null) {
|
|
deleteTaskFromDb(task.getTaskId());
|
|
return true;
|
|
}
|
|
if (task.getExecuteTime().isBefore(now) || task.getExecuteTime().isEqual(now)) {
|
|
executeTask(task);
|
|
deleteTaskFromDb(task.getTaskId());
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 执行延迟任务
|
|
*/
|
|
private void executeTask(PendingTaskInfo task) {
|
|
try {
|
|
switch (task.getType()) {
|
|
case "settle":
|
|
onSettle(task.getRoundId(), task.getGroupId(), task.getDigit());
|
|
break;
|
|
case "auto_next":
|
|
onAutoNext(task.getGroupId());
|
|
break;
|
|
case "start_rp":
|
|
onStartRp(task.getRoundId(), task.getGroupId());
|
|
break;
|
|
case "bot_send_rp":
|
|
botSendRp(task.getRoundId(), task.getGroupId());
|
|
break;
|
|
default:
|
|
log.warn("未知任务类型: {}", task.getType());
|
|
}
|
|
} catch (Exception e) {
|
|
log.error("执行任务失败: taskId={}, error={}", task.getTaskId(), e.getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 自动开始下一期
|
|
*/
|
|
private void onAutoNext(Long groupId) {
|
|
try {
|
|
gameService.startGame(groupId, 0L); // 0L表示系统自动
|
|
} catch (Exception e) {
|
|
log.error("自动开始下一期失败: groupId={}, error={}", groupId, e.getMessage());
|
|
}
|
|
}
|
|
}
|