最近和其他部门合作项目,当然我是负责php接口方面的工作,
get到一些东西,所以来分享记录一下。
项目需求:
题目将通过主持人ipad投射至大屏幕,选手按‘抢答’
按钮进行抢答。抢答成功,选手所在组,以及大屏幕上广播抢答成功者的ipad屏幕,
抢答失败选手,返回抢答失败界面。
需求分析:
这里抢答,其实就是和秒杀活动机制一样了,不过这里场景可能稍微复杂点,
需要用到强弱连接,实时广播,大家可以去看看GatewayWordker当然,今天我们只是单纯
讨论抢答机制是如何实现。那么既然抢答,就要考虑高并发问题了。
思路分析:
1.把题目的状态写在redis里面,比如题目还没有被抢状态为1,抢完状态为0
2.选手进行抢答时,查询redis状态,为0,直接返回,题目已经被抢完;
3.选手进行抢答时,查询redis状态,为1,进入下一步逻辑操作,修改redis状态为0,
进入数据库查询改题目数据,运用行级琐机制。返回逻辑处理结果
框架依然用的是laravel,开发模式用的是仓库模式,这用有利于项目后期的维护和升级。
(数据字段涉及安全,暂时用test1等表示便好)
/******首先进行题目获取,并把题目对应的redis编号改为1******/
1 /** 2 * @desc 随机获得抢答题题目 3 * @date 2017/4/19 17:26 4 * @param [type] 5 * @author 十月桂花香十里 6 * @return [bool or array] 7 */ 8 public function getQuickQuestion(){ 9 //获取抢答题随机题号id 10 $randNum = $this->getRandArray(config('test.start_quick_id'),config('test.end_quick_id'),config('djm.max_quick')); 11 DB::transaction(function () use ($randNum){ 12 //将抢答题题号id写进redis,值为1题目可以进行抢答,0不可以进行抢答(为了便于后期维护,0和1都可以写进配置文件中) 13 $redis = PRedis::connection('default'); 14 foreach($randNum as $k=>$v){ 15 $redis->set("check_quick_id_".$v,1); 16 } 17 //将数据库中抢答题对应的id记录,test3状态改为可进行抢答 18 //DjmQuestion::whereIn('id', $randNum)->update(['test3'=>1]); 19 }); 20 $res = DjmQuestion::whereIn('id', $randNum)
21 ->orderByRaw(DB::raw("FIELD(id, ".implode(',', $randNum).")"))
22 ->get(['id','test1','test2','test3','test4']); 23 if($res){
24 foreach($res as $k=>$v){ 25 $res[$k]['test6'] = json_decode($v['test6'],true); 26 } 27 //处理需要返回的数组 28 return $res; 29 }else return false;
30 }
/******利用php的array_slice函数实现编号的随机选择******/
1 /* 2 * function getTenNum( int $min, int $max, int $num) 3 * 生成一定数量的随机数 4 * $min 和 $max: 指定随机数的范围 5 * $num: 指定生成数量 6 */ 7 public function getRandArray($min,$max,$num){ 8 $array = range($min,$max); 9 shuffle($array); 10 $array = array_slice($array, -$num); 11 return $array; 12 }
/******选手抢答题目逻辑的实现******/
1 /** 2 * @desc 选手进行题目的抢答 3 * @date 2017/4/19 18:19 4 * @param [$id $uid] 5 * @author 十月桂花香十里 6 * @return [bool or array] 7 */ 8 public function userQuickAnswer($id='',$uid){ 9 //判断uid是否是答题选手 10 if(!in_array($uid, config('test1.check_uid'))) return false; 11 //判断$id是否存在 12 if($id < config('test1.start_quick_id') || $id > config('test1.end_quick_id')) return false; 13 //redis判断题目是否可以进行抢答 14 $redis = PRedis::connection('default'); 15 $check_quick_status = $redis->get("check_quick_id_".$id); 16 if($check_quick_status ==1){ 17 //运用事务,添加共享锁 18 //DB::transaction(function () use ($redis,$id){ 19 //将redis的状态和数据库field3改为0,变为不可抢答 20 //DjmQuestion::where('id',$id)->sharedLock()->update(['test1'=>0]); 21 //}); 22 //将redis的状态改为0,变为不可抢答 23 $redis->set("check_quick_id_".$id,0); 24 //redis绑定此题和选手的关系 25 $redis->set("check_quick_id_".$id."_".$uid,1); 26 //获取本条数据记录
28 return DjmQuestion::find($id)->toArray();
29 }else return false; 30 }
/******选手抢答题目回答的实现******/
1 /** 2 * @desc 选手进行抢答题的回答 3 * @date 2017/4/20 10:09 4 * @param [$id,$uid] 5 * @author 1245049149@qq.com 6 * @return [bool or array] 7 */ 8 public function userQuickAnswerResult($id,$uid,$answer){ 9 //判断uid是否是答题选手 10 if(!in_array($uid, config('djm.check_uid'))) return false; 11 //判断$id是否存在 12 if($id < config('djm.start_quick_id') || $id > config('djm.end_quick_id')) return false; 13 //redis判断此题和选手是否绑定关系 14 $redis = PRedis::connection('default'); 15 $check_user_quick_status = $redis->get("check_quick_id_".$id."_".$uid); 16 if($check_user_quick_status == 1){ 17 //关系如果已经绑定,判断选手答题情况 18 $redis->set("check_quick_id_".$id."_".$uid,0); 19 $check_answer = config('djm.check_answer'); //题目编号答案对照(如果题目数量不多且固定的话,建议写进配置文件中,不用查询数据库) 20 if($check_answer[$id] == strtoupper($answer)){ 21 //回答正确分数自加5 22 DjmQuestionScore::where('test10',$uid) 23 ->where('test11',config('djm.quick_test11'))
24 ->increment('test12',5); 25 return array( 26 'msg' => '回答正确', 27 'status' => 1, 28 ); 29 }else{ 30 //回答错误分数自减5 32 DjmQuestionScore::where('test10',$uid) 33 ->where('test11',config('djm.quick_test11'))
34 ->decrement('score',5); 35 return array( 36 'msg' => '回答错误,正确答案:'.$check_answer[$id], 37 'test12' => $check_answer[$id], 38 'status' => 0, 39 ); 40 } 41 }else return false; 42 }
ok,功能至此实现了。