本文目录导读:

搭建一个在线考试系统是一个典型的Web开发项目,涉及用户管理、题库管理、试卷生成、考试监控、自动评分等核心模块,下面我从系统架构、数据库设计、核心功能实现、安全性考虑四个维度,详细说明如何用PHP完成这个项目。
技术选型与系统架构
| 层次 | 推荐技术 | 说明 |
|---|---|---|
| 后端语言 | PHP 8.x | 推荐使用Laravel(企业级)或ThinkPHP(国内常用)框架 |
| 数据库 | MySQL 8.0 + Redis | MySQL存储核心数据,Redis实现防重复提交、缓存 |
| 前端框架 | Vue.js / Layui | 后台管理用Layui做快速搭建,考试界面用Vue实现交互 |
| 辅助工具 | Composer, Git, Nginx | 包管理、版本控制、高性能Web服务器 |
系统模块划分:
- 用户端:注册/登录、考试列表、参加考试、查看成绩
- 教师端:题库管理、试卷组卷、考试发布、成绩统计
- 管理员端:用户管理、系统设置、数据备份
核心数据库设计(MySQL)
以下是几个最核心的数据库表设计示例:
-- 1. 题库表(支持单选/多选/判断/填空) CREATE TABLE `questions` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `type` tinyint(4) NOT NULL COMMENT '1:单选 2:多选 3:判断 4:填空', `content` text NOT NULL COMMENT '题目内容(支持HTML)', `options` json DEFAULT NULL COMMENT '选项JSON,如["A.北京","B.上海"]', `answer` text NOT NULL COMMENT '正确答案', `score` decimal(5,2) DEFAULT '1.00' COMMENT '分值', `category_id` int(11) DEFAULT NULL COMMENT '分类ID', `difficulty` tinyint(4) DEFAULT '1' COMMENT '难度1-5', `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `category_id` (`category_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 2. 试卷表 CREATE TABLE `exams` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, varchar(255) NOT NULL COMMENT '试卷名称', `duration` int(11) NOT NULL COMMENT '考试时长(分钟)', `start_time` datetime NOT NULL COMMENT '开始时间', `end_time` datetime NOT NULL COMMENT '结束时间', `pass_score` decimal(5,2) DEFAULT NULL COMMENT '及格分数', `is_random` tinyint(1) DEFAULT '0' COMMENT '是否随机抽题', `status` tinyint(4) DEFAULT '0' COMMENT '0未发布 1进行中 2已结束', `created_by` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 3. 试卷题目关联表(支持固定+随机混合组卷) CREATE TABLE `exam_questions` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `exam_id` int(11) NOT NULL, `question_id` int(11) NOT NULL, `sort_order` int(11) DEFAULT '0' COMMENT '排序', `score` decimal(5,2) DEFAULT NULL COMMENT '该题在此试卷中的分值', PRIMARY KEY (`id`), UNIQUE KEY `exam_question` (`exam_id`,`question_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 4. 考生答题记录表 CREATE TABLE `exam_answers` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL, `exam_id` int(11) NOT NULL, `question_id` int(11) NOT NULL, `answer` text COMMENT '考生答案', `is_correct` tinyint(1) DEFAULT NULL COMMENT '是否正确', `score` decimal(5,2) DEFAULT '0.00' COMMENT '得分', `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `user_exam_question` (`user_id`,`exam_id`,`question_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
核心功能实现代码(Laravel示例)
自动组卷(随机抽题与固定题混合)
// ExamService.php
public function generatePaper($examId)
{
$exam = Exam::findOrFail($examId);
// 如果已是随机组卷模式,删除原有题目关联
if ($exam->is_random) {
ExamQuestion::where('exam_id', $examId)->delete();
// 从题库按分类和难度随机抽取
$questions = Question::where('category_id', $exam->category_id)
->where('difficulty', $exam->difficulty)
->inRandomOrder()
->limit($exam->question_count)
->get();
foreach ($questions as $index => $q) {
ExamQuestion::create([
'exam_id' => $examId,
'question_id' => $q->id,
'sort_order' => $index + 1,
'score' => $q->score
]);
}
}
// 固定组卷逻辑:前端拖拽排序后提交ID列表
}
防作弊:考试开始与提交
// ExamController.php
public function startExam($examId)
{
$user = auth()->user();
$exam = Exam::findOrFail($examId);
// 检查考试时间
if (now() < $exam->start_time || now() > $exam->end_time) {
throw new \Exception('考试不在有效时间内');
}
// 检查是否已开始(防重复进入)
$attempt = ExamAttempt::firstOrCreate([
'user_id' => $user->id,
'exam_id' => $examId
], [
'started_at' => now(),
'status' => 'in_progress'
]);
// 生成唯一token防作弊
$attempt->token = Str::random(32);
$attempt->save();
// 把token放入Redis,设置过期时间为考试时长
Redis::setex("exam:{$examId}:user:{$user->id}:token", $exam->duration * 60, $attempt->token);
// 打乱题目顺序(随机模式)
$questions = $exam->questions()->orderBy('sort_order')->get();
if ($exam->random_questions) {
$questions = $questions->shuffle();
}
return view('exam.take', compact('exam', 'questions', 'attempt'));
}
public function submitExam(Request $request, $examId)
{
$user = auth()->user();
$attempt = ExamAttempt::where('user_id', $user->id)
->where('exam_id', $examId)
->where('status', 'in_progress')
->firstOrFail();
// 验证防作弊token
$token = Redis::get("exam:{$examId}:user:{$user->id}:token");
if ($request->token !== $token) {
return back()->with('error', '非法请求');
}
DB::beginTransaction();
try {
// 批量插入答案
foreach ($request->answers as $questionId => $answer) {
ExamAnswer::updateOrCreate([
'user_id' => $user->id,
'exam_id' => $examId,
'question_id' => $questionId
], [
'answer' => is_array($answer) ? json_encode($answer) : $answer
]);
}
// 自动评分
$totalScore = 0;
$answers = ExamAnswer::where('user_id', $user->id)
->where('exam_id', $examId)
->get();
foreach ($answers as $ans) {
$question = Question::find($ans->question_id);
$correct = $this->judgeAnswer($question, $ans->answer);
$ans->is_correct = $correct;
$ans->score = $correct ? $question->score : 0;
$ans->save();
$totalScore += $ans->score;
}
// 记录总成绩
$attempt->total_score = $totalScore;
$attempt->status = 'completed';
$attempt->completed_at = now();
$attempt->save();
// 清理Redis token
Redis::del("exam:{$examId}:user:{$user->id}:token");
DB::commit();
return redirect()->route('exam.result', $attempt->id);
} catch (\Exception $e) {
DB::rollBack();
Log::error('提交考试失败', ['error' => $e->getMessage()]);
return back()->with('error', '提交失败,请重试');
}
}
自动评分核心逻辑
private function judgeAnswer($question, $userAnswer)
{
switch ($question->type) {
case 1: // 单选
return $userAnswer === $question->answer;
case 2: // 多选(答案用逗号分隔,顺序无关)
$correct = explode(',', $question->answer);
$user = is_string($userAnswer) ? explode(',', $userAnswer) : $userAnswer;
sort($correct);
sort($user);
return $correct === $user;
case 3: // 判断
return (int)$userAnswer === (int)$question->answer;
case 4: // 填空(支持模糊匹配:去除空格、大小写)
return strtolower(trim($userAnswer)) === strtolower(trim($question->answer));
default:
return false;
}
}
安全性关键措施
- 防止重复提交:使用Redis记录每个考生的考试状态token,提交后立即销毁。
- 考试中途断网处理:每30秒自动保存答案到数据库(Ajax异步保存)。
- 防作弊机制:
- 限制IP同一账号同时登录
- 禁止复制粘贴(JavaScript
oncopy事件) - 全屏模式启动考试(
Fullscreen API) - 检测切屏次数(监听
visibilitychange事件)
- SQL注入防护:使用ORM(Eloquent)查询,避免原生SQL拼接。
- XSS防护输入时做HTML转义,输出时使用 自动转义。
部署与优化建议
-
PHP-FPM与Nginx配置:
server { listen 80; server_name exam.example.com; root /var/www/exam/public; index index.php; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } } -
性能优化:
- 使用MySQL索引(
user_id, exam_id联合索引) - 考试中实时评量的题目用Redis缓存答案,考试结束后统一落库
- 静态资源(CSS/JS)使用CDN加速
- 使用MySQL索引(
-
定时任务(Laravel Scheduler):
// 每天凌晨清理过期考试记录 $schedule->command('exam:cleanup-expired')->daily(); // 每分钟检查考试状态并自动归档 $schedule->command('exam:archive-finished')->everyMinute();
推荐学习资源与开源项目
| 类型 | 名称 | 说明 |
|---|---|---|
| 开源项目 | LaravelExam(GitHub) | 基于Laravel的完整在线考试系统 |
| 开源项目 | ExamOnline(Gitee) | 基于ThinkPHP6,支持多题型 |
| 课程 | Laracast《Building a Quiz App》 | 英文,基础实现 |
| 书籍 | 《PHP与MySQL高性能应用开发》 | 中文,架构设计参考 |
总结建议
对于初学者,建议按以下步骤迭代:
- 第一版:只用PHP原生+MySQL,实现手动组卷、单选判断自动改卷
- 第二版:引入Laravel框架,增加用户角色、多题型、防作弊
- 第三版:加入Redis缓存、分布式部署、考试监控大屏
如果你需要一个现成的快速搭建方案,可以告诉我你的具体需求(如支持多少人同时在线、需要哪些题型),我可以提供一份可运行的简化版源码架构。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播