GameRound status 调整
This commit is contained in:
@@ -683,6 +683,8 @@ public enum ErrorCodeEnum {
|
||||
IM_OPENIM_INVITE_GROUP_FAILED(14607, "error.14607"),
|
||||
/** OpenIM 踢出群成员失败 */
|
||||
IM_OPENIM_KICK_GROUP_FAILED(14608, "error.14608"),
|
||||
/** OpenIM 获取群成员列表失败 */
|
||||
IM_OPENIM_GET_GROUP_MEMBER_LIST_FAILED(14618, "error.14618"),
|
||||
/** OpenIM 添加好友失败 */
|
||||
IM_OPENIM_ADD_FRIEND_FAILED(14609, "error.14609"),
|
||||
/** OpenIM 删除好友失败 */
|
||||
|
||||
@@ -39,16 +39,13 @@ public class GameController {
|
||||
*/
|
||||
@PostMapping("/start")
|
||||
public R<GameRound> startGame(@RequestBody GameOperateDTO dto) {
|
||||
try {
|
||||
Long userId = UserContext.getUserId();
|
||||
var round = gameService.startGame(dto.getGroupId(), userId);
|
||||
// 触发游戏开始后的定时器和广播
|
||||
int betWindowSeconds = gameJob.getConfigInt("bet_window_seconds", 60);
|
||||
gameJob.onGameStart(round.getId(), dto.getGroupId(), betWindowSeconds);
|
||||
return R.ok(round);
|
||||
} catch (RuntimeException e) {
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,15 +53,12 @@ public class GameController {
|
||||
*/
|
||||
@PostMapping("/end")
|
||||
public R<GameRound> endBet(@RequestBody GameOperateDTO dto) {
|
||||
try {
|
||||
Long userId = UserContext.getUserId();
|
||||
var round = gameService.endBet(dto.getGroupId(), userId);
|
||||
// 触发结束接注后的广播和发红包流程
|
||||
gameJob.onEndBet(round.getId(), dto.getGroupId());
|
||||
return R.ok(round);
|
||||
} catch (RuntimeException e) {
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,13 +66,10 @@ public class GameController {
|
||||
*/
|
||||
@PostMapping("/stop")
|
||||
public R<Void> stopAutoNext(@RequestBody GameOperateDTO dto) {
|
||||
try {
|
||||
Long userId = UserContext.getUserId();
|
||||
gameService.stopAutoNext(dto.getGroupId(), userId);
|
||||
return R.ok();
|
||||
} catch (RuntimeException e) {
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,7 +77,6 @@ public class GameController {
|
||||
*/
|
||||
@PostMapping("/bet")
|
||||
public R<BetOrder> bet(@RequestBody BetDTO dto) {
|
||||
try {
|
||||
Long userId = UserContext.getUserId();
|
||||
var bet = betService.placeBet(userId, dto.getGroupId(),
|
||||
dto.getClientMsgId(), dto.getPlayType(), dto.getBetAmount());
|
||||
@@ -94,9 +84,7 @@ public class GameController {
|
||||
return R.ok();
|
||||
}
|
||||
return R.ok(bet);
|
||||
} catch (RuntimeException e) {
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,12 +92,9 @@ public class GameController {
|
||||
*/
|
||||
@GetMapping("/bet/trial")
|
||||
public R<List<DigitTrialVo>> trialDigit(Long groupId, Long roundId) {
|
||||
try {
|
||||
List<DigitTrialVo> trials = gameService.trialDigit(groupId, roundId);
|
||||
return R.ok(trials);
|
||||
} catch (RuntimeException e) {
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,7 +111,6 @@ public class GameController {
|
||||
*/
|
||||
@PostMapping("/digit/set")
|
||||
public R<Void> setDigit(@RequestBody SetDigitDTO dto) {
|
||||
try {
|
||||
Long userId = UserContext.getUserId();
|
||||
|
||||
// 验证白名单权限
|
||||
@@ -138,8 +122,6 @@ public class GameController {
|
||||
|
||||
gameService.setTargetDigit(dto.getGroupId(), dto.getDigit(), userId);
|
||||
return R.ok();
|
||||
} catch (RuntimeException e) {
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.tailbet.model.entity.RedPacket;
|
||||
import com.tailbet.model.entity.RedPacketRecv;
|
||||
import com.tailbet.model.vo.R;
|
||||
import com.tailbet.model.vo.UserContext;
|
||||
import com.tailbet.service.IGameService;
|
||||
import com.tailbet.service.IRedPacketService;
|
||||
import com.tailbet.util.RedisLockUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -20,20 +21,21 @@ import org.springframework.web.bind.annotation.*;
|
||||
public class RedPacketController {
|
||||
|
||||
private final IRedPacketService redPacketService;
|
||||
private final IGameService gameService;
|
||||
|
||||
/**
|
||||
* 发红包
|
||||
*/
|
||||
@PostMapping("/send")
|
||||
public R<RedPacket> sendRp(@RequestBody SendRpDTO dto) {
|
||||
try {
|
||||
Long userId = UserContext.getUserId();
|
||||
// roundId从当前group的进行中局获取
|
||||
var ongoingRound = gameService.getOngoingRound(dto.getGroupId());
|
||||
Long roundId = ongoingRound == null ? null : ongoingRound.getId();
|
||||
var rp = redPacketService.sendRp(userId, dto.getGroupId(),
|
||||
dto.getTotalAmount(), dto.getTotalCount(), dto.getRoundId());
|
||||
dto.getTotalAmount(), dto.getTotalCount(), roundId);
|
||||
return R.ok(rp);
|
||||
} catch (RuntimeException e) {
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,7 +43,6 @@ public class RedPacketController {
|
||||
*/
|
||||
@PostMapping("/receive")
|
||||
public R<RedPacketRecv> receiveRp(@RequestBody ReceiveRpDTO dto) {
|
||||
try {
|
||||
Long userId = UserContext.getUserId();
|
||||
// 使用分布式锁防止多领
|
||||
var recv = RedisLockUtil.lock("rp:receive:" + dto.getRpId(),
|
||||
@@ -50,9 +51,7 @@ public class RedPacketController {
|
||||
return R.fail("领取失败,请重试");
|
||||
}
|
||||
return R.ok(recv);
|
||||
} catch (RuntimeException e) {
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -100,7 +100,7 @@ public class GameJob {
|
||||
try {
|
||||
List<GameRound> ongoingRounds = gameRoundMapper.selectList(
|
||||
new LambdaQueryWrapper<GameRound>()
|
||||
.eq(GameRound::getStatus, 0));
|
||||
.lt(GameRound::getStatus, IGameService.STATUS_FINISHED));
|
||||
|
||||
if (ongoingRounds.isEmpty()) {
|
||||
log.info("无进行中的局,无需恢复");
|
||||
@@ -269,7 +269,7 @@ public class GameJob {
|
||||
@Scheduled(fixedDelay = 10000)
|
||||
public void checkRoundTimeout() {
|
||||
List<GameRound> ongoingRounds = gameRoundMapper.selectList(new LambdaQueryWrapper<GameRound>()
|
||||
.eq(GameRound::getStatus, 0));
|
||||
.eq(GameRound::getStatus, IGameService.STATUS_BETTING));
|
||||
|
||||
for (GameRound round : ongoingRounds) {
|
||||
// 检查是否超过接注时间
|
||||
@@ -452,6 +452,11 @@ public class GameJob {
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新局状态为开始发红包
|
||||
round.setStatus(IGameService.STATUS_RP_SENDING);
|
||||
round.setStartRpTime(LocalDateTime.now());
|
||||
gameRoundMapper.updateById(round);
|
||||
|
||||
// 广播开始发红包
|
||||
broadcastStartRp(groupId, round.getRoundNo());
|
||||
|
||||
|
||||
@@ -22,9 +22,4 @@ public class SendRpDTO {
|
||||
* 红包个数
|
||||
*/
|
||||
private Integer totalCount;
|
||||
|
||||
/**
|
||||
* 绑定的局ID(可选)
|
||||
*/
|
||||
private Long roundId;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ public class GameRound {
|
||||
private String roundNo;
|
||||
|
||||
/**
|
||||
* 状态:0进行中 1已结束
|
||||
* 状态:0开始下注 1结束下注 2开始发红包 3已结束
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
|
||||
@@ -1583,6 +1583,64 @@ public class OpenIMApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取群成员列表
|
||||
* 对应接口:POST /group/get_group_member_list
|
||||
* <p>
|
||||
* pagination.showNumber 每页数量必须大于 0。
|
||||
* filter 保留字段,入口校验取值范围 0-5,当前实现不使用该字段过滤结果。
|
||||
* </p>
|
||||
*
|
||||
* @param groupID 群组 ID
|
||||
* @param pageNumber 页码,从 1 开始
|
||||
* @param showNumber 每页数量,必须大于 0
|
||||
* @param keyword 按用户 ID 或昵称过滤(可选)
|
||||
* @return 群成员信息列表
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<Map<String, Object>> getGroupMemberList(String groupID, int pageNumber, int showNumber, String keyword) {
|
||||
String url = openIMProperties.resolveServerApiUrl() + "/group/get_group_member_list";
|
||||
|
||||
Map<String, Object> pagination = new HashMap<>();
|
||||
pagination.put("pageNumber", pageNumber);
|
||||
pagination.put("showNumber", showNumber);
|
||||
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("pagination", pagination);
|
||||
body.put("groupID", groupID);
|
||||
// body.put("filter", filter);
|
||||
if (keyword != null && !keyword.isBlank()) {
|
||||
body.put("keyword", keyword);
|
||||
}
|
||||
|
||||
OpenIMBaseResp resp = doPost(url, body);
|
||||
if (!isSuccess(resp)) {
|
||||
String errMsg = resp.getErrMsg() != null ? resp.getErrMsg() : "";
|
||||
// 群组不存在(重复拉取时触发),返回空列表
|
||||
if (resp.getErrCode() == 1004
|
||||
|| errMsg.contains("RecordNotFound")
|
||||
|| errMsg.contains("not found")
|
||||
|| errMsg.contains("GroupIDNotFound")) {
|
||||
log.debug("【OpenIM】获取群成员列表:群组不存在: groupID={}", groupID);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
log.error("【OpenIM】获取群成员列表失败: groupID={}, pageNumber={}, showNumber={}, errCode={}, errMsg={}",
|
||||
groupID, pageNumber, showNumber, resp.getErrCode(), resp.getErrMsg());
|
||||
throw new BusinessException(ErrorCodeEnum.IM_OPENIM_GET_GROUP_MEMBER_LIST_FAILED, resp.getErrMsg());
|
||||
}
|
||||
|
||||
Map<String, Object> data = (Map<String, Object>) resp.getData();
|
||||
if (data == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
Object membersObj = data.get("members");
|
||||
if (!(membersObj instanceof List<?>)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return (List<Map<String, Object>>) membersObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量查询 OpenIM 中存在的群组 ID 集合(验证专用)
|
||||
* 对应接口:POST /group/get_groups_info
|
||||
|
||||
@@ -17,8 +17,14 @@ import java.util.List;
|
||||
public interface IGameService {
|
||||
|
||||
// 局状态常量
|
||||
int STATUS_ONGOING = 0;
|
||||
int STATUS_ENDED = 1;
|
||||
int STATUS_BETTING = 0; // 开始下注
|
||||
int STATUS_BET_CLOSED = 1; // 结束下注,等待发红包
|
||||
int STATUS_RP_SENDING = 2; // 开始发红包,等待红包领完
|
||||
int STATUS_FINISHED = 3; // 已结束
|
||||
|
||||
// 向后兼容别名
|
||||
int STATUS_ONGOING = STATUS_BETTING;
|
||||
int STATUS_ENDED = STATUS_FINISHED;
|
||||
|
||||
// 玩法赔率
|
||||
BigDecimal ODDS_SINGLE_DOUBLE = new BigDecimal("1");
|
||||
@@ -32,6 +38,11 @@ public interface IGameService {
|
||||
*/
|
||||
GameRound getOngoingRound(Long groupId);
|
||||
|
||||
/**
|
||||
* 获取群当前正在下注的局
|
||||
*/
|
||||
GameRound getBettingRound(Long groupId);
|
||||
|
||||
/**
|
||||
* 生成期号
|
||||
*/
|
||||
|
||||
@@ -61,12 +61,12 @@ public class BetServiceImpl implements IBetService {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 检查进行中的局
|
||||
// 检查进行中的局(下注中)
|
||||
GameRound round = gameRoundMapper.selectOne(new LambdaQueryWrapper<GameRound>()
|
||||
.eq(GameRound::getGroupId, groupId)
|
||||
.eq(GameRound::getStatus, 0));
|
||||
.eq(GameRound::getStatus, IGameService.STATUS_BETTING));
|
||||
if (round == null) {
|
||||
throw new RuntimeException("当前没有进行中的局");
|
||||
throw new RuntimeException("当前不在下注阶段");
|
||||
}
|
||||
|
||||
// 检查余额
|
||||
|
||||
@@ -143,10 +143,10 @@ public class FakeUserServiceImpl implements IFakeUserService {
|
||||
String fakeIdStr = fakeList.get(RANDOM.nextInt(fakeList.size()));
|
||||
Long fakeId = Long.parseLong(fakeIdStr);
|
||||
|
||||
// 检查是否有进行中的局
|
||||
// 检查是否有下注中的局
|
||||
GameRound round = gameRoundMapper.selectOne(new LambdaQueryWrapper<GameRound>()
|
||||
.eq(GameRound::getGroupId, groupId)
|
||||
.eq(GameRound::getStatus, 0));
|
||||
.eq(GameRound::getStatus, com.tailbet.service.IGameService.STATUS_BETTING));
|
||||
|
||||
if (round == null) {
|
||||
return;
|
||||
|
||||
@@ -44,13 +44,27 @@ public class GameServiceImpl implements IGameService {
|
||||
private final IAuditService auditService;
|
||||
|
||||
/**
|
||||
* 获取群当前进行中的局
|
||||
* 获取群当前未结束的局(包含下注中、已封盘、发红包中)
|
||||
*/
|
||||
@Override
|
||||
public GameRound getOngoingRound(Long groupId) {
|
||||
return gameRoundMapper.selectOne(new LambdaQueryWrapper<GameRound>()
|
||||
.eq(GameRound::getGroupId, groupId)
|
||||
.eq(GameRound::getStatus, STATUS_ONGOING));
|
||||
.lt(GameRound::getStatus, STATUS_FINISHED)
|
||||
.orderByDesc(GameRound::getCreateTime)
|
||||
.last("LIMIT 1"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取群当前正在下注中的局
|
||||
*/
|
||||
@Override
|
||||
public GameRound getBettingRound(Long groupId) {
|
||||
return gameRoundMapper.selectOne(new LambdaQueryWrapper<GameRound>()
|
||||
.eq(GameRound::getGroupId, groupId)
|
||||
.eq(GameRound::getStatus, STATUS_BETTING)
|
||||
.orderByDesc(GameRound::getCreateTime)
|
||||
.last("LIMIT 1"));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,7 +111,7 @@ public class GameServiceImpl implements IGameService {
|
||||
GameRound round = new GameRound();
|
||||
round.setGroupId(groupId);
|
||||
round.setRoundNo(generateRoundNo(groupId));
|
||||
round.setStatus(STATUS_ONGOING);
|
||||
round.setStatus(STATUS_BETTING);
|
||||
round.setStartTime(LocalDateTime.now());
|
||||
round.setDigitStrategy("random");
|
||||
round.setAutoNext(1);
|
||||
@@ -116,18 +130,20 @@ public class GameServiceImpl implements IGameService {
|
||||
@Override
|
||||
@Transactional
|
||||
public GameRound endBet(Long groupId, Long userId) {
|
||||
GameRound round = getOngoingRound(groupId);
|
||||
GameRound round = getBettingRound(groupId);
|
||||
if (round == null) {
|
||||
throw new RuntimeException("没有进行中的局");
|
||||
throw new RuntimeException("当前没有下注中的局");
|
||||
}
|
||||
|
||||
// 校验群主权限
|
||||
// 校验群主权限(系统调用时userId=0L跳过校验)
|
||||
if (!Long.valueOf(0L).equals(userId)) {
|
||||
Group group = groupMapper.selectById(groupId);
|
||||
if (!group.getOwnerUserId().equals(userId)) {
|
||||
throw new RuntimeException("只有群主可以结束接注");
|
||||
}
|
||||
}
|
||||
|
||||
round.setStatus(STATUS_ENDED);
|
||||
round.setStatus(STATUS_BET_CLOSED);
|
||||
round.setEndBetTime(LocalDateTime.now());
|
||||
gameRoundMapper.updateById(round);
|
||||
|
||||
@@ -165,9 +181,9 @@ public class GameServiceImpl implements IGameService {
|
||||
@Override
|
||||
@Transactional
|
||||
public void setTargetDigit(Long groupId, Integer digit, Long userId) {
|
||||
GameRound round = getOngoingRound(groupId);
|
||||
GameRound round = getBettingRound(groupId);
|
||||
if (round == null) {
|
||||
throw new RuntimeException("没有进行中的局");
|
||||
throw new RuntimeException("当前没有下注中的局");
|
||||
}
|
||||
|
||||
round.setTargetDigit(digit);
|
||||
@@ -315,7 +331,7 @@ public class GameServiceImpl implements IGameService {
|
||||
|
||||
// 更新局状态
|
||||
round.setFinishTime(LocalDateTime.now());
|
||||
round.setStatus(STATUS_ENDED);
|
||||
round.setStatus(STATUS_FINISHED);
|
||||
gameRoundMapper.updateById(round);
|
||||
|
||||
// 审计日志
|
||||
@@ -348,7 +364,7 @@ public class GameServiceImpl implements IGameService {
|
||||
List<GameRound> allRounds = gameRoundMapper.selectList(new LambdaQueryWrapper<GameRound>()
|
||||
.eq(GameRound::getGroupId, groupId));
|
||||
stat.setTotalRounds(allRounds.size());
|
||||
stat.setOngoingRounds((int) allRounds.stream().filter(r -> r.getStatus() == STATUS_ONGOING).count());
|
||||
stat.setOngoingRounds((int) allRounds.stream().filter(r -> r.getStatus() != null && r.getStatus() < STATUS_FINISHED).count());
|
||||
|
||||
// 统计注单
|
||||
List<BetOrder> allBets = betOrderMapper.selectList(new LambdaQueryWrapper<BetOrder>()
|
||||
|
||||
Reference in New Issue
Block a user