Java抽奖实现

来源:互联网 发布:sql 存储过程输出 编辑:程序博客网 时间:2024/06/04 19:14

1 概述

项目开发中经常会有抽奖这样的营销活动的需求,例如:积分大转盘、刮刮乐、老虎机等等多种形式,其实后台的实现方法是一样的,本文介绍一种常用的抽奖实现方法。

整个抽奖过程包括以下几个方面:

  1. 奖品
  2. 奖品池
  3. 抽奖算法
  4. 奖品限制
  5. 奖品发放

2 奖品

奖品包括奖品、奖品概率和限制、奖品记录。
奖品表:

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='抽奖记录表';

3 奖品池

奖品池是根据奖品的概率和限制组装成的抽奖用的池子。主要包括奖品的总池值和每个奖品所占的池值(分为开始值和结束值)两个维度。

  • 奖品的总池值:所有奖品池值的总和。
  • 每个奖品的池值:算法可以变通,常用的有以下两种方式 :

    1. 奖品的概率*10000(保证是整数)
    2. 奖品的概率10000奖品的剩余数量

奖品池bean:

public class PrizePool implements Serializable{    /**     * 总池值     */    private int total;    /**     * 池中的奖品     */    private List<PrizePoolBean> 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<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 抽奖算法

整个抽奖算法为:

1. 随机奖品池总池值以内的整数 2. 循环比较奖品池中的所有奖品,随机数落到哪个奖品的池区间即为哪个奖品中奖。

抽奖代码:

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 奖品限制

实际抽奖中对一些比较大的奖品往往有数量限制,比如:某某奖品一天最多被抽中5次、某某奖品每位用户只能抽中一次。。等等类似的限制,对于这样的限制我们分为两种情况来区别对待:

1. 限制的奖品比较少,通常不多于3个:这种情况我们可以再组装奖品池的时候就把不符合条件的奖品过滤掉,这样抽中的奖品都是符合条件的。例如,在上面的超级大富翁抽奖代码中,我们规定现金奖品一天只能被抽中5次,那么我们可以根据判断条件分别组装出有现金的奖品和没有现金的奖品。2. 限制的奖品比较多,这样如果要采用第一种方式,就会导致组装奖品非常繁琐,性能低下,我们可以采用抽中奖品后校验抽中的奖品是否符合条件,如果不符合条件则返回一个固定的奖品即可。

6 奖品发放

奖品发放可以采用工厂模式进行发放:不同的奖品类型走不同的奖品发放处理器,示例代码如下:
奖品发放:

/**     * 异步分发奖品     * @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);        }    }

工厂类:

@Componentpublic 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() + "已经发放!");    }}
阅读全文
'); })();
0 0
原创粉丝点击
热门IT博客
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 抑郁症手抖 感觉手抖 特发性震颤手抖 甲状腺手抖 摇头手抖是什么病 手抖的病 手抖是咋回事 手抖的厉害 手一直抖 手抖咋回事 手抖严重 总是手抖 手指发抖 年轻人拿东西手抖怎么回事 怎么克服紧张手抖 什么病会引起手抖 青少年为什么会手抖 心理素质太差容易紧张手抖 问道手游集市抢公示脚本2018 我抢了灭霸的无限手 问道手游抢万年脚本 问道手游抢装备辅助器 问道手游押镖 男士手拎包品牌 手拎绳 手拎 饭盒袋 饭盒袋便当袋 饭盒包 手拷 严刑拷打 拷打 女体拷打研究所2 狱中拷打女囚 非刑拷打 性刑具折磨拷打男人 拷鬼棒 拷打折磨肌肉男 硬盘对拷 绷扒吊拷