java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot抽奖活动

基于SpringBoot实现抽奖活动的四种策略

作者:风象南

抽奖活动是产品运营中常见的用户激励和互动手段,通过随机性和奖励刺激用户参与度,提升用户活跃度和留存率,在技术实现上,抽奖系统涉及到随机算法、奖品分配、防作弊机制等多方面内容,本文将介绍基于SpringBoot实现抽奖活动的5种策略,需要的朋友可以参考下

一、基于内存的简单抽奖策略

1.1 基本原理

最简单的抽奖策略是将所有奖品信息加载到内存中,通过随机数算法从奖品池中选取一个奖品。

这种方式实现简单,适合奖品种类少、规则简单的小型抽奖活动。

1.2 实现方式

首先定义奖品实体:

@Data
public class Prize {
    private Long id;
    private String name;
    private String description;
    private Integer probability; // 中奖概率,1-10000之间的数字,表示万分之几
    private Integer stock;       // 库存
    private Boolean available;   // 是否可用
}

然后实现抽奖服务:

@Service
public class SimpleDrawService {
    
    private final List<Prize> prizePool = new ArrayList<>();
    private final Random random = new Random();
    
    // 初始化奖品池
    @PostConstruct
    public void init() {
        // 奖品1: 一等奖,概率0.01%,库存10
        Prize firstPrize = new Prize();
        firstPrize.setId(1L);
        firstPrize.setName("一等奖");
        firstPrize.setDescription("iPhone 14 Pro");
        firstPrize.setProbability(1); // 万分之1
        firstPrize.setStock(10);
        firstPrize.setAvailable(true);
        
        // 奖品2: 二等奖,概率0.1%,库存50
        Prize secondPrize = new Prize();
        secondPrize.setId(2L);
        secondPrize.setName("二等奖");
        secondPrize.setDescription("AirPods Pro");
        secondPrize.setProbability(10); // 万分之10
        secondPrize.setStock(50);
        secondPrize.setAvailable(true);
        
        // 奖品3: 三等奖,概率1%,库存500
        Prize thirdPrize = new Prize();
        thirdPrize.setId(3L);
        thirdPrize.setName("三等奖");
        thirdPrize.setDescription("100元优惠券");
        thirdPrize.setProbability(100); // 万分之100
        thirdPrize.setStock(500);
        thirdPrize.setAvailable(true);
        
        // 奖品4: 谢谢参与,概率98.89%,无限库存
        Prize noPrize = new Prize();
        noPrize.setId(4L);
        noPrize.setName("谢谢参与");
        noPrize.setDescription("再接再厉");
        noPrize.setProbability(9889); // 万分之9889
        noPrize.setStock(Integer.MAX_VALUE);
        noPrize.setAvailable(true);
        
        prizePool.add(firstPrize);
        prizePool.add(secondPrize);
        prizePool.add(thirdPrize);
        prizePool.add(noPrize);
    }
    
    // 抽奖方法
    public synchronized Prize draw() {
        // 生成一个1-10000之间的随机数
        int randomNum = random.nextInt(10000) + 1;
        
        int probabilitySum = 0;
        for (Prize prize : prizePool) {
            if (!prize.getAvailable() || prize.getStock() <= 0) {
                continue; // 跳过不可用或无库存的奖品
            }
            
            probabilitySum += prize.getProbability();
            if (randomNum <= probabilitySum) {
                // 减少库存
                prize.setStock(prize.getStock() - 1);
                
                // 如果库存为0,设置为不可用
                if (prize.getStock() <= 0) {
                    prize.setAvailable(false);
                }
                
                return prize;
            }
        }
        
        // 如果所有奖品都不可用,返回默认奖品
        return getDefaultPrize();
    }
    
    private Prize getDefaultPrize() {
        for (Prize prize : prizePool) {
            if (prize.getName().equals("谢谢参与")) {
                return prize;
            }
        }
        
        // 创建一个默认奖品
        Prize defaultPrize = new Prize();
        defaultPrize.setId(999L);
        defaultPrize.setName("谢谢参与");
        defaultPrize.setDescription("再接再厉");
        return defaultPrize;
    }
}

控制器实现:

@RestController
@RequestMapping("/api/draw")
public class DrawController {
    
    @Autowired
    private SimpleDrawService drawService;
    
    @GetMapping("/simple")
    public Prize simpleDraw() {
        return drawService.draw();
    }
}

