【项目实站】 php 实现抽奖代码详解【中篇】 如何面对需求变更

接上篇。点击查看--》 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 实现抽奖代码详解【下篇】重构后的代码

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值