接上篇。点击查看--》项目实站】 php 实现抽奖代码详解【中篇】 如何面对需求变更
上篇讲了如何设计代码,封装需求, 接下来就是代码实现了,如下:
class DemoLottery{
public function lottery($lottery_id , $uid){
//一些防并发的代码,略。。。。
try {
//取用户
$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 );return true; }
catch (Exception $e) {
Yii::log($e->getMessage(), CLogger::LEVEL_INFO, "log_error"); $trans->rollback(); return false;
} } }
//调用代码 $uid = 1; $lottery_id = 1; $lotteryObj = new DemoLottery(); $lotteryOjb->lottery($lottery_id , $uid);
首先处理校验问题,这里代码有点绕。但这是一种非常常用的写法,很多框架内的实现(例如yii 的validate ) 就是用的这种写法。 这种写法习惯了后,还有个好的副作用,就是以后看框架源码会比较顺畅一点点
/**
* 校验抽奖.
* @param $lotteryRow 抽奖活动
* @param $userRow
*/
public function validate($lotteryRow , $userRow){
//将基础版本的校验移过来.
if( !$lotteryRow ) exit( "活动不存在!" );
$time = time();
if( $time < $lotteryRow["start_time"] || $time > $lotteryRow["end_time"]) { return array(false , "活动未开始或已结束");}
$sql = "select * from demo_user where id = ".$userRow["id"];
$userRow = Yii::app()->db->createCommand($sql)->queryRow();
if( $userRow["free_chance"] + $userRow["points"]/$lotteryRow["spend_point"] < 1) { return array(false , "无抽奖机会");}
//对json 里配置的需要校验的部分进行校验。
$jsonParam = json_decode($lotteryRow["params"]);
if(isset($jsonParam->validate)){
foreach( $jsonParam->validate as $key => $options ):
$methodName = "valid_".$key;
$className = "lotteryValid".ucfirst($key);
//如果存在内部方法,则调用内部方法进行校验.
if( method_exists($this , $methodName)){ //如果有 $this->valid_perday_limit 则使用$this->valid_day_limit 进行校验。
list($bool , $msg ) = $this->$methodName($lotteryRow , $userRow , $options);
if( !$bool) exit($msg);
}else if(@class_exists($className)){ //若无内联方法,则找到对应的校验类,调用对应的校验对象进行校验。
$validObj = new $className;
/**@var $validObj abstractLotteryValidate */
$validObj->setLotteryRow($lotteryRow);
$validObj->setUserRow($userRow);
$validObj->setOptions($options);
list($bool , $msg ) = $validObj->validate();
if( !$bool) exit($msg);
}
endforeach;
}
return array(true , "success");
}
这样,形成了一个约定,当你配置里面的validate
有一个 perday_limit (每日限制)的设置的时候,
你可以在DemoLottery 中写一个 valid_perday_limit($xx) 方法来进行校验。
也可以 用 lotteryValidPerday_limit 对象来处理。 下面这段代码就是 map 的规则。
$methodName = "valid_".$key;
$className = "lotteryValid".ucfirst($key);
下面,一样取一个例子来实现。
//校验当日的抽奖上限次数。 使用的内联方法 $this-> valid_perday_limit 进行校验。
private function valid_perday_limit( $lotteryRow , $userRow , $options){
//特殊不受限制天数
if( isset($options->except_days) && $options->except_days){
$except_day_arr = explode("," , $options->except_days);
if( in_array( date("Y-m-d") , $except_day_arr)) { return array(true , "success");}
}
$s_time = strtotime(date("Y-m-d")); $e_time = strtotime(date("Y-m-d 23:59:59"));
$sql = "select count(id) from demo_prize_log where uid = ".$userRow["id"]." && create_at>= $s_time && create_at <= $e_time";
$count = Yii::app()->db->createCommand($sql)->queryScalar();
if( $count > $options->limit ){
return array(false , "单日次数超限!");
} else {
return array(true , "success");
}
}
//使用外部校验类校验方法。
//每周的星期六,日 ,限vip,5,6,7 可以抽奖.
class lotteryValidVip_level extends abstractLotteryValidate{
public function validate(){
$options = $this->options;
$w = date("w"); //今天周几
if(isset($options->week_num) && in_array($w , explode(",", $options->week_num))){
$user_level = $this->userRow["vip_level"];
if( ! in_array($user_level , explode("," , $options->limit))){
return array(false , "用户vip等级不符");
}
}
return array(true , "success");
}
}
//抽象校验类。
abstract class abstractLotteryValidate {
public $userRow = array();
public $lotteryRow = array();
public $options;
public function setUserRow( $userRow) { $this->userRow = $userRow;}
public function setLotteryRow( $lotteryRow) { $this->lotteryRow = $lotteryRow;}
public function setOptions( $options) { $this->options = $options; }
/**
* @return array( bool ,msg)
*/
abstract public function validate();
} //2.2 使用掉抽奖机会。
//校验完后。接下来是消耗掉用户的抽奖机会或积分。
public function useChance($lotteryRow ,$userRow){ //
$lottery_type = $userRow["free_chance"]>0?"free_chance":"points"; //用户使用哪种方式抽奖。
//扣除掉用户的抽奖机会.
if( $lottery_type == "free_chance"){
$sql = "update demo_user set free_chance = free_chance - 1 where id = ".$userRow["id"];
$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 = ".$userRow["id"];;
$bool = Yii::app()->db->createCommand($sql)->execute();
if( !$bool ){ throw new Exception("执行失败".$sql); }
}
return array( true ,"success");
}
//后面的写法类似,暂不赘述。