【项目实站】 php 实现抽奖代码详解【上篇】 基础实现

基本思路:用户生成一个随机数,和出奖的奖品设置的随机数比对一下。符合规则则中奖(用户的随机数< 奖品设置的概率值),不符则未中奖。 

一 项目准备期,需求确认。

和产品大哥一阵切磋后,认为
需求1.0
//1 抽奖活动有起止时间
//2 奖品有限制个数的大奖,和不限次数的小奖。为了要用户开心,每抽必中。至于成本什么的,把抽奖回报率设好,按标准线来。
//3 后来有位产品大哥说,可否做个代码,让内定的人中指定的奖品。
//(代码并不难,但基于开发的底线,我断然拒绝了,当一个企业这种文化占主导位置时,直接离开是比较好的选择)
//4 抽奖前端是用老虎机,还是砸蛋,还是转盘。 中选择了老虎机模式。
//5 每个用户签到后,赠送三次免费机会,剩余的使用积分进行抽奖。 抽奖时,优先使用免费抽奖机会,然后再使用积分抽奖。单用户每天限抽10次。
//6 产品大哥一翻成本估算,市场调研,自我分析后,定下来奖品分12 个等级。前8级有数量限制 最后四级无数量限制。
    //奖品类型(实物大奖,投资红包,会员积分,抽奖机会)
//7 用户可以看见他的抽奖纪录。
//8 指定奖品,要在指定时间后才可以出奖。(避免第一天大奖被中完)

得到需要后,工作开始。 //备注,为简化描述,后面部分代码及表格进行简化。

2 表格建立。

  (1  用户表 2 活动表  3 奖品表  4 中奖纪录表 )

1 用户表。demo_user


2 抽奖活动表 demo_lottery


3 奖品表。demo_prizes

用repeat_type 来区分是否可重复中奖。  prize_status 记录是否中过奖。

start_time 设置最早出奖时间
prize_type 来区分奖励什么东西
prize_amount  记录奖多少。

