用Java写了一个抽奖功能,年会抽奖靠它了
概述
项目开发中经常会有抽奖这样的营销活动的需求,例如:积分大转盘、刮刮乐、老虎机等等多种形式,其实后台的实现方法是一样的,本文介绍一种常用的抽奖实现方法。
整个抽奖过程包括以下几个方面: 奖品 奖品池 抽奖算法 奖品限制 奖品发放 奖品奖品包括奖品、奖品概率和限制、奖品记录。
奖品表: 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="抽奖记录表"; 奖品池
奖品池是根据奖品的概率和限制组装成的抽奖用的池子。主要包括奖品的总池值和每个奖品所占的池值(分为开始值和结束值)两个维度。 最新面试题整理好了,点击Java面试库小程序在线刷题。 奖品的总池值:所有奖品池值的总和。 每个奖品的池值:算法可以变通,常用的有以下两种方式 :
奖品的概率*10000(保证是整数) 奖品的概率10000奖品的剩余数量
奖品池bean: public class PrizePool implements Serializable{ /** * 总池值 */ private int total; /** * 池中的奖品 */ private List poolBeanList; }
池中的奖品bean: public class PrizePoolBean implements Serializable{ /** * 数据库中真实奖品的ID */ private Long id; /** * 奖品的开始池值 */ private int begin; /** * 奖品的结束池值 */ private int end; }
奖品池的组装代码: /** * 获取超级大富翁的奖品池 * @param zillionaireProductMap 超级大富翁奖品map * @param flag true:有现金 false:无现金 * @return */ private PrizePool getZillionairePrizePool(Map zillionaireProductMap, boolean flag) { //总的奖品池值 int total = 0; List poolBeanList = new ArrayList<>(); for(Entry 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; } 抽奖算法
整个抽奖算法为: 随机奖品池总池值以内的整数 循环比较奖品池中的所有奖品,随机数落到哪个奖品的池区间即为哪个奖品中奖。
推荐一个 Spring Boot 基础教程及实战示例:
https://github.com/javastacks/spring-boot-best-practice
抽奖代码: 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次、某某奖品每位用户只能抽中一次…等等类似的限制,对于这样的限制我们分为两种情况来区别对待: 限制的奖品比较少,通常不多于3个:这种情况我们可以再组装奖品池的时候就把不符合条件的奖品过滤掉,这样抽中的奖品都是符合条件的。例如,在上面的超级大富翁抽奖代码中,我们规定现金奖品一天只能被抽中5次,那么我们可以根据判断条件分别组装出有现金的奖品和没有现金的奖品。 限制的奖品比较多,这样如果要采用第一种方式,就会导致组装奖品非常繁琐,性能低下,我们可以采用抽中奖品后校验抽中的奖品是否符合条件,如果不符合条件则返回一个固定的奖品即可。 奖品发放
奖品发放可以采用工厂模式进行发放:不同的奖品类型走不同的奖品发放处理器,示例代码如下:
奖品发放: /** * 异步分发奖品 * @param prizeList * @throws Exception */ @Async("myAsync") @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED) public Future sendPrize(Long memberId, List 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); } }
工厂类: @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; } }
奖品发放类举例: /** * 红包奖品发放类 */ @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() + "已经发放!"); } }
原文链接:https://blog.csdn.net/wang258533488/article/details/78901303
什么是年味?让你恋恋不忘的年味到底是什么呢?街上终于冷冷清清的了,行人也少了,北京的环路上终于也不堵车了,坐下来可以琢磨琢磨除夕的年夜饭都吃些什么了,这就是年味么?小时候的年味不是这个样子啊什么是年味呢?(可以继续在文章下留
样片蔡司ZeissIkonikonta52026X9皮腔折叠机样片及介绍样片蔡司ZeissIkonikonta52026X9皮腔折叠机,相机介绍ZeissIkonIkonta系列折叠皮腔机,是德国Ziess公司出品的中画幅相机了。使用120胶卷,可以拍
人类高质量男性被央妈封杀听说过人类高质量男性吗?最近,人类高质量男性徐勤根被禁言了。走红不到一个月,徐勤根就摊上了大事儿,但只能说他,活该。有网友曝光,徐勤根的粉丝群月付费两万五才能进入还有媒体爆料,徐勤
评测欢迎新同学!realmeBook开箱测评夏天夏天悄悄过去留下小秘密我是尤娜,硬件数码爱好者一只。我的开箱测评都是自费购买,尽量保证中立客观不恰饭。文章结尾,我都会给出该产品的推荐指数,以及在这个测评时间点的购买建议。如以
禄来35旁轴ROLLEI35旁轴相机样片乐凯100过期卷试拍首先相机是这个样子的rollei35Rollei35诞生于1966年,由天才设计师HeinzWaaske亲手缔造,可谓当时世界上尺寸最小的135相机。它以体积小巧简单易用等特点迅速
朱茵吃醋,原来是因为披荆斩棘的哥哥中的他作者斜杠先生披荆斩棘的哥哥一经播出,点沸全网!陈小春张云龙张晋黄贯中等老男神的加入让大家直呼爷青回!正当大家沉浸在哥哥们的舞台时,朱茵的一条微博却让气氛有些凝重。节目刚经播出,朱茵
装修前必看系列冰箱也有大学问,万元级冰箱选购攻略马上就要到金九银十的家装旺季了,很多人已经开始着手为新家选购各式各样的家用电器了。尤其是冰箱洗衣机空调之类的大家电,不像普通消费类小家电更换频率那么高,买回家很可能就要陪伴很多年,
汉唐车友高尔夫球场体验赛又是一个秋高气爽的周末,一切都在阳光和蓝天的映照下显得格外美好!下午跟随比亚迪车友车队,来到长江之滨的高尔夫俱乐部,体验一把挥杆击球的快乐。蔚蓝的天空下碧绿的球场草地,心旷神怡!车
再三踌躇,终于还是定了唐dmi中秋节期间,销售跟我说有活动,让我去店里看看我带着老婆一起去到店里,老婆看了还是挺喜欢的,说没想到国产车也能做的这么好前脸的话也采用了一个龙脸的设计,看起来还是挺凶的,就是没有纯电
汉ev5g丹拿体验刚订完唐dmi,朋友说想买汉我就带他到4s店去看车,家里给了他五十万,但是又步想买suv,说要买性能车,然后我就带她去去看了汉,汉ev的顶配百公里3。9s,销售带他试驾了一圈,朋友
win10系统更新失败提示还原旧版本的解决方法有不少深度技术的win10系统用户都应该遇到过在电脑系统更新的时候,会出现更新失败并一直提示还原旧版本的问题,但是还原旧版本的时候还是不行,就一直死循环了。下面,深度技术系统小编就
恒大玩砸了一hr恒大恒大,原意是永远搞大。但这一次,究竟是搞大还是搞崩,真就不一定了!这两天,恒大财富暴雷的消息,在坊间流传。恒大总部自己也发声承认,公司确实遇到前所未有的困难。前所未有4个
突然宣布!大洗牌开始一hr最近,教育界的大事,一件接着一件!今天,又迎来了一个重磅!刚刚,教育部召开新闻发布会,宣布中小学生要进一步强化体育课和课外锻炼!废话不多说,按照惯例,我直接将重点挑出来给你们
洲明科技发布2021半年报净利增加14。388月8日晚间,洲明科技发布半年度报告。公告显示,公司半年度营业收入2,931,744,207。96元,同比增加57。57,归属上市公司股东的净利润90,904,856。55元,同比
万亿体育市场开口,LED显示屏行业进场指南针看这里国务院印发的新文件全民健身计划(20212025),计划中谈到对体育产业的推进,包括对2000个以上体育公园全民健身中心公共体育场馆等健身场地设施大规模建设和体育产业数字化转型,助
转型年2021,与您预先窥探未来前沿技术如果颠覆是2020年的标志,那2021应该是转型。而迈入未来,市场将展现什么样的情景呢?根据Gartner研究分析,技术行业的下一个发展阶段将主要聚焦于以人为本位置独立和弹性交付三
2021年中国商显大屏市场销售预计达到604亿元,同比增长超222020年是激荡与变革的一年,新冠疫情加速了社会智能化数字化转型。2021年商显行业推出众多智能化沉浸式的显示解决方案,在5GAIIoT等新技术的催化下,商用显示设备不仅仅局限于单
轰动!刚刚宣布中国唯一一hr今天,一只命运的蝴蝶,在中国突然展开了它颠覆之翅!这次看似不太起眼的翅膀扇动,却很可能将在不远的将来,引起山呼海啸般的风暴!刚刚突然传来大消息中科院研究团队构建了113个光子
吉林迎来特大机遇中国新电商大会,将在省会长春举行牛气冲天是好年,吉林省迎来新机遇。据相关媒体报道,中国首届新电商大会,在吉林省省会城市长春举办,这也标志着吉林,再次迎来大机遇。有哪些是新电商呢?直播电商社交电商社区电商等,都可以
深夜突发!美国终于得手一hr挣扎复挣扎!台积电,还是逃不过这一劫啊!针对美国要求上交库存客户订单等敏感资料一事,此前声称不会泄露客户的机密资料的台积电22日表态称,将会在11月8日截止日期前,把相关资料
雪上加霜!限电令下LED显示屏行业如何找寻充电宝?9月本该是金九银十的生产旺季,奈何受煤价飞涨造成的电力短缺因素叠加碳中和的大背景,在能源消耗双重调控下,限电政策给制造业带来了一波暴击。对于LED显示屏行业来说,上半年刚经历过卷土
以亿级的产能扩产MiniMicroLED即将开启第二次风口?2020年,新冠疫情下小间距LED显示屏行业在经历了寒潮之后,2021年迅速迎来了全新的局面。上半年几大上市公司业绩一路飘红,如联建光电收入5。02亿元,同比增长153。46,实现