【專案實站】 php 實現抽獎程式碼詳解【上篇】 基礎實現
阿新 • • 發佈:2019-02-07
基本思路:使用者生成一個隨機數,和出獎的獎品設定的隨機數比對一下。符合規則則中獎(使用者的隨機數< 獎品設定的概率值),不符則未中獎。
一 專案準備期,需求確認。
和產品大哥一陣切磋後,認為需求1.0 //1 抽獎活動有起止時間 //2 獎品有限制個數的大獎,和不限次數的小獎。為了要使用者開心,每抽必中。至於成本什麼的,把抽獎回報率設好,按標準線來。 //3 後來有位產品大哥說,可否做個程式碼,讓內定的人中指定的獎品。 //(程式碼並不難,但基於開發的底線,我斷然拒絕了,當一個企業這種文化佔主導位置時,直接離開是比較好的選擇) //4 抽獎前端是用老虎機,還是砸蛋,還是轉盤。 中選擇了老虎機模式。 //5 每個使用者簽到後,贈送三次免費機會,剩餘的使用積分進行抽獎。 抽獎時,優先使用免費抽獎機會,然後再使用積分抽獎。單使用者每天限抽10次。 //6 產品大哥一翻成本估算,市場調研,自我分析後,定下來獎品分12 個等級。前8級有數量限制 最後四級無數量限制。 //獎品型別(實物大獎,投資紅包,會員積分,抽獎機會) //7 使用者可以看見他的抽獎紀錄。 //8 指定獎品,要在指定時間後才可以出獎。(避免第一天大獎被中完)
得到需要後,工作開始。 //備註,為簡化描述,後面部分程式碼及表格進行簡化。
2 表格建立。
(1 使用者表 2 活動表 3 獎品表 4 中獎紀錄表 )
1 使用者表。demo_user2 抽獎活動表 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;
}
}
一個基礎版的抽獎就出來了。下一篇講如何對當前程式碼進行有效的重構。使其應對產品大哥的各種需求的變化。