Source: blog.csdn.net/wang258533488/article/details/78901303
1 Overview
In project development, there are often the needs of marketing activities such as lucky draw, such as integral turntable, scratch music, slot machine and so on. In fact, the implementation method of the background is the same. This paper introduces a common implementation method of lucky draw.
The whole lottery process includes the following aspects:
-
prize
-
Prize pool
-
Lottery algorithm
-
Prize restriction
-
Prize distribution
2 prizes
Prizes include prizes, prize probabilities and limits, and prize records.
Prize table:
CREATE TABLE `points_luck_draw_prize` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(50) DEFAULT NULL COMMENT 'Prize name', `url` varchar(50) DEFAULT NULL COMMENT 'Picture address', `value` varchar(20) DEFAULT NULL, `type` tinyint(4) DEFAULT NULL COMMENT 'Type 1:Red envelope 2:Integral 3:Experience gold 4:Thank you for your patronage 5:custom', `status` tinyint(4) DEFAULT NULL COMMENT 'state', `is_del` bit(1) DEFAULT NULL COMMENT 'Delete', `position` int(5) DEFAULT NULL COMMENT 'position', `phase` int(10) DEFAULT NULL COMMENT 'Number of periods', `create_time` datetime DEFAULT NULL, `update_time` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=164 DEFAULT CHARSET=utf8mb4 COMMENT='Prize list';
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 'prize ID', `points_prize_phase` int(10) DEFAULT NULL COMMENT 'Number of prize periods', `probability` float(4,2) DEFAULT NULL COMMENT 'probability', `frozen` int(11) DEFAULT NULL COMMENT 'Freezing times of products after drawing', `prize_day_max_times` int(11) DEFAULT NULL COMMENT 'The maximum number of times that the commodity platform draws every day', `user_prize_month_max_times` int(11) DEFAULT NULL COMMENT 'The maximum number of times each user draws the product per month', `create_time` datetime DEFAULT NULL, `update_time` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=114 DEFAULT CHARSET=utf8mb4 COMMENT='Lottery probability limit table';
Prize record form:
CREATE TABLE `points_luck_draw_record` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `member_id` bigint(20) DEFAULT NULL COMMENT 'user ID', `member_mobile` varchar(11) DEFAULT NULL COMMENT 'Mobile phone number of winning user', `points` int(11) DEFAULT NULL COMMENT 'Consumption integral', `prize_id` bigint(20) DEFAULT NULL COMMENT 'prize ID', `result` smallint(4) DEFAULT NULL COMMENT '1:Win the prize 2:Failed to win the prize', `month` varchar(10) DEFAULT NULL COMMENT 'Winning month', `daily` date DEFAULT NULL COMMENT 'Winning date (excluding time)', `create_time` datetime DEFAULT NULL, `update_time` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3078 DEFAULT CHARSET=utf8mb4 COMMENT='Lottery record sheet';
3 prize pool
Prize pool is a pool for lucky draw assembled according to the probability and limitation of prizes. It mainly includes two dimensions: the total pool value of prizes and the pool value occupied by each prize (divided into start value and end value).
-
Total prize pool value: the sum of all prize pool values.
-
The pool value of each prize: the algorithm can be flexible. There are two common methods:
-
Probability of prize * 10000 (guaranteed to be an integer)
-
Probability of prize 10000 remaining number of prizes
-
Prize pool bean:
public class PrizePool implements Serializable{ /** * Total pool value */ private int total; /** * Prizes in the pool */ private List<PrizePoolBean> poolBeanList; }
Prize bean s in pool:
public class PrizePoolBean implements Serializable{ /** * ID of the real prize in the database */ private Long id; /** * Start pool value of prize */ private int begin; /** * End pool value of prize */ private int end; }
Assembly code of prize pool:
/** * Get the super millionaire's prize pool * @param zillionaireProductMap Super millionaire prize map * @param flag true:There's cash false: no cash * @return */ private PrizePool getZillionairePrizePool(Map<Long, ActivityProduct> zillionaireProductMap, boolean flag) { //Total prize pool value int total = 0; List<PrizePoolBean> poolBeanList = new ArrayList<>(); for(Entry<Long, ActivityProduct> entry : zillionaireProductMap.entrySet()){ ActivityProduct product = entry.getValue(); //Cashless prize pool, filtering out prizes of cash type if(!flag && product.getCategoryId() == ActivityPrizeTypeEnums.XJ.getType()){ continue; } //Assemble prize pool 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:
-
An integer within the total pool value of the random prize pool
-
Cycle and compare all prizes in the prize pool, and randomly count the pool range of which prize is the winning prize.
Lottery Code:
public static PrizePoolBean getPrize(PrizePool prizePool){ //Get the total prize pool value int total = prizePool.getTotal(); //Get random number Random rand=new Random(); int random=rand.nextInt(total); //Circular comparison pool interval for(PrizePoolBean prizePoolBean : prizePool.getPoolBeanList()){ if(random >= prizePoolBean.getBengin() && random < prizePoolBean.getEnd()){ return prizePoolBean; } } return null; }
5 prize restrictions
In the actual lottery, there are often quantitative restrictions on some large prizes. For example, a certain prize can be drawn up to 5 times a day, and each user can only draw a certain prize once.. And other similar restrictions. For such restrictions, we can treat them differently in two cases:
-
There are few restricted prizes, usually no more than 3: in this case, we can filter out the unqualified prizes when we reassemble the prize pool, so that all the prizes are qualified. For example, in the super millionaire lottery code above, we stipulate that cash prizes can only be drawn five times a day, so we can assemble cash prizes and cash free prizes respectively according to the judgment conditions.
-
There are many restricted prizes. In this way, if the first method is adopted, the assembly of prizes will be very cumbersome and the performance will be low. After winning the prize, we can check whether the prize meets the conditions. If not, we can return a fixed prize.
6 prize distribution
Prizes can be distributed in factory mode: different prize types use different prize distribution processors. The example code is as follows:
Prize distribution:
/** * Asynchronous distribution of prizes * @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){ //Filter out the prizes that thank you for patronizing if(prizeDto.getType() == PointsLuckDrawTypeEnum.XXHG.getType()){ continue; } //Obtain the prize distribution category from the factory according to the prize type SendPrizeProcessor sendPrizeProcessor = sendPrizeProcessorFactory.getSendPrizeProcessor( PointsLuckDrawTypeEnum.getPointsLuckDrawTypeEnumByType(prizeDto.getType())); if(ObjectUtil.isNotNull(sendPrizeProcessor)){ //Distribute prizes sendPrizeProcessor.send(memberId, prizeDto); } } return new AsyncResult<>(Boolean.TRUE); }catch (Exception e){ //If the prize distribution fails, the log will be recorded saveSendPrizeErrorLog(memberId, prizeList); LOGGER.error("There is an exception in the bonus distribution of points lottery", e); return new AsyncResult<>(Boolean.FALSE); } }
Factory type:
@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("The name was not found[" + processorName + "]Send prize processor"); } return processor; } }
Examples of prize distribution:
/** * Red envelope prize distribution */ @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 { // Issue red envelopes Coupon coupon = couponService.receiveCoupon(memberId, Long.parseLong(prizeDto.getValue())); //Send station letter messageLogService.insertActivityMessageLog(memberId, "You won the points draw" + coupon.getAmount() + "Yuan financial red envelope has arrived. Thank you for your participation", "Notification of winning points"); //Output log LOGGER.info(memberId + "In the points lottery" + prizeDto.getPrizeName() + "Issued!"); } }