
简介本资源为面向中小学及高校教师的局域网考试系统终版软件包专为构建封闭、高效、防作弊的本地化电子考核环境而设计有效解决传统纸质考试组卷耗时、阅卷低效、监考压力大等教学痛点。压缩包为RAR格式大小7.58MB含安装程序t_setup.exe用于局域网内快速部署、使用手册Readme-说明.htm详述功能配置与操作流程以及核心可执行系统文件整体结构精简开箱即用。目前已有207人学习下载适用于机房统考、课堂随测、期中期末模拟等多种教学场景。用户可直接部署运行获得试题库管理、自动阅卷与成绩统计、考试时间/题型/难度个性化设置、屏幕监控等防作弊机制以及基于答题数据的教学分析支持显著提升教学评估效率与公平性。1. 局域网考试系统终版一个不依赖公网、5分钟能跑通、支持百人并发的离线考试闭环某高校期末前一周教务老师突然接到通知机房网络策略收紧所有外网访问被禁——而原定用的在线考试平台恰好依赖云服务鉴权和实时题库同步。临时切换方案来不及了。这时候有人从U盘里掏出一个叫“局域网考试系统终版”的压缩包双击start_server.bat打开浏览器输入http://192.168.1.100:808030秒内完成教师端登录学生用任意电脑连同一Wi-Fi打开同地址扫码入场——整场120人《数据结构》随堂测验零掉线、零超时、零提交失败。这不是Demo是真实压测过327台虚拟机的终版交付物。它不碰外网、不装数据库服务、不改路由器配置核心逻辑全在单机Python进程里跑教师出题用Excel拖拽导入学生答题后自动批改生成PDF成绩单。如果你正被“离线”“局域网”“快速部署”“防作弊基础能力”这几个词卡住这个项目就是为你写的——它不是教学玩具而是从考场血泪经验里熬出来的最小可行考试闭环。2. 技术栈与架构设计为什么选FlaskSQLiteZeroMQ而不是Django或Vue全家桶这个系统没上K8s没配Nginx反向代理甚至没写一行TypeScript。它的技术选型不是“落后”而是对局域网场景的精准克制带宽窄千兆交换机但学生机多为百兆网口、终端杂Win7到Win11、Chrome到Edge旧版、运维零人力教师自己双击启动。我们拆开requirements.txt和app/目录看它怎么用最轻量的组合扛住真实压力。2.1 核心服务分层三层解耦每层可独立替换整个系统按职责切为三个进程通过本地IPC通信避免单体臃肿Web服务层Flask只做HTTP路由、模板渲染、静态资源托管。所有业务逻辑剥离仅调用core.api模块的函数。关键点app.py中禁用debugTrue且threadedFalse——因为后续要加ZeroMQ消息队列多线程会引发socket竞争。业务逻辑层纯Python模块位于core/下含exam_manager.py试卷生命周期、student_session.py考生状态机、auto_grader.py客观题规则引擎。这里没有ORM所有数据操作直连SQLite用sqlite3.Connection手动管理事务避免SQLAlchemy的连接池在局域网高并发下的锁等待。通信协调层ZeroMQ PUB/SUB这是“终版”区别于初版的关键。初版用文件轮询监听学生交卷延迟高达8秒终版改用zmq.PUB由Web层广播“新试卷发布”zmq.SUB在批改进程里订阅实现毫秒级事件驱动。ZeroMQ不依赖中心broker适合无服务器环境——这点常被忽略但恰恰是局域网部署的隐形刚需。提示ZeroMQ的tcp://*:5555绑定地址必须显式设为127.0.0.1而非*否则Windows防火墙会弹窗拦截。这是第1个踩坑点后面会集中讲。2.2 数据持久化SQLite不是凑合而是刻意为之很多人看到SQLite就皱眉觉得“不专业”。但在本项目里它解决了三个硬需求零安装依赖学生机不用装MySQL服务教师机不用配root密码。db/exam.db是单文件备份就是复制这个文件。ACID保障足够考试场景本质是“写少读多”——教师创建试卷写1次、学生答题写1次、教师导出成绩读1次。SQLite的WAL模式在100并发写入下平均延迟12ms实测数据见docs/benchmark.md。迁移成本归零migrations/目录下只有两个SQL文件001_init.sql建表002_add_lock_field.sql加防重复提交字段。没有Alembic没有版本号管理——因为终版定义里“升级”“覆盖整个文件夹”。来看core/db.py里最关键的连接封装import sqlite3 from contextlib import contextmanager DB_PATH db/exam.db contextmanager def get_db_connection(): conn sqlite3.connect(DB_PATH, timeout20.0) # 关键timeout设为20秒防锁死 conn.row_factory sqlite3.Row # 支持字典式取值row[id] 而非 row[0] try: yield conn conn.commit() except Exception: conn.rollback() raise finally: conn.close()这段代码藏着两个血泪经验第一timeout20.0不是拍脑袋——局域网下SQLite默认2秒超时但学生同时点击“交卷”时写锁堆积会导致部分请求直接报OperationalError: database is locked第二row_factory设为sqlite3.Row让后续所有查询结果可像字典一样用字段名取值省去硬编码索引的维护成本。你改表结构时业务代码不用动row[2]这种玄学数字。2.3 静态资源与前端逻辑为什么用原生JSBootstrap 4static/目录下没有Webpack配置没有node_modules只有js/main.js和css/bootstrap.min.css。原因很实在学生机可能禁用JavaScript执行权限或浏览器版本太老如IE11而Bootstrap 4的CSS兼容性覆盖到IE10JS组件则全部阉割只留最简交互。比如学生答题页的“倒计时”功能不用setInterval而是靠服务端每次渲染时注入当前剩余秒数!-- templates/student/exam.html -- div idcountdown># Windows PowerShell管理员权限 PS C:\ python -m venv exam_env PS C:\ exam_env\Scripts\activate.ps1 # 若提示执行策略受限先运行 Set-ExecutionPolicy RemoteSigned -Scope CurrentUser (exam_env) PS C:\ pip install -r requirements.txt注意requirements.txt里明确锁死了Flask2.0.3而非Flask2.0。这是因Flask 2.1引入了异步视图而本系统的auto_grader.py有阻塞IO操作读取学生上传的代码文件并执行沙箱检测异步化反而导致CPU占用飙升。这个版本锁定是压测后确定的稳定点。3.2 初始化数据库与默认账号首次运行前必须初始化数据库并创建管理员账号。系统提供init_db.py脚本它会创建db/目录若不存在执行migrations/001_init.sql插入默认教师账号admin/123456(exam_env) PS C:\exam_system python init_db.py ✅ 数据库初始化成功 ✅ 默认管理员账号已创建admin / 123456 ✅ 请用此账号登录 http://localhost:8080/admin提示脚本会检查db/exam.db是否存在若存在则跳过初始化避免误覆盖已有考试数据。这是安全底线不能绕过。3.3 启动服务双击bat还是命令行推荐方式是双击start_server.bat内容极其简单echo off cd /d %~dp0 call exam_env\Scripts\activate.bat python app.py pause它做了三件事切换到项目根目录、激活虚拟环境、启动主程序。pause是为了防止窗口闪退——方便教师看到启动成功的日志。如果你用命令行等价命令是(exam_env) PS C:\exam_system python app.py * Serving Flask app app (lazy loading) * Environment: production * Debug mode: off * Running on http://127.0.0.1:8080 (Press CTRLC to quit)关键点来了Running on http://127.0.0.1:8080意味着服务只监听本机回环地址。学生机打不开必须改成局域网可访问地址(exam_env) PS C:\exam_system python app.py --host 0.0.0.0 --port 8080或者更稳妥地在app.py顶部修改if __name__ __main__: app.run(host0.0.0.0, port8080, threadedFalse) # ← 显式指定host改完这行学生机才能用http://192.168.1.100:8080访问。这是新手最高频的“打不开”问题根源。3.4 教师端操作三步创建一场考试登录浏览器打开http://192.168.1.100:8080/admin用admin/123456登录导入试题点击【题库管理】→【Excel导入】上传符合模板的.xlsx文件模板在docs/exam_template.xlsx模板要求A列题干、B列选项A、C列选项B...、H列正确答案如A,C、I列分值系统会校验空题干、重复题号失败时返回具体行号错误发布考试点击【考试管理】→【新建考试】填写名称、时长、允许进入时间范围勾选刚导入的题目 → 【发布】发布后页面自动跳转到“考试详情”显示二维码。学生用微信“扫一扫”即可入场——二维码本质是/student/join?exam_idabc123的短链接服务端生成时已加密签名防伪造。3.5 学生端体验无感接入强制交卷学生无需注册扫码后进入/student/exam/abc123页面页面顶部显示倒计时服务端注入的ISO时间单选题用Radio按钮多选题用Checkbox填空题用input typetext提交按钮灰色不可点直到所有必答题完成前端JS校验防漏答倒计时归零时自动触发document.getElementById(submit-btn).click()无需学生操作交卷后页面显示“提交成功”并给出预计批改时间通常3秒。这是因为客观题批改在内存中完成不走数据库写入——auto_grader.py拿到学生答案后直接比对题库中的correct_answer字段生成JSON结果再异步写入数据库。4. 常见问题排查5个真实翻车现场与解法这个系统在3所高校的机房实测过以下问题是教师反馈Top 5每个都附带现象、根因和一招解决。4.1 现象学生扫码后页面空白控制台报Failed to load resource: net::ERR_CONNECTION_REFUSED原因教师机防火墙阻止了8080端口入站。Windows Defender默认会拦截非微软签名程序的端口监听。解决打开“Windows安全中心”→“防火墙和网络保护”→“允许应用通过防火墙”点击“更改设置”勾选python.exe路径为C:\exam_system\exam_env\Scripts\python.exe的“专用”和“公用”重启服务注意不要关防火墙这是安全底线。只放行必要端口。4.2 现象教师导入Excel后提示“题干不能为空”但明明填了文字原因Excel单元格格式为“常规”但实际存储的是数值型如题干写了1Excel自动转成数字1导入时变成1.0字符串校验失败。解决选中题干列 → 右键“设置单元格格式” → “文本”重新输入题干或F2编辑后回车强制转文本保存为.xlsx再导入这是Excel的玄学不是代码Bug。建议在docs/里放一份已设好格式的空白模板。4.3 现象多名学生同时交卷时部分人收到“提交失败请重试”原因SQLite写锁竞争。当10学生在1秒内点击提交INSERT INTO submissions会排队超时后抛出OperationalError: database is locked。解决修改core/db.py中get_db_connection的timeout参数conn sqlite3.connect(DB_PATH, timeout30.0) # 从20秒提到30秒并在app.py的/submit路由里加重试逻辑已内置for _ in range(3): try: with get_db_connection() as conn: # 执行插入 break except sqlite3.OperationalError as e: if database is locked in str(e): time.sleep(0.5) # 退避0.5秒再试 else: raise4.4 现象学生答题页倒计时不准比教师端快/慢几分钟原因学生机系统时间与教师机不同步。服务端注入的是datetime.now(timezone.utc)但前端用new Date()解析时若学生机时区设为东八区会多加8小时。解决统一用UTC时间戳传递前端用Date.parse()解析div idcountdown>const endTimestamp parseInt(el.dataset.endTimestamp); // 直接传毫秒数 const endTime new Date(endTimestamp);4.5 现象考试结束后教师导出PDF成绩单为空白页原因reportlab库在无GUI环境如Windows Server Core下无法加载字体导致PDF生成失败静默返回空内容。解决下载simhei.ttf黑体到static/fonts/目录修改core/pdf_generator.py注册字体from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont pdfmetrics.registerFont(TTFont(SimHei, static/fonts/simhei.ttf))在PDF样式中指定styles[Normal].fontName SimHei5. 二次开发指南如何添加主观题批改、对接校园LDAP、定制防作弊策略“终版”不是终点而是你二次开发的起点。我参与过3个同类项目的落地发现80%的定制需求集中在三个方向主观题支持、身份认证升级、防作弊强化。下面给出可直接抄的代码片段和验证方法不讲理论只给能跑通的方案。5.1 添加主观题支持图片上传与教师人工批阅主观题的核心是“分离存储”和“状态机”。不能把学生上传的图片存进SQLiteBLOB太大也不能让教师在网页里直接打开大图卡顿。终版预留了uploads/目录和subjective/路由只需补全两处第一步修改数据库增加主观题表在migrations/003_add_subjective.sql中添加CREATE TABLE subjective_answers ( id INTEGER PRIMARY KEY AUTOINCREMENT, submission_id INTEGER NOT NULL, question_id INTEGER NOT NULL, image_path TEXT NOT NULL, -- 存相对路径如 uploads/20240520_102345.jpg score REAL DEFAULT NULL, -- 教师打分NULL表示未批阅 comment TEXT, -- 批注 FOREIGN KEY(submission_id) REFERENCES submissions(id) );然后运行python migrate.py该脚本已内置会按序号执行SQL文件。第二步学生端上传逻辑static/js/student.js// 当主观题区域失去焦点时触发上传 document.querySelectorAll(.subjective-input).forEach(el { el.addEventListener(blur, function() { const formData new FormData(); const fileInput this.nextElementSibling; // 假设input typefile紧邻textarea if (fileInput.files.length 0) { formData.append(image, fileInput.files[0]); formData.append(question_id, this.dataset.qid); fetch(/api/upload_subjective, { method: POST, body: formData }).then(r r.json()).then(data { if (data.success) { this.dataset.uploaded true; this.placeholder ✅ 已上传 data.filename; } }); } }); });第三步教师端批阅界面templates/admin/grade_subjective.html用Bootstrap Card网格展示待批阅题关键点是“预加载缩略图”{% for ans in pending_answers %} div classcard mb-3 img src/uploads/{{ ans.image_path }}?thumb1 classcard-img-top alt题{{ ans.question_id }} div classcard-body h6 classcard-title题 {{ ans.question_id }}/h6 p classcard-text{{ ans.student_name }} 的作答/p form action/admin/grade_subjective methodpost div classmb-3 label classform-label得分0-10/label input typenumber namescore classform-control min0 max10 step0.5 required /div div classmb-3 label classform-label评语/label textarea namecomment classform-control/textarea /div input typehidden nameanswer_id value{{ ans.id }} button typesubmit classbtn btn-primary提交批阅/button /form /div /div {% endfor %}验证方法用Postman发一个POST /api/upload_subjective带image文件和question_id检查uploads/下是否生成文件且数据库subjective_answers表有记录。这是主观题开发的最小闭环。5.2 对接校园LDAP用python-ldap替换本地账号很多学校已有统一身份认证要求学生用学号/密码登录。终版的auth.py模块已预留接口# core/auth.py def verify_credentials(username, password): 验证凭据先查本地失败则查LDAP if _verify_local(username, password): return True, local # ↓ 新增LDAP验证分支 ↓ try: import ldap conn ldap.initialize(ldap://ldap.school.edu.cn:389) conn.set_option(ldap.OPT_REFERRALS, 0) conn.simple_bind_s(fuid{username},oustudents,dcschool,dcedu,dccn, password) return True, ldap except ldap.INVALID_CREDENTIALS: return False, ldap except Exception as e: app.logger.error(fLDAP验证异常: {e}) return False, error关键配置在config.pyLDAP_SERVER ldap://ldap.school.edu.cn:389 LDAP_BASE_DN oustudents,dcschool,dcedu,dccn LDAP_BIND_TEMPLATE uid{username}, LDAP_BASE_DN注意python-ldap在Windows上编译困难建议用预编译wheelpip install python-ldap‑3.4.3‑cp39‑cp39‑win_amd64.whl从https://www.lfd.uci.edu/~gohlke/pythonlibs/ 下载。5.3 定制防作弊屏幕监控切屏检测的轻量实现终版不集成摄像头监控涉及隐私合规但提供了“切屏检测”钩子。原理是学生端每隔3秒发一次心跳携带document.hidden状态页面是否在后台// static/js/student.js setInterval(() { fetch(/api/heartbeat, { method: POST, headers: {Content-Type: application/json}, body: JSON.stringify({ exam_id: window.EXAM_ID, hidden: document.hidden, // true表示切屏 timestamp: Date.now() }) }); }, 3000);服务端app.py中接收并记录app.route(/api/heartbeat, methods[POST]) def heartbeat(): data request.get_json() # 记录到SQLite的session_log表需先建表 with get_db_connection() as conn: conn.execute( INSERT INTO session_log (exam_id, student_id, hidden, timestamp) VALUES (?, ?, ?, ?), (data[exam_id], session.get(student_id), data[hidden], data[timestamp]) ) return jsonify({status: ok})教师端可查/admin/session_log?exam_idabc123看到每个学生的切屏时间戳列表。这是低成本防作弊的“后悔药”——考后复盘时一眼看出谁频繁切屏。从那以后我每次给学校做考试系统交付都会强制走一遍“三机测试”一台教师机、一台学生机Chrome、一台学生机Edge旧版全程不碰外网只用手机热点组局域网。这能暴露90%的兼容性问题。希望帮到你。本文还有配套的精品资源点击获取