尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
局域网考试系统终版:离线内网200人稳定考试全方案
简介局域网考试系统终版是一套面向学校与培训机构的软件化考试方案依托局域网环境实现出题、作答、阅卷与成绩统计的全流程管理适合教师用于课堂测验、校级统考或技能考核。压缩包约7.58MB内含安装程序和使用手册部署简单无需复杂配置即可在机房或校内网络中使用。系统提供试题库管理与组卷、客观题自动阅卷、考试过程防作弊监控、成绩数据统计分析等功能并支持考试时间、难度、题型等个性化设置由于运行在相对封闭的局域网内抗外部干扰能力强可有效保障考试公平性。已有206人学习/下载对希望快速搭建电子考试环境、提升考核效率的教师和教学管理者而言是一份可直接投入使用的工具型资源。1. 局域网考试系统终版断网机房怎么稳定跑完一场两百人的考试把一场两百人的考试搬进机房局域网最怕的不是有人瞄邻座屏幕而是考到一半服务端把答卷请求全拒了。局域网考试系统终版讲的就是一套完全跑在离线内网里的考试方案出题、下发、作答、自动收卷、阅卷、成绩导出全程不依赖外网。它解决的是机房断网、公有云考试平台进不来、学生端浏览器版本参差这三类最现实的问题适合学校机房、企业认证培训这类固定场地。你不用买服务器一台普通 PC 就能扛一场考试前提是架构选对、参数调稳再把几个高频翻车点提前堵死——这正是下面要展开的。2. 选型与骨架为什么浏览器答题端比装客户端更省事2.1 B/S 与 C/S 的取舍先回答三个问题再动手做局域网考试系统第一个分岔路就是答题端长什么样。常见做法是 B/S 架构学生用浏览器打开http://服务器IP:8080就能考原因很直接机房机器常年不更新让每台机器装客户端光是版本不一致就能耗掉半天浏览器天然跨平台Windows、国产 Linux 发行版、macOS 都能跑你只需要在开考前确认浏览器内核版本。C/S 客户端的优势在反作弊——能截屏、锁剪贴板、检测考试无关进程但代价是每次升级都要重装出兼容问题还得逐台排障。我判断选谁就看三个问题学生机配置是否统一、有没有专人维护客户端、反作弊要求是不是硬性。某培训中心早期坚持做桌面客户端版本装机装到第三排就开始有机器打不开考试当天变成逐台救火最后退回浏览器方案白白损失一场模拟考的成本。除非你确实需要进程级监控否则我建议优先 B/S把省下的精力放在服务端稳定性上。B/S 也不是没有前置条件。机房老机器如果还跑着陈旧 IE 内核localStorage、WebSocket、fetch 这些接口通通不靠谱。上线前我用一个静态探测页访问一个 JS 文件能弹出版本标识就算过弹不出来的机器要么换内核要么换浏览器。这个动作看起来琐碎却是避坑的第一道闸。2.2 服务端技术组合FastAPI SQLite 的边界在哪单机内网场景服务端没必要上微服务更没必要绕开数据库自己做文件存储。我一般会选 FastAPI自带接口文档异步支持好内网单机部署简单。存储选型可以直接用下表对号入座。存储方案适用规模并发写部署成本关键注意点SQLite开 WAL单场 200 人以内串行写读并发没问题零随应用走必须开 WAL写操作集中管理MySQL / MariaDB300 人以上或长期多场考试连接池可调多一个服务进程调 max_connections写库统一走事务Redis只做状态缓存和锁高多一个服务进程考试核心数据别放它掉电就丢如果预计单场不超过两个班SQLite 是最省心的选择备份就是拷一个文件。超过三百人的联考我会直接上 MySQL省得并发写锁问题在考试当天爆雷。Redis 我只拿它存在线状态和分布式锁答卷数据必须落 SQLite 或 MySQL这是底线。服务端骨架先跑起来核心是登录发令牌和上报服务器时间# app.py —— 局域网考试系统服务端骨架FastAPI from fastapi import FastAPI, HTTPException import sqlite3, uuid, time app FastAPI() DB exam.db def get_conn(): conn sqlite3.connect(DB, timeout5) conn.row_factory sqlite3.Row return conn app.post(/api/login) def login(student_no: str, exam_id: str): token uuid.uuid4().hex # 每个考生每次登录一个随机令牌 with get_conn() as conn: conn.execute( INSERT INTO session(token, student_no, exam_id, created_at ) VALUES(?,?,?,datetime(now)), (token, student_no, exam_id)) conn.commit() return {token: token, server_time: int(time.time())}登录接口一次性做三件事生成唯一令牌、记录会话、把服务器当前时间戳返回给前端。server_time是后面倒计时校准的基准前端所有时间计算都围绕它展开而不是读本机时钟。令牌要跟具体考试场次绑定后续每次提交都带它服务端才能定位到人。2.3 最小可跑通一台 PC 部署到内网的三条命令服务端写好后部署环节我习惯固定成一套命令避免每次考前临场发挥# 内网部署Ubuntu/Debian 系已装 python3-venv python3 -m venv venv source venv/bin/activate pip install fastapi uvicorn nohup uvicorn app:app --host 0.0.0.0 --port 8080 exam.log 21 hostname -I # 打印本机内网 IP把地址发给学生这里有几个必须讲清的参数。--host 0.0.0.0表示监听所有网卡只写 127.0.0.1 学生根本连不上--port 8080避开 80 端口可能的权限和占用问题nohup保证关闭终端后服务不退出日志重定向到exam.log出问题先看这个文件。hostname -I拿到的是内网地址比如192.168.1.20学生端访问http://192.168.1.20:8080即可教师端也是同一个地址。部署完要做一次本机自测先curl http://127.0.0.1:8080/api/login看有没有响应再用另一台机器访问内网 IP。连不通时八成是防火墙拦了 8080 端口我踩过两次这个坑后来直接把放行命令写进部署脚本一次到位。3. 核心功能实现组卷、提交、收卷与在线状态的代码级拆解3.1 题库与答卷表设计一份支撑乱序组卷的建表 SQL考试系统最核心的不是接口而是表结构。题目、答卷、会话三张表互相咬合字段设计不好后期加功能就是灾难。我常用的建表语句如下-- 题库表一道题一行客观题答案直接落库 CREATE TABLE question ( id INTEGER PRIMARY KEY AUTOINCREMENT, exam_id INTEGER NOT NULL, type TEXT CHECK(type IN (single,multi,judge,subjective)), content TEXT NOT NULL, -- 题干 options TEXT, -- 选项JSON 字符串 answer TEXT, -- 客观题标准答案主观题留空 score REAL NOT NULL DEFAULT 2 ); -- 答卷表每个考生一份题目和答案都存 JSON CREATE TABLE paper ( id INTEGER PRIMARY KEY AUTOINCREMENT, exam_id INTEGER NOT NULL, student_no TEXT NOT NULL, questions TEXT NOT NULL, -- 组卷后固定题目 JSON含乱序 answers TEXT, -- 作答 JSON按题号存 status TEXT DEFAULT doing, -- doing / submitted / graded started_at TEXT, submit_at TEXT );两个设计细节值得说明。questions字段在组卷时就把该考生这份卷子固定下来包含题目顺序和选项顺序的乱序结果这样考生中途断网重进拿到的还是同一份卷子不会出现刷新后题号对不上的问题。answers按题号存 JSON后续自动阅卷直接遍历不需要再做复杂的关联查询。组卷时用「考生号 场次号」做随机种子保证同一考生重复进入时卷面稳定# build_paper.py —— 稳定乱序组卷 from random import Random def build_paper(exam_id, student_no, question_ids): rng Random(f{exam_id}-{student_no}) # 种子固定重进考场卷面不变 shuffled question_ids.copy() rng.shuffle(shuffled) return shuffledRandom(f{exam_id}-{student_no})是关键同样的输入永远得到同样的乱序结果。如果不加种子每次刷新都重新洗牌学生答到一半刷新页面题号全变直接引发投诉。选项乱序也一样处理把每个选项的位置存进考生专属的 JSON 里。3.2 自动收卷逻辑以服务端时间为准不信任客户端时钟自动收卷最容易出问题的点是用前端倒计时归零来触发交卷。学生本机时间不准、调快调慢都是不可控变量。我的做法是服务端开考时算好结束时间戳交卷接口只认服务端时间# 收卷逻辑 —— 服务端时间判定超时新答案一律作废 import time END_TIME 1750000000 # 开考时由服务端写入秒级时间戳 app.post(/api/submit) def submit(payload: dict): token payload[token] answers payload[answers] # 前端传全量答案服务端做增量合并 if time.time() END_TIME: answers {} # 超时后仅保留已保存内容 save_answers(token, answers) return {ok: True, saved: len(answers)}注意这里前端传的是全量答案但服务端不能直接覆盖要做增量合并按题号读取旧值只更新本次传来的题目last_write_time大的优先保留。这样断网重连后的补交不会把之前已答的题冲掉。answers {}那行是超时兜底代表服务端拒绝接收超时新答案但已保存的作答还在。前端倒计时只是展示层我让它每秒减一同时每 30 秒用一次轻量接口校准与服务器的偏移。学生看到的时间和服务端判定时间允许有几秒误差但超过十秒就会引发「我明明还没交卷系统显示已结束」的投诉所以校准不能省。3.3 心跳保活与切屏上报前端两个必需函数在线状态和切屏记录是局域网考试系统相对互联网考试少有的优势所有学生都在同一网段状态检测延迟低。前端我固定写两个函数// heartbeat.js —— 每 30 秒上报一次在线状态并做时钟偏移校准 function heartbeat() { fetch(/api/heartbeat, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ token: localStorage.getItem(exam_token) }) }).catch(() { /* 网络瞬断不处理靠重连机制兜底 */ }); // 切屏检测离开页面超过 3 秒记一次违规 document.addEventListener(visibilitychange, function () { if (document.hidden) { if (!window.__leave_start) window.__leave_start Date.now(); } else { if (window.__leave_start Date.now() - window.__leave_start 3000) { reportViolation(switch_window, Math.round((Date.now() - window.__leave_start) / 1000)); } window.__leave_start null; } }); } setInterval(heartbeat, 30000);心跳间隔 30 秒是平衡点太短会放大并发压力200 个学生就是每 30 秒 200 个请求太长则断线检测不及时。切屏判定用document.hidden而不是blur事件因为后者在点击浏览器外部区域时也会误触发。3 秒阈值是我用下来的经验值低于 3 秒的误切太多高于 5 秒又会漏掉真实切屏。违规上报走独立接口不能阻塞答题主流程。有些方案把切屏上报做成同步请求页面卡顿学生就有借口说「系统问题害我做不完」这类责任事故要坚决避免。4. 参数调优与压测并发、时钟误差与断网兜底三关4.1 SQLite 并发调优WAL、busy_timeout 与写串行化单场 200 人的考试交卷瞬间会同时涌进几十个写请求SQLite 默认配置在这种场景下必现database is locked。建库后立刻执行几条 PRAGMA# init_db.py —— SQLite 高并发必备的三个开关 def init_db(): with get_conn() as conn: conn.execute(PRAGMA journal_modeWAL) # 读写不互斥 conn.execute(PRAGMA busy_timeout5000) # 等待锁 5 秒 conn.execute(PRAGMA synchronousNORMAL) # 平衡安全与性能WAL 模式让读操作和写操作不再相互阻塞这在考试系统里非常关键学生不断保存答案写同时教师端刷新查看进度读如果不开 WAL读会把写堵死。busy_timeout5000表示拿不到写锁时最多等 5 秒而不是立刻报错。synchronousNORMAL是性能和安全的折中单纯追求稳妥可以保留 FULL但内网环境掉电概率低我选 NORMAL。SQLite 是单写者模型WAL 改善读并发写瓶颈依然存在。我会把所有写库操作收敛到一个全局锁里串行执行import threading write_lock threading.Lock() # 全局写锁所有写库走这同一把锁 def write_paper(token, answers): with write_lock: # 只有拿到锁的请求才能写库天然避免锁竞争 save_answers(token, answers)这个锁看起来是性能瓶颈实际是保护伞。交卷峰值 200 个写请求排队执行每个几毫秒总共也就一两秒远好过让 SQLite 自己处理锁冲突后抛错重试。超过 300 人的场次我直接换 MySQL不在 SQLite 上硬扛。4.2 压测看什么失败的请求数和 P95 延迟部署完成后用ab在局域网内做一轮交卷压测比任何代码审查都有效# 模拟 1000 次提交、100 并发观察失败率和延迟 ab -n 1000 -c 100 -p submit.json -T application/json \ http://127.0.0.1:8080/api/submit-n 1000是总请求数-c 100是并发数-p submit.json指定请求体里面放一份带令牌和答案的 JSON。压测结果我只盯三个指标Failed requests必须为 0Requests per second反映吞吐局域网内单机跑出 500 以上算合格Time per request看平均延迟超过 200 毫秒说明写锁或连接池有问题。压测请求体里的令牌要提前从数据库准备一批否则全部请求都会因为非法令牌被拒绝测出来的全是接口鉴权的开销而不是真实写库性能。这个细节我翻车过一次压测报告看起来正常实际根本没压到数据库。4.3 断网兜底草稿本地暂存与重连续传局域网也会断网Wi-Fi 掉线、交换机重启都经历过。断网期间不能让学生白答前端把答案实时写进 localStorage网络恢复后补交// draft.js —— 断网期间答案先落本地心跳恢复后补交 function saveDraft() { const draft gatherAnswers(); localStorage.setItem(exam_draft_ token, JSON.stringify(draft)); } window.addEventListener(beforeunload, saveDraft); async function resyncDraft() { const draft localStorage.getItem(exam_draft_ token); if (draft) { await fetch(/api/submit, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ token, answers: JSON.parse(draft) }) }); localStorage.removeItem(exam_draft_ token); // 确认落库再清 } }两个细节必须强调。localStorage容量约 5MB纯文本答案够用但如果主观题不限字数学生能写爆我会在题目配置里加上限比如每题最多 500 字。补交之后一定要等服务端返回成功再清除本地草稿否则服务端没写进去草稿就没了等于白答。教师端遇到大面积掉线时我在服务端加了「宽容收卷」开关延迟交卷时限默认 5 分钟防止个别学生因断网被误伤。5. 避坑清单局域网考试系统上线后最常见的 5 个翻车现场5.1 白屏事故心跳连接泄漏把服务端拖死现象开考十分钟后部分学生页面白屏刷新后恢复随后白屏人数越来越多服务端连接数随时间线性增长。原因有些方案用 WebSocket 做心跳且每个心跳周期新建连接不释放服务端文件句柄被耗光。用 fetch 短连接做心跳则不存在这个问题但长连接方案处理不当就是隐患。解决心跳统一改用短连接fetchWebSocket 只保留给「强制收卷」这类需要服务端主动推送的场景同时给 WebSocket 客户端的重连次数设上限超过五次就退化为轮询保证异常时降级而不是雪崩。5.2 交卷成功但成绩查不到被吞掉的事务现象学生端提示交卷成功教师端成绩表格里没有记录查数据库发现session有记录paper表为空或status还是doing。原因提交接口内部抛异常被宽泛的except捕获后仍然返回成功或者answers字段内容超长插入失败后异常被吞掉前端不知道真实结果。解决交卷接口必须返回数据库落库确认的凭证前端只认这个凭证才算成功。代码里严格区分事务边界异常路径必须回滚并返回 500try: save_answers(token, answers) conn.commit() return {ok: True, answer_id: answer_id} except Exception: conn.rollback() raise HTTPException(500, 保存失败请重试)我后来在paper表加了version字段每次保存递增教师端复核时发现版本号异常就能快速定位是哪一次保存出了问题。5.3 同一账号两台电脑同时登录旧 token 没失效现象学生用两台机器登录同一个考号后登录的机器把先登录的顶掉成绩互相覆盖答题时间错乱。原因登录接口只生成新 token没有让旧 token 失效服务端无法区分哪个会话是当前有效会话。解决新登录生成 token 前先把同student_no的所有旧会话置为失效并记录设备标识UPDATE session SET statuskicked WHERE student_no? AND statusactive; INSERT INTO session(token, student_no, exam_id, status) VALUES(?,?,?,active);同时限制同一账号只能存在一个active会话提交接口校验 token 状态非active直接拒绝。这个改动让我处理掉了考前最常见的账号共享问题。5.4 倒计时停在两分钟后台标签页定时器被节流现象学生切到其他标签页再回来倒计时停在两分钟不动继续答题后服务端却提示考试已经结束。原因浏览器为了省电对后台标签页的setInterval做节流最低降到每分钟一次倒计时被冻结而服务端收卷是照常进行的。解决前端倒计时可以继续用本地定时器但每 30 秒的心跳请求会带回应收的结束时间戳前端拿它校正显示误差开考弹窗明确提示「切走标签页倒计时可能不准后果自负」把预期管理做在前面判定是否超时永远以服务端为准前端只做展示。5.5 SQLite database is locked写锁风暴现象交卷高峰出现大量 500 错误日志里刷database is locked越接近结束时间报错越多。原因没开 WAL读操作长时间占用数据库文件写请求全部被拒或busy_timeout设置过短排队中的写请求直接放弃。解决按 4.1 的三条 PRAGMA 初始化数据库写操作统一走全局锁串行化压测阶段就要模拟交卷峰值而不是只测单用户保存。如果现场已经爆发这个错误临时扩容的办法是调大busy_timeout到 10000并把教师端的「实时刷新进度」功能改成 30 秒轮询减少读对写的干扰。6. 终版收尾技巧自动阅卷、盲评与一份答卷备份习惯终版和初版的分水岭不在功能多少而在考后能不能三分钟出成绩、一分钟复核异常。客观题自动阅卷没什么玄学就是逐题比对# grade.py —— 客观题判分多选策略可配 def grade(question, my_answer): if question[type] in (single, judge): return question[answer] my_answer if question[type] multi: return set(question[answer]) set(my_answer) return None # 主观题走人工盲评注意多选判分策略要提前定好全对才给分是最严策略漏选给一半分是宽松策略只改一行为issubset判断。我建议按考试性质选认证类考试用全对制培训类考试用漏选半分制并在试卷说明里写清楚考后改策略会引发连锁翻案。主观题盲评是这个系统的收尾亮点阅卷接口读答卷时不返回student_no只返回题目序号和作答内容评完之后再按paper_id回填分数。双评取均值、分差超过阈值自动进入三评这套逻辑跑在一张评分表上就能撑起百人规模的主观题阅卷。最后是血泪经验换来的两个习惯每场考试结束先把exam.db备份一份带日期的存档再把paper表导出成 JSON 一并留存阅卷系统只读取备份副本绝不直接在原始库上改。某次复核争议成绩时我靠备份库里每道题的保存时间戳还原了学生的作答轨迹比任何口头解释都有说服力。后来我把备份命令写进了收卷脚本每次考完自动执行再没人为这事在考后加班。这套方案我前后迭代了三轮每一轮都在解决真实考场里冒出来的问题希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

