123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463 |
- <?php
- /**
- * Created by PhpStorm.
- * User : zgcLives
- * CreateTime : 2022/4/16 16:21
- */
- namespace addons\exam\library;
- use addons\exam\enum\CommonStatus;
- use addons\exam\enum\PaperMode;
- use addons\exam\enum\RoomSignupStatus;
- use addons\exam\model\GradeModel;
- use addons\exam\model\PaperModel;
- use addons\exam\model\QuestionModel;
- use addons\exam\model\RoomGradeModel;
- use addons\exam\model\RoomModel;
- use addons\exam\model\RoomSignupModel;
- use app\admin\model\exam\MaterialQuestionModel;
- use think\Db;
- /**
- * 考试相关服务
- */
- class ExamService
- {
- /**
- * 获取试卷题目
- * @param $paper_id
- * @param int $room_id
- * @return array`
- */
- public static function getExamQuestion($paper_id, $room_id = 0)
- {
- if (!$paper_id) {
- fail('缺少试卷ID');
- }
- $paper = self::validPaper($paper_id, $room_id,true);
- switch ($paper['mode']) {
- case PaperMode::RANDOM:
- $questions = self::getRandomQuestions($paper);
- break;
- case PaperMode::FIX:
- $questions = self::getFixQuestions($paper);
- break;
- default:
- fail('试卷取题模式有误');
- }
- return [
- 'paper' => $paper,
- 'questions' => $questions,
- 'start_time' => time(),
- ];
- }
- /**
- * 获取试卷随机题
- * @param $paper
- * @return array
- */
- public static function getRandomQuestions($paper)
- {
- $configs = $paper->configs;
- $questions = [];
- if (!isset($configs['cate_ids'])) {
- fail('试卷随机取题配置有误');
- }
- foreach (QuestionModel::kindList as $kind) {
- if (!isset($configs[strtolower($kind)])) {
- continue;
- }
- $kind_config = $configs[strtolower($kind)];
- // 使用难度选题
- if ($kind_config['use_difficulty']) {
- foreach ($kind_config['difficulty'] as $difficulty => $value) {
- if ($value['count']) {
- $question = QuestionModel::getListByCateAndKind($configs['cate_ids'], $kind, ['materialQuestions.question']);
- $questions = array_merge(
- $questions,
- $question->where('difficulty', $difficulty)->limit($value['count'])->select()
- // hidden_list_keys($question->where('difficulty', $difficulty)->limit($value['count'])->select(), ['answer', 'explain'])
- );
- }
- }
- } else {
- if ($kind_config['count']) {
- $question = QuestionModel::getListByCateAndKind($configs['cate_ids'], $kind, ['materialQuestions.question']);
- // dd(collection($question->limit($kind_config['count'])->select())->toArray());
- $questions = array_merge(
- $questions,
- $question->limit($kind_config['count'])->select()
- // hidden_list_keys($question->limit($kind_config['count'])->select(), ['answer', 'explain'])
- );
- }
- }
- }
- // 合并材料题子题目
- $questions = QuestionModel::mergeMaterialQuestions($questions);
- return hidden_list_keys($questions, ['answer', 'explain', 'origin_answer']);
- }
- /**
- * 获取试卷固定题
- * @param $paper
- * @return array
- */
- public static function getFixQuestions($paper, $hidden = true)
- {
- $questions = QuestionModel::getFixListByPaper($paper['id'], ['materialQuestions.question']);
- // 合并材料题子题目
- $questions = QuestionModel::mergeMaterialQuestions($questions);
- if ($hidden) {
- return hidden_list_keys($questions, ['answer', 'explain', 'origin_answer']);
- }
- return $questions;
- }
- /**
- * 试卷考试
- * @param $user_id
- * @param $paper_id
- * @param $user_questions
- * @param $start_time
- * @param $paper
- * @return array
- */
- public static function paperExam($user_id, $paper_id, $user_questions, $start_time, &$paper, $from_room = false)
- {
- // 验证试卷
- $paper = self::validPaper($paper_id, $from_room ? 1 : 0,false);
- if (!$questions_ids = array_column($user_questions, 'id')) {
- fail('提交的题目数据有误');
- }
- $answers = array_column($user_questions, 'answer'); // 用户答案
- $material_ids = array_column($user_questions, 'material_id'); // 材料题id
- $total_score = 0; // 试卷总分
- $error_count = 0; // 错误题目数量
- $error_ids = []; //错误题目id
- if ($paper['mode'] == PaperMode::RANDOM) {
- $questions = QuestionModel::whereIn('id', $questions_ids)->orderRaw("find_in_set(id, '" . implode(',', $questions_ids) . "')")->select();
- } else {
- $questions = self::getFixQuestions($paper, false);
- }
- // 材料题分数
- $material_score = [];
- foreach ($questions as $key => $question) {
- $score = 0;
- // 随机取题
- if ($paper['mode'] == PaperMode::RANDOM) {
- $kind = $question['kind'];
- $difficulty = $question['difficulty'];
- // 属于材料题子题
- if (isset($material_ids[$key]) && $material_ids[$key]) {
- if ($material_question = QuestionModel::where('id', $material_ids[$key])->cache(60)->find()) {
- $kind = 'MATERIAL';
- $difficulty = $material_question['difficulty'];
- // $score = PaperModel::getSingleScore($paper['configs'], strtolower($kind), strtolower($difficulty)); // 每题分数
- // 材料题子题目设定的分数
- $score = MaterialQuestionModel::where('parent_question_id', $material_ids[$key])->where('question_id', $question['id'])->cache(60)->value('score');
- }
- } else {
- $score = PaperModel::getSingleScore($paper['configs'], strtolower($kind), strtolower($difficulty)); // 每题分数
- }
- } else {
- // 固定取题
- $score = $question['score'];
- if ($question['id'] == 764) {
- // dd([$score, $question, isset($material_ids[$key]), $material_ids[$key]]);
- }
- }
- switch ($question['kind']) {
- case 'JUDGE': // 判断题
- case 'SINGLE': // 单选题
- case 'MULTI': // 多选题
- // 答题正确
- if (strtoupper($answers[$key]) == $question['answer']) {
- $total_score += $score;
- $user_questions[$key]['is_right'] = true;
- } else {
- array_push($error_ids, $question['id']);
- $error_count++;
- $user_questions[$key]['is_right'] = false;
- // 记录错题
- QuestionModel::recordWrong($question['id'], $user_id, $answers[$key]);
- // $question->logWrong($user_id, $answers[$key]);
- }
- break;
- case 'FILL': // 填空题
- $user_answers = $answers[$key];
- $fill_right_count = 0;
- $question['answer'] = is_array($question['answer']) ? $question['answer'] : json_decode($question['answer'], true);
- foreach ($question['answer'] as $fill_key => $fill_answer) {
- foreach ($fill_answer['answers'] as $answer) {
- if (isset($user_answers[$fill_key]) && str_trim($user_answers[$fill_key]) == str_trim($answer)) {
- $fill_right_count++;
- break;
- }
- }
- }
- // 所有填空项全对
- if ($fill_right_count == count($question['answer'])) {
- $user_questions[$key]['is_right'] = true;
- $total_score += $score;
- } else {
- $user_questions[$key]['is_right'] = false;
- array_push($error_ids, $question['id']);
- $error_count++;
- // 记录错题
- QuestionModel::recordWrong($question['id'], $user_id, $answers[$key]);
- // $question->logWrong($user_id, $answers[$key]);
- }
- break;
- case 'SHORT': // 简答题
- // 答案得分配置
- $answer_config = is_string($question['answer']) ? json_decode($question['answer'], true) : $question['answer'];
- $user_answers = $answers[$key];
- $right_score = 0;
- $answer_score = [];
- foreach ($answer_config['config'] as $answer_item) {
- /*if ($right_score < $score) {*/
- // 匹配答案关键词
- if (strpos($user_answers, $answer_item['answer']) !== false) {
- // $right_score += $answer_item['score'];
- $right_score = $score;
- // 得分情况
- $answer_score[] = [
- 'answer' => $answer_item['answer'],
- // 'score' => min($score, $answer_item['score']), //这里不对
- 'score' => $score,
- 'keyword_score' => $answer_item['score'],
- 'max_score' => $score,
- ];
- }
- /*}*/
- }
- // 最高得分不能超过题目分数
- // $right_score = min($right_score, $score);//这里不对
- // 有得分
- if ($right_score > 0) {
- $user_questions[$key]['is_right'] = true;
- $total_score += $right_score;
- } else {
- $user_questions[$key]['is_right'] = false;
- array_push($error_ids, $question['id']);
- $error_count++;
- // 记录错题
- QuestionModel::recordWrong($question['id'], $user_id, $answers[$key]);
- }
- $user_questions[$key]['answer_score'] = $answer_score;
- break;
- }
- //记录总答题次数,答对数,答错数
- Db::name('exam_question')->where('id',$question['id'])->setInc('total_count');
- $field = $user_questions[$key]['is_right'] ? 'right_count' : 'error_count';
- Db::name('exam_question')->where('id',$question['id'])->setInc($field);
- }
- // 递增参与人次
- $paper->setInc('join_count');
- $result = [
- 'total_score' => $paper['total_score'], // 试卷总分
- 'score' => $total_score, // 考试分数
- 'is_pass' => $total_score >= $paper['pass_score'], // 是否及格
- 'pass_score' => $paper['pass_score'], // 及格分数
- 'total_count' => count($questions), // 题目数量
- 'right_count' => count($questions) - $error_count, // 答对数量
- 'error_count' => $error_count, // 答错数量
- 'start_time' => $start_time, // 开始时间
- 'grade_time' => $paper['limit_time'] ? min(time() - $start_time, $paper['limit_time']) : time() - $start_time,// 考试用时
- 'error_ids' => implode(',', $error_ids), // 错误题目id
- 'question_ids' => implode(',', $questions_ids), // 试题ID集合
- 'user_answers' => json_encode($user_questions, JSON_UNESCAPED_UNICODE), // 用户答案集合
- 'configs' => json_encode($paper['configs']), // 试卷配置
- 'mode' => $paper['mode'], // 试卷选题模式
- ];
- return $result;
- }
- /**
- * 考场考试
- * @param $user_id
- * @param $room_id
- * @param $room_grade_id
- * @param $questions
- * @param $start_time
- * @param $paper
- * @param $room
- * @param $is_makeup
- * @param RoomGradeModel|null $room_grade_log
- * @return array
- */
- public static function roomExam($user_id, $room_id, $room_grade_id, $questions, $start_time, &$paper, &$room, &$is_makeup, &$room_grade_log)
- {
- // 验证考场信息
- $room = self::validRoom($user_id, $room_id, $room_grade_id, $is_makeup, $room_grade_log);
- return self::paperExam($user_id, $room['paper_id'], $questions, $start_time, $paper, true);
- }
- /**
- * 预创建考场考试记录(消耗一次考试记录,避免重复进入考场看题)
- * @param $room_id
- * @param $user_id
- * @return int
- */
- public static function preRoomGrade($room_id, $user_id)
- {
- if (!$room_id) {
- return 0;
- }
- // 验证考场信息
- $room = self::validRoom($user_id, $room_id, 0, $is_makeup);
- // 创建考场考试记录
- $grade = RoomGradeModel::create([
- 'user_id' => $user_id,
- 'room_id' => $room_id,
- 'cate_id' => $room['cate_id'],
- 'paper_id' => $room['paper_id'],
- 'score' => 0,
- 'is_pass' => 0,
- 'is_makeup' => $is_makeup,
- 'total_score' => $room['paper']['total_score'],
- 'total_count' => $room['paper']['quantity'],
- 'right_count' => 0,
- 'error_count' => $room['paper']['quantity'],
- 'rank' => 0,
- 'is_pre' => 1,// 标记为预载入,提交成绩时须改为0
- 'grade_time' => 0,
- ]);
- return $grade['id'];
- }
- /**
- * 验证试卷
- * @param int $paper_id 试卷ID
- * @param int $room_id 考场ID
- * @return PaperModel|null
- */
- private static function validPaper($paper_id, $room_id = 0,$checktime = false)
- {
- $paper = PaperModel::get($paper_id);
- $user_id = getUserId();
- switch (true) {
- case !$paper:
- fail('试卷信息不存在');
- case $paper->status != CommonStatus::NORMAL:
- fail('试卷未开启');
- case $paper->mode == PaperMode::RANDOM && !$paper->configs:
- fail('试卷未配置');
- }
- // 普通考试
- /*if (!$room_id) {
- if ($user_id && $paper['day_limit_count'] > 0 && GradeModel::getUserDateGradeCount($paper_id, $user_id) >= $paper['day_limit_count']) {
- fail('当前试卷考试次数已达今日上限,明天再来吧~');
- }*/
- if($checktime){
- if ($paper['end_time'] > 0 && $paper['end_time'] < time()) {
- fail('该试卷已截止,不能参与考试了');
- }
- }
- /*}*/
- return $paper;
- }
- /**
- * 验证考场
- * @param int $user_id 考试用户
- * @param int $room_id 试卷ID
- * @param int $room_grade_id 考场预创建成绩ID
- * @param int $is_makeup 返回是否是补考
- * @param RoomGradeModel|null $room_grade_log 预创建的成绩记录
- * @return RoomModel|null
- */
- private static function validRoom($user_id, $room_id, $room_grade_id, &$is_makeup, &$room_grade_log = null)
- {
- $room = RoomModel::get($room_id);
- switch (true) {
- case !$room:
- fail('考场信息不存在');
- case $room['status'] != CommonStatus::NORMAL:
- fail('考场未开启');
- case time() < $room['start_time'] || time() > $room['end_time']:
- fail('考场时间未开始或已结束');
- case !$roomSignup = RoomSignupModel::where('room_id', $room_id)->where('user_id', $user_id)->find():
- fail('您尚未报名此考场');
- case $roomSignup['status'] != RoomSignupStatus::ACCEPT:
- fail('您的考场报名信息状态有误');
- }
- // 考场允许补考
- if ($room['is_makeup'] == 1 && $room['makeup_count'] > 0) {
- // $query = RoomGradeModel::where('room_id', $room_id)->where('paper_id', $room['paper_id'])->where('user_id', $user_id);
- // 考试次数
- $room_exam_count = RoomGradeModel::where('room_id', $room_id)->where('paper_id', $room['paper_id'])->where('user_id', $user_id)->count();
- // 补考次数
- $makeup_count = RoomGradeModel::where('room_id', $room_id)->where('paper_id', $room['paper_id'])->where('user_id', $user_id)->where('is_makeup', 1)->count();
- $min_makeup_count = $makeup_count - ($room_grade_id ? 1 : 0);
- if ($min_makeup_count >= $room['makeup_count']) {
- fail("已超过补考次数");
- }
- $last_exam_log = RoomGradeModel::where('room_id', $room_id)->where('paper_id', $room['paper_id'])->where('user_id', $user_id)->order('id desc')->find();
- if ($last_exam_log && $last_exam_log['is_pass'] != 0) {
- fail('最后一次考试已及格,不需要补考了');
- }
- // 考试次数大于0视为补考
- $is_makeup = $room_exam_count > 1 ? 1 : 0;
- } else {
- if (RoomGradeModel::where('room_id', $room_id)->where('user_id', $user_id)->where('is_pre', 0)->count() > 0) {
- fail('您已参加过该考场考试了');
- }
- $is_makeup = 0;
- }
- // 考场预创建记录验证
- if ($room_grade_id) {
- if (!$room_grade_log = RoomGradeModel::where('id', $room_grade_id)->where('user_id', $user_id)->find()) {
- fail('考场成绩错误');
- } else if ($room_grade_log['is_pre'] == 0) {
- fail('本次考场考试已提交过成绩了,请勿重复提交');
- }
- }
- return $room;
- }
- }
|