1. 程式人生 > >【專案實站】 php 實現抽獎程式碼詳解【上篇】 基礎實現

【專案實站】 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;
        }

測試哥一測,給出反饋如下。
//單次抽獎是沒問題,一併發就出現了問題。比如將一等獎概率變大,一個一等獎被有可能被中兩次。 


調整後代碼如下: 

    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;
        }
    
    }
一個基礎版的抽獎就出來了。下一篇講如何對當前程式碼進行有效的重構。使其應對產品大哥的各種需求的變化。