【Webman+MySQL答题系统七】提交与自动判分:三种题型规则实现
果子发表于:2026-10-05 20:55:45浏览:1次
本篇目标
实现交卷判分:接收答案集合、按三种题型比对、漏选给半分、事务写入成绩与明细。这是整个系统最核心的一段代码。

一、判分流程

规则回顾(第一篇定死的):
| 题型 | 判分规则 |
|---|---|
| 单选 / 判断 | 完全一致得分,否则 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 做统计与错题本。
栏目分类全部>

