package com.tailbet.service.impl; import com.tailbet.model.entity.User; import com.tailbet.model.entity.GameRound; import com.tailbet.mapper.UserMapper; import com.tailbet.mapper.GameRoundMapper; import com.tailbet.service.IFakeUserService; import com.tailbet.service.IBetService; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.util.*; /** * 假人服务实现 */ @Slf4j @RequiredArgsConstructor @Service public class FakeUserServiceImpl implements IFakeUserService { private final UserMapper userMapper; private final GameRoundMapper gameRoundMapper; private final IBetService betService; private final StringRedisTemplate redisTemplate; private static final String FAKE_ACTIVE_KEY = "fake:active:"; private static final Random RANDOM = new Random(); // 假人昵称素材 private static final String[] NICKNAME_PREFIXES = {"小", "老", "阿", "超", "酷"}; private static final String[] NICKNAME_SUFFIXES = {"哥", "弟", "姐", "妹", "宝"}; /** * 初始化群假人 */ @Override public void initGroupFakeUsers(Long groupId, int minCount, int maxCount) { int count = minCount + RANDOM.nextInt(maxCount - minCount + 1); log.info("为群{}初始化{}个假人", groupId, count); for (int i = 0; i < count; i++) { createFakeUser(groupId); } } /** * 创建单个假人 */ @Override public User createFakeUser(Long groupId) { User fake = new User(); fake.setUsername("fake_" + UUID.randomUUID().toString().replace("-", "").substring(0, 12)); fake.setNickname(generateNickname()); fake.setPoints(BigDecimal.valueOf(1000 + RANDOM.nextInt(49000))); fake.setIsFake(1); fake.setIsBot(0); fake.setIsOps(0); fake.setIsWhite(0); fake.setStatus(1); userMapper.insert(fake); return fake; } /** * 生成随机昵称 */ private String generateNickname() { String prefix = NICKNAME_PREFIXES[RANDOM.nextInt(NICKNAME_PREFIXES.length)]; String suffix = NICKNAME_SUFFIXES[RANDOM.nextInt(NICKNAME_SUFFIXES.length)]; int number = RANDOM.nextInt(1000); return prefix + suffix + number; } /** * 每日重置活跃假人 */ @Override @Scheduled(cron = "0 0 0 * * ?") public void resetDailyActiveFakes() { // 清理昨天的活跃标记 Set keys = redisTemplate.opsForSet().members(FAKE_ACTIVE_KEY + "yesterday"); if (keys != null) { for (String userId : keys) { redisTemplate.opsForSet().remove(FAKE_ACTIVE_KEY + "yesterday", userId); } } // 查询所有假人 List fakes = userMapper.selectList(new LambdaQueryWrapper() .eq(User::getIsFake, 1) .eq(User::getStatus, 1)); if (fakes.isEmpty()) { return; } // 随机选15-20个作为今日活跃 int activeCount = 15 + RANDOM.nextInt(6); activeCount = Math.min(activeCount, fakes.size()); Collections.shuffle(fakes); List activeUserIds = new ArrayList<>(); for (int i = 0; i < activeCount; i++) { activeUserIds.add(String.valueOf(fakes.get(i).getId())); } // 存入Redis String today = java.time.LocalDate.now().toString(); redisTemplate.opsForSet().add(FAKE_ACTIVE_KEY + today, activeUserIds.toArray(new String[0])); log.info("今日活跃假人已更新: {}个", activeCount); } /** * 检查用户是否是今日活跃假人 */ @Override public boolean isTodayActiveFake(Long userId) { String today = java.time.LocalDate.now().toString(); Boolean isMember = redisTemplate.opsForSet().isMember(FAKE_ACTIVE_KEY + today, String.valueOf(userId)); return Boolean.TRUE.equals(isMember); } /** * 假人下注 */ @Override public void fakeBet(Long groupId) { // 获取今日活跃假人 String today = java.time.LocalDate.now().toString(); Set activeFakes = redisTemplate.opsForSet().members(FAKE_ACTIVE_KEY + today); if (activeFakes == null || activeFakes.isEmpty()) { return; } // 随机选一个假人 List fakeList = new ArrayList<>(activeFakes); String fakeIdStr = fakeList.get(RANDOM.nextInt(fakeList.size())); Long fakeId = Long.parseLong(fakeIdStr); // 检查是否有下注中的局 GameRound round = gameRoundMapper.selectOne(new LambdaQueryWrapper() .eq(GameRound::getGroupId, groupId) .eq(GameRound::getStatus, com.tailbet.service.IGameService.STATUS_BETTING)); if (round == null) { return; } // 随机玩法和金额 String[] playTypes = {"单", "双", "大", "小"}; String playType = playTypes[RANDOM.nextInt(playTypes.length)]; BigDecimal amount = BigDecimal.valueOf((1 + RANDOM.nextInt(10)) * 10L); // 10,20,30...100 try { betService.placeBet(fakeId, groupId, "fake_" + UUID.randomUUID(), playType, amount); log.debug("假人{}下注: {} {}", fakeId, playType, amount.toPlainString()); } catch (Exception e) { log.debug("假人下注失败: {}", e.getMessage()); } } /** * 获取假人列表 */ @Override public List getFakeUsers() { return userMapper.selectList(new LambdaQueryWrapper() .eq(User::getIsFake, 1) .eq(User::getStatus, 1)); } /** * 从现有假人中随机挑选指定数量 */ @Override public List pickFakeUsers(int count) { List fakes = getFakeUsers(); if (fakes.isEmpty()) { return List.of(); } Collections.shuffle(fakes, RANDOM); int size = Math.min(count, fakes.size()); return new ArrayList<>(fakes.subList(0, size)); } }