您的当前位置:首页>全部文章>文章详情

【Webman+MySQL答题系统七】提交与自动判分:三种题型规则实现

果子发表于:2026-10-05 20:55:45浏览:1次TAG: #PHP #MySql #答题系统

本篇目标

实现交卷判分:接收答案集合、按三种题型比对、漏选给半分、事务写入成绩与明细。这是整个系统最核心的一段代码。

第七篇封面

一、判分流程

提交与自动判分流程图

规则回顾(第一篇定死的):

题型判分规则
单选 / 判断完全一致得分,否则 0 分
多选完全一致满分;漏选一半分;多选/错选 0 分

二、答案归一化

比对前先把两边答案"洗干净":去空格、拆开、排序、重拼。这样 "C,A" 与 "a, C" 都能对上 "A,C":

private function norm(string $ans): string
{
    $arr = array_values(array_filter(
        array_map('trim', explode(',', $ans)),
        fn($v) => $v !== ''
    ));
    sort($arr);
    return strtoupper(implode(',', $arr));
}

三、单题判分

/**
 * @return array{0: bool 是否完全正确, 1: float 得分}
 */
private function judgeOne(int $type, string $user, string $right, float $score): array
{
    $u = $this->norm($user);
    $r = $this->norm($right);

    if ($u === $r) {
        return [true, $score];
    }
    // 多选漏选:用户答案是正确答案的真子集,给一半
    if ($type == 2 && $u !== '') {
        $uArr = explode(',', $u);
        $rArr = explode(',', $r);
        $noWrongPick = count(array_diff($uArr, $rArr)) === 0;
        if ($noWrongPick && count($uArr) < count($rArr)) {
            return [false, round($score / 2, 1)]; // 半对:记入错题但给一半分
        }
    }
    return [false, 0.0];
}

漏选的判定精髓在 array_diff:用户选的每一项都必须出现在正确答案里(没选错),且数量少于正确答案(确实漏了)。半对的题 is_right 记 0,会进错题本——业务上"没答满分"就该复盘。

四、交卷接口

public function submit(Request $request)
{
    $paperId = (int)$request->post('paper_id');
    $userId  = 1;                                // 第九篇换成登录态里的 uid
    $answers = (array)$request->post('answers', []); // [question_id => 'A' / 'A,C']
    $useSec  = (int)$request->post('use_seconds', 0);

    $paper = Db::table('paper')->where('id', $paperId)->where('status', 1)->first();
    if (!$paper) {
        return error('试卷不存在或已停用', 404);
    }

    $questions = Db::table('paper_question as pq')
        ->join('question as q', 'q.id', '=', 'pq.question_id')
        ->where('pq.paper_id', $paperId)
        ->get(['q.id', 'q.type', 'q.answer', 'q.score']);

    try {
        [$recordId, $score] = Db::transaction(function () use ($paper, $questions, $answers, $userId, $useSec) {
            $total = 0.0;

            $recordId = Db::table('record')->insertGetId([
                'paper_id'    => $paper->id,
                'user_id'     => $userId,
                'score'       => 0,          // 先占位,判完更新
                'is_pass'     => 0,
                'use_seconds' => $useSec,
                'create_time' => date('Y-m-d H:i:s'),
            ]);

            foreach ($questions as $q) {
                [$ok, $get] = $this->judgeOne(
                    $q->type,
                    (string)($answers[$q->id] ?? ''),
                    $q->answer,
                    (float)$q->score
                );
                $total += $get;

                Db::table('record_answer')->insert([
                    'record_id'    => $recordId,
                    'question_id'  => $q->id,
                    'answer'       => $this->norm((string)($answers[$q->id] ?? '')),
                    'is_right'     => $ok ? 1 : 0,
                    'get_score'    => $get,
                ]);
            }

            Db::table('record')->where('id', $recordId)->update([
                'score'   => $total,
                'is_pass' => $total >= (float)$paper->pass_score ? 1 : 0,
            ]);

            return [$recordId, $total];
        });
    } catch (\Exception $e) {
        return error('判分失败:' . $e->getMessage());
    }

    return success([
        'record_id'   => $recordId,
        'score'       => $score,
        'total_score' => (float)$paper->total_score,
        'is_pass'     => $score >= (float)$paper->pass_score,
    ], '交卷成功');
}

整个判分在一个事务里:record 占位行 + N 条明细 + 回写总分,任何一步炸了全部回滚,不会出现"有成绩没明细"的脏数据。

五、验证

curl -X POST http://127.0.0.1:8787/api/answer/submit \
  -d "paper_id=1" -d "use_seconds=353" \
  -d "answers[1]=B" -d "answers[2]=A,D" -d "answers[3]=A"

// 预期:第2题漏选(正确 A,C,D)得一半分 1.5,总分 2+1.5+1 = 4.5
// {"code":0,"msg":"交卷成功","data":{"score":4.5,...}}

SELECT score, is_pass FROM record ORDER BY id DESC LIMIT 1;
SELECT question_id, answer, is_right, get_score FROM record_answer ORDER BY id DESC LIMIT 3;

再用错误答案、空答案各交一次卷,确认 0 分路径也正常。

小结

判分闭环完成:归一化、三题型规则、漏选半分、事务落库。下一篇基于 record / record_answer 做统计与错题本。