1.3 优缺点分析

优点:

缺点:

1.4 适用场景

二、基于数据库的抽奖策略

2.1 基本原理

将奖品信息、抽奖记录等数据存储在数据库中,通过数据库事务来保证奖品库存的准确性和抽奖记录的完整性。

这种方式适合需要持久化数据并且对奖品库存有严格管理要求的抽奖活动。

2.2 实现方式

数据库表设计:

-- 奖品表
CREATE TABLE prize (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    description VARCHAR(255),
    probability INT NOT NULL COMMENT '中奖概率,1-10000之间的数字,表示万分之几',
    stock INT NOT NULL COMMENT '库存',
    available BOOLEAN DEFAULT TRUE COMMENT '是否可用'
);

-- 抽奖记录表
CREATE TABLE draw_record (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL COMMENT '用户ID',
    prize_id BIGINT COMMENT '奖品ID',
    draw_time DATETIME NOT NULL COMMENT '抽奖时间',
    ip VARCHAR(50) COMMENT '用户IP地址',
    INDEX idx_user_id (user_id)
);

-- 抽奖活动表
CREATE TABLE draw_activity (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL COMMENT '活动名称',
    start_time DATETIME NOT NULL COMMENT '开始时间',
    end_time DATETIME NOT NULL COMMENT '结束时间',
    daily_limit INT DEFAULT 1 COMMENT '每人每日抽奖次数限制',
    total_limit INT DEFAULT 10 COMMENT '每人总抽奖次数限制',
    active BOOLEAN DEFAULT TRUE COMMENT '是否激活'
);

实体类:

@Data
@Entity
@Table(name = "prize")
public class Prize {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    private String description;
    
    private Integer probability;
    
    private Integer stock;
    
    private Boolean available;
}

@Data
@Entity
@Table(name = "draw_record")
public class DrawRecord {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "user_id")
    private Long userId;
    
    @Column(name = "prize_id")
    private Long prizeId;
    
    @Column(name = "draw_time")
    private LocalDateTime drawTime;
    
    private String ip;
}

@Data
@Entity
@Table(name = "draw_activity")
public class DrawActivity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    @Column(name = "start_time")
    private LocalDateTime startTime;
    
    @Column(name = "end_time")
    private LocalDateTime endTime;
    
    @Column(name = "daily_limit")
    private Integer dailyLimit;
    
    @Column(name = "total_limit")
    private Integer totalLimit;
    
    private Boolean active;
}

Repository 接口:

public interface PrizeRepository extends JpaRepository<Prize, Long> {
    List<Prize> findByAvailableTrueAndStockGreaterThan(int stock);
}

public interface DrawRecordRepository extends JpaRepository<DrawRecord, Long> {
    long countByUserIdAndDrawTimeBetween(Long userId, LocalDateTime start, LocalDateTime end);
    
    long countByUserId(Long userId);
}

public interface DrawActivityRepository extends JpaRepository<DrawActivity, Long> {
    Optional<DrawActivity> findByActiveTrue();
}

服务实现:

@Service
@Transactional
public class DatabaseDrawService {
    
    @Autowired
    private PrizeRepository prizeRepository;
    
    @Autowired
    private DrawRecordRepository drawRecordRepository;
    
    @Autowired
    private DrawActivityRepository drawActivityRepository;
    
    private final Random random = new Random();
    
    public Prize draw(Long userId, String ip) {
        // 检查活动是否有效
        DrawActivity activity = drawActivityRepository.findByActiveTrue()
                .orElseThrow(() -> new RuntimeException("No active draw activity"));
        
        LocalDateTime now = LocalDateTime.now();
        if (now.isBefore(activity.getStartTime()) || now.isAfter(activity.getEndTime())) {
            throw new RuntimeException("Draw activity is not in progress");
        }
        
        // 检查用户抽奖次数限制
        checkDrawLimits(userId, activity);
        
        // 获取所有可用奖品
        List<Prize> availablePrizes = prizeRepository.findByAvailableTrueAndStockGreaterThan(0);
        if (availablePrizes.isEmpty()) {
            throw new RuntimeException("No available prizes");
        }
        
        // 计算总概率
        int totalProbability = availablePrizes.stream()
                .mapToInt(Prize::getProbability)
                .sum();
        
        // 生成随机数
        int randomNum = random.nextInt(totalProbability) + 1;
        
        // 根据概率选择奖品
        int probabilitySum = 0;
        Prize selectedPrize = null;
        
        for (Prize prize : availablePrizes) {
            probabilitySum += prize.getProbability();
            if (randomNum <= probabilitySum) {
                selectedPrize = prize;
                break;
            }
        }
        
        if (selectedPrize == null) {
            throw new RuntimeException("Failed to select a prize");
        }
        
        // 减少库存
        selectedPrize.setStock(selectedPrize.getStock() - 1);
        if (selectedPrize.getStock() <= 0) {
            selectedPrize.setAvailable(false);
        }
        prizeRepository.save(selectedPrize);
        
        // 记录抽奖
        DrawRecord record = new DrawRecord();
        record.setUserId(userId);
        record.setPrizeId(selectedPrize.getId());
        record.setDrawTime(now);
        record.setIp(ip);
        drawRecordRepository.save(record);
        
        return selectedPrize;
    }
    
