Java implements the lottery function

This article mainly introduces the lottery function implemented by Java in detail. The sample code in the article is very detailed and has certain reference value. Interested friends can refer to it.

The examples in this article share the specific code for implementing the lottery function in Java for your reference. The specific content is as follows

1 Overview

In project development, there is often a need for marketing activities such as lottery, such as: point carousel, scratch-off, slot machine, etc. In fact, the background implementation method is the same. This article introduces a commonly used method of lottery implementation.

The whole lottery process includes the following aspects:

  1. prize
  2. Prize pool
  3. Lottery algorithm
  4. Prize restrictions
  5. Prize distribution

2 prizes

Prizes include prizes, prize probabilities and limitations, and prize records.
Prize 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='奖品表';

Prize probability limit table:

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='抽奖概率限制表';

Prize record table:

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 is a lottery pool assembled according to the probability and restriction of the prize. It mainly includes two dimensions: the total pool value of the prizes and the pool value (divided into a start value and an end value) occupied by each prize.

Total prize pool value: the sum of all prize pool values.
Pool value of each prize: The algorithm can be flexible, and the following two methods are commonly used:
1) The probability of the prize *10000 (guaranteed to be an integer)
2) The probability of the prize 10000 The remaining number of
prizes prize pool bean:

public class PrizePool implements Serializable{
 /**
  * 总池值
  */
 private int total;
 /**
  * 池中的奖品
  */
 private List<PrizePoolBean> poolBeanList;
}

Prize beans in the pool:

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

The assembly code of the prize pool:

/**
  * 获取超级大富翁的奖品池
  * @param zillionaireProductMap 超级大富翁奖品map
  * @param flag true:有现金 false:无现金
  * @return
  */
 private PrizePool getZillionairePrizePool(Map<Long, ActivityProduct> zillionaireProductMap, boolean flag) {
    
    
  //总的奖品池值
  int total = 0;
  List<PrizePoolBean> poolBeanList = new ArrayList<>();
  for(Entry<Long, ActivityProduct> entry : zillionaireProductMap.entrySet()){
    
    
   ActivityProduct product = entry.getValue();
   //无现金奖品池,过滤掉类型为现金的奖品
   if(!flag && product.getCategoryId() == ActivityPrizeTypeEnums.XJ.getType()){
    
    
    continue;
   }
   //组装奖品池奖品
   PrizePoolBean prizePoolBean = new PrizePoolBean();
   prizePoolBean.setId(product.getProductDescriptionId());
   prizePoolBean.setBengin(total);
   total = total + product.getEarnings().multiply(new BigDecimal("10000")).intValue();
   prizePoolBean.setEnd(total);
   poolBeanList.add(prizePoolBean);
  }

  PrizePool prizePool = new PrizePool();
  prizePool.setTotal(total);
  prizePool.setPoolBeanList(poolBeanList);
  return prizePool;
}

4 Lottery Algorithm

The whole lottery algorithm is:

  1. An integer within the total value of the random prize pool
  2. Comparing all the prizes in the prize pool in a round-robin manner, the random number falls into the range of the prize pool which is the prize.
    Lucky draw code:
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

In actual lottery draws, there are often restrictions on the number of relatively large prizes. For example, a certain prize can be drawn up to 5 times a day, and each user can only draw once for a certain prize. . And so on similar restrictions, we divide such restrictions into two situations to treat them differently:

  1. There are relatively few restricted prizes, usually no more than three: in this case, we can filter out the unqualified prizes when we reassemble the prize pool, so that the prizes drawn are eligible. For example, in the above code for the super-monopoly lottery, we stipulate that cash prizes can only be drawn 5 times a day, then we can assemble cash prizes and prizes without cash according to the judgment conditions.
  2. There are many restricted prizes, so if the first method is to be used, the assembly of the prizes will be very cumbersome and the performance will be low. We can use the prizes to check whether the prizes meet the conditions, and if they do not meet the conditions, return one A fixed prize is enough.

6 Prize distribution

Prizes can be distributed in a factory mode: different prize types use different prize distribution processors. The sample code is as follows:
Prize distribution:

/**
  * 异步分发奖品
  * @param prizeList
  * @throws Exception
  */
 @Async("myAsync")
 @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
 public Future<Boolean> sendPrize(Long memberId, List<PrizeDto> prizeList){
    
    
  try {
    
    
   for(PrizeDto prizeDto : prizeList){
    
    
    //过滤掉谢谢惠顾的奖品
    if(prizeDto.getType() == PointsLuckDrawTypeEnum.XXHG.getType()){
    
    
     continue;
    }
    //根据奖品类型从工厂中获取奖品发放类
    SendPrizeProcessor sendPrizeProcessor = sendPrizeProcessorFactory.getSendPrizeProcessor(
     PointsLuckDrawTypeEnum.getPointsLuckDrawTypeEnumByType(prizeDto.getType()));
    if(ObjectUtil.isNotNull(sendPrizeProcessor)){
    
    
     //发放奖品
     sendPrizeProcessor.send(memberId, prizeDto);
    }
   }
   return new AsyncResult<>(Boolean.TRUE);
  }catch (Exception e){
    
    
   //奖品发放失败则记录日志
   saveSendPrizeErrorLog(memberId, prizeList);
   LOGGER.error("积分抽奖发放奖品出现异常", e);
   return new AsyncResult<>(Boolean.FALSE);
  }
}

Factory class:

@Component
public class SendPrizeProcessorFactory implements ApplicationContextAware{
    
    
 private ApplicationContext applicationContext;

 @Override
 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    
    
  this.applicationContext = applicationContext;
 }

 public SendPrizeProcessor getSendPrizeProcessor(PointsLuckDrawTypeEnum typeEnum){
    
    
  String processorName = typeEnum.getSendPrizeProcessorName();
  if(StrUtil.isBlank(processorName)){
    
    
   return null;
  }
  SendPrizeProcessor processor = applicationContext.getBean(processorName, SendPrizeProcessor.class);
  if(ObjectUtil.isNull(processor)){
    
    
   throw new RuntimeException("没有找到名称为【" + processorName + "】的发送奖品处理器");
  }
  return processor;
 }
}

Examples of prizes:

/**
 * 红包奖品发放类
 */
@Component("sendHbPrizeProcessor")
public class SendHbPrizeProcessor implements SendPrizeProcessor{
    
    
 private Logger LOGGER = LoggerFactory.getLogger(SendHbPrizeProcessor.class);
 @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() + "元理财红包已到账,谢谢参与",
   "积分抽大奖中奖通知");
  //输出log日志
  LOGGER.info(memberId + "在积分抽奖中抽中的" + prizeDto.getPrizeName() + "已经发放!");
 }
}

The above is the whole content of this article, I hope it will be helpful to everyone's study, and I hope everyone will support you

Guess you like

Origin blog.csdn.net/p1830095583/article/details/114944972