游戏流程改版
This commit is contained in:
@@ -0,0 +1,176 @@
|
|||||||
|
package com.tailbet.job;
|
||||||
|
|
||||||
|
import com.tailbet.config.OpenIMProperties;
|
||||||
|
import com.tailbet.mapper.DigitWhiteMapper;
|
||||||
|
import com.tailbet.mapper.DrawRecordMapper;
|
||||||
|
import com.tailbet.mapper.GameRoundMapper;
|
||||||
|
import com.tailbet.mapper.UserMapper;
|
||||||
|
import com.tailbet.model.entity.DigitWhite;
|
||||||
|
import com.tailbet.model.entity.DrawRecord;
|
||||||
|
import com.tailbet.model.entity.GameRound;
|
||||||
|
import com.tailbet.model.entity.User;
|
||||||
|
import com.tailbet.openim.OpenIMApiClient;
|
||||||
|
import com.tailbet.service.IGameService;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游戏广播器 —— 把 OpenIM 消息发送逻辑从 {@link GameJob} 抽出
|
||||||
|
*
|
||||||
|
* <p>负责所有游戏流程的 OpenIM 消息:
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link #broadcastStartBet} —— 开始下注</li>
|
||||||
|
* <li>{@link #broadcastEndBet} —— 结束下注</li>
|
||||||
|
* <li>{@link #broadcastStartRp} —— 开始发红包</li>
|
||||||
|
* <li>{@link #broadcastResult} —— 开奖结果(含近30期历史)</li>
|
||||||
|
* <li>{@link #notifyWhiteUsers} —— 通知尾数控制白名单</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>所有方法失败时只记日志、不抛异常,避免阻塞调用方业务流程。
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class GameBroadcaster {
|
||||||
|
|
||||||
|
/** sessionType: 1=单聊 */
|
||||||
|
private static final int SESSION_TYPE_C2C = 1;
|
||||||
|
/** sessionType: 3=群聊 */
|
||||||
|
private static final int SESSION_TYPE_GROUP = 3;
|
||||||
|
/** contentType: 101=文本 */
|
||||||
|
private static final int CONTENT_TYPE_TEXT = 101;
|
||||||
|
|
||||||
|
private final OpenIMApiClient openIMApiClient;
|
||||||
|
private final OpenIMProperties openIMProperties;
|
||||||
|
private final GameRoundMapper gameRoundMapper;
|
||||||
|
private final DrawRecordMapper drawRecordMapper;
|
||||||
|
private final DigitWhiteMapper digitWhiteMapper;
|
||||||
|
private final UserMapper userMapper;
|
||||||
|
private final IGameService gameService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知白名单用户(尾数试算完成)
|
||||||
|
*/
|
||||||
|
public void notifyWhiteUsers(Long roundId) {
|
||||||
|
try {
|
||||||
|
GameRound round = gameRoundMapper.selectById(roundId);
|
||||||
|
if (round == null) return;
|
||||||
|
|
||||||
|
List<DigitWhite> whites = digitWhiteMapper.selectList(new LambdaQueryWrapper<>());
|
||||||
|
for (DigitWhite white : whites) {
|
||||||
|
User user = userMapper.selectById(white.getUserId());
|
||||||
|
if (user == null) continue;
|
||||||
|
|
||||||
|
sendC2C(user.getId(), "尾数试算完成,请前往修改尾数。期号: " + round.getRoundNo());
|
||||||
|
log.info("通知白名单用户尾数试算完成: userId={}, roundId={}", user.getId(), roundId);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("通知白名单用户失败: roundId={}, error={}", roundId, e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 广播开始下注
|
||||||
|
*/
|
||||||
|
public void broadcastStartBet(Long groupId, String roundNo) {
|
||||||
|
sendGroup(groupId, roundNo + "期开始下注");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 广播结束下注
|
||||||
|
*/
|
||||||
|
public void broadcastEndBet(Long groupId, String roundNo) {
|
||||||
|
sendGroup(groupId, roundNo + "期结束下注");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 广播开始发红包
|
||||||
|
*/
|
||||||
|
public void broadcastStartRp(Long groupId, String roundNo) {
|
||||||
|
sendGroup(groupId, roundNo + "期开始发红包");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 广播开奖结果(含近30期历史)
|
||||||
|
*/
|
||||||
|
public void broadcastResult(Long groupId, int digit, String roundNo) {
|
||||||
|
try {
|
||||||
|
String playWinDesc = gameService.getPlayWinDesc(digit);
|
||||||
|
StringBuilder message = new StringBuilder()
|
||||||
|
.append(roundNo).append("期开奖\n")
|
||||||
|
.append("尾数: ").append(digit).append("\n")
|
||||||
|
.append("中奖玩法: ").append(playWinDesc);
|
||||||
|
|
||||||
|
String history = getRecentHistory(groupId);
|
||||||
|
if (!history.isEmpty()) {
|
||||||
|
message.append("\n\n近30期:\n").append(history);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendGroup(groupId, message.toString());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("广播开奖结果失败: groupId={}, digit={}, error={}", groupId, digit, e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取近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();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 底层发送封装 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送群聊文本消息
|
||||||
|
*/
|
||||||
|
private void sendGroup(Long groupId, String content) {
|
||||||
|
try {
|
||||||
|
// 注意: 实际生产中需要通过Group表映射获取OpenIM群ID
|
||||||
|
openIMApiClient.sendMsg(
|
||||||
|
openIMProperties.getAdminUserId(),
|
||||||
|
String.valueOf(groupId),
|
||||||
|
SESSION_TYPE_GROUP,
|
||||||
|
CONTENT_TYPE_TEXT,
|
||||||
|
java.util.Map.of("content", content)
|
||||||
|
);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("群消息发送失败: groupId={}, content={}, error={}", groupId, content, e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送单聊文本消息
|
||||||
|
*/
|
||||||
|
private void sendC2C(Long userId, String content) {
|
||||||
|
try {
|
||||||
|
openIMApiClient.sendMsg(
|
||||||
|
openIMProperties.getAdminUserId(),
|
||||||
|
String.valueOf(userId),
|
||||||
|
SESSION_TYPE_C2C,
|
||||||
|
CONTENT_TYPE_TEXT,
|
||||||
|
java.util.Map.of("content", content)
|
||||||
|
);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("单聊消息发送失败: userId={}, content={}, error={}", userId, content, e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,20 +3,15 @@ package com.tailbet.job;
|
|||||||
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.DigitWhiteMapper;
|
|
||||||
import com.tailbet.mapper.GameRoundMapper;
|
import com.tailbet.mapper.GameRoundMapper;
|
||||||
import com.tailbet.mapper.GroupMemberMapper;
|
import com.tailbet.mapper.GroupMemberMapper;
|
||||||
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;
|
||||||
import com.tailbet.mapper.UserMapper;
|
import com.tailbet.mapper.UserMapper;
|
||||||
import com.tailbet.model.entity.DigitWhite;
|
|
||||||
import com.tailbet.model.entity.DrawRecord;
|
|
||||||
import com.tailbet.model.entity.GroupMember;
|
import com.tailbet.model.entity.GroupMember;
|
||||||
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.openim.OpenIMApiClient;
|
|
||||||
import com.tailbet.service.IGameService;
|
import com.tailbet.service.IGameService;
|
||||||
import com.tailbet.service.IRedPacketService;
|
import com.tailbet.service.IRedPacketService;
|
||||||
import com.tailbet.service.IFakeUserService;
|
import com.tailbet.service.IFakeUserService;
|
||||||
@@ -47,6 +42,8 @@ import java.util.stream.Collectors;
|
|||||||
* <li>{@link #scanBotSendRpTimeouts()} —— 发包超时由机器人代发</li>
|
* <li>{@link #scanBotSendRpTimeouts()} —— 发包超时由机器人代发</li>
|
||||||
* <li>{@link #scanAutoNextTimeouts()} —— 结算后自动开下一期</li>
|
* <li>{@link #scanAutoNextTimeouts()} —— 结算后自动开下一期</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>所有 OpenIM 消息广播逻辑由 {@link GameBroadcaster} 负责。
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
@@ -56,16 +53,13 @@ public class GameJob {
|
|||||||
private final GameRoundMapper gameRoundMapper;
|
private final GameRoundMapper gameRoundMapper;
|
||||||
private final RedPacketMapper redPacketMapper;
|
private final RedPacketMapper redPacketMapper;
|
||||||
private final RedPacketRecvMapper redPacketRecvMapper;
|
private final RedPacketRecvMapper redPacketRecvMapper;
|
||||||
private final DrawRecordMapper drawRecordMapper;
|
|
||||||
private final SysConfigMapper sysConfigMapper;
|
private final SysConfigMapper sysConfigMapper;
|
||||||
private final UserMapper userMapper;
|
private final UserMapper userMapper;
|
||||||
private final DigitWhiteMapper digitWhiteMapper;
|
|
||||||
private final GroupMemberMapper groupMemberMapper;
|
private final GroupMemberMapper groupMemberMapper;
|
||||||
private final IGameService gameService;
|
private final IGameService gameService;
|
||||||
private final IRedPacketService redPacketService;
|
private final IRedPacketService redPacketService;
|
||||||
private final IFakeUserService fakeUserService;
|
private final IFakeUserService fakeUserService;
|
||||||
private final OpenIMApiClient openIMApiClient;
|
private final GameBroadcaster broadcaster;
|
||||||
private final com.tailbet.config.OpenIMProperties openIMProperties;
|
|
||||||
|
|
||||||
// 配置缓存
|
// 配置缓存
|
||||||
private final Map<String, String> configCache = new ConcurrentHashMap<>();
|
private final Map<String, String> configCache = new ConcurrentHashMap<>();
|
||||||
@@ -214,19 +208,26 @@ public class GameJob {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 扫描下注超时局 —— 每 2 秒
|
* 扫描下注超时局 —— 每 2 秒
|
||||||
* <p>条件:{@code status=0 (下注中) AND end_bet_time < NOW()}
|
* <p>条件:{@code status=0 (下注中) AND start_time + bet_window_seconds 秒 < NOW()}
|
||||||
|
* <p>说明:{@code end_bet_time} 在 {@code startGame} 时为 null(仅群主手动 {@code endBet} 时写入),
|
||||||
|
* 所以基准时间戳必须用 {@code start_time},与原 {@code recoverRoundTasks} 的 fallback 逻辑一致。
|
||||||
*/
|
*/
|
||||||
@Scheduled(fixedDelay = 2000)
|
@Scheduled(fixedDelay = 2000)
|
||||||
public void scanRoundTimeouts() {
|
public void scanRoundTimeouts() {
|
||||||
|
int betWindowSeconds = getConfigInt("bet_window_seconds", 60);
|
||||||
|
LocalDateTime threshold = LocalDateTime.now().minusSeconds(betWindowSeconds);
|
||||||
List<GameRound> overdue = gameRoundMapper.selectList(
|
List<GameRound> overdue = gameRoundMapper.selectList(
|
||||||
new LambdaQueryWrapper<GameRound>()
|
new LambdaQueryWrapper<GameRound>()
|
||||||
.eq(GameRound::getStatus, IGameService.STATUS_BETTING)
|
.eq(GameRound::getStatus, IGameService.STATUS_BETTING)
|
||||||
.lt(GameRound::getEndBetTime, LocalDateTime.now())
|
.isNotNull(GameRound::getStartTime)
|
||||||
|
.lt(GameRound::getStartTime, threshold)
|
||||||
.last("LIMIT 50"));
|
.last("LIMIT 50"));
|
||||||
|
|
||||||
for (GameRound round : overdue) {
|
for (GameRound round : overdue) {
|
||||||
try {
|
try {
|
||||||
gameService.endBet(round.getGroupId(), 0L); // 0L 表示系统自动触发
|
gameService.endBet(round.getGroupId(), 0L); // 0L 表示系统自动触发
|
||||||
|
// 触发结束接注后的广播和发红包流程
|
||||||
|
onEndBet(round.getId(), round.getGroupId());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("自动结束接注失败: roundId={}, error={}", round.getId(), e.getMessage(), e);
|
log.error("自动结束接注失败: roundId={}, error={}", round.getId(), e.getMessage(), e);
|
||||||
}
|
}
|
||||||
@@ -327,7 +328,7 @@ public class GameJob {
|
|||||||
public void onGameStart(Long roundId, Long groupId, int intervalSeconds) {
|
public void onGameStart(Long roundId, Long groupId, int intervalSeconds) {
|
||||||
GameRound round = gameRoundMapper.selectById(roundId);
|
GameRound round = gameRoundMapper.selectById(roundId);
|
||||||
if (round != null) {
|
if (round != null) {
|
||||||
broadcastStartBet(groupId, round.getRoundNo());
|
broadcaster.broadcastStartBet(groupId, round.getRoundNo());
|
||||||
}
|
}
|
||||||
log.info("游戏开始: roundId={}, {}秒后结束接注", roundId, intervalSeconds);
|
log.info("游戏开始: roundId={}, {}秒后结束接注", roundId, intervalSeconds);
|
||||||
}
|
}
|
||||||
@@ -339,9 +340,9 @@ public class GameJob {
|
|||||||
public void onEndBet(Long roundId, Long groupId) {
|
public void onEndBet(Long roundId, Long groupId) {
|
||||||
GameRound round = gameRoundMapper.selectById(roundId);
|
GameRound round = gameRoundMapper.selectById(roundId);
|
||||||
if (round != null) {
|
if (round != null) {
|
||||||
broadcastEndBet(groupId, round.getRoundNo());
|
broadcaster.broadcastEndBet(groupId, round.getRoundNo());
|
||||||
}
|
}
|
||||||
notifyWhiteUsers(roundId);
|
broadcaster.notifyWhiteUsers(roundId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -368,7 +369,7 @@ public class GameJob {
|
|||||||
gameRoundMapper.updateById(round);
|
gameRoundMapper.updateById(round);
|
||||||
|
|
||||||
// 广播开始发红包
|
// 广播开始发红包
|
||||||
broadcastStartRp(groupId, round.getRoundNo());
|
broadcaster.broadcastStartRp(groupId, round.getRoundNo());
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("开始发红包失败: roundId={}, error={}", roundId, e.getMessage(), e);
|
log.error("开始发红包失败: roundId={}, error={}", roundId, e.getMessage(), e);
|
||||||
@@ -451,7 +452,7 @@ public class GameJob {
|
|||||||
// 广播开奖结果
|
// 广播开奖结果
|
||||||
GameRound round = gameRoundMapper.selectById(roundId);
|
GameRound round = gameRoundMapper.selectById(roundId);
|
||||||
if (round != null) {
|
if (round != null) {
|
||||||
broadcastResult(groupId, digit, round.getRoundNo());
|
broadcaster.broadcastResult(groupId, digit, round.getRoundNo());
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("局结算完成: roundId={}, digit={}", roundId, digit);
|
log.info("局结算完成: roundId={}, digit={}", roundId, digit);
|
||||||
@@ -462,115 +463,6 @@ public class GameJob {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 通知白名单用户(尾数试算完成)
|
|
||||||
*/
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自动开始下一期
|
* 自动开始下一期
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user