    private void checkDrawLimits(Long userId, DrawActivity activity) {
        // 检查每日抽奖次数限制
        LocalDateTime startOfDay = LocalDate.now().atStartOfDay();
        LocalDateTime endOfDay = LocalDate.now().plusDays(1).atStartOfDay().minusNanos(1);
        
        long dailyDraws = drawRecordRepository.countByUserIdAndDrawTimeBetween(userId, startOfDay, endOfDay);
        if (dailyDraws >= activity.getDailyLimit()) {
            throw new RuntimeException("Daily draw limit exceeded");
        }
        
        // 检查总抽奖次数限制
        long totalDraws = drawRecordRepository.countByUserId(userId);
        if (totalDraws >= activity.getTotalLimit()) {
            throw new RuntimeException("Total draw limit exceeded");
        }
    }
}

控制器实现:

@RestController
@RequestMapping("/api/draw")
public class DatabaseDrawController {
    
    @Autowired
    private DatabaseDrawService databaseDrawService;
    
    @GetMapping("/database")
    public Prize databaseDraw(@RequestParam Long userId, HttpServletRequest request) {
        String ip = request.getRemoteAddr();
        return databaseDrawService.draw(userId, ip);
    }
}

2.3 优缺点分析

优点:

缺点:

2.4 适用场景

三、基于Redis的高性能抽奖策略

3.1 基本原理

利用Redis的高性能和原子操作特性来实现抽奖系统,将奖品信息和库存存储在Redis中,通过Lua脚本实现原子抽奖操作。这种方式适合高并发抽奖场景,能够提供极高的性能和可靠的数据一致性。

3.2 实现方式

首先配置Redis:

@Configuration
public class RedisConfig {
    
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        
        template.afterPropertiesSet();
        return template;
    }
}

抽奖服务实现:

@Service
public class RedisDrawService {
    
    private static final String PRIZE_HASH_KEY = "draw:prizes";
    private static final String DAILY_DRAW_COUNT_KEY = "draw:daily:";
    private static final String TOTAL_DRAW_COUNT_KEY = "draw:total:";
    private static final String DRAW_RECORD_KEY = "draw:records:";
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    
    @PostConstruct
    public void init() {
        // 初始化奖品数据
        if (!redisTemplate.hasKey(PRIZE_HASH_KEY)) {
            Map<String, Prize> prizes = new HashMap<>();
            
            Prize firstPrize = new Prize();
            firstPrize.setId(1L);
            firstPrize.setName("一等奖");
            firstPrize.setDescription("iPhone 14 Pro");
            firstPrize.setProbability(1); // 万分之1
            firstPrize.setStock(10);
            firstPrize.setAvailable(true);
            prizes.put("1", firstPrize);
            
            Prize secondPrize = new Prize();
            secondPrize.setId(2L);
            secondPrize.setName("二等奖");
            secondPrize.setDescription("AirPods Pro");
            secondPrize.setProbability(10); // 万分之10
            secondPrize.setStock(50);
            secondPrize.setAvailable(true);
            prizes.put("2", secondPrize);
            
            Prize thirdPrize = new Prize();
            thirdPrize.setId(3L);
            thirdPrize.setName("三等奖");
            thirdPrize.setDescription("100元优惠券");
            thirdPrize.setProbability(100); // 万分之100
            thirdPrize.setStock(500);
            thirdPrize.setAvailable(true);
            prizes.put("3", thirdPrize);
            
            Prize noPrize = new Prize();
            noPrize.setId(4L);
            noPrize.setName("谢谢参与");
            noPrize.setDescription("再接再厉");
            noPrize.setProbability(9889); // 万分之9889
            noPrize.setStock(Integer.MAX_VALUE);
            noPrize.setAvailable(true);
            prizes.put("4", noPrize);
            
            // 将奖品信息存储到Redis
            redisTemplate.opsForHash().putAll(PRIZE_HASH_KEY, prizes);
        }
    }
    
