Backend Development 10 min read

Design and Implementation of a Points-Based Lottery System Using Spring Boot

This article explains how to design and implement a points‑based lottery (luck draw) system in Java with Spring Boot, covering database schema for prizes, probability limits, prize pools, the random draw algorithm, prize restrictions, and asynchronous prize distribution using a factory pattern.

Top Architect
Top Architect
Top Architect
Design and Implementation of a Points-Based Lottery System Using Spring Boot

The article describes a common requirement in project development: implementing a points‑based lottery (luck draw) with various prize types such as cash red packets, points, experience money, and custom rewards.

2 Prize

Prizes are stored in three tables: the prize definition table, the probability limit table, and the prize record table.

CREATE TABLE `points_luck_draw_prize` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) DEFAULT NULL COMMENT '奖品名称',
  `url` varchar(50) DEFAULT NULL COMMENT '图片地址',
  `value` varchar(20) DEFAULT NULL,
  `type` tinyint(4) DEFAULT NULL COMMENT '类型1:红包2:积分3:体验金4:谢谢惠顾5:自定义',
  `status` tinyint(4) DEFAULT NULL COMMENT '状态',
  `is_del` bit(1) DEFAULT NULL COMMENT '是否删除',
  `position` int(5) DEFAULT NULL COMMENT '位置',
  `phase` int(10) DEFAULT NULL COMMENT '期数',
  `create_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=164 DEFAULT CHARSET=utf8mb4 COMMENT='奖品表';
CREATE TABLE `points_luck_draw_probability` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `points_prize_id` bigint(20) DEFAULT NULL COMMENT '奖品ID',
  `points_prize_phase` int(10) DEFAULT NULL COMMENT '奖品期数',
  `probability` float(4,2) DEFAULT NULL COMMENT '概率',
  `frozen` int(11) DEFAULT NULL COMMENT '商品抽中后的冷冻次数',
  `prize_day_max_times` int(11) DEFAULT NULL COMMENT '该商品平台每天最多抽中的次数',
  `user_prize_month_max_times` int(11) DEFAULT NULL COMMENT '每位用户每月最多抽中该商品的次数',
  `create_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=114 DEFAULT CHARSET=utf8mb4 COMMENT='抽奖概率限制表';
CREATE TABLE `points_luck_draw_record` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `member_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `member_mobile` varchar(11) DEFAULT NULL COMMENT '中奖用户手机号',
  `points` int(11) DEFAULT NULL COMMENT '消耗积分',
  `prize_id` bigint(20) DEFAULT NULL COMMENT '奖品ID',
  `result` smallint(4) DEFAULT NULL COMMENT '1:中奖 2:未中奖',
  `month` varchar(10) DEFAULT NULL COMMENT '中奖月份',
  `daily` date DEFAULT NULL COMMENT '中奖日期(不包括时间)',
  `create_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3078 DEFAULT CHARSET=utf8mb4 COMMENT='抽奖记录表';

3 Prize Pool

The prize pool aggregates total pool value and individual prize intervals (begin and end). Two common ways to calculate a prize's interval are using probability*10000 or probability*10000*remaining_quantity .

public class PrizePool implements Serializable {
    /** 总池值 */
    private int total;
    /** 池中的奖品 */
    private List<PrizePoolBean> poolBeanList;
}
public class PrizePoolBean implements Serializable {
    /** 数据库中真实奖品的ID */
    private Long id;
    /** 奖品的开始池值 */
    private int begin;
    /** 奖品的结束池值 */
    private int end;
}

4 Lottery Algorithm

The algorithm picks a random integer within the total pool value and finds the prize whose interval contains that number.

public static PrizePoolBean getPrize(PrizePool prizePool) {
    int total = prizePool.getTotal();
    Random rand = new Random();
    int random = rand.nextInt(total);
    for (PrizePoolBean prizePoolBean : prizePool.getPoolBeanList()) {
        if (random >= prizePoolBean.getBengin() && random < prizePoolBean.getEnd()) {
            return prizePoolBean;
        }
    }
    return null;
}

5 Prize Restrictions

Restrictions can be handled either by filtering out ineligible prizes when building the pool (suitable for a few restricted items) or by checking after a prize is drawn and falling back to a default prize (better for many restrictions).

6 Prize Distribution

Prize distribution uses a factory pattern to obtain a specific processor based on prize type, allowing asynchronous and transactional handling.

@Async("myAsync")
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public Future
sendPrize(Long memberId, List<PrizeDto> prizeList) {
    for (PrizeDto prizeDto : prizeList) {
        if (prizeDto.getType() == PointsLuckDrawTypeEnum.XXHG.getType()) {
            continue; // skip "thanks for participating"
        }
        SendPrizeProcessor processor = sendPrizeProcessorFactory.getSendPrizeProcessor(
            PointsLuckDrawTypeEnum.getPointsLuckDrawTypeEnumByType(prizeDto.getType()));
        if (processor != null) {
            processor.send(memberId, prizeDto);
        }
    }
    return new AsyncResult<>(Boolean.TRUE);
}
@Component("sendHbPrizeProcessor")
public class SendHbPrizeProcessor implements SendPrizeProcessor {
    @Resource
    private CouponService couponService;
    @Resource
    private MessageLogService messageLogService;
    @Override
    public void send(Long memberId, PrizeDto prizeDto) throws Exception {
        Coupon coupon = couponService.receiveCoupon(memberId, Long.parseLong(prizeDto.getValue()));
        messageLogService.insertActivityMessageLog(memberId,
            "你参与积分抽大奖活动抽中的" + coupon.getAmount() + "元理财红包已到账,谢谢参与",
            "积分抽大奖中奖通知");
        LOGGER.info(memberId + "在积分抽奖中抽中的" + prizeDto.getPrizeName() + "已经发放!");
    }
}

The article concludes with a call to join the architect community and provides additional reading links.

backendjavaAlgorithmDatabaseSpring Bootasynclottery
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.