TinyCircuit-AI:在MCU上部署极简神经网络的工程实践

TinyCircuit-AI:在MCU上部署极简神经网络的工程实践

1. 项目缘起与核心定位 TinyCircuit-AI 这个名字,第一次看到的时候我脑子里蹦出来的是两个画面:一个是面包板上插着几根跳线的微型电路,另一个是跑在浏览器里那种轻量到几乎无感的推理引擎。后来跟几个做嵌入式和边缘计算的朋友聊了聊&#x…

📅 2026/10/15 16:43:31
STM32与HX711电子秤开发:从驱动移植到标定滤波的完整指南

STM32与HX711电子秤开发:从驱动移植到标定滤波的完整指南

简介:STM32与HX711组合的高精度电子秤工程源码,面向电子、嵌入式方向初学者及有称重项目需求的开发者。资源基于STM32F4系列实现HX711驱动,覆盖SCK/DOUT接口时序控制、数据读取与限幅平均滤波,并附带去皮和比例调整功能&#xff0…

📅 2026/10/15 16:43:31
S32K3xx CAN FD硬件邮箱过滤驱动开发实战

S32K3xx CAN FD硬件邮箱过滤驱动开发实战

1. 项目概述:为什么在S32K上手写CAN FD邮箱过滤驱动是件“不得不做”的事在汽车电子和工业控制领域摸爬滚打十多年,我经手过几十个基于NXP S32K系列MCU的项目——从车身域控制器原型到电池管理系统BMS的量产固件。每次遇到CAN FD通信需求,团队…