    public Prize draw(Long userId) {
        // 检查用户抽奖限制
        checkDrawLimits(userId);
        
        // 获取所有可用奖品
        Map<Object, Object> prizeMap = redisTemplate.opsForHash().entries(PRIZE_HASH_KEY);
        List<Prize> availablePrizes = new ArrayList<>();
        
        for (Object obj : prizeMap.values()) {
            Prize prize = (Prize) obj;
            if (prize.getAvailable() && prize.getStock() > 0) {
                availablePrizes.add(prize);
            }
        }
        
        if (availablePrizes.isEmpty()) {
            throw new RuntimeException("No available prizes");
        }
        
        // 使用Lua脚本进行原子抽奖操作
        String script = "local prizes = redis.call('HGETALL', KEYS[1]) " +
                "local random = math.random(1, 10000) " +
                "local sum = 0 " +
                "local selected = nil " +
                "for id, prize in pairs(prizes) do " +
                "  if prize.available and prize.stock > 0 then " +
                "    sum = sum + prize.probability " +
                "    if random <= sum then " +
                "      selected = prize " +
                "      prize.stock = prize.stock - 1 " +
                "      if prize.stock <= 0 then " +
                "        prize.available = false " +
                "      end " +
                "      redis.call('HSET', KEYS[1], id, prize) " +
                "      break " +
                "    end " +
                "  end " +
                "end " +
                "return selected";
        
        // 由于Lua脚本在Redis中执行复杂对象有限制,我们这里简化处理,使用Java代码模拟
        // 实际生产环境建议使用更细粒度的Redis数据结构和脚本
        
        // 模拟抽奖逻辑
        Prize selectedPrize = drawPrizeFromPool(availablePrizes);
        
        // 减少库存并更新Redis
        selectedPrize.setStock(selectedPrize.getStock() - 1);
        if (selectedPrize.getStock() <= 0) {
            selectedPrize.setAvailable(false);
        }
        redisTemplate.opsForHash().put(PRIZE_HASH_KEY, selectedPrize.getId().toString(), selectedPrize);
        
        // 记录抽奖
        incrementUserDrawCount(userId);
        recordUserDraw(userId, selectedPrize);
        
        return selectedPrize;
    }
    
    private Prize drawPrizeFromPool(List<Prize> prizes) {
        int totalProbability = prizes.stream()
                .mapToInt(Prize::getProbability)
                .sum();
        
        int randomNum = new Random().nextInt(totalProbability) + 1;
        
        int probabilitySum = 0;
        for (Prize prize : prizes) {
            probabilitySum += prize.getProbability();
            if (randomNum <= probabilitySum) {
                return prize;
            }
        }
        
        // 默认返回最后一个奖品(通常是"谢谢参与")
        return prizes.get(prizes.size() - 1);
    }
    
    private void checkDrawLimits(Long userId) {
        // 检查每日抽奖次数
        String dailyKey = DAILY_DRAW_COUNT_KEY + userId + ":" + LocalDate.now();
        Integer dailyCount = (Integer) redisTemplate.opsForValue().get(dailyKey);
        
        if (dailyCount != null && dailyCount >= 3) { // 假设每日限制3次
            throw new RuntimeException("Daily draw limit exceeded");
        }
        
        // 检查总抽奖次数
        String totalKey = TOTAL_DRAW_COUNT_KEY + userId;
        Integer totalCount = (Integer) redisTemplate.opsForValue().get(totalKey);
        
        if (totalCount != null && totalCount >= 10) { // 假设总限制10次
            throw new RuntimeException("Total draw limit exceeded");
        }
    }
    
