Java實現(xiàn)抽獎功能
本文實例為大家分享了Java實現(xiàn)抽獎功能的具體代碼,供大家參考,具體內(nèi)容如下
1 概述
項目開發(fā)中經(jīng)常會有抽獎這樣的營銷活動的需求,例如:積分大轉(zhuǎn)盤、刮刮樂、老虎機等等多種形式,其實后臺的實現(xiàn)方法是一樣的,本文介紹一種常用的抽獎實現(xiàn)方法。
整個抽獎過程包括以下幾個方面:
- 獎品
- 獎品池
- 抽獎算法
- 獎品限制
- 獎品發(fā)放
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 '狀態(tài)', `is_del` bit(1) DEFAULT NULL COMMENT '是否刪除', `position` int(5) DEFAULT NULL COMMENT '位置', `phase` int(10) DEFAULT NULL COMMENT '期數(shù)', `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 '獎品期數(shù)', `probability` float(4,2) DEFAULT NULL COMMENT '概率', `frozen` int(11) DEFAULT NULL COMMENT '商品抽中后的冷凍次數(shù)', `prize_day_max_times` int(11) DEFAULT NULL COMMENT '該商品平臺每天最多抽中的次數(shù)', `user_prize_month_max_times` int(11) DEFAULT NULL COMMENT '每位用戶每月最多抽中該商品的次數(shù)', `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 獎品池
獎品池是根據(jù)獎品的概率和限制組裝成的抽獎用的池子。主要包括獎品的總池值和每個獎品所占的池值(分為開始值和結(jié)束值)兩個維度。
- 獎品的總池值:所有獎品池值的總和。
- 每個獎品的池值:算法可以變通,常用的有以下兩種方式 :
1)、獎品的概率*10000(保證是整數(shù))
2)、獎品的概率10000獎品的剩余數(shù)量
獎品池bean:
public class PrizePool implements Serializable{
/**
* 總池值
*/
private int total;
/**
* 池中的獎品
*/
private List<PrizePoolBean> poolBeanList;
}
池中的獎品bean:
public class PrizePoolBean implements Serializable{
/**
* 數(shù)據(jù)庫中真實獎品的ID
*/
private Long id;
/**
* 獎品的開始池值
*/
private int begin;
/**
* 獎品的結(jié)束池值
*/
private int end;
}
獎品池的組裝代碼:
/**
* 獲取超級大富翁的獎品池
* @param zillionaireProductMap 超級大富翁獎品map
* @param flag true:有現(xiàn)金 false:無現(xiàn)金
* @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();
//無現(xiàn)金獎品池,過濾掉類型為現(xiàn)金的獎品
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. 隨機獎品池總池值以內(nèi)的整數(shù)
2. 循環(huán)比較獎品池中的所有獎品,隨機數(shù)落到哪個獎品的池區(qū)間即為哪個獎品中獎。
抽獎代碼:
public static PrizePoolBean getPrize(PrizePool prizePool){
//獲取總的獎品池值
int total = prizePool.getTotal();
//獲取隨機數(shù)
Random rand=new Random();
int random=rand.nextInt(total);
//循環(huán)比較獎品池區(qū)間
for(PrizePoolBean prizePoolBean : prizePool.getPoolBeanList()){
if(random >= prizePoolBean.getBengin() && random < prizePoolBean.getEnd()){
return prizePoolBean;
}
}
return null;
}
5 獎品限制
實際抽獎中對一些比較大的獎品往往有數(shù)量限制,比如:某某獎品一天最多被抽中5次、某某獎品每位用戶只能抽中一次。。等等類似的限制,對于這樣的限制我們分為兩種情況來區(qū)別對待:
1. 限制的獎品比較少,通常不多于3個:這種情況我們可以再組裝獎品池的時候就把不符合條件的獎品過濾掉,這樣抽中的獎品都是符合條件的。例如,在上面的超級大富翁抽獎代碼中,我們規(guī)定現(xiàn)金獎品一天只能被抽中5次,那么我們可以根據(jù)判斷條件分別組裝出有現(xiàn)金的獎品和沒有現(xiàn)金的獎品。
2. 限制的獎品比較多,這樣如果要采用第一種方式,就會導致組裝獎品非常繁瑣,性能低下,我們可以采用抽中獎品后校驗抽中的獎品是否符合條件,如果不符合條件則返回一個固定的獎品即可。
6 獎品發(fā)放
獎品發(fā)放可以采用工廠模式進行發(fā)放:不同的獎品類型走不同的獎品發(fā)放處理器,示例代碼如下:
獎品發(fā)放:
/**
* 異步分發(fā)獎品
* @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;
}
//根據(jù)獎品類型從工廠中獲取獎品發(fā)放類
SendPrizeProcessor sendPrizeProcessor = sendPrizeProcessorFactory.getSendPrizeProcessor(
PointsLuckDrawTypeEnum.getPointsLuckDrawTypeEnumByType(prizeDto.getType()));
if(ObjectUtil.isNotNull(sendPrizeProcessor)){
//發(fā)放獎品
sendPrizeProcessor.send(memberId, prizeDto);
}
}
return new AsyncResult<>(Boolean.TRUE);
}catch (Exception e){
//獎品發(fā)放失敗則記錄日志
saveSendPrizeErrorLog(memberId, prizeList);
LOGGER.error("積分抽獎發(fā)放獎品出現(xiàn)異常", 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 + "】的發(fā)送獎品處理器");
}
return processor;
}
}
獎品發(fā)放類舉例:
/**
* 紅包獎品發(fā)放類
*/
@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 {
// 發(fā)放紅包
Coupon coupon = couponService.receiveCoupon(memberId, Long.parseLong(prizeDto.getValue()));
//發(fā)送站內(nèi)信
messageLogService.insertActivityMessageLog(memberId,
"你參與積分抽大獎活動抽中的" + coupon.getAmount() + "元理財紅包已到賬,謝謝參與",
"積分抽大獎中獎通知");
//輸出log日志
LOGGER.info(memberId + "在積分抽獎中抽中的" + prizeDto.getPrizeName() + "已經(jīng)發(fā)放!");
}
}
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
淺析Java如何優(yōu)雅的設(shè)計接口狀態(tài)碼和異常
HTTP協(xié)議里定義了一系列的狀態(tài)碼用來表明請求的狀態(tài),如常用的200表示請求正常,404表示請求的資源不存在,所以本文就來和大家討論一下如何優(yōu)雅的設(shè)計接口狀態(tài)碼和異常,感興趣的可以了解下2024-03-03
基于Transactional事務(wù)的使用以及注意說明
這篇文章主要介紹了Transactional事務(wù)的使用以及注意說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07
SpringBoot排除不需要的自動配置類DataSourceAutoConfiguration問題
這篇文章主要介紹了SpringBoot排除不需要的自動配置類DataSourceAutoConfiguration問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07
SpringMVC中利用@InitBinder來對頁面數(shù)據(jù)進行解析綁定的方法
本篇文章主要介紹了SpringMVC中利用@InitBinder來對頁面數(shù)據(jù)進行解析綁定的方法,非常具有實用價值,需要的朋友可以參考下2018-03-03

