【Webman+MySQL答题系统八】成绩统计与错题本接口
果子发表于:2026-10-05 20:55:46浏览:1次
本篇目标
三个统计接口:我的成绩、试卷维度统计(平均分/及格率)、个人错题本。全部基于 record 与 record_answer 聚合。

一、数据关系

二、我的成绩列表
app/controller/StatController.php:
<?php
namespace app\controller;
use support\Request;
use support\Db;
class StatController
{
public function myRecords(Request $request)
{
$userId = (int)$request->get('user_id', 1); // 第九篇换成登录态
$items = Db::table('record as r')
->join('paper as p', 'p.id', '=', 'r.paper_id')
->where('r.user_id', $userId)
->orderByDesc('r.id')
->limit(20)
->get(['r.id', 'p.title', 'r.score', 'p.total_score',
'r.is_pass', 'r.use_seconds', 'r.create_time']);
return success(['items' => $items]);
}
}
三、试卷统计
public function paperStat(Request $request)
{
$paperId = (int)$request->get('paper_id');
$stat = Db::table('record')
->where('paper_id', $paperId)
->selectRaw(
'count(*) as join_count, ' .
'round(avg(score), 1) as avg_score, ' .
'max(score) as max_score, ' .
'sum(is_pass) as pass_count'
)
->first();
if (!$stat || $stat->join_count == 0) {
return error('该试卷暂无答题记录');
}
return success([
'join_count' => $stat->join_count,
'avg_score' => $stat->avg_score,
'max_score' => $stat->max_score,
'pass_rate' => round($stat->pass_count * 100 / $stat->join_count, 1) . '%',
]);
}
一条 SQL 聚合四个指标,PHP 端只算及格率——聚合下推数据库是统计接口的第一选择,数据量大时再考虑缓存表。
四、错题本
核心思路:用户全部 record_answer 里 is_right=0 的行,按 question_id 聚合计错次,带出题干与解析:
public function wrongBook(Request $request)
{
$userId = (int)$request->get('user_id', 1);
$items = Db::table('record_answer as ra')
->join('record as r', 'r.id', '=', 'ra.record_id')
->join('question as q', 'q.id', '=', 'ra.question_id')
->where('r.user_id', $userId)
->where('ra.is_right', 0)
->groupBy('q.id', 'q.title', 'q.type', 'q.options', 'q.answer', 'q.analysis')
->orderByDesc('wrong_count')
->limit(50)
->selectRaw(
'q.id, q.title, q.type, q.options, q.answer, q.analysis, ' .
'count(*) as wrong_count'
)
->get();
// options 手动 decode + 计算我最近一次的答案
$items = array_map(function ($row) {
$row->options = json_decode($row->options, true);
$last = Db::table('record_answer as ra')
->join('record as r', 'r.id', '=', 'ra.record_id')
->where('r.user_id', (int)request()->get('user_id', 1))
->where('ra.question_id', $row->id)
->orderByDesc('ra.id')
->value('ra.answer');
$row->my_last_answer = $last ?: '';
return $row;
}, $items);
return success(['items' => $items]);
}
两个细节:
- 错题本此时才返回 answer 和 analysis——复盘场景本来就要给学员看正确答案,与第六篇的防泄题不冲突;
- groupBy 把 select 里所有非聚合列都列上,兼容 MySQL 5.7 的 only_full_group_by。
五、路由与验证
Route::group('/api/stat', function () {
Route::get('/my_records', [StatController::class, 'myRecords']);
Route::get('/paper_stat', [StatController::class, 'paperStat']);
Route::get('/wrong_book', [StatController::class, 'wrongBook']);
});
curl "http://127.0.0.1:8787/api/stat/wrong_book?user_id=1"
curl "http://127.0.0.1:8787/api/stat/paper_stat?paper_id=1"
用第七篇的半对题验证:wrong_book 应包含第 2 题,wrong_count 至少为 1,my_last_answer 是 "A,D"。
小结
统计三接口完成。业务功能全齐了,下一篇补上登录鉴权,把"写死 user_id=1"换掉。
栏目分类全部>

