简介
最近闲的无聊,将以前做的一个微信抽奖小demo拿来分享一下,以便加深印象。
效果图
1.gif业务要求
在一个奖池中放一堆奖品,分别给它们设置不同的数量,比如一等奖A10个,二等奖B,30个,三等奖C10个,然后设置参与人数C人
条件是:
当奖品数大于参与人数,100%中奖。
当奖品A发放完是,不能被抽中。
当奖品发放完毕是,该抽奖活动自动下架。
同一个用户如果中奖了,将不能继续参与该活动。
这里只讨论下其中的核心算法的设计及一个示例函数,算法之外的系统控制暂不提及。
实现抽奖的方法应该有很多,没有仔细去考察和搜索那些非常复杂的算法,这里仅做了一个简单的假设,并在此基础上推出后面所有的控制逻辑。
实现方法
java核心抽奖代码如下:
public class LotteryUtil {
/**
*
* @param orignalRates 传人每一个奖品概率的集合,(集合的第一个参数是0.0 表示百分比中奖)
* @return
*/
public static int lottery(List<Double> orignalRates) {
if (orignalRates == null || orignalRates.isEmpty()) {
return -1;
}
int size = orignalRates.size();
// 计算总概率,这样可以保证不一定总概率是1
double sumRate = 0d;
for (double rate : orignalRates) {
sumRate += rate;
}
// 计算每个物品在总概率的基础下的概率情况
List<Double> sortOrignalRates = new ArrayList<Double>(size);
Double tempSumRate = 0d;
/*遍历奖品概率的集合,计算每一个奖品的中间区间*/
for (double rate : orignalRates) {
tempSumRate += rate;
sortOrignalRates.add(tempSumRate/sumRate);
}
// 根据区块值来获取抽取到的物品索引
double nextDouble = Math.random();
sortOrignalRates.add(nextDouble);
Collections.sort(sortOrignalRates);
return sortOrignalRates.indexOf(nextDouble);
}
}
抽奖的业务逻辑代码如下
/*awardItems获取奖品的一个集合*/
if (activityUserDao.getCountByOpenId(Award.WHEEL_AWARD_TYPE, wid, open_id) <= 0) {
/* awardItems获取奖品的一个集合 */
List<Award> awardItems = awardDao.getByActivity(aw.getWheel_id(), Award.WHEEL_AWARD_TYPE);
/* lotterys存放每一个奖品的中奖概率集合 */
List<Double> lotterys = new ArrayList<Double>();
/* 获取总的奖品数量 */
int count = 0;
for (Award a : awardItems) {
count += a.getProvide_count();
}
if (aw.getPeople_count() <= count) {
lotterys.add(0.0); // 100%中奖
} else {
/* 预计参与人数减去奖品数 除以参与人数 = 未中奖概率 */
lotterys.add((double) (aw.getPeople_count() - count) / (double) aw.getPeople_count());
}
/* 遍历奖品集合,获取每一个奖品中奖概率 */
for (Award a : awardItems) {
if (a.getOver_count() > 0) {
lotterys.add((double) a.getProvide_count() / (double) aw.getPeople_count());
} else {
lotterys.add(0.0);
}
}
// 计算中奖概率
int index = LotteryUtil.lottery(lotterys);
if (index > 0) {// 中奖
Award a = awardItems.get(index - 1);
long key = Math.round(Math.random() * (999999 - 100000) + 100000); // 6位数中奖序列号
// 修改商品剩余数量 + 记录序列号
if (awardDao.doLowerOverCount(a.getAward_id()) > 0
&& activityUserDao.doInsert(new ActivityUser(aw.getPublic_id(), Award.WHEEL_AWARD_TYPE, wid, a.getAward_id(), key + "", open_id)) > 0) {
rb.setCode(index);
rb.setData(key);
rb.setMessage(a.getAward_name());
} else {
rb.setCode(0);
}
}
// 抽奖记录
activityRecordDao.doInsert(new ActivityRecord(open_id, Award.WHEEL_AWARD_TYPE, wid, request.getRemoteAddr()));
前端抽奖工具类
/**
* 注意:本插件运用了rem屏幕适配方案,一律采用rem作为单位,若项目中不是采用这种方案的,此段代码不会影响功能使用,仅会影响控件样式
*/
(function(win, doc, $) {
var defaultOpt = {
rotateNum: 5, //转盘转动圈数
body: "", //大转盘整体的选择符或zepto对象
disabledHandler: function() {}, //禁止抽奖时回调
clickCallback: function() {}, //点击抽奖按钮,再次回调中实现访问后台获取抽奖结果,拿到抽奖结果后显示抽奖画面
KinerLotteryHandler: function(deg) {} //抽奖结束回调
};
function KinerLottery(opts) {
this.opts = $.extend(true, {}, defaultOpt, opts);
this.doing = false;
this.init();
}
KinerLottery.prototype.setOpts = function(opts) {
this.opts = $.extend(true, {}, defaultOpt, opts);
this.init();
};
KinerLottery.prototype.init = function() {
var self = this;
this.defNum = this.opts.rotateNum * 360; //转盘需要转动的角度
// console.log(this.defNum);
// alert(this.defNum);
//点击抽奖
$('#box').on('click', ".KinerLotteryBtn", function() {
if($(this).hasClass('start') && !self.doing) {
self.opts.clickCallback.call(self);
} else {
var key = $(this).hasClass('no_start') ? "noStart" : $(this).hasClass('completed') ? "completed" : "illegal";
self.opts.disabledHandler(key);
}
});
$(this.opts.body).find('.KinerLotteryContent').get(0).addEventListener('webkitTransitionEnd', function() {
self.doing = false;
var deg = $(self.opts.body).attr('data-deg');
if(self.opts.direction == 0) {
$(self.opts.body).attr('data-deg', 360 - deg);
$(self.opts.body).find('.KinerLotteryContent').css({
'-webkit-transition': 'none',
'transition': 'none',
'-webkit-transform': 'rotate(' + (deg) + 'deg)',
'transform': 'rotate(' + (deg) + 'deg)'
});
self.opts.KinerLotteryHandler(360 - deg);
} else {
$(self.opts.body).attr('data-deg', deg);
$(self.opts.body).find('.KinerLotteryContent').css({
'-webkit-transition': 'none',
'transition': 'none',
'-webkit-transform': 'rotate(' + (-deg) + 'deg)',
'transform': 'rotate(' + (-deg) + 'deg)'
});
self.opts.KinerLotteryHandler(deg);
}
});
};
KinerLottery.prototype.goKinerLottery = function(_deg) {
if(this.doing) {
return;
}
var deg = _deg + this.defNum;
var realDeg = this.opts.direction == 0 ? deg : -deg;
this.doing = true;
$(this.opts.body).find('.KinerLotteryBtn').addClass('doing');
$(this.opts.body).find('.KinerLotteryContent').css({
'-webkit-transition': 'all 5s',
'transition': 'all 5s',
'-webkit-transform': 'rotate(' + (realDeg) + 'deg)',
'transform': 'rotate(' + (realDeg) + 'deg)'
});
$(this.opts.body).attr('data-deg', _deg);
};
win.KinerLottery = KinerLottery;
})(window, document, $);
前端js调用抽奖类
/**
* @author wjb
* @description
* @version 1.0.0 2017/2/11
*/
app.controller("wheelOneController", ['$scope', '$stateParams', '$neu_', 'awardService', '$filter', '$timeout', 'util.alert', 'cfg', 'wxService', function($scope, $stateParams, $neu_, awardService, $filter, $timeout, alert, cfg, wxService) {
/*中奖开始时间*/
$scope.wheelStatu = {
start: true,
noStart: false,
completed: false
}
/*错误信息提示*/
$scope.errorMsg = "";
/*定义奖品数据变量*/
$scope.awards = [];
/*活动的id和活动的微信公众号ID*/
var activity_id = $neu_.isEmpty($stateParams.activity_id) ? 1 : $stateParams.activity_id;
var public_id = $neu_.isEmpty($stateParams.public_id) ? 1 : $stateParams.public_id;
var open_id = $neu_.isEmpty($stateParams.open_id) ? cfg.openId : $stateParams.open_id;
cfg.public_id = public_id;
cfg.open_id = open_id;
cfg.activity_id = activity_id;
//alert(cfg.public_id+"=="+cfg.activity_id+"=="+cfg.open_id );
/*获取活动信息*/
wxService.setConfig();
awardService.getWheelInfo(activity_id, public_id).then(function(res) {
//console.dir(res)
if(res.success) {
$scope.wheelStatu.start = true;
} else {
if(res.code == 1 || res.code == 3) {
$scope.wheelStatu.noStart = true;
} else if(res.code == 2) {
$scope.wheelStatu.completed = true;
}
$scope.errorMsg = res.msg;
}
$scope.wheelInfo = res.data;
});
awardService.getAwards(activity_id, public_id).then(function(res) {
$scope.awards = res.data;
});
/*奖品预览*/
var result = [];
$scope.showPic = function(pic) {
if(result.length == 0) {
$neu_.each($scope.awards, function(item) {
result.push(cfg.resourcePath + item.img);
});
}
wxService.previewImage(cfg.resourcePath + pic, result);
}
/*中奖结果集*/
$scope.result = [];
$scope.user = {
user_name: '',
phone: '',
activity_id: activity_id,
user_openid: open_id
};
/**
* 提交中奖人信息
*/
$scope.submit = function() {
$(".actBody_close").click();
$scope.isLoading = true;
awardService.updateLotteryUser($scope.user).then(function(res) {
$scope.isLoading = false;
if(res.success) {
alert('您的中奖信息已备案,我们的客服人员稍后会联系您,如何领取奖品');
} else {
alert('提交失败');
}
})
//alert('哈哈上当了吧^_^_^_^')
}
$scope.load = function() {
$timeout(function() {
ActBounced();
$("#layer").hide();
new KinerLottery({
rotateNum: 5, //转盘转动圈数
body: "#box", //大转盘整体的选择符或zepto对象
direction: 0, //0为顺时针转动,1为逆时针转动
disabledHandler: function(key) {
switch(key) {
case "noStart":
$scope.$apply(function() {
alert($scope.errorMsg);
});
break;
case "completed":
$scope.$apply(function() {
alert($scope.errorMsg);
});
break;
}
}, //禁止抽奖时回调
clickCallback: function() {
var this_ = this;
//此处访问接口获取奖品
$scope.isLoading = true;
awardService.startAweel(activity_id, open_id).then(function(res) {
$scope.isLoading = false;
if(isDebug){
this_.goKinerLottery(0);
}else{
if(res.success) {
var index = cfg.isDebug ? Math.floor(Math.random() * 5) : res.code;
$scope.result = $filter('awardToAngle')(index, $scope.awards);
if(index == 0) {
this_.opts.disabledHandler("noStart");
} else {
this_.goKinerLottery($scope.result[1]);
}
} else {
alert(res.msg);
}
}
})
}, //点击抽奖按钮,再次回调中实现访问后台获取抽奖结果,拿到抽奖结果后显示抽奖画面
KinerLotteryHandler: function(deg) {
$("#smallActAdv").click();
}
});
}, 500);
/*分享授权的地址*/
$timeout(function(){
var share ={shareUrl:"weixin/oauth.html?isBase=true&type=6&a_id="+activity_id+"&p_id="+public_id+"&appid="+cfg.appId};
wxService.onMenuShareAppMessage(share);
},3000)
}
}])
网友评论