如何用PHP项目搭建在线考试系统?

wen PHP项目 7

本文目录导读:

如何用PHP项目搭建在线考试系统?

  1. 技术选型与系统架构
  2. 核心数据库设计(MySQL)
  3. 核心功能实现代码(Laravel示例)
  4. 安全性关键措施
  5. 部署与优化建议
  6. 推荐学习资源与开源项目
  7. 总结建议

搭建一个在线考试系统是一个典型的Web开发项目,涉及用户管理、题库管理、试卷生成、考试监控、自动评分等核心模块,下面我从系统架构、数据库设计、核心功能实现、安全性考虑四个维度,详细说明如何用PHP完成这个项目。

技术选型与系统架构

层次 推荐技术 说明
后端语言 PHP 8.x 推荐使用Laravel(企业级)或ThinkPHP(国内常用)框架
数据库 MySQL 8.0 + Redis MySQL存储核心数据,Redis实现防重复提交、缓存
前端框架 Vue.js / Layui 后台管理用Layui做快速搭建,考试界面用Vue实现交互
辅助工具 Composer, Git, Nginx 包管理、版本控制、高性能Web服务器

系统模块划分

  1. 用户端:注册/登录、考试列表、参加考试、查看成绩
  2. 教师端:题库管理、试卷组卷、考试发布、成绩统计
  3. 管理员端:用户管理、系统设置、数据备份

核心数据库设计(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;
    }
}

安全性关键措施

  1. 防止重复提交:使用Redis记录每个考生的考试状态token,提交后立即销毁。
  2. 考试中途断网处理:每30秒自动保存答案到数据库(Ajax异步保存)。
  3. 防作弊机制
    • 限制IP同一账号同时登录
    • 禁止复制粘贴(JavaScript oncopy 事件)
    • 全屏模式启动考试(Fullscreen API
    • 检测切屏次数(监听 visibilitychange 事件)
  4. SQL注入防护:使用ORM(Eloquent)查询,避免原生SQL拼接。
  5. XSS防护输入时做HTML转义,输出时使用 自动转义。

部署与优化建议

  1. 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;
     }
    }
  2. 性能优化

    • 使用MySQL索引(user_id, exam_id 联合索引)
    • 考试中实时评量的题目用Redis缓存答案,考试结束后统一落库
    • 静态资源(CSS/JS)使用CDN加速
  3. 定时任务(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高性能应用开发》 中文,架构设计参考

总结建议

对于初学者,建议按以下步骤迭代:

  1. 第一版:只用PHP原生+MySQL,实现手动组卷、单选判断自动改卷
  2. 第二版:引入Laravel框架,增加用户角色、多题型、防作弊
  3. 第三版:加入Redis缓存、分布式部署、考试监控大屏

如果你需要一个现成的快速搭建方案,可以告诉我你的具体需求(如支持多少人同时在线、需要哪些题型),我可以提供一份可运行的简化版源码架构。

抱歉,评论功能暂时关闭!