本文目录导读:

使用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;
// 注意需在提交时还原答案映射
通过以上架构,你可以搭建一个支持自定义组卷、防作弊、自动评分、成绩分析的完整题库系统,建议先从基础题库管理功能开始实现,逐步加入在线考试模块,确保每个模块稳定后再进行扩展。