接上篇。点击查看--》 php 实现抽奖代码详解【上篇】 基础实现
产品试用了一下抽奖后,提出了一些新的需求如下。(请不要问为什么不早说)
//需求2.0
//1 单个用户,单天抽奖次数要限制一下,不要超过10次, 指定某g不受限制,周六日,仅限会员等级5以上的才可以抽。
//2 我们的(单次)奖励,有大约1000 个,在15天的时间内让客户抽完。
//3 1,2,3 等奖,上个月累积消费10万且vip5 以上的用户才可以中奖。
//4 单个用户,1,2,3 等奖只能中一个,4,5,6 等奖最多中两个
//5 中完奖了,怎么要也通知下客户
//6 奖励类别,除了实物,积分,抽奖机会,红包,再加上一个直接返现至用户的账户余额中去。
//7 所有红包都是45天的有效期,100 倍的使用解锁金额。
//备注,上述需求仅用于举例。真实项目中变更往往更多,w1 不幸 碰上一个奇葩的产品,可以折腾到你怀疑人生。
这个时候,有个建议,你要重新审视下产品提出需求变更,是真的为了产品更好一些,还是自己在拍大脑。一但后者占比太多,而且可沟通调整的余地不大时,建议选择离开。 效率 = 产出 / 所做的事 ,无意义的事做太多了,效率自然低下了。
同时,细心的测试姐也提出了 (这里便于表述,只能中一次的为大奖,可以反复中的为小奖)
当大奖存在时,用户未中大奖时,小奖的高等级奖的中奖概率被提升了,有违公平性。
举个例子。大奖 1等奖 10% 概率 2 等奖 20% 小奖 3 等奖 30% 4 等奖70 %
当1 ,2 等奖未被抽完时,用户随机数 rand(1,100) , 只要60 以内,就可以至少中3等奖。
而当1,2 等奖如果被抽完时,用户随机数 rand(1,100) , 只要30 以外,就只能4等奖了。 这明显不公平
//取出奖品数据
$sql = "select * from demo_prizes where start_time < $time && prize_status = 1 order by level asc";
$prizesRows = Yii::app()->db->createCommand($sql)->queryAll();
$user_rand = rand(1,100); //生成一个用户随机数.
$lotteryPrize = array(); //用户抽中的奖品
$temp = 0;
foreach( $prizesRows as $key => $prizeRow ): //一个个的比对。
$temp = $temp + $prizeRow["rand_num"];
if( $temp > $user_rand ) {
$lotteryPrize = $prizeRow; //抽中
break;
}
endforeach;
//生成客户中奖纪录,并奖励用户。
$msg = "恭喜抽中".$lotteryPrize["level"]."等奖".$lotteryPrize["title"];
开发完了一个基础版本的抽奖后。我们重新看待抽奖。
抽奖是 用户 在满足参与抽奖的条件下, 使用 一次机会,或付出 积分 , 在产品 精心设计的出奖套路下抽出奖品,并依据 奖品特性,发放给客户。
上述七个需求变化,事实上的需求变化点>20个。只要合理的针对黑色部分进行有效封装,问题就得以较好的解决了。
总结如下:
满足参与抽奖的条件下 -----> 就是抽奖操作前,进行 校验,通过了,则继续抽奖,通不过则返回
精心设计的出奖套路 -----> 无非是根据 用户的数据特性,算出用户还能抽的奖品等级。
依据奖品特性,发放给客户。 ----> 每种不同的奖品,结合活动的配置数据,将奖励发放给客户。这样有新的奖品类型进入时,直接组装上去即可。
至于:我们的(单次)奖励,有大约1000 个,在15天的时间内让客户抽完。 这个小需求,后台做个批量发布奖品即可。
//经过一番改动后,代码变成了这样。
public function lottery($lottery_id , $uid){
//一些防并发的代码,略。。。。
try {
//1 获取数据数据。(用户+抽奖活动)
$trans = Yii::app()->db->beginTransaction();
$sql = "select * from demo_user where id = $uid for update"; //对用户数据加行锁
$userRow = Yii::app()->db->createCommand($sql)->queryRow();
//进行校验处理,用户是否可抽奖。
$sql = "select * from demo_lottery where id = $lottery_id ";
$lotteryRow = Yii::app()->db->createCommand($sql)->queryRow(); //得到抽奖活动数据
//2.1 校验用户可否抽奖
list( $bool , $msg) = $this->validate($lotteryRow , $userRow); //进行校验。
if( !$bool ) exit($msg );
//2.2 使用抽奖机会。
list($bool ,$msg)= $this->useChance($lotteryRow , $userRow); //使用掉用户的抽奖机会。
if( !$bool ) exit($msg );
//2.3 获得奖品数据
$prizeRow = $this->getPrize( $lotteryRow , $userRow);
//2.4 将奖品发放给客户。
list($bool , $msg ) = $this->award($lotteryRow , $userRow , $prizeRow);
if( !$bool ) exit($msg );
$trans->commit();
return self::response($lotteryRow ,$userRow , $prizeRow);;
return true;
} catch (Exception $e) {
Yii::log($e->getMessage(), CLogger::LEVEL_INFO, "log_error");
$trans->rollback();
return false;
}
}
上面代码,直接把抽奖问题,分解为四步。
list( $bool , $msg) = self::validate($lotteryRow , $userRow); //进行校验。
//在之前代码的校验基础上,可以很好的应对类似如下需求。
//1 单个用户,单天抽奖次数要限制一下,不要超过10次,最后一天随便抽。周六日限vip5.
list($bool ,$msg)= self::useChance($lotteryRow , $userRow); //使用掉用户的抽奖机会。
//当出现其它抵用券的需求变更时,或指活抽奖活动使用机会规则发生变化时,这里处理
$prizeRow = self::getPrize( $lotteryRow , $userRow);
//3 1,2,3 等奖,上个月累积消费10万且vip5 以上的用户才可以中奖。
//4 单个用户,1,2,3 等奖只能中一个,4,5,6 等奖最多中两个
//5 以及测试说的原有算法公平性问题的调整。
list($bool , $msg ) = self::award($lotteryRow , $userRow , $prizeRow);
//6 奖励类别,除了实物,积分,抽奖机会,红包,再加上一个直接返现至用户的账户余额中去。
//7 所有红包都是45天的有效期,100 倍的使用解锁金额。
//简言之,奖不同的东西,使用工厂方法调不同的对象来实现。这样便于动态扩展。
受限语文能力,写点总结,大家将就看下:
这样经过一翻捣鼓,程序开始变得有弹性起来,类似的变化得到较好的封装。在抽象层,不进行任何的具体实现。通过特定的规则,来动态的完成一个个的实现(例如,校验,算出奖品), 即使有新的需求产生,此处代码也不用改变,按接指定的接口实现具体的行为后,组装上来即可。
这样写还有个额外的好处,他把一个问题分解为四个可以独立开发测试的问题。
至于为什么要这样写,我是参考代码大全里面的推荐的,如有更好的办法,欢迎指点。
接下来,做一点小小的准备。(以下表格设定是为便于说明,进行相对简化,例如真实情况下账号表和用户表是分离的,且有可能一用户多个账号等)
会员表,增加账号余额,和vip 等级。
抽奖活动表的 params 里来设定参数。来定制需求。
以校验为例,我们假定一种配置写法 ,然后程序运行时,会依据配置,按规则进行校验。
{
"validate":
[
{"perday_limit":{"limit":10,"except_days":"2018-08-18"}}, //每日限10次,除开2018-08-18.
{"vip_level":{"limit":"5,6,7","week_num":"5,6"}} //周六日,限vip 等级。
],
//一些其它的配置。
"award": //奖励项设置,红包解锁倍数,红包有效期天数等。
{
"redbag_unfrozen_mult":100,
"redbag_expire_days":45}
}
//写json 很麻烦,可以通过下列方法快速生成json.
$arr = array( "validate" =>array( array("perday_limit"=> array("limit"=>10 , "except_days" => "2018-08-18")), array("vip_level" => array("limit"=> "5,6,7" , "week_num"=> "5,6" )) //周六日 ,仅限vip 5,6,7 等级的用户参数 ) ); echo json_encode($arr );有了上述的配置后。具体实现起来,就可以对每个活动,动态设定规则。 即使有新增校验规则,只仅需较小改动即可满足。
list( $bool , $msg) = self::validate($lotteryRow , $userRow); //进行校验。
//这段代码,将依次按lotteryRow 里的 params 解析出来的json->validate 逐个去遍历,执行校验。 新的规则来了后,往validate 里写配置规则即可。
下一篇 php 实现抽奖代码详解【下篇】重构后的代码