简介:这是一套面向教学评估与考试组织场景的局域网考试系统完整源码包,适合高校教师、教务管理人员及.NET开发者研究部署与二次定制。系统采用B/S架构,覆盖试题库管理、组卷策略、考生信息维护、自动阅卷、成绩统计与防作弊等核心模块,并包含模拟考试、错题集等拓展功能,可满足局域网内练习与正式测试的双重需求。压缩包共84个文件,约4.08MB,以cs源码、resx资源文件、exe可执行程序、msi安装包为主,辅以ini配置、mdb数据库、sln解决方案及图标素材,服务端与客户端结构分明,便于按模块阅读与调试。目前已有450人学习下载。通过源码可深入理解考试流程控制、组卷算法与成绩管理实现,是学习WinForm项目架构与教育信息化系统开发的实用参考。
1. 局域网考试系统:断网环境下的考场,为什么反而更考验架构
机房断网、Wi-Fi 关闭、外网出口封死,监考老师拿着一台笔记本当服务器,五十台学生机通过交换机连进来答题——这是局域网考试系统最典型的落地场景。它要解决的核心问题不是「怎么把卷子显示出来」,而是断网条件下如何保证试卷下发不丢、答案回收不重、交卷时间统一、断电后还能恢复。适合谁用?学校机房管理员、企业内训考核负责人、认证机构考务实施人员,以及需要在内网做无纸化测评的团队。很多人以为局域网考试系统就是把 Web 应用搬到内网跑,真正做过才知道,网络隔离带来的时钟同步、并发写入、断线重连、数据一致性才是决定成败的地方。这篇笔记按「先立架构、再动手部署、最后排坑」的顺序,把一套可复现的方案讲清楚。
2. 局域网考试系统的架构选型:B/S 还是 C/S,数据库怎么定
2.1 三种常见架构的取舍
局域网考试系统的架构选择,直接决定了后面部署和排障的难度。常见做法有三类:纯 B/S(浏览器访问)、C/S(客户端安装)、以及 B/S 加本地缓存的混合模式。纯 B/S 部署最省事,学生机只要浏览器打开服务器 IP 就能答题,但浏览器在断线时的行为不可控,刷新即丢答案;C/S 客户端可以本地落盘,断线重连后补传,但每台机器都要装,版本更新麻烦;混合模式是现在比较稳的方案——浏览器负责渲染,前端用 IndexedDB 或 localStorage 做本地暂存,定时向服务器同步。
我一般会推荐混合模式,理由很直接:机房网络虽然叫「局域网」,但交换机抖动、网线松动、学生误拔线的情况一点都不少。纯 B/S 一旦断线,学生答了半小时的题可能全没了,这是考务事故。混合模式让答案先落本地,再异步上传,把「网络可用性」和「答题连续性」解耦。
选型时还要考虑并发规模。五十人以内,单台服务器加 SQLite 都能扛;一百到三百人,建议 MySQL 或 PostgreSQL;超过五百人同时在线,就要考虑读写分离或者把答题写入做成队列。别小看这个数字,考试的特点是「同一秒所有人都在提交」,瞬时写入压力比日常业务高得多。
2.2 数据库与时钟同步的硬性要求
局域网考试系统对数据库的要求,和普通 Web 应用不太一样。核心是两点:写入要能扛住瞬时并发,时间字段要能作为判卷依据。我一般用 MySQL 8 的 InnoDB,答题记录表按exam_id + student_id建联合索引,提交时用INSERT ... ON DUPLICATE KEY UPDATE保证同一学生重复提交不会产生脏数据。
时钟同步是容易被忽略的坑。局域网里服务器和学生机的系统时间可能差几分钟,如果交卷时间以客户端时间为准,就会出现「有人提前交、有人超时还在答」的争议。正确做法是:所有时间判断以服务器时间为准,客户端只负责显示倒计时,每次心跳同步一次剩余时间。
-- 答题记录表:用唯一键保证同一学生同一题只有一条有效记录 CREATE TABLE answer_record ( id BIGINT AUTO_INCREMENT PRIMARY KEY, exam_id INT NOT NULL, student_id VARCHAR(32) NOT NULL, question_id INT NOT NULL, answer TEXT, client_ts DATETIME, -- 客户端提交时间,仅作参考 server_ts DATETIME DEFAULT CURRENT_TIMESTAMP, -- 服务器落库时间,判卷依据 synced TINYINT DEFAULT 0, -- 0=本地暂存待同步,1=已入库 UNIQUE KEY uk_stu_q (exam_id, student_id, question_id), KEY idx_exam_stu (exam_id, student_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这段建表语句的关键在UNIQUE KEY uk_stu_q,它让重复提交变成更新而不是插入,避免断线重连后同一题出现两条记录。server_ts用数据库默认值生成,不信任客户端传上来的时间。synced字段配合前端本地缓存使用,标记哪些答案还没成功上传。
参数上,student_id用 VARCHAR 而不是 INT,是因为学号常带字母前缀;answer用 TEXT 是为了兼容多选、填空、简答等题型。如果考试规模大,可以把answer_record按exam_id做分区,减少单表体积。
2.3 部署拓扑与最小可跑环境
一套能跑起来的局域网考试系统,最小拓扑是:一台服务器(可以是普通 PC 或工控机)、一台交换机、若干学生机。服务器装 Nginx + 应用服务 + 数据库,学生机只需要浏览器。如果做混合模式,前端静态资源由 Nginx 直接托管,答题接口走应用服务。
部署时我习惯把服务器 IP 固定,比如192.168.1.100,学生机通过这个地址访问。DHCP 也可以,但考场环境里固定 IP 更省心,避免 IP 变化导致学生找不到入口。下面是一个 Nginx 配置片段,用于托管前端并反代答题接口:
server { listen 80; server_name 192.168.1.100; # 前端静态资源 location / { root /opt/exam/web; index index.html; try_files $uri $uri/ /index.html; } # 答题接口反代到后端 location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_read_timeout 30s; # 交卷高峰适当调大 } }try_files那行是为了支持前端路由,刷新页面不会 404。proxy_read_timeout默认 60s,考试交卷瞬间请求集中,如果后端处理慢,超时会导致学生看到「提交失败」,所以这里显式设置并配合后端异步落库。root指向的目录要提前放好构建产物,权限设为 Nginx 用户可读。
3. 局域网考试系统怎么部署:从零到学生能答题的完整步骤
3.1 服务器环境准备与依赖安装
先在服务器上装好基础环境。以 Ubuntu 22.04 为例,装 JDK 或 Node 运行时、MySQL、Nginx。假设后端用 Spring Boot 打包成 jar,前端是 Vue 构建的静态文件。命令按顺序执行:
# 更新源并安装基础组件 sudo apt update sudo apt install -y openjdk-17-jre mysql-server nginx # 启动并设置开机自启 sudo systemctl enable --now mysql nginx # 创建数据库和专用账号 sudo mysql -e "CREATE DATABASE exam_db DEFAULT CHARSET utf8mb4;" sudo mysql -e "CREATE USER 'exam'@'localhost' IDENTIFIED BY 'Exam@2024';" sudo mysql -e "GRANT ALL ON exam_db.* TO 'exam'@'localhost';" sudo mysql -e "FLUSH PRIVILEGES;"装完先别急着启动应用,确认 MySQL 和 Nginx 都在跑:systemctl status mysql和systemctl status nginx。数据库账号密码不要用 root,考试系统虽然在内网,但学生机数量多,弱口令被扫的风险依然存在。Exam@2024只是示例,实际部署换成强密码并记录在考务手册里。
3.2 应用配置与启动参数
后端应用的配置文件里,重点是数据库连接、端口、以及考试相关的业务参数。下面是一个application.yml的核心片段:
server: port: 8080 tomcat: max-threads: 200 # 交卷高峰线程数,按考生数 1.5 倍设 accept-count: 300 # 等待队列 spring: datasource: url: jdbc:mysql://127.0.0.1:3306/exam_db?useSSL=false&serverTimezone=Asia/Shanghai username: exam password: Exam@2024 hikari: maximum-pool-size: 50 # 连接池上限,别超过 MySQL max_connections connection-timeout: 3000 exam: duration-minutes: 90 # 考试时长 heartbeat-seconds: 10 # 客户端心跳间隔 autosave-seconds: 15 # 本地暂存间隔 allow-reconnect: true # 允许断线重连max-threads和maximum-pool-size要匹配。如果考生 100 人,Tomcat 线程 200 够用,但连接池 50 意味着同时只有 50 个请求能拿到数据库连接,其余排队。交卷时如果每人都触发一次写库,排队时间会拉长。稳妥做法是把交卷写入做成异步:接口收到请求先返回「已接收」,后台线程池慢慢落库。
heartbeat-seconds设 10 秒,是为了让服务器能及时发现掉线学生,同时不至于心跳包太频繁压垮网络。autosave-seconds设 15 秒,是本地暂存的节奏,断线时最多丢 15 秒的答案,这个损失考务上可以接受。
3.3 学生机接入与答题流程验证
服务器起来后,用一台学生机做全流程验证。浏览器打开http://192.168.1.100,登录、选题、答题、交卷,每一步都确认。重点验证三个场景:正常交卷、答题中断网再恢复、以及服务器重启后学生能否继续。
断网恢复的验证方法:答题到一半,拔掉学生机网线,继续答几题,再插回网线。观察前端是否提示「离线暂存」,恢复后是否自动补传。这个流程能跑通,说明混合模式生效了。服务器重启验证:考试中途重启后端服务,学生端应该显示「连接中断,正在重连」,服务恢复后自动续上,已答内容不丢。
// 前端本地暂存与同步的核心逻辑 const AUTOSAVE_KEY = 'exam_answers'; // 每 15 秒把当前答案写入 localStorage setInterval(() => { const answers = collectAnswers(); // 从页面收集当前答案 localStorage.setItem(AUTOSAVE_KEY, JSON.stringify({ examId: EXAM_ID, studentId: STUDENT_ID, data: answers, ts: Date.now() })); }, 15000); // 心跳:每 10 秒向服务器同步一次,顺便拉取剩余时间 setInterval(async () => { try { const res = await fetch('/api/heartbeat', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({examId: EXAM_ID, studentId: STUDENT_ID}) }); const {remainSeconds} = await res.json(); updateCountdown(remainSeconds); // 以服务器时间为准刷新倒计时 await flushLocalAnswers(); // 把本地暂存补传上去 } catch (e) { showOfflineTip(); // 网络异常提示,不阻断答题 } }, 10000);这段代码的关键是「本地暂存」和「心跳补传」分离。暂存不依赖网络,心跳失败也不影响答题,只是提示离线。flushLocalAnswers在心跳成功时把 localStorage 里的答案批量提交,服务端用前面说的唯一键做幂等,重复提交不会出错。updateCountdown用服务器返回的剩余秒数刷新,避免客户端时间被篡改。
4. 局域网考试系统避坑:五个真实翻车现场
4.1 交卷瞬间服务器 502,学生以为没交上
现象:考试结束前五分钟,大量学生点击交卷,页面返回 502,学生反复点击,考务群炸锅。原因:交卷接口同步写库,瞬时并发超过 Tomcat 线程和连接池上限,请求堆积后 Nginx 超时返回 502。解决:把交卷改成异步——接口只做参数校验和入队,立即返回「已接收」,后台消费者慢慢落库;同时前端在收到「已接收」后锁定交卷按钮,禁止重复点击。Nginx 的proxy_read_timeout调到 30s 以上,给排队留时间。
4.2 断线重连后答案重复,判卷出现两条记录
现象:学生断网后重连,同一道题在数据库里有两条记录,判卷时分数算重。原因:前端补传时用了INSERT而不是幂等更新,或者唯一键没建对。解决:答题表必须建(exam_id, student_id, question_id)唯一键,提交语句用INSERT ... ON DUPLICATE KEY UPDATE。前端补传前先拉一次服务器已有答案,做本地合并,减少无效请求。
4.3 倒计时不准,有人多答了十分钟
现象:考试结束,有学生说自己的倒计时比别人的慢,多答了题。原因:倒计时用客户端setInterval累加,浏览器切到后台标签页时定时器被节流,时间走慢。解决:倒计时不用累加,每次心跳用服务器返回的remainSeconds直接覆盖显示;页面visibilitychange事件触发时立即拉一次服务器时间。服务器端判卷以server_ts为准,超时提交的答案标记为无效。
4.4 服务器 IP 变了,全场学生找不到入口
现象:考场临时换了网段,服务器 IP 从192.168.1.100变成192.168.0.100,学生机还开着旧地址,全部白屏。原因:服务器用 DHCP 获取 IP,或者考务人员手动改网络没通知。解决:服务器必须配静态 IP,并在交换机上做 IP-MAC 绑定;学生机入口用短域名或 hosts 解析,比如exam.local,IP 变了只改一处。考前用一台样机全流程验证,别等开考才发现。
4.5 数据库磁盘满,考试中途写入失败
现象:考试进行到一半,学生提交答案报错,后台日志显示Disk full。原因:MySQL 的 binlog 和临时文件把磁盘占满,或者答题记录表没做清理,历史数据堆积。解决:考前检查磁盘剩余空间,至少留 20%;binlog 设置过期时间expire_logs_days=7;答题记录按考试场次归档,考完导出后清理。监控脚本在磁盘低于 15% 时告警,别等写不进去才发现。
5. 局域网考试系统的压测与验收:开考前怎么确认它扛得住
压测是局域网考试系统上线前最值得花时间的一步。我一般用wrk或JMeter模拟并发交卷,重点看三个指标:交卷接口的 P99 延迟、数据库连接池等待时间、以及服务器 CPU 和内存。下面是一个用wrk压测交卷接口的示例:
# 模拟 100 并发,持续 60 秒,POST 交卷请求 wrk -t4 -c100 -d60s -s submit.lua http://192.168.1.100/api/submit # submit.lua 内容:构造交卷请求体 # wrk.method = "POST" # wrk.body = '{"examId":1,"studentId":"S001","answers":[...]}' # wrk.headers["Content-Type"] = "application/json"-t4是 4 个线程,-c100是 100 个并发连接,-d60s持续 60 秒。看输出里的Latency和Requests/sec,如果 P99 超过 2 秒,说明后端处理不过来,要加线程或改异步。压测数据用真实题量构造,别用空请求,否则测不出数据库写入的压力。
验收清单我习惯列成表格,考前逐项打勾:
| 检查项 | 合格标准 | 验证方法 |
|---|---|---|
| 交卷 P99 延迟 | < 2 秒 | wrk 压测 100 并发 |
| 断线重连 | 答案不丢不重 | 拔网线答题再插回 |
| 倒计时准确性 | 与服务器差 < 1 秒 | 对比服务器时间 |
| 磁盘剩余 | > 20% | df -h |
| 数据库连接池 | 无等待超时 | 压测时看日志 |
| 服务器重启恢复 | 学生可继续答题 | 中途重启后端 |
压测通过不代表万事大吉,考场环境还有变量:交换机性能、网线质量、学生机浏览器版本。我的习惯是考前一天用真实机房做一次全流程演练,让几位老师当「学生」走一遍,把问题暴露在开考前。这套方案值不值得做?如果每年有多次考试需求,一次投入能把考务从「收卷、判卷、登分」里解放出来,答案是值得。但别指望部署完就不管,考前检查和压测是必须保留的动作,这是我踩过坑之后养成的习惯。希望帮到你。
本文还有配套的精品资源,点击获取