如何用PHP项目搭建题库系统?

wen PHP项目 2

本文目录导读:

如何用PHP项目搭建题库系统?

  1. 系统架构规划
  2. 模块功能实现
  3. 安全与性能优化
  4. 部署与扩展建议
  5. 常见问题处理

使用PHP搭建一个功能完整的题库系统,通常需要涵盖题库管理试卷生成在线考试成绩统计等核心模块,以下是一个从零开始的技术方案与实施步骤,适合有一定PHP基础的中级开发者。


系统架构规划

技术栈推荐

  • 后端语言:PHP 7.4+ (建议使用ThinkPHP/Laravel框架提高开发效率)
  • 前端:HTML5 + Bootstrap 5 + jQuery (或Vue.js提升交互)
  • 数据库:MySQL 5.7+ (或MariaDB)
  • 缓存:Redis (可选,用于高并发场景)
  • Web服务器:Nginx/Apache

核心数据表设计(MySQL)

CREATE TABLE `questions` (
  `id` int UNSIGNED AUTO_INCREMENT,
  `type` tinyint NOT NULL COMMENT '1单选 2多选 3判断 4填空 5简答',
  `category_id` int UNSIGNED DEFAULT 0 COMMENT '分类ID',
  `difficulty` tinyint DEFAULT 1 COMMENT '1简单 2中等 3困难',
  `content` text NOT NULL COMMENT '题目内容(支持HTML)',
  `options` text COMMENT '选项(JSON: {a:选项A,b:选项B,...})',
  `answer` text NOT NULL COMMENT '正确答案',
  `analysis` text COMMENT '解析',
  `score` decimal(5,1) DEFAULT 5.0 COMMENT '默认分值',
  `status` tinyint DEFAULT 1 COMMENT '1启用 0禁用',
  `created_at` datetime,
  `updated_at` datetime,
  INDEX `idx_category` (`category_id`),
  INDEX `idx_type` (`type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 试卷表
CREATE TABLE `exams` (
  `id` int UNSIGNED AUTO_INCREMENT, varchar(200) NOT NULL,
  `duration` int DEFAULT 60 COMMENT '考试时长(分钟)',
  `total_score` decimal(6,1) DEFAULT 100,
  `start_time` datetime COMMENT '考试开始时间',
  `end_time` datetime COMMENT '考试截止时间',
  `type` tinyint DEFAULT 0 COMMENT '0模拟 1正式',
  `status` tinyint DEFAULT 0 COMMENT '0未发布 1进行中 2已结束',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;
-- 试卷-题目关联表
CREATE TABLE `exam_questions` (
  `exam_id` int UNSIGNED,
  `question_id` int UNSIGNED,
  `score` decimal(5,1) NOT NULL COMMENT '该题在此试卷中的分值',
  `sort` tinyint DEFAULT 0 COMMENT '排序',
  PRIMARY KEY (`exam_id`, `question_id`)
) ENGINE=InnoDB;
-- 考试记录表
CREATE TABLE `exam_records` (
  `id` int UNSIGNED AUTO_INCREMENT,
  `user_id` int UNSIGNED,
  `exam_id` int UNSIGNED,
  `start_time` datetime,
  `submit_time` datetime,
  `score` decimal(6,1) DEFAULT 0 COMMENT '实际得分',
  `status` tinyint DEFAULT 0 COMMENT '0未交卷 1已交卷',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;
-- 答题详情表
CREATE TABLE `answer_details` (
  `record_id` int UNSIGNED,
  `question_id` int UNSIGNED,
  `user_answer` text,
  `correct_answer` text,
  `score` decimal(5,1) COMMENT '该题得分',
  `is_correct` tinyint DEFAULT 0,
  PRIMARY KEY (`record_id`, `question_id`)
) ENGINE=InnoDB;

模块功能实现

第一步:题库管理模块(管理员后台)

题目导入(支持批量添加)

// 示例:通过 Excel 导入题目
public function importQuestions($filePath) {
    $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
    $spreadsheet = $reader->load($filePath);
    $data = $spreadsheet->getActiveSheet()->toArray();
    $db = Db::name('questions');
    foreach ($data as $row) {
        $question = [
            'type' => $row[0], // 试题类型
            'content' => $row[1], 
            'options' => json_encode([
                'A' => $row[2], 
                'B' => $row[3], 
                'C' => $row[4], 
                'D' => $row[5]
            ]),
            'answer' => $row[6],
            'analysis' => $row[7] ?? '',
            'category_id' => 0,
            'created_at' => date('Y-m-d H:i:s')
        ];
        $db->insert($question);
    }
    return ['code' => 200, 'msg' => '导入成功'];
}

智能组卷算法(按条件随机抽取)

public function generateExam($params) {
    // 从题库中按条件抽取题目
    $where = [];
    if (!empty($params['category_id'])) {
        $where[] = ['category_id', '=', $params['category_id']];
    }
    if (!empty($params['difficulty'])) {
        $where[] = ['difficulty', '=', $params['difficulty']];
    }
    $questions = Db::name('questions')
        ->where($where)
        ->orderRaw('RAND()') // 随机排序
        ->limit($params['count'])
        ->select();
    // 计算总分并组装试卷
    $totalScore = 0;
    $examQuestions = [];
    foreach ($questions as $q) {
        $examQuestions[] = [
            'question_id' => $q['id'],
            'score' => $q['score'],
            'sort' => $q['id']
        ];
        $totalScore += $q['score'];
    }
    // 创建试卷记录(事务)
    Db::transaction(function() use ($params, $examQuestions, $totalScore) {
        $examId = Db::name('exams')->insertGetId([
            'title' => $params['title'],
            'total_score' => $totalScore,
            'duration' => $params['duration'],
            'created_at' => date('Y-m-d H:i:s')
        ]);
        foreach ($examQuestions as &$item) {
            $item['exam_id'] = $examId;
        }
        Db::name('exam_questions')->insertAll($examQuestions);
    });
    return ['code' => 200, 'exam_id' => $examId];
}

第二步:在线考试模块

前端答题界面(核心交互逻辑)

// 使用AJAX定时保存答题进度
let timer = null;
let remainingTime = examDuration * 60;
function startTimer() {
    timer = setInterval(() => {
        remainingTime--;
        if (remainingTime <= 0) {
            clearInterval(timer);
            autoSubmitExam(); // 自动交卷
        }
        // 更新前端倒计时显示
        $('#timerDisplay').text(formatTime(remainingTime));
        // 每30秒自动保存一次答题进度
        if (remainingTime % 30 === 0) {
            saveProgress();
        }
    }, 1000);
}
// 提交单题答案
function submitAnswer(questionId, answer) {
    $.ajax({
        url: '/api/submit-answer',
        type: 'POST',
        data: {
            exam_id: currentExamId,
            question_id: questionId,
            answer: answer
        },
        success: function(response) {
            // 更新答题卡状态(高亮已答题目)
            updateQuestionCard(questionId, true);
        }
    });
}

自动评分程序(支持多题型)

public function autoGrade($recordId) {
    $answers = Db::name('answer_details')
        ->where('record_id', $recordId)
        ->select();
    $totalScore = 0;
    foreach ($answers as &$answer) {
        $question = Db::name('questions')->find($answer['question_id']);
        $isCorrect = false;
        switch ($question['type']) {
            case 1: // 单选
                $isCorrect = $answer['user_answer'] === $question['answer'];
                break;
            case 2: // 多选
                $userAnswers = explode(',', $answer['user_answer']);
                $correctAnswers = explode(',', $question['answer']);
                sort($userAnswers);
                sort($correctAnswers);
                $isCorrect = $userAnswers === $correctAnswers;
                break;
            case 3: // 判断
                $isCorrect = strtoupper($answer['user_answer']) === strtoupper($question['answer']);
                break;
            case 4: // 填空(支持模糊匹配)
                $userAns = trim(mb_strtolower($answer['user_answer']));
                $correctAns = trim(mb_strtolower($question['answer']));
                $isCorrect = $userAns === $correctAns || 
                           similar_text($userAns, $correctAns) > 80;
                break;
            case 5: // 简答(需人工评分,标记为待批改)
                $answer['is_correct'] = -1; // -1表示待批改
                Db::name('answer_details')->update($answer);
                continue 2;
        }
        $answer['is_correct'] = $isCorrect ? 1 : 0;
        $answer['score'] = $isCorrect ? $question['score'] : 0;
        $totalScore += $answer['score'];
        Db::name('answer_details')->update($answer);
    }
    // 更新总成绩
    Db::name('exam_records')
        ->where('id', $recordId)
        ->update(['score' => $totalScore, 'status' => 1]);
}

第三步:成绩统计模块

// 获取考试成绩分布
public function getScoreDistribution($examId) {
    $scores = Db::name('exam_records')
        ->where('exam_id', $examId)
        ->column('score');
    $distribution = [
        '0-59' => 0,
        '60-69' => 0,
        '70-79' => 0,
        '80-89' => 0,
        '90-100' => 0
    ];
    foreach ($scores as $score) {
        if ($score < 60) $distribution['0-59']++;
        elseif ($score < 70) $distribution['60-69']++;
        elseif ($score < 80) $distribution['70-79']++;
        elseif ($score < 90) $distribution['80-89']++;
        else $distribution['90-100']++;
    }
    return $distribution;
}
// 试题正确率统计
public function getQuestionAccuracy($examId) {
    $examQuestions = Db::name('exam_questions')
        ->where('exam_id', $examId)
        ->column('question_id');
    $result = [];
    foreach ($examQuestions as $qid) {
        $total = Db::name('answer_details')
            ->where('question_id', $qid)
            ->count();
        $correct = Db::name('answer_details')
            ->where('question_id', $qid)
            ->where('is_correct', 1)
            ->count();
        $result[$qid] = [
            'total_answers' => $total,
            'correct_count' => $correct,
            'accuracy' => $total > 0 ? round($correct / $total, 4) : 0
        ];
    }
    return $result;
}

安全与性能优化

防作弊策略

// 防止切屏(前端检测)
document.addEventListener('visibilitychange', function() {
    if (document.hidden) {
        // 记录切屏次数
        ajax('/api/record-switch-tab', { count: 1 });
    }
});
// 防止复制粘贴
document.addEventListener('copy', function(e) {
    e.preventDefault();
    alert('考试期间禁止复制!');
});
// 后端验证:禁止同一用户多设备登录
public function checkUserLogin($userId, $sessionId) {
    $currentSession = Db::name('user_sessions')
        ->where('user_id', $userId)
        ->value('session_id');
    return $currentSession === $sessionId;
}

数据安全

  • 所有输入使用参数绑定防止SQL注入答案存储在数据库中加密存储(可选)
  • 试卷导出支持PDF加密

性能优化

  • 题库索引:category_id, type, difficulty组合索引
  • 缓存热点数据:用Redis缓存分类列表、高频题目
  • 分页加载:个人答题记录使用分页查询
  • 数据库读写分离(高并发场景)

部署与扩展建议

快速启动命令(以ThinkPHP为例)

composer create-project topthink/think question-bank
cd question-bank
php think run  # 开发服务器 0.0.0.0:8000

安装依赖

# Excel导入导出
composer require phpoffice/phpspreadsheet
# PDF生成
composer require mpdf/mpdf
# Redis
composer require predis/predis

扩展功能方向

  • AI智能出题:接入大模型API根据知识点自动生成题目
  • 错题本:记录用户错题,支持自动组卷重做
  • 学习分析:通过答题数据生成个人知识图谱
  • WebSocket实时考试:支持监督模式,教师实时查看答题进度

常见问题处理

Q: 如何防止用户直接从数据库获取答案?

A: 答案字段加密存储,前端仅返回题目信息,评分在后端完成。

Q: 并发交卷如何处理?

A: 使用数据库锁:

Db::transaction(function() use ($recordId) {
    // 记录锁
    $record = Db::name('exam_records')
        ->lock(true)
        ->find($recordId);
    if ($record['status'] == 1) return; // 已交卷
    // 继续评分逻辑...
});

Q: 支持随机题库选项打乱?

A: 返回前端时对选择题选项shuffle

// 后端随机打乱选项顺序
$options = json_decode($question['options'], true);
$keys = array_keys($options);
shuffle($keys);
$shuffledOptions = [];
foreach ($keys as $key) {
    $shuffledOptions[$key] = $options[$key];
}
$question['options'] = $shuffledOptions;
// 注意需在提交时还原答案映射

通过以上架构,你可以搭建一个支持自定义组卷、防作弊、自动评分、成绩分析的完整题库系统,建议先从基础题库管理功能开始实现,逐步加入在线考试模块,确保每个模块稳定后再进行扩展。

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