// +---------------------------------------------------------------------- namespace App\Services\Api; use App\Models\ExamAccessLogModel; use App\Models\ExamAnswerModel; use App\Models\ExamAnswerTopicModel; use App\Models\ExamErrorModel; use App\Models\ExamPaperModel; use App\Models\ExamTopicModel; use App\Services\BaseService; use App\Services\ConfigService; use App\Services\RedisService; /** * 试卷服务-服务类 * @author laravel开发员 * @since 2020/11/11 * @package App\Services\Api */ class PaperService extends BaseService { // 静态对象 protected static $instance = null; /** * 构造函数 * @author laravel开发员 * @since 2020/11/11 */ public function __construct() { $this->model = new ExamPaperModel(); } /** * 静态入口 */ public static function make() { if (!self::$instance) { self::$instance = new static(); } return self::$instance; } /** * @param $params * @param int $pageSize * @return array */ public function getDataList($params, $pageSize = 15) { $page = isset($params['page'])? $params['page'] : 1; $cacheKey = "caches:paper:list_{$page}_{$pageSize}:".md5(json_encode($params)); $datas = RedisService::get($cacheKey); // 访问次数统计 $sc = isset($params['sc'])? $params['sc'] : 0; $type = isset($params['type'])? $params['type'] : 0; $sceneType = isset($params['scene_type'])? $params['scene_type'] : 0; if (empty($sc) && !in_array($sceneType, [3,4,6])) { ExamAccessLogModel::saveLog(date('Y-m-d'), $type, $sceneType); } if($datas){ return $datas; } $userId = isset($params['user_id'])? $params['user_id'] : 0; $query = $this->getQuery($params); $list = $query->with(['answer'=>function($query) use($userId){ $query->where('user_id', $userId); }])->where('a.topic_count','>', 0)->select(['a.*','b.id as rid', 'b.score', 'b.accurate_count','b.user_id', 'b.answer_times', 'b.answer_count', 'b.is_submit']) ->orderBy('a.create_time','desc') ->paginate($pageSize > 0 ? $pageSize : 9999999); $list = $list? $list->toArray() :[]; if($list){ foreach($list['data'] as &$item){ $item['score'] = !empty($item['score'])? $item['score'] : 0; $item['is_submit'] = !empty($item['is_submit'])? $item['is_submit'] : 0; $item['answer_count'] = !empty($item['answer_count'])? $item['answer_count'] : 0; $item['accurate_count'] = !empty($item['accurate_count'])? $item['accurate_count'] : 0; $item['date'] = $item['create_time'] ? datetime($item['create_time'], 'Y-m-d') : ''; } } $rows = isset($list['data'])? $list['data'] : []; $datas = [ 'pageSize'=> $pageSize, 'total'=> isset($list['total'])? $list['total'] : 0, 'list'=> $rows ]; if($rows){ RedisService::set($cacheKey, $datas, rand(300, 600)); } return $datas; } /** * 查询 * @param $params * @return mixed */ public function getQuery($params) { $where = ['a.status'=>1,'a.mark'=>1]; $status = isset($params['status'])? $params['status'] : 0; $type = isset($params['type'])? $params['type'] : 0; $sceneType = isset($params['scene_type'])? $params['scene_type'] : 0; $subjectId = isset($params['subject_id'])? $params['subject_id'] : 0; if($status>0){ $where['a.status'] = $status; } if($type>0){ $where['a.type'] = $type; } if($sceneType>0){ $where['a.scene_type'] = $sceneType; } if($subjectId>0){ $where['a.subject_id'] = $subjectId; } $userId = isset($params['user_id'])? $params['user_id'] : 0; return $this->model->from('exam_papers as a') ->leftJoin('exam_answers as b', function($join) use($userId){ // 未完成交卷的答题数据 $join->on('b.paper_id','=','a.id')->where(['b.user_id'=>$userId,'b.status'=>1,'b.mark'=>1]); }) ->where($where) ->where(function ($query) use($params){ $keyword = isset($params['keyword'])? $params['keyword'] : ''; if($keyword){ $query->where('a.name','like',"%{$keyword}%"); } }); } /** * 最近是否答过该题 * @param $userId 用户ID * @param $paperId 试卷ID * @param int $submit 是否已交卷,1-是,0-否 * @return array|mixed */ public function getLastAnswer($userId, $paperId, $submit=0) { $cacheKey = "caches:paper:answer_last_{$userId}:{$paperId}_{$submit}"; $data = RedisService::get($cacheKey); if($data){ return $data; } $lastTime = ConfigService::make()->getConfigByCode('submit_paper_time', 30); $lastTime = $lastTime>=1 && $lastTime <= 150? $lastTime : 30; $data = ExamAnswerModel::where(['user_id'=>$userId,'is_submit'=> $submit,'paper_id'=> $paperId,'status'=>1,'mark'=>1]) ->where(function($query) use($lastTime, $submit){ if($submit<=0){ // 未交卷 $query->where('is_submit', 0)->orWhere('answer_last_at','>=', time() - $lastTime * 60); }else{ $query->where('is_submit', $submit)->orWhere('answer_last_at','<', time() - $lastTime * 60); } }) ->select(['id','paper_id','score','answer_last_at','answer_last_id']) ->first(); $data = $data? $data->toArray() : []; if($data){ RedisService::set($cacheKey, $data, rand(5, 10)); } return $data; } /** * 用户最近答题记录 * @param $userId * @param $paperId * @return array|mixed */ public function getLastAnswerLogId($userId,$paperId) { $cacheKey = "caches:paper:log_{$userId}_{$paperId}"; $id = RedisService::get($cacheKey); if($id){ return $id; } $id = ExamAnswerModel::where(['user_id'=> $userId,'paper_id'=> $paperId,'status'=>1,'mark'=>1]) ->where('create_time','>=', strtotime(date('Y-m-d'))) ->value('id'); if($id){ RedisService::set($cacheKey, $id, rand(5, 10)); } return $id; } /** * 获取详情 * @param $id * @return array|mixed */ public function getInfo($userId, $paperId, $params=[]) { $lid = isset($params['lid'])? intval($params['lid']) : 0; $tid = isset($params['tid'])? intval($params['tid']) : 0; $rid = isset($params['rid'])? intval($params['rid']) : 0; $type = isset($params['type'])? intval($params['type']) : 1; $cacheKey = "caches:paper:info_{$userId}:p{$paperId}_t{$tid}_r{$rid}_l{$lid}"; $info = RedisService::get($cacheKey); if($info){ return $info; } // 若进行答题 if($rid<=0) { if($type != 1){ // 判断N分钟内是否有未交卷的答题 $lastAnswerInfo = $this->getLastAnswer($userId, $paperId, 0); $rid = isset($lastAnswerInfo['id']) ? $lastAnswerInfo['id'] : 0; }else { $rid = $this->getLastAnswerLogId($userId, $paperId); } } $where = ['a.id'=> $paperId,'a.status'=>1,'a.mark'=>1]; $info = $this->model->from('exam_papers as a') ->leftJoin('exam_answers as b',function($join) use($rid,$userId){ $join->on('b.paper_id','=','a.id')->where(['b.id'=>$rid,'b.user_id'=>$userId,'b.status'=>1]); }) ->where($where) ->select(['a.id as paper_id','b.id as rid','b.score','b.accurate_count','b.is_submit','b.answer_count','b.answer_last_id','b.answer_times','a.name','a.type','a.scene_type','a.subject_id','a.score_total','a.topic_count','a.total_time','a.is_charge','a.create_time','a.status']) ->first(); $info = $info? $info->toArray() : []; if($info){ $info['create_time'] = $info['create_time']? datetime($info['create_time'],'Y-m-d') : ''; $info['answer_count'] = isset($info['answer_count'])? intval($info['answer_count']) : 0; $info['accurate_count'] = isset($info['accurate_count'])? intval($info['accurate_count']) : 0; $info['score'] = isset($info['score'])? intval($info['score']) : 0; $info['rid'] = isset($info['rid'])? intval($info['rid']) : 0; // 剩余时间 $isSubmit = isset($info['is_submit'])? $info['is_submit'] : 0; $totalTime = isset($info['total_time'])? intval($info['total_time']) : 0; $totalTime = $totalTime>0? $totalTime : ConfigService::make()->getConfigByCode('answer_total_time', 1800); $info['answer_times'] = isset($info['answer_times'])? intval($info['answer_times']) : 0; $info['remain_time'] = $totalTime>$info['answer_times']? $totalTime-$info['answer_times'] : 0; $info['remain_time'] = $isSubmit? 0 : $info['remain_time']; $info['remain_time_text'] = $info['remain_time']? format_times($info['remain_time']) : '00:00'; $info['progress'] = $info['topic_count']?intval($info['answer_count']/$info['topic_count'] * 100) : 0; // 当前题目 //$prefix = env('DB_PREFIX','_lev'); $model = ExamTopicModel::from('exam_topics as a') ->leftJoin('exam_answers_topics as b', function($join) use($rid, $userId){ // 是否有最近答题记录 $join->on('b.topic_id','=',"a.id")->where("b.answer_log_id",'=', $rid)->where(['b.user_id'=>$userId,'b.status'=>1,"b.mark"=> 1]); }) ->where(['a.paper_id'=> $paperId,'a.status'=>1,'a.mark'=>1]) ->where(function($query) use($tid){ // 答题卡选择的题目,否则默认按题目排序返回第一题 if($tid>0){ $query->where('a.id', $tid); } }); $info['topic'] = $model->select(['a.*','b.id as answer_topic_id','b.answer_log_id','b.answer as submit_answer','b.answer_analysis','b.answer_type as submit_answer_type','b.score as submit_score','b.accurate']) ->orderBy('a.sort','desc') ->orderBy('a.id','asc') ->first(); $info['topic'] = $info['topic']? $info['topic']->toArray() : []; $topicId = isset($info['topic']['id'])? $info['topic']['id'] : 0; if($info['topic'] && $topicId) { $info['topic']['accurate'] = isset($info['topic']['accurate'])?$info['topic']['accurate'] : -1; $info['topic']['topic_analysis'] = isset($info['topic']['topic_analysis'])?$info['topic']['topic_analysis'] : ''; $info['topic']['answer_analysis'] = isset($info['topic']['answer_analysis'])?$info['topic']['answer_analysis'] : ''; $info['topic']['topic_analysis'] = $info['topic']['topic_analysis']?$info['topic']['topic_analysis'] : $info['topic']['answer_analysis']; $info['topic']['submit_answer'] = isset($info['topic']['submit_answer'])?$info['topic']['submit_answer'] : ''; $info['topic']['submit_answer_type'] = isset($info['topic']['submit_answer_type'])?$info['topic']['submit_answer_type'] : 1; $info['topic']['submit_answers'] = ''; $info['topic']['collect_answers'] = []; if($info['topic']['submit_answer_type'] == 3){ $info['topic']['submit_answer'] = get_image_url($info['topic']['submit_answer']); }else if($info['topic']['submit_answer_type'] == 4){ $info['topic']['submit_answer'] = $info['topic']['submit_answer']?json_decode($info['topic']['submit_answer'], true):[]; }else{ $info['topic']['submit_answer'] = format_content($info['topic']['submit_answer']); } if($info['topic']['show_type'] == 1){ $info['topic']['topic_name'] = format_content($info['topic']['topic_name']); }else if($info['topic']['show_type'] == 2){ $info['topic']['topic_name'] = get_image_url($info['topic']['topic_name']); // 已经答题,返回答案 if($rid>0 || $info['topic']['submit_answer']){ $info['topic']['topic_analysis'] = get_image_url($info['topic']['topic_analysis']); if(preg_match("/(images|temp)/", $info['topic']['correct_answer'])){ $info['topic']['correct_answer'] = get_image_url($info['topic']['correct_answer']); } if(preg_match("/(images|temp)/", $info['topic']['answer_A'])){ $info['topic']['answer_A'] = get_image_url($info['topic']['answer_A']); } if(preg_match("/(images|temp)/", $info['topic']['answer_B'])){ $info['topic']['answer_B'] = get_image_url($info['topic']['answer_B']); } if(preg_match("/(images|temp)/", $info['topic']['answer_C'])){ $info['topic']['answer_C'] = get_image_url($info['topic']['answer_C']); } if(preg_match("/(images|temp)/", $info['topic']['answer_D'])){ $info['topic']['answer_D'] = get_image_url($info['topic']['answer_D']); } if(preg_match("/(images|temp)/", $info['topic']['answer_E'])){ $info['topic']['answer_E'] = get_image_url($info['topic']['answer_E']); } if(preg_match("/(images|temp)/", $info['topic']['answer_F'])){ $info['topic']['answer_F'] = get_image_url($info['topic']['answer_F']); } } } // 未答题隐藏答案 // if(empty($info['topic']['submit_answer'])){ // $info['topic']['correct_answer'] = '未答题'; // $info['topic']['topic_analysis'] = ''; // } // 多选题 $topicType = isset($info['topic']['topic_type'])? $info['topic']['topic_type'] : ''; if($topicType == '多选题'){ $info['topic']['submit_answers'] = $info['topic']['submit_answer']?explode(',', $info['topic']['submit_answer']) : ''; $info['topic']['correct_answers'] = $info['topic']['correct_answer']?explode(',', $info['topic']['correct_answer']) : []; } $info['topic']['answers'] = []; if($info['topic']['answer_A']){ $info['topic']['answers'][] = ['code'=>'A','value'=> $info['topic']['answer_A']]; } if($info['topic']['answer_B']){ $info['topic']['answers'][] = ['code'=>'B','value'=> $info['topic']['answer_B']]; } if($info['topic']['answer_C']){ $info['topic']['answers'][] = ['code'=>'C','value'=> $info['topic']['answer_C']]; } if($info['topic']['answer_D']){ $info['topic']['answers'][] = ['code'=>'D','value'=> $info['topic']['answer_D']]; } if($info['topic']['answer_E']){ $info['topic']['answers'][] = ['code'=>'E','value'=> $info['topic']['answer_E']]; } if($info['topic']['answer_F']){ $info['topic']['answers'][] = ['code'=>'F','value'=> $info['topic']['answer_F']]; } // 上一题 $info['last'] = ExamTopicModel::where(['paper_id'=> $paperId,'status'=>1,'mark'=>1]) ->where('id','<', $topicId) ->select(['id','topic_name']) ->orderBy('sort','asc') ->orderBy('id','desc') ->first(); $info['last'] = $info['last']? $info['last']->toArray() :['id'=>0]; // 下一题 $info['next'] = ExamTopicModel::where(['paper_id'=> $paperId,'status'=>1,'mark'=>1]) ->where('id','>', $topicId) ->select(['id','topic_name']) ->orderBy('sort','desc') ->orderBy('id','asc') ->first(); $info['next'] = $info['next']? $info['next']->toArray() :['id'=>0]; } RedisService::set($cacheKey, $info, rand(10, 20)); } return $info; } /** * 获取随机试卷 * @param $userId 用户 * @param $type 试卷类型 * @param $sceneType 场景 * @param false $refresh * @return array|mixed */ public function getRandomPaper($userId, $type, $sceneType, $refresh=false) { $cacheKey = "caches:papers:random_{$userId}:{$type}_{$sceneType}"; $data = RedisService::get($cacheKey); if($data && !$refresh){ return $data; } $data = $this->model->where(['type'=>$type,'scene_type'=>$sceneType,'status'=>1,'mark'=>1]) ->orderByRaw('RAND()') ->first(); $data = $data? $data->toArray() : []; if($data){ $data['score'] = 0; $data['paper_id'] = $data['id']; $data['accurate_count'] = 0; $data['date'] = date('Y-m-d'); $data['answer_count'] = 0; $data['answer_times'] = 0; RedisService::set($cacheKey, $data, rand(10, 20)); } return $data; } /** * 纠错 * @param $userId * @param $params * @return bool */ public function submitError($userId, $params) { $id = isset($params['id'])? $params['id'] : 0; $rid = isset($params['rid'])? $params['rid'] : 0; $type = isset($params['type'])? $params['type'] : 0; $moduleType = isset($params['module_type'])? $params['module_type'] : 1; $description = isset($params['description'])? $params['description'] : ''; if($id<=0 || $moduleType<=0){ $this->error = '参数错误'; return false; } if($type<=0){ $this->error = '请选择错误类型'; return false; } if(empty($description)){ $this->error = '请输入错误描述'; return false; } $limitTime = ConfigService::make()->getConfigByCode('limit_exam_error_submit', 5); $cacheKey = "caches:paper:errors_{$userId}:{$id}_{$rid}"; if(RedisService::get($cacheKey) && $limitTime>0){ $this->error = '您近期已提交过该题错误,请稍后再试~'; return false; } $paperInfo = $this->model->where(['id' => $id, 'mark' => 1])->first(); if (empty($paperInfo)) { $this->error = '试题不存在'; return false; } if($limitTime>0 && $logId = ExamErrorModel::where(['user_id'=> $userId,'rid'=>$rid,'paper_id'=> $id,'mark'=>1])->where('create_time','>=', time() - $limitTime* 60)->value('id')){ $this->error = '您近期已提交过该题错误,请稍后再试~'; RedisService::set($cacheKey, ['id'=> $logId,'paper_id'=>$id], $limitTime* 60); return false; } $data = [ 'user_id'=> $userId, 'paper_id'=> $id, 'rid'=> $rid, 'type'=> $type, 'module_type'=> $moduleType, 'description'=> $description, 'image_url'=> isset($params['image_url']) && $params['image_url']? get_image_path($params['image_url']) : '', 'create_time'=>time(), 'status'=>1, 'mark'=>1 ]; if(!$logId = ExamErrorModel::insertGetId($data)){ $this->error = '提交失败'; return false; } $this->error = '提交成功'; RedisService::set($cacheKey, $data, $limitTime* 60); return ['id'=> $logId, 'paper_id'=> $id]; } }