(真实项目中,例如奖红包时,有使用红包限制,例如满多少才可以用红包,红包使用范围等,还有这个奖品限在哪个抽奖活动中等  这里简化处理。暂略

4 中奖纪录表。 demo_prize_log


3 一点点数据准备工作。

//准备了三个用户,创建了一个抽奖活动,设置了四个奖励,两个实物(单次) 两个虚拟奖(可多次中) 
 //初始化用户数据。
        $sql = "truncate demo_user";
        Yii::app()->db->createCommand($sql)->execute();
        $sql = "INSERT INTO demo_user SET id = 1,username = 'user1',free_chance = 1 , points = 100";
        $bool = Yii::app()->db->createCommand($sql)->execute();if( !$bool ){ throw new Exception("执行失败".$sql); }
        $sql = "INSERT INTO demo_user SET id = 2,username = 'user2',free_chance = 2 , points = 800";
        $bool = Yii::app()->db->createCommand($sql)->execute();if( !$bool ){ throw new Exception("执行失败".$sql); }
        $sql = "INSERT INTO demo_user SET id = 3,username = 'user3',free_chance = 3 , points =999";
        $bool = Yii::app()->db->createCommand($sql)->execute();if( !$bool ){ throw new Exception("执行失败".$sql); }

        //初始化活动数据。
        $sql = "truncate demo_lottery";
        Yii::app()->db->createCommand($sql)->execute();
        $start_time = time();
        $end_time = $start_time + 86400;  //一天后期
        $sql = "insert into demo_lottery set id = 1, title = '抽奖活动1' ,status =1,start_time = $start_time, end_time = $end_time , spend_point = 10 ";
        echo $sql;
        $bool = Yii::app()->db->createCommand($sql)->execute();
        if( !$bool ){ throw new Exception("执行失败".$sql); }

        $sql = "truncate demo_prizes"; //清空奖品池
        Yii::app()->db->createCommand($sql)->execute();

        //一等奖,中奖概率为10 %  ,仅限中一次。
        $sql = "insert into demo_prizes set `level` = 1, title = 'ihpone',start_time = $start_time , rand_num = 10,repeat_type = 'once',prize_type = 'real_goods'";
        $bool = Yii::app()->db->createCommand($sql)->execute();       if( !$bool ){ throw new Exception("执行失败".$sql); }
        //二等奖,中奖概率为10 %  ,仅限中一次。
        $sql = "insert into demo_prizes set `level` = 2, title = '扫地机器人',start_time = $start_time , rand_num = 10,repeat_type = 'once',prize_type = 'real_goods'";
        $bool = Yii::app()->db->createCommand($sql)->execute();       if( !$bool ){ throw new Exception("执行失败".$sql); }
       //三等等奖,中奖概率为20 %  ,不限中奖次数。
        $sql = "insert into demo_prizes set `level` = 3, title = '888积分',start_time = $start_time , rand_num = 20,repeat_type = 'repeatable',prize_type = 'points',prize_amount = 888";
        $bool = Yii::app()->db->createCommand($sql)->execute();       if( !$bool ){ throw new Exception("执行失败".$sql); }
        //四等等等奖,中奖概率为80 %  ,不限中奖次数。
        $sql = "insert into demo_prizes set `level` = 4, title = '3次抽奖机会',start_time = $start_time , rand_num = 80,repeat_type = 'repeatable',prize_type = 'lottery_chance',prize_amount=3";
        $bool = Yii::app()->db->createCommand($sql)->execute();       if( !$bool ){ throw new Exception("执行失败".$sql); }
        
        $sql = "truncate demo_prize_log";   //清空中奖记录。
        Yii::app()->db->createCommand($sql)->execute();



4 第一个版本的抽奖代码。

 public function lottery($lottery_id , $uid){

        if(!preg_match("/^\d+$/" , $lottery_id.$uid)) { exit("参数异常");}  //小习惯,碰到数值型参数,验一道

        //确认活动是否开启。
        $sql = "select * from demo_lottery where id = $lottery_id ";
        $lotteryRow = Yii::app()->db->createCommand($sql)->queryRow();
        if( !$lotteryRow )  exit( "活动不存在!" );

        $time = time();
        if( $time < $lotteryRow["start_time"] || $time > $lotteryRow["end_time"]) { exit("活动暂未开始或已结束");}

        $sql = "select * from demo_user where id = $uid";
        $userRow = Yii::app()->db->createCommand($sql)->queryRow();
        if( $userRow["free_chance"] + $userRow["points"]/$lotteryRow["spend_point"] < 1) { exit("无抽奖机会");}

        try {
            $trans = Yii::app()->db->beginTransaction();

            //取出奖品数据
            $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"];
            $lottery_type = $userRow["free_chance"]>0?"free_chance":"points";  //用户使用哪种方式抽奖。
            $sql = "insert into demo_prize_log set uid = $uid ,prize_id = ".$lotteryPrize["id"].",create_at =$time,msg='$msg',lottery_type = '$lottery_type'";
            $bool = Yii::app()->db->createCommand($sql)->execute();
            if( !$bool ){ throw new Exception("执行失败".$sql); }

            //扣除掉用户的抽奖机会.
            if( $lottery_type == "free_chance"){
                $sql = "update demo_user set free_chance = free_chance - 1 where id = $uid";
                $bool = Yii::app()->db->createCommand($sql)->execute();  if( !$bool ){ throw new Exception("执行失败".$sql); }
            }else{
                $spend_point = $lotteryRow["spend_point"];
                $sql  = "update demo_user set points = points - $spend_point  where id = $uid";
                $bool = Yii::app()->db->createCommand($sql)->execute();
                if( !$bool ){ throw new Exception("执行失败".$sql); }
            }


            //最后,执行奖励部分。 根据不同的类型进行发奖。
            $prize_amount = $lotteryPrize["prize_amount"];
            switch ($lotteryPrize["prize_type"])
            {
                case "points":
                    //执行积分奖励。
                    $sql = "update demo_user set points = points +$prize_amount where id = $uid";
                    $bool = Yii::app()->db->createCommand($sql)->execute();
                    if( !$bool ){ throw new Exception("执行失败".$sql); }
                    break;
                case "lottery_chance":
                    //执行抽奖奖励。
                    $sql = "update demo_user set free_chance = free_chance +$prize_amount where id = $uid";
                    $bool = Yii::app()->db->createCommand($sql)->execute();
                    if( !$bool ){ throw new Exception("执行失败".$sql); }
                    break;
                default:
                break;
            }

            //更新单次奖品的状态。变为不可抽奖.
            if( $lotteryPrize["repeat_type"] == "once"){
                $sql = "update demo_prizes set prize_status  = 2 where id = ".$lotteryPrize["id"];
                $bool = Yii::app()->db->createCommand($sql)->execute();
                if( !$bool ){ throw new Exception("执行失败".$sql); }
            }

            $trans->commit();
            return true;
        } catch (Exception $e) {
            Yii::log($e->getMessage(), CLogger::LEVEL_INFO, "log_error");
            $trans->rollback();
            return false;
        }

测试哥一测,给出反馈如下。
//单次抽奖是没问题,一并发就出现了问题。比如将一等奖概率变大,一个一等奖被有可能被中两次。 

于是折腾了下代码,使用mysql 行锁处理。可以查看另一篇blog-->  php 使用msyql 行锁防止高并发请求时扣库存异常 【防爆单,超卖】


调整后代码如下: 

    public function lottery($lottery_id , $uid){

        if(!preg_match("/^\d+$/" , $lottery_id.$uid)) { exit("参数异常");}  //小习惯,碰到数值型参数,验一道

        //确认活动是否开启。
        $sql = "select * from demo_lottery where id = $lottery_id ";
        $lotteryRow = Yii::app()->db->createCommand($sql)->queryRow();
        if( !$lotteryRow )  exit( "活动不存在!" );

        $time = time();
        if( $time < $lotteryRow["start_time"] || $time > $lotteryRow["end_time"]) { exit("活动暂未开始或已结束");}


        try {
            $trans = Yii::app()->db->beginTransaction();

            $sql = "select * from demo_user where id = $uid for update";  //对用户数据加行锁
            $userRow = Yii::app()->db->createCommand($sql)->queryRow();
            if( $userRow["free_chance"] + $userRow["points"]/$lotteryRow["spend_point"] < 1) { exit("无抽奖机会");}


            //取出奖品数据
            $sql  = "select * from demo_prizes  where start_time < $time && prize_status = 1 order by level asc for update";  //对奖品数据加行锁
            $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"];
            $lottery_type = $userRow["free_chance"]>0?"free_chance":"points";  //用户使用哪种方式抽奖。
            $sql = "insert into demo_prize_log set uid = $uid ,prize_id = ".$lotteryPrize["id"].",create_at =$time,msg='$msg',lottery_type = '$lottery_type'";
            $bool = Yii::app()->db->createCommand($sql)->execute();
            if( !$bool ){ throw new Exception("执行失败".$sql); }

            //扣除掉用户的抽奖机会.
            if( $lottery_type == "free_chance"){
                $sql = "update demo_user set free_chance = free_chance - 1 where id = $uid";
                $bool = Yii::app()->db->createCommand($sql)->execute();  if( !$bool ){ throw new Exception("执行失败".$sql); }
            }else{
                $spend_point = $lotteryRow["spend_point"];
                $sql  = "update demo_user set points = points - $spend_point  where id = $uid";
                $bool = Yii::app()->db->createCommand($sql)->execute();
                if( !$bool ){ throw new Exception("执行失败".$sql); }
            }


            //最后,执行奖励部分。 根据不同的类型进行发奖。
            $prize_amount = $lotteryPrize["prize_amount"];
            switch ($lotteryPrize["prize_type"])
            {
                case "points":
                    //执行积分奖励。
                    $sql = "update demo_user set points = points +$prize_amount where id = $uid";
                    $bool = Yii::app()->db->createCommand($sql)->execute();
                    if( !$bool ){ throw new Exception("执行失败".$sql); }
                    break;
                case "lottery_chance":
                    //执行抽奖奖励。
                    $sql = "update demo_user set free_chance = free_chance +$prize_amount where id = $uid";
                    $bool = Yii::app()->db->createCommand($sql)->execute();
                    if( !$bool ){ throw new Exception("执行失败".$sql); }
                    break;
                default:
                break;
            }

            //更新单次奖品的状态。变为不可抽奖.
            if( $lotteryPrize["repeat_type"] == "once"){
                $sql = "update demo_prizes set prize_status  = 2 where id = ".$lotteryPrize["id"];
                $bool = Yii::app()->db->createCommand($sql)->execute();
                if( !$bool ){ throw new Exception("执行失败".$sql); }
            }

            $trans->commit();
            return true;
        } catch (Exception $e) {
            Yii::log($e->getMessage(), CLogger::LEVEL_INFO, "log_error");
            $trans->rollback();
            return false;
        }
    
    }
一个基础版的抽奖就出来了。下一篇讲如何对当前代码进行有效的重构。使其应对产品大哥的各种需求的变化。


更值得一看的是下一篇  --》  php 实现抽奖代码详解【中篇】 面对需求变更


  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值