【Webman+MySQL答题系统五】试卷管理模块:组卷与题目关联
果子发表于:2026-10-05 20:55:44浏览:1次
本篇目标
实现组卷:创建试卷并关联题目(多对多)、自动汇总总分、试卷列表带题目数统计、删除时联动清理。

一、试卷与题目的关系
一道题可进多份卷、一份卷含多道题,经典多对多,靠 paper_question 中间表维系:

二、模型
<?php
namespace app\model;
use support\Model;
class Paper extends Model
{
protected $table = 'paper';
protected $guarded = [];
}
三、控制器:创建试卷
app/controller/PaperController.php。创建时传题目 ID 列表,总分由服务端汇总——不要信任前端传来的 total_score:
<?php
namespace app\controller;
use support\Request;
use support\Db;
use app\model\Paper;
class PaperController
{
public function add(Request $request)
{
$title = trim((string)$request->post('title'));
$passScore = (float)$request->post('pass_score', 60);
$duration = (int)$request->post('duration', 60);
$questionIds = $request->post('question_ids'); // 数组或逗号分隔字符串
if ($title === '') {
return error('试卷标题不能为空');
}
if (is_string($questionIds)) {
$questionIds = array_filter(explode(',', $questionIds));
}
$questionIds = array_values(array_unique(array_map('intval', (array)$questionIds)));
if (!$questionIds) {
return error('至少选择一道题');
}
try {
$paperId = Db::transaction(function () use ($title, $passScore, $duration, $questionIds) {
// 取真实存在的题目与分值
$rows = Db::table('question')
->whereIn('id', $questionIds)
->where('status', 1)
->get(['id', 'score']);
if ($rows->count() != count($questionIds)) {
throw new \Exception('存在无效或停用的题目');
}
$totalScore = $rows->sum('score');
$paperId = Db::table('paper')->insertGetId([
'title' => $title,
'total_score' => $totalScore,
'pass_score' => $passScore,
'duration' => $duration,
'status' => 1,
'create_time' => date('Y-m-d H:i:s'),
]);
// 保持传入顺序作为 sort
$sort = 0;
foreach ($questionIds as $qid) {
Db::table('paper_question')->insert([
'paper_id' => $paperId,
'question_id' => $qid,
'sort' => ++$sort,
]);
}
return $paperId;
});
} catch (\Exception $e) {
return error($e->getMessage());
}
return success(['id' => $paperId], '组卷成功');
}
}
事务保证"试卷行 + N 行关联"要么全成功要么全回滚,中途失败不会留半张卷。分数汇总放在事务内查询,天然防并发下的读写不一致。
四、试卷列表(带题目数)
public function list(Request $request)
{
$page = max(1, (int)$request->get('page', 1));
$limit = min(50, max(1, (int)$request->get('limit', 10)));
$query = Db::table('paper as p')
->leftJoin('paper_question as pq', 'pq.paper_id', '=', 'p.id')
->groupBy('p.id', 'p.title', 'p.total_score', 'p.pass_score', 'p.duration', 'p.status')
->orderBy('p.id', 'desc');
$total = Db::table('paper')->count();
$items = $query->forPage($page, $limit)
->selectRaw('p.id, p.title, p.total_score, p.pass_score, p.duration, p.status, count(pq.id) as question_count')
->get();
return success(['total' => $total, 'items' => $items]);
}
两个易错点:
- MySQL 5.7 默认 only_full_group_by,SELECT 里出现的非聚合列必须都进 GROUP BY,否则直接报 SQL 错;
- Db::table 查询不走模型 casts,所以这里没有 JSON 字段问题,但记住这个差异,下一篇取卷时会专门处理。
五、停用与删除
public function setStatus(Request $request)
{
$id = (int)$request->post('id');
$status = (int)$request->post('status') === 1 ? 1 : 0;
Db::table('paper')->where('id', $id)->update(['status' => $status]);
return success([], '操作成功');
}
public function delete(Request $request)
{
$id = (int)$request->post('id');
$has = Db::table('record')->where('paper_id', $id)->count();
if ($has) {
return error('已有答题记录,建议停用而不是删除');
}
Db::transaction(function () use ($id) {
Db::table('paper_question')->where('paper_id', $id)->delete();
Db::table('paper')->where('id', $id)->delete();
});
return success([], '删除成功');
}
有历史成绩的卷子停用即可,删了统计就断链了——数据一旦产生消费,物理删除就要慎重。
六、路由与验证
Route::group('/api/paper', function () {
Route::post('/add', [PaperController::class, 'add']);
Route::get('/list', [PaperController::class, 'list']);
Route::post('/set_status', [PaperController::class, 'setStatus']);
Route::post('/delete', [PaperController::class, 'delete']);
});
curl -X POST http://127.0.0.1:8787/api/paper/add \
-d "title=Webman 基础卷" -d "pass_score=4" \
-d "question_ids[]=1" -d "question_ids[]=2" -d "question_ids[]=3"
// {"code":0,"msg":"组卷成功","data":{"id":1}}
小结
组卷完成:事务写入、服务端算总分、列表聚合题目数、删除保护历史数据。下一篇切到学员视角:取卷作答。
栏目分类全部>
推荐文章
- 【Webman+MySQL答题系统七】提交与自动判分:三种题型规则实现
- iframe嵌套微信公众号不显示最佳解决方案,使用cors-anywhere 解决跨域问题
- OpenClaw 核心概念:Gateway、Agent、Channel 一次讲清
- 【Webman+MySQL答题系统四】题目管理模块:增删改查接口实战
- 大模型量化技术:INT8 与 INT4 是什么
- ThinkPHP6.0.3+ElementAdmin+UniAPP多端新闻网站、App 源码
- PhpStorm 链接管理Mysql数据库(远程数据库和本地数据库)
- 主流开源大模型盘点:LLaMA、Qwen、DeepSeek 等
- 大模型的「幻觉」是什么?为什么会胡说八道
- 大模型微调入门:LoRA 是什么