📅 2026/10/15 16:43:31
MORE NEWS

更多资讯

📰

重言式判别程序课程设计:从语法树到真值表算法的Python实现

简介:重言式判别程序课程设计是面向数据结构课程实践的小型项目资料,适合计算机及相关专业学生参考布尔表达式与二叉树算法。资源围绕重言式(逻辑恒等式)判别问题,讲解如何用二叉树表示逻辑表达式,并以后序…

📰

OpenCV模型训练实战:轻量级机器学习在边缘设备的落地

1. 这不是“调个库就能跑”的模型训练——OpenCV的模型训练到底在训什么?很多人看到“OpenCV模型训练”这个标题,第一反应是:OpenCV不是做图像处理的吗?它还能训练模型?是不是搞错了?是不是该用PyTorch或Te…

📰

数组与字符串题:架构师的底层建模能力训练场

1. 为什么架构师必须亲手写透数组和字符串题——不是考你背题,是考你建模的肌肉记忆“想当架构师,数据结构与算法不过关可不行”——这句话在技术圈被反复提起,但很多人误以为它只是HR筛简历的门槛标签,或者面试官用来“压一压应届…

📰

普元EOS7.5生成Restful接口实战指南

1. 项目概述:为什么一个老派企业级平台还在认真做Restful接口? “普元EOS7.5生成RestFul接口服务”——这个标题乍看像一句内部系统文档里的操作备注,甚至有点过时感。毕竟现在聊微服务、云原生、低代码,动辄就是Spring Cloud Al…

📰

如何读懂 LycheeMem:动作感知四级语义检索流水线源码级拆解指南

【免费下载链接】LycheeMem Lightweight Long-Term Memory for LLM Agents. 项目地址: https://gitcode.com/gh_mirrors/ly/LycheeMem 点击查看 免费下载 LycheeMem 是面向 LLM Agent 的轻量级长期记忆框架(Lightweight Long-Term Memory for LLM Agent…

📰

使用Dify做一个客服对话记录分析

windows中搭建dify,请见上一篇。本篇介绍使用Dify做一个客服对话记录分析工作流1.1 创建工作流首先,在 Dify 平台中创建一个新的工作流,命名为「客服对话记录分析」。创建完成后,工作流概览如下所示。工作流概览如下:1…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