概述 项目开发中经常会有抽奖这样的营销活动的需求,例如:积分大转盘、刮刮乐、老虎机等等多种形式,其实后台的实现方法是一样的,本文介绍一种常用的抽奖实现方法。 整个抽奖过程包括以下几个方面:奖品奖品池抽奖算法奖品限制奖品发放奖品奖品包括奖品、奖品概率和限制、奖品记录。 奖品表:CREATETABLEpointsluckdrawprize(idbigint(20)NOTNULLAUTOINCREMENT,namevarchar(50)DEFAULTNULLCOMMENT奖品名称,urlvarchar(50)DEFAULTNULLCOMMENT图片地址,valuevarchar(20)DEFAULTNULL,typetinyint(4)DEFAULTNULLCOMMENT类型1:红包2:积分3:体验金4:谢谢惠顾5:自定义,statustinyint(4)DEFAULTNULLCOMMENT状态,isdelbit(1)DEFAULTNULLCOMMENT是否删除,positionint(5)DEFAULTNULLCOMMENT位置,phaseint(10)DEFAULTNULLCOMMENT期数,createtimedatetimeDEFAULTNULL,updatetimedatetimeDEFAULTNULL,PRIMARYKEY(id))ENGINEInnoDBAUTOINCREMENT164DEFAULTCHARSETutf8mb4COMMENT奖品表; 奖品概率限制表:CREATETABLEpointsluckdrawprobability(idbigint(20)NOTNULLAUTOINCREMENT,pointsprizeidbigint(20)DEFAULTNULLCOMMENT奖品ID,pointsprizephaseint(10)DEFAULTNULLCOMMENT奖品期数,probabilityfloat(4,2)DEFAULTNULLCOMMENT概率,frozenint(11)DEFAULTNULLCOMMENT商品抽中后的冷冻次数,prizedaymaxtimesint(11)DEFAULTNULLCOMMENT该商品平台每天最多抽中的次数,userprizemonthmaxtimesint(11)DEFAULTNULLCOMMENT每位用户每月最多抽中该商品的次数,createtimedatetimeDEFAULTNULL,updatetimedatetimeDEFAULTNULL,PRIMARYKEY(id))ENGINEInnoDBAUTOINCREMENT114DEFAULTCHARSETutf8mb4COMMENT抽奖概率限制表; 奖品记录表:CREATETABLEpointsluckdrawrecord(idbigint(20)NOTNULLAUTOINCREMENT,memberidbigint(20)DEFAULTNULLCOMMENT用户ID,membermobilevarchar(11)DEFAULTNULLCOMMENT中奖用户手机号,pointsint(11)DEFAULTNULLCOMMENT消耗积分,prizeidbigint(20)DEFAULTNULLCOMMENT奖品ID,resultsmallint(4)DEFAULTNULLCOMMENT1:中奖2:未中奖,monthvarchar(10)DEFAULTNULLCOMMENT中奖月份,dailydateDEFAULTNULLCOMMENT中奖日期(不包括时间),createtimedatetimeDEFAULTNULL,updatetimedatetimeDEFAULTNULL,PRIMARYKEY(id))ENGINEInnoDBAUTOINCREMENT3078DEFAULTCHARSETutf8mb4COMMENT抽奖记录表;奖品池 奖品池是根据奖品的概率和限制组装成的抽奖用的池子。主要包括奖品的总池值和每个奖品所占的池值(分为开始值和结束值)两个维度。最新面试题整理好了,点击Java面试库小程序在线刷题。奖品的总池值:所有奖品池值的总和。每个奖品的池值:算法可以变通,常用的有以下两种方式: 奖品的概率10000(保证是整数)奖品的概率10000奖品的剩余数量 奖品池bean:publicclassPrizePoolimplementsSerializable{总池值池中的奖品privateListPrizePoolBeanpoolBeanL} 池中的奖品bean:publicclassPrizePoolBeanimplementsSerializable{数据库中真实奖品的IDprivateL奖品的开始池值奖品的结束池值} 奖品池的组装代码:获取超级大富翁的奖品池paramzillionaireProductMap超级大富翁奖品mapparamflagtrue:有现金false:无现金returnprivatePrizePoolgetZillionairePrizePool(MapLong,ActivityProductzillionaireProductMap,booleanflag){总的奖品池值inttotal0;ListPrizePoolBeanpoolBeanListnewArrayList();for(EntryLong,ActivityProductentry:zillionaireProductMap。entrySet()){ActivityProductproductentry。getValue();无现金奖品池,过滤掉类型为现金的奖品if(!flagproduct。getCategoryId()ActivityPrizeTypeEnums。XJ。getType()){}组装奖品池奖品PrizePoolBeanprizePoolBeannewPrizePoolBean();prizePoolBean。setId(product。getProductDescriptionId());prizePoolBean。setBengin(total);totaltotalproduct。getEarnings()。multiply(newBigDecimal(10000))。intValue();prizePoolBean。setEnd(total);poolBeanList。add(prizePoolBean);}PrizePoolprizePoolnewPrizePool();prizePool。setTotal(total);prizePool。setPoolBeanList(poolBeanList);returnprizeP}抽奖算法 整个抽奖算法为:随机奖品池总池值以内的整数循环比较奖品池中的所有奖品,随机数落到哪个奖品的池区间即为哪个奖品中奖。 推荐一个SpringBoot基础教程及实战示例: https:github。comjavastacksspringbootbestpractice 抽奖代码:publicstaticPrizePoolBeangetPrize(PrizePoolprizePool){获取总的奖品池值inttotalprizePool。getTotal();获取随机数RandomrandnewRandom();intrandomrand。nextInt(total);循环比较奖品池区间for(PrizePoolBeanprizePoolBean:prizePool。getPoolBeanList()){if(randomprizePoolBean。getBengin()randomprizePoolBean。getEnd()){returnprizePoolB}}}奖品限制 实际抽奖中对一些比较大的奖品往往有数量限制,比如:某某奖品一天最多被抽中5次、某某奖品每位用户只能抽中一次等等类似的限制,对于这样的限制我们分为两种情况来区别对待:限制的奖品比较少,通常不多于3个:这种情况我们可以再组装奖品池的时候就把不符合条件的奖品过滤掉,这样抽中的奖品都是符合条件的。例如,在上面的超级大富翁抽奖代码中,我们规定现金奖品一天只能被抽中5次,那么我们可以根据判断条件分别组装出有现金的奖品和没有现金的奖品。限制的奖品比较多,这样如果要采用第一种方式,就会导致组装奖品非常繁琐,性能低下,我们可以采用抽中奖品后校验抽中的奖品是否符合条件,如果不符合条件则返回一个固定的奖品即可。奖品发放 奖品发放可以采用工厂模式进行发放:不同的奖品类型走不同的奖品发放处理器,示例代码如下: 奖品发放:异步分发奖品paramprizeListthrowsExceptionAsync(myAsync)Transactional(rollbackForException。class,propagationPropagation。REQUIRED)publicFutureBooleansendPrize(LongmemberId,ListPrizeDtoprizeList){try{for(PrizeDtoprizeDto:prizeList){过滤掉谢谢惠顾的奖品if(prizeDto。getType()PointsLuckDrawTypeEnum。XXHG。getType()){}根据奖品类型从工厂中获取奖品发放类SendPrizeProcessorsendPrizeProcessorsendPrizeProcessorFactory。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);}} 工厂类:ComponentpublicclassSendPrizeProcessorFactoryimplementsApplicationContextAware{privateApplicationContextapplicationCOverridepublicvoidsetApplicationContext(ApplicationContextapplicationContext)throwsBeansException{this。applicationContextapplicationC}publicSendPrizeProcessorgetSendPrizeProcessor(PointsLuckDrawTypeEnumtypeEnum){StringprocessorNametypeEnum。getSendPrizeProcessorName();if(StrUtil。isBlank(processorName)){}SendPrizeProcessorprocessorapplicationContext。getBean(processorName,SendPrizeProcessor。class);if(ObjectUtil。isNull(processor)){thrownewRuntimeException(没有找到名称为【processorName】的发送奖品处理器);}}} 奖品发放类举例:红包奖品发放类Component(sendHbPrizeProcessor)publicclassSendHbPrizeProcessorimplementsSendPrizeProcessor{privateLoggerLOGGERLoggerFactory。getLogger(SendHbPrizeProcessor。class);ResourceprivateCouponServicecouponSResourceprivateMessageLogServicemessageLogSOverridepublicvoidsend(LongmemberId,PrizeDtoprizeDto)throwsException{发放红包CouponcouponcouponService。receiveCoupon(memberId,Long。parseLong(prizeDto。getValue()));发送站内信messageLogService。insertActivityMessageLog(memberId,你参与积分抽大奖活动抽中的coupon。getAmount()元理财红包已到账,谢谢参与,积分抽大奖中奖通知);输出log日志LOGGER。info(memberId在积分抽奖中抽中的prizeDto。getPrizeName()已经发放!);}} 原文链接:https:blog。csdn。netwang258533488articledetails78901303