    private void incrementUserDrawCount(Long userId) {
        // 增加每日抽奖次数
        String dailyKey = DAILY_DRAW_COUNT_KEY + userId + ":" + LocalDate.now();
        redisTemplate.opsForValue().increment(dailyKey, 1);
        // 设置过期时间(第二天凌晨过期)
        long secondsUntilTomorrow = ChronoUnit.SECONDS.between(
                LocalDateTime.now(), 
                LocalDate.now().plusDays(1).atStartOfDay());
        redisTemplate.expire(dailyKey, secondsUntilTomorrow, TimeUnit.SECONDS);
        
        // 增加总抽奖次数
        String totalKey = TOTAL_DRAW_COUNT_KEY + userId;
        redisTemplate.opsForValue().increment(totalKey, 1);
    }
    
    private void recordUserDraw(Long userId, Prize prize) {
        String recordKey = DRAW_RECORD_KEY + userId;
        Map<String, Object> record = new HashMap<>();
        record.put("userId", userId);
        record.put("prizeId", prize.getId());
        record.put("prizeName", prize.getName());
        record.put("drawTime", LocalDateTime.now().toString());
        
        redisTemplate.opsForList().leftPush(recordKey, record);
    }
}

控制器实现:

@RestController
@RequestMapping("/api/draw")
public class RedisDrawController {
    
    @Autowired
    private RedisDrawService redisDrawService;
    
    @GetMapping("/redis")
    public Prize redisDraw(@RequestParam Long userId) {
        return redisDrawService.draw(userId);
    }
}

3.3 优缺点分析

优点:

缺点:

3.4 适用场景

四、基于权重概率的抽奖策略

4.1 基本原理

基于权重概率的抽奖策略是在普通抽奖基础上增加了更复杂的概率计算逻辑,可以根据用户特征、活动规则动态调整奖品中奖概率。

例如,可以根据用户等级、消费金额、活动参与度等因素调整抽奖权重,实现精细化控制。

4.2 实现方式

首先定义动态权重计算接口:

public interface WeightCalculator {
    // 根据用户信息计算权重调整因子
    double calculateWeightFactor(Long userId);
}

// VIP用户权重计算器
@Component
public class VipWeightCalculator implements WeightCalculator {
    
    @Autowired
    private UserService userService;
    
    @Override
    public double calculateWeightFactor(Long userId) {
        User user = userService.getUserById(userId);
        
        // 根据用户VIP等级调整权重
        switch (user.getVipLevel()) {
            case 0: return 1.0;  // 普通用户,不调整
            case 1: return 1.2;  // VIP1,提高20%中奖率
            case 2: return 1.5;  // VIP2,提高50%中奖率
            case 3: return 2.0;  // VIP3,提高100%中奖率
            default: return 1.0;
        }
    }
}

// 新用户权重计算器
@Component
public class NewUserWeightCalculator implements WeightCalculator {
    
    @Autowired
    private UserService userService;
    
    @Override
    public double calculateWeightFactor(Long userId) {
        User user = userService.getUserById(userId);
        
        // 注册时间少于7天的新用户提高中奖率
        if (ChronoUnit.DAYS.between(user.getRegistrationDate(), LocalDate.now()) <= 7) {
            return 1.5; // 提高50%中奖率
        }
        
        return 1.0;
    }
}

// 活跃度权重计算器
@Component
public class ActivityWeightCalculator implements WeightCalculator {
    
    @Autowired
    private UserActivityService userActivityService;
    
    @Override
    public double calculateWeightFactor(Long userId) {
        int activityScore = userActivityService.getActivityScore(userId);
        
        // 根据活跃度调整权重
        if (activityScore >= 100) {
            return 1.3; // 提高30%中奖率
        } else if (activityScore >= 50) {
            return 1.1; // 提高10%中奖率
        }
        
        return 1.0;
    }
}

然后实现基于权重的抽奖服务:

@Service
public class WeightedDrawService {
    
    @Autowired
    private PrizeRepository prizeRepository;
    
    @Autowired
    private DrawRecordRepository drawRecordRepository;
    
    @Autowired
    private List<WeightCalculator> weightCalculators;
    
    private final Random random = new Random();
    
