1. 程式人生 > 其它 >用 Java 寫一個抽獎功能,太秀了~

用 Java 寫一個抽獎功能,太秀了~

概述

專案開發中經常會有抽獎這樣的營銷活動的需求,例如:積分大轉盤、刮刮樂、LH機等等多種形式,其實後臺的實現方法是一樣的,本文介紹一種常用的抽獎實現方法。

整個抽獎過程包括以下幾個方面:

  • 獎品
  • 獎品池
  • 抽獎演算法
  • 獎品限制
  • 獎品發放

獎品

獎品包括獎品、獎品概率和限制、獎品記錄。

獎品表:

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='抽獎記錄表';

獎品池

獎品池是根據獎品的概率和限制組裝成的抽獎用的池子。主要包括獎品的總池值和每個獎品所佔的池值(分為開始值和結束值)兩個維度。

  • 獎品的總池值:所有獎品池值的總和。

  • 每個獎品的池值:演算法可以變通,常用的有以下兩種方式 :

    • 獎品的概率*10000(保證是整數)
    • 獎品的概率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;
    }

抽獎演算法

整個抽獎演算法為:

  • 隨機獎品池總池值以內的整數
  • 迴圈比較獎品池中的所有獎品,隨機數落到哪個獎品的池區間即為哪個獎品中獎。

抽獎程式碼:

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<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);
        }
    }

工廠類:

@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

版權宣告:本文為CSDN博主「秦霜」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處連結及本宣告。

近期熱文推薦:

1.1,000+ 道 Java面試題及答案整理(2022最新版)

2.勁爆!Java 協程要來了。。。

3.Spring Boot 2.x 教程,太全了!

4.別再寫滿屏的爆爆爆炸類了,試試裝飾器模式,這才是優雅的方式!!

5.《Java開發手冊(嵩山版)》最新發布,速速下載!

覺得不錯,別忘了隨手點贊+轉發哦!