| 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', '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;    }}
 |