
简介这是一套基于Web的考试系统自动组卷功能的完整实现方案面向计算机相关专业学生、教师及初级开发人员解决传统考试系统中组卷效率低、策略单一、扩展性差等实际问题。资源包含476个文件以181个Java后端代码、88个JavaScript前端逻辑、77个GIF动效资源及33个CSS样式文件为核心辅以JSP页面、XML配置、SQL脚本与Markdown文档整体压缩包仅2.6MB轻量但结构完整涵盖SpringCloud微服务架构与Python辅助工具标签体现技术融合。已有59人学习下载资源经导师指导并获95分高分答辩认可代码全部实测可运行提供从需求分析、数据库设计、自动组卷算法难度/题型/知识点权重控制、前后端交互到部署说明的全流程资料含全局样式文件如layui.css、layer.css、login.min.css与模块化前端组件便于毕设复用、课设改造或进阶学习。1. 这不是又一个“学生交卷就完事”的考试系统它用规则引擎题库权重实时冲突检测在浏览器里跑出真正可投产的自动组卷逻辑你见过凌晨三点还在手动拖拽题型、反复核对知识点覆盖率、删掉第7套试卷因为“单选题重复了两道去年真题”的教务老师吗这不是教学管理痛点是传统考试系统在 Web 端长期被忽视的硬伤——所谓“自动组卷”90%只是随机抽题固定题量拼凑一上线就被教师打回“这组的难度断层太大”“算法没考虑章节分布”“学生考完发现三道大题全出自同一节”。本项目标题里的“基于WEB的考试系统自动组卷”核心不在“Web界面有多漂亮”而在于它把组卷从“伪自动化”推进到“可配置、可验证、可审计”的工程级实现前端用 Layui jQuery-UI 构建拖拽式策略面板后端用 PHPFastAdmin 框架实现带约束求解的组卷引擎MySQL 表结构明确区分题库元数据、试卷模板、策略规则、生成日志四层关系。它适合高校教务处部署、职业培训中心批量出卷、甚至在线教育平台嵌入题库服务——前提是你愿意花2小时看懂它的策略配置表设计而不是直接 npm install 一个“自动组卷”包然后祈祷不翻车。2. 从题库建模到策略落地为什么必须用四张表撑起组卷逻辑而不是一张“题目表”硬扛2.1 题库表question_bank不是简单存题干而是为组卷埋下所有决策锚点自动组卷不是“随机选题”而是“在约束条件下求可行解”。这意味着每道题必须携带足够多的维度标签供策略引擎实时计算。本项目 MySQL 中question_bank表的关键字段远超基础文本存储CREATE TABLE question_bank ( id int(11) NOT NULL AUTO_INCREMENT, content text NOT NULL COMMENT 题干含HTML格式, answer text COMMENT 标准答案JSON格式支持多选/填空/主观题结构化, difficulty tinyint(2) NOT NULL DEFAULT 3 COMMENT 难度系数1-5用于难度均衡计算, knowledge_point varchar(255) NOT NULL COMMENT 知识点编码如DB0103数据库-索引优化, question_type enum(single,multiple,fill,judge,essay) NOT NULL COMMENT 题型, score decimal(5,2) NOT NULL DEFAULT 1.00 COMMENT 默认分值, used_count int(11) NOT NULL DEFAULT 0 COMMENT 历史组卷使用次数防高频题扎堆, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_kp_type_diff (knowledge_point,question_type,difficulty) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;提示knowledge_point字段必须是结构化编码非纯文本例如MATH0201表示“高等数学-第二章-第一节”这样组卷时才能按章节树做覆盖率校验used_count不是装饰字段——引擎每次生成试卷后会原子性更新避免同一题在7天内重复出现在同一教师名下试卷中。2.2 策略模板表paper_strategy才是真正的“组卷大脑”它把业务语言翻译成SQL条件教师在 Layui 界面拖拽设置的“单选20题、难度均值3.2±0.5、知识点覆盖至少3个二级章节”最终落地为paper_strategy表中一条记录字段示例值说明name“期末高数A卷”策略名称供教师选择rules_json{type_ratio:{single:0.4,multiple:0.3},difficulty_range:[2.8,3.6],kp_coverage:[MATH01,MATH02,MATH03],max_same_kp:2}JSON 存储完整策略不是字符串拼接后端解析后生成动态 WHERE 条件status11启用0停用策略灰度发布必备created_by1001创建人ID关联用户表关键逻辑在 PHP 层rules_json解析后引擎不直接执行SELECT * FROM question_bank WHERE ...而是先生成候选题池Candidate Pool再用贪心算法迭代填充试卷——比如先按kp_coverage拆解为3个子任务各选至少1题再在每个子任务内按difficulty_range筛选最后全局调整type_ratio。这种分层求解比纯 SQLORDER BY RAND()可控得多。2.3 试卷快照表paper_snapshot解决“考完发现题目错了”的溯源死结很多系统考完才暴露问题教师改了题干但已生成的试卷还显示旧内容。本项目用paper_snapshot表固化每次组卷结果CREATE TABLE paper_snapshot ( id int(11) NOT NULL AUTO_INCREMENT, paper_id int(11) NOT NULL COMMENT 关联paper表主键, strategy_id int(11) NOT NULL COMMENT 生成所用策略ID, question_list text NOT NULL COMMENT JSON数组含题ID、分值、题型、生成时的difficulty/kp等快照, generate_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, generator_ip varchar(45) DEFAULT NULL COMMENT 生成者IP用于审计, PRIMARY KEY (id), KEY idx_paper_id (paper_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;question_list字段存的是完整快照[{qid:123,score:2,difficulty:3.1,kp:MATH0201},...]。这意味着即使原题被编辑已生成试卷仍能100%还原考试现场——教务查卷时点开试卷就能看到“当时这道题的难度标定是3.1知识点是MATH0201”而不是去翻Git历史找半年前的题库备份。3. Layui jQuery-UI 实现的策略配置面板不是炫技而是让教师真正掌控规则3.1 用 Layui 的 form.render() 动态绑定策略字段避免“改了难度范围却没生效”的玄学bugLayui 表单提交前必须显式触发form.render()重绘否则动态添加的滑块slider或复选框checkbox值不会被form.val()读取。本项目在策略编辑页关键代码如下// 初始化难度范围滑块 layui.use([slider, form], function(){ var slider layui.slider; var form layui.form; // 创建滑块组件 slider.render({ elem: #difficulty-slider, range: true, value: [2.5, 3.5], min: 1, max: 5, step: 0.1, change: function(value){ // 滑块变动时同步更新隐藏input的value确保form.val()能读到 $(#difficulty_min).val(value[0]); $(#difficulty_max).val(value[1]); form.render(select); // 重要重绘select以同步联动选项 } }); // 监听表单提交 form.on(submit(strategy-submit), function(data){ // 必须先render否则动态控件值丢失 form.render(); var formData form.val(strategy-form); // 此时formData包含滑块、多选框等所有字段 $.post(/admin/paper_strategy/save, formData, function(res){ layer.msg(res.msg); }); return false; }); });参数说明form.render(select)不是可有可无的——当知识点多选框由AJAX动态加载与滑块联动时若不重绘selectform.val()会返回空数组。这是 Layui 2.8.x 版本的已知行为文档未强调但实测必踩坑。3.2 jQuery-UI 的 sortable 实现题型权重拖拽排序背后是策略JSON的实时重组教师拖动“单选题40% → 多选题30% → 判断题20% → 主观题10%”时前端不是简单改变DOM顺序而是实时重构rules_json中的type_ratio对象// 初始化可排序列表 $(#type-ratio-list).sortable({ update: function(event, ui) { var ratios {}; $(#type-ratio-list li).each(function(index) { var type $(this).data(type); //>-- 查看试卷ID123的知识点分布 SELECT SUBSTRING_INDEX(kp_snapshot, ., 2) AS chapter_code, COUNT(*) as question_count FROM paper_snapshot ps JOIN JSON_TABLE(ps.question_list, $[*] COLUMNS ( kp_snapshot VARCHAR(50) PATH $.kp_snapshot )) AS jt WHERE ps.paper_id 123 GROUP BY chapter_code;结果应返回至少3行如MATH01,MATH02,MATH03且每行question_count 1。若只有2行说明策略中kp_coverage设置未生效或题库中对应章节无可用题——此时需查question_bank表SELECT COUNT(*) FROM question_bank WHERE knowledge_point LIKE MATH03% AND difficulty BETWEEN 2.0 AND 3.5;5.2 难度均衡性不能只看平均值要算标准差用PHP脚本批量审计新建audit_difficulty.php遍历最近100套试卷快照?php $pdo new PDO(mysql:hostlocalhost;dbnameexam, $user, $pass); $stmt $pdo-query(SELECT question_list FROM paper_snapshot WHERE generate_time DATE_SUB(NOW(), INTERVAL 7 DAY) LIMIT 100); $all_difficulties []; while ($row $stmt-fetch(PDO::FETCH_ASSOC)) { $questions json_decode($row[question_list], true); foreach ($questions as $q) { if (isset($q[difficulty_snapshot])) { $all_difficulties[] $q[difficulty_snapshot]; } } } $mean array_sum($all_difficulties) / count($all_difficulties); $variance 0; foreach ($all_difficulties as $d) { $variance pow($d - $mean, 2); } $std_dev sqrt($variance / count($all_difficulties)); echo 近7天试卷难度均值: . round($mean, 2) . \n; echo 标准差: . round($std_dev, 2) . 理想值 0.4\n; // 标准差0.6说明难度波动过大需检查策略中difficulty_range是否过宽或题库难度标注失真 ?血泪经验我们曾发现标准差高达0.87追查发现30%的题目标注difficulty5满分5但实际教学反馈这些题学生平均得分率仅45%属于“伪高难”。最终推动教研组重标难度用average_score_rate * 5作为新难度值比主观标注可靠得多。5.3 知识点离散度用Excel透视表一眼揪出“扎堆题”导出paper_snapshot.question_list中所有kp_snapshot字段用Excel做透视表行kp_snapshot知识点编码值计数显示该知识点在所有试卷中出现总次数筛选只显示计数 5 的知识点若发现DB0101数据库-基本概念出现23次而DB0302数据库-事务隔离仅出现1次说明题库建设严重失衡。此时不应调策略而要补题——在question_bank中新增knowledge_pointDB0302的题目并设置difficulty3.0让引擎下次组卷自然倾斜。我坚持在每次上线新策略前用这三招跑一遍真实试卷数据。不是为了证明代码没错而是确认它真的在帮教师解决问题——当教务主任指着报表说“上月主观题占比从12%提到25%学生反馈题型更合理”我知道那行INSERT INTO question_usage_log的ON DUPLICATE KEY 逻辑终于没白写。希望帮到你。本文还有配套的精品资源点击获取