Java实现抽奖功能
本文实例为大家分享了Java实现抽奖功能的具体代码,供大家参考,具体内容如下
1概述
项目开发中经常会有抽奖这样的营销活动的需求,例如:积分大转盘、刮刮乐、老虎机等等多种形式,其实后台的实现方法是一样的,本文介绍一种常用的抽奖实现方法。
整个抽奖过程包括以下几个方面:
- 奖品
- 奖品池
- 抽奖算法
- 奖品限制
- 奖品发放
2奖品
奖品包括奖品、奖品概率和限制、奖品记录。
奖品表:
CREATETABLE`points_luck_draw_prize`( `id`bigint(20)NOTNULLAUTO_INCREMENT, `name`varchar(50)DEFAULTNULLCOMMENT'奖品名称', `url`varchar(50)DEFAULTNULLCOMMENT'图片地址', `value`varchar(20)DEFAULTNULL, `type`tinyint(4)DEFAULTNULLCOMMENT'类型1:红包2:积分3:体验金4:谢谢惠顾5:自定义', `status`tinyint(4)DEFAULTNULLCOMMENT'状态', `is_del`bit(1)DEFAULTNULLCOMMENT'是否删除', `position`int(5)DEFAULTNULLCOMMENT'位置', `phase`int(10)DEFAULTNULLCOMMENT'期数', `create_time`datetimeDEFAULTNULL, `update_time`datetimeDEFAULTNULL, PRIMARYKEY(`id`) )ENGINE=InnoDBAUTO_INCREMENT=164DEFAULTCHARSET=utf8mb4COMMENT='奖品表';
奖品概率限制表:
CREATETABLE`points_luck_draw_probability`( `id`bigint(20)NOTNULLAUTO_INCREMENT, `points_prize_id`bigint(20)DEFAULTNULLCOMMENT'奖品ID', `points_prize_phase`int(10)DEFAULTNULLCOMMENT'奖品期数', `probability`float(4,2)DEFAULTNULLCOMMENT'概率', `frozen`int(11)DEFAULTNULLCOMMENT'商品抽中后的冷冻次数', `prize_day_max_times`int(11)DEFAULTNULLCOMMENT'该商品平台每天最多抽中的次数', `user_prize_month_max_times`int(11)DEFAULTNULLCOMMENT'每位用户每月最多抽中该商品的次数', `create_time`datetimeDEFAULTNULL, `update_time`datetimeDEFAULTNULL, PRIMARYKEY(`id`) )ENGINE=InnoDBAUTO_INCREMENT=114DEFAULTCHARSET=utf8mb4COMMENT='抽奖概率限制表';
奖品记录表:
CREATETABLE`points_luck_draw_record`( `id`bigint(20)NOTNULLAUTO_INCREMENT, `member_id`bigint(20)DEFAULTNULLCOMMENT'用户ID', `member_mobile`varchar(11)DEFAULTNULLCOMMENT'中奖用户手机号', `points`int(11)DEFAULTNULLCOMMENT'消耗积分', `prize_id`bigint(20)DEFAULTNULLCOMMENT'奖品ID', `result`smallint(4)DEFAULTNULLCOMMENT'1:中奖2:未中奖', `month`varchar(10)DEFAULTNULLCOMMENT'中奖月份', `daily`dateDEFAULTNULLCOMMENT'中奖日期(不包括时间)', `create_time`datetimeDEFAULTNULL, `update_time`datetimeDEFAULTNULL, PRIMARYKEY(`id`) )ENGINE=InnoDBAUTO_INCREMENT=3078DEFAULTCHARSET=utf8mb4COMMENT='抽奖记录表';
3奖品池
奖品池是根据奖品的概率和限制组装成的抽奖用的池子。主要包括奖品的总池值和每个奖品所占的池值(分为开始值和结束值)两个维度。
- 奖品的总池值:所有奖品池值的总和。
- 每个奖品的池值:算法可以变通,常用的有以下两种方式:
1)、奖品的概率*10000(保证是整数)
2)、奖品的概率10000奖品的剩余数量
奖品池bean:
publicclassPrizePoolimplementsSerializable{
/**
*总池值
*/
privateinttotal;
/**
*池中的奖品
*/
privateListpoolBeanList;
}
池中的奖品bean:
publicclassPrizePoolBeanimplementsSerializable{
/**
*数据库中真实奖品的ID
*/
privateLongid;
/**
*奖品的开始池值
*/
privateintbegin;
/**
*奖品的结束池值
*/
privateintend;
}
奖品池的组装代码:
/** *获取超级大富翁的奖品池 *@paramzillionaireProductMap超级大富翁奖品map *@paramflagtrue:有现金false:无现金 *@return */ privatePrizePoolgetZillionairePrizePool(MapzillionaireProductMap,booleanflag){ //总的奖品池值 inttotal=0; List poolBeanList=newArrayList<>(); for(Entry entry:zillionaireProductMap.entrySet()){ ActivityProductproduct=entry.getValue(); //无现金奖品池,过滤掉类型为现金的奖品 if(!flag&&product.getCategoryId()==ActivityPrizeTypeEnums.XJ.getType()){ continue; } //组装奖品池奖品 PrizePoolBeanprizePoolBean=newPrizePoolBean(); prizePoolBean.setId(product.getProductDescriptionId()); prizePoolBean.setBengin(total); total=total+product.getEarnings().multiply(newBigDecimal("10000")).intValue(); prizePoolBean.setEnd(total); poolBeanList.add(prizePoolBean); } PrizePoolprizePool=newPrizePool(); prizePool.setTotal(total); prizePool.setPoolBeanList(poolBeanList); returnprizePool; }
4抽奖算法
整个抽奖算法为:
1.随机奖品池总池值以内的整数
2.循环比较奖品池中的所有奖品,随机数落到哪个奖品的池区间即为哪个奖品中奖。
抽奖代码:
publicstaticPrizePoolBeangetPrize(PrizePoolprizePool){
//获取总的奖品池值
inttotal=prizePool.getTotal();
//获取随机数
Randomrand=newRandom();
intrandom=rand.nextInt(total);
//循环比较奖品池区间
for(PrizePoolBeanprizePoolBean:prizePool.getPoolBeanList()){
if(random>=prizePoolBean.getBengin()&&random
5奖品限制
实际抽奖中对一些比较大的奖品往往有数量限制,比如:某某奖品一天最多被抽中5次、某某奖品每位用户只能抽中一次。。等等类似的限制,对于这样的限制我们分为两种情况来区别对待:
1.限制的奖品比较少,通常不多于3个:这种情况我们可以再组装奖品池的时候就把不符合条件的奖品过滤掉,这样抽中的奖品都是符合条件的。例如,在上面的超级大富翁抽奖代码中,我们规定现金奖品一天只能被抽中5次,那么我们可以根据判断条件分别组装出有现金的奖品和没有现金的奖品。
2.限制的奖品比较多,这样如果要采用第一种方式,就会导致组装奖品非常繁琐,性能低下,我们可以采用抽中奖品后校验抽中的奖品是否符合条件,如果不符合条件则返回一个固定的奖品即可。
6奖品发放
奖品发放可以采用工厂模式进行发放:不同的奖品类型走不同的奖品发放处理器,示例代码如下:
奖品发放:
/**
*异步分发奖品
*@paramprizeList
*@throwsException
*/
@Async("myAsync")
@Transactional(rollbackFor=Exception.class,propagation=Propagation.REQUIRED)
publicFuturesendPrize(LongmemberId,ListprizeList){
try{
for(PrizeDtoprizeDto:prizeList){
//过滤掉谢谢惠顾的奖品
if(prizeDto.getType()==PointsLuckDrawTypeEnum.XXHG.getType()){
continue;
}
//根据奖品类型从工厂中获取奖品发放类
SendPrizeProcessorsendPrizeProcessor=sendPrizeProcessorFactory.getSendPrizeProcessor(
PointsLuckDrawTypeEnum.getPointsLuckDrawTypeEnumByType(prizeDto.getType()));
if(ObjectUtil.isNotNull(sendPrizeProcessor)){
//发放奖品
sendPrizeProcessor.send(memberId,prizeDto);
}
}
returnnewAsyncResult<>(Boolean.TRUE);
}catch(Exceptione){
//奖品发放失败则记录日志
saveSendPrizeErrorLog(memberId,prizeList);
LOGGER.error("积分抽奖发放奖品出现异常",e);
returnnewAsyncResult<>(Boolean.FALSE);
}
}
工厂类:
@Component
publicclassSendPrizeProcessorFactoryimplementsApplicationContextAware{
privateApplicationContextapplicationContext;
@Override
publicvoidsetApplicationContext(ApplicationContextapplicationContext)throwsBeansException{
this.applicationContext=applicationContext;
}
publicSendPrizeProcessorgetSendPrizeProcessor(PointsLuckDrawTypeEnumtypeEnum){
StringprocessorName=typeEnum.getSendPrizeProcessorName();
if(StrUtil.isBlank(processorName)){
returnnull;
}
SendPrizeProcessorprocessor=applicationContext.getBean(processorName,SendPrizeProcessor.class);
if(ObjectUtil.isNull(processor)){
thrownewRuntimeException("没有找到名称为【"+processorName+"】的发送奖品处理器");
}
returnprocessor;
}
}
奖品发放类举例:
/**
*红包奖品发放类
*/
@Component("sendHbPrizeProcessor")
publicclassSendHbPrizeProcessorimplementsSendPrizeProcessor{
privateLoggerLOGGER=LoggerFactory.getLogger(SendHbPrizeProcessor.class);
@Resource
privateCouponServicecouponService;
@Resource
privateMessageLogServicemessageLogService;
@Override
publicvoidsend(LongmemberId,PrizeDtoprizeDto)throwsException{
//发放红包
Couponcoupon=couponService.receiveCoupon(memberId,Long.parseLong(prizeDto.getValue()));
//发送站内信
messageLogService.insertActivityMessageLog(memberId,
"你参与积分抽大奖活动抽中的"+coupon.getAmount()+"元理财红包已到账,谢谢参与",
"积分抽大奖中奖通知");
//输出log日志
LOGGER.info(memberId+"在积分抽奖中抽中的"+prizeDto.getPrizeName()+"已经发放!");
}
}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。