    public Prize draw(Long userId) {
        // 获取所有可用奖品
        List<Prize> availablePrizes = prizeRepository.findByAvailableTrueAndStockGreaterThan(0);
        if (availablePrizes.isEmpty()) {
            throw new RuntimeException("No available prizes");
        }
        
        // 计算用户的总权重因子
        double weightFactor = calculateTotalWeightFactor(userId);
        
        // 创建带权重的奖品列表
        List<WeightedPrize> weightedPrizes = createWeightedPrizeList(availablePrizes, weightFactor);
        
        // 根据权重选择奖品
        Prize selectedPrize = selectPrizeByWeight(weightedPrizes);
        
        // 减少库存
        selectedPrize.setStock(selectedPrize.getStock() - 1);
        if (selectedPrize.getStock() <= 0) {
            selectedPrize.setAvailable(false);
        }
        prizeRepository.save(selectedPrize);
        
        // 记录抽奖
        recordDraw(userId, selectedPrize);
        
        return selectedPrize;
    }
    
    private double calculateTotalWeightFactor(Long userId) {
        // 从所有权重计算器获取权重并相乘
        return weightCalculators.stream()
                .mapToDouble(calculator -> calculator.calculateWeightFactor(userId))
                .reduce(1.0, (a, b) -> a * b);
    }
    
    private List<WeightedPrize> createWeightedPrizeList(List<Prize> prizes, double weightFactor) {
        List<WeightedPrize> weightedPrizes = new ArrayList<>();
        
        for (Prize prize : prizes) {
            WeightedPrize weightedPrize = new WeightedPrize();
            weightedPrize.setPrize(prize);
            
            // 调整中奖概率
            if (prize.getName().equals("谢谢参与")) {
                // 对于"谢谢参与",权重因子反向作用(权重越高,越不容易"谢谢参与")
                weightedPrize.setAdjustedProbability((int) (prize.getProbability() / weightFactor));
            } else {
                // 对于实际奖品,权重因子正向作用(权重越高,越容易中奖)
                weightedPrize.setAdjustedProbability((int) (prize.getProbability() * weightFactor));
            }
            
            weightedPrizes.add(weightedPrize);
        }
        
        return weightedPrizes;
    }
    
    private Prize selectPrizeByWeight(List<WeightedPrize> weightedPrizes) {
        // 计算总概率
        int totalProbability = weightedPrizes.stream()
                .mapToInt(WeightedPrize::getAdjustedProbability)
                .sum();
        
        // 生成随机数
        int randomNum = random.nextInt(totalProbability) + 1;
        
        // 根据概率选择奖品
        int probabilitySum = 0;
        for (WeightedPrize weightedPrize : weightedPrizes) {
            probabilitySum += weightedPrize.getAdjustedProbability();
            if (randomNum <= probabilitySum) {
                return weightedPrize.getPrize();
            }
        }
        
        // 默认返回最后一个奖品(通常是"谢谢参与")
        return weightedPrizes.get(weightedPrizes.size() - 1).getPrize();
    }
    
    private void recordDraw(Long userId, Prize prize) {
        DrawRecord record = new DrawRecord();
        record.setUserId(userId);
        record.setPrizeId(prize.getId());
        record.setDrawTime(LocalDateTime.now());
        drawRecordRepository.save(record);
    }
    
    // 带权重的奖品类
    @Data
    private static class WeightedPrize {
        private Prize prize;
        private int adjustedProbability;
    }
}

控制器实现:

@RestController
@RequestMapping("/api/draw")
public class WeightedDrawController {
    
    @Autowired
    private WeightedDrawService weightedDrawService;
    
    @GetMapping("/weighted")
    public Prize weightedDraw(@RequestParam Long userId) {
        return weightedDrawService.draw(userId);
    }
}

4.3 优缺点分析

优点:

缺点:

4.4 适用场景

五、方案对比

6.1 性能对比

抽奖策略响应速度并发支持资源消耗扩展性
内存抽奖极快
数据库抽奖中等中等中等
Redis抽奖中等
权重抽奖中等中等

6.2 功能对比

抽奖策略奖品管理抽奖记录用户限制防作弊定制性
内存抽奖基础
数据库抽奖完善完善支持基础中等
Redis抽奖完善完善支持中等
权重抽奖完善完善支持极高

六、结语

在实际项目中,我们需要根据业务需求、用户规模、性能要求等因素,选择合适的抽奖策略或组合多种策略,以构建高效、可靠、安全的抽奖系统。

无论选择哪种抽奖策略,都需要关注系统的公平性、性能、可靠性和安全性,不断优化和改进。

以上就是基于SpringBoot实现抽奖活动的四种策略的详细内容,更多关于SpringBoot抽奖活动的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文