简介:局域网考试系统是一套可在本地网络部署的在线考试平台,主要面向需要内部测评、在线练习与成绩管理的学校、培训机构及企业。系统覆盖题库管理、组卷策略、自动批阅、考生信息管理、防作弊等核心环节,项目采用C#开发,并附带完整源码,便于开发者研究学习或按需定制。压缩包为rar格式,大小4.08MB,内含84个文件,包括C#源码(cs)、界面资源(resx)、可执行程序(exe)、安装程序(msi)、配置文件(ini)、数据库文件(mdb)及解决方案(sln)等,涵盖客户端与服务端两部分,目录结构清晰,方便直接编译运行或二次开发。目前已有451人学习下载,适合有一定.NET基础的学生或开发者,作为局域网考试系统的教学案例或毕业设计参考。通过这套源码,读者可以快速理解在线考试系统的整体架构、业务逻辑与数据交互方式,并在此基础上扩展模拟考试、错题集等功能。
1. 局域网考试系统到底是什么:断网也能考的考场方案
你负责过机房考试就会懂,外网在线考试最怕的不是考生不会,而是所有人的题目一起卡在白屏上。局域网考试系统把整套考试服务装进考场内部:一台普通PC做服务器,考生机用浏览器访问,题目、答案、成绩全部留在本网段,外网断了照常考。它解决的是稳定性问题——局域网内往返延迟只有几毫秒,几十个考生并发拉页面也不容易打爆;它也解决公平性问题——访问范围被物理网络圈住,外部代考和远程协助的入口被切掉。玩过 radmin lan 局域网联机的人都知道,局域网内延迟低到接近零,考试系统对延迟同样敏感,所以把服务端放进考场比依赖云端更务实。适合学校机房、企业技能认证、职业鉴定这类网络封闭但监考需求明确的场景。这篇按我踩过的坑,从选型讲到部署,再讲 5 个最常翻车的现场。
2. 局域网考试系统选型:B/S 还是 C/S,预算和防作弊要求说了算
2.1 B/S 架构:WAMP 服务器加浏览器就是最小可行系统
第一次搭建局域网考试系统,我建议直接上 B/S 架构。原因很直接:机房里的机器配置参差不齐,浏览器是每台机器都有的,部署成本最低。服务端用 WAMP 套件(Windows + Apache + MySQL + PHP)就能跑起来,一个压缩包装完,不用单独装数据库客户端。WAMP 服务器局域网访问配置好之后,考生机打开浏览器输入服务器 IP 就能进考场,也不用考虑跨平台兼容——Windows、Linux 客户端统一走浏览器。
B/S 架构还有一个隐藏优势:升级题目和考试配置只需要改服务器,客户端零维护。考试结束要换题库,直接改数据库;要调整考试时间,改一条记录就行。你不需要像 C/S 架构那样跑到每一台机器上更新客户端,这点在 60 台机器的机房里能省下整整一个下午。
但选 B/S 也要接受它的短板。最大问题是断网容错——如果教室交换机故障或者网线被踢掉,这台考生机就彻底掉线,已答题目如果没及时落库就会丢。所以用 B/S 方案时必须配心跳机制和答题实时保存,不能等到交卷才一次性提交。另外,如果考场要求极度严格的防作弊,比如需要锁死屏幕、禁用任务管理器、监控进程,B/S 能做到的很有限,浏览器沙箱把很多系统级操作挡住了。
2.2 C/S 架构:什么时候需要桌面客户端
C/S 架构(客户端/服务器)在两种场景下值得考虑。第一种是考试环境长期固定、网络条件极差,需要客户端本地缓存题目,断网后考生继续答题,网络恢复再同步成绩。第二种是有硬性防作弊要求,客户端可以抢占屏幕、禁用 USB、屏蔽 Alt+Tab、记录鼠标轨迹,这些能力浏览器拿不到。
C/S 的开发栈我见过两类。一类是 C# + WinForms 或 WPF,机房清一色 Windows 时维护成本低,部署用安装包静默安装就能推开。另一类是 Electron + Web 技术栈,界面用前端写,考试体验更现代,但打包体积大,旧机房电脑跑起来内存吃紧。选 C/S 之前要算一笔账:开发量大概是 B/S 的两到三倍,而且每次改动都要重新分发客户端,局域网传输文件比 U 盘拷贝靠谱,但版本管理乱七八糟的时候会非常痛苦。
如果只做客观题、总体量不超过几百人同时考,B/S 的性能完全够。只有当你要做现场编程题、需要锁屏、或者要求离线答题时,再考虑 C/S。预算有限就老实选 B/S,不必为了“看起来专业”去造客户端。
2.3 架构对比表与选型建议
| 对比项 | B/S 架构(WAMP + 浏览器) | C/S 架构(客户端 + 服务器) |
|---|---|---|
| 部署成本 | 低,只需装一台服务端 | 高,每台考生机要装客户端 |
| 客户端兼容性 | 浏览器搞定,不做依赖 | 要处理 Win7/Win10 兼容性 |
| 断网容错 | 弱,需要心跳 + 实时落库兜底 | 强,本地缓存可离线答题 |
| 防作弊能力 | 一般,能检测切屏但锁不住系统 | 强,可锁屏、禁 USB、禁任务管理器 |
| 开发与维护成本 | 低,改服务器即完成升级 | 高,版本迭代要重发客户端 |
我的选型结论很实际:第一次做局域网考试系统、团队没有现成客户端经验,直接用 B/S。先把考试流程跑通,后续有防作弊硬要求再给 B/S 套一层桌面壳(比如用 WebView 封装浏览器),这个俗称“B/S 的壳,C/S 的心”,升级时仍只动服务器。
3. 用 WAMP 搭局域网考试服务端:建表、抽题接口和断电续考的最小实现
3.1 环境准备:让 WAMP 服务器对局域网可见的三个开关
B/S 方案的服务端环境,我一般用 WAMP 套件。安装完成后,默认情况下 Apache 只监听本机回环地址,局域网考生机访问不到。要开放访问,需要处理三个位置。
第一,Apache 的 httpd.conf 里监听地址要写成Listen 0.0.0.0:80,而不是默认的Listen 127.0.0.1:80。第二,Windows 防火墙要放行 80 端口入站,否则局域网内其他机器 ping 得通但页面打不开。第三,MySQL 虽然只需要本机访问,但建议把max_connections从默认的 100 调大到 200,因为考生进场高峰时浏览器可能会发起多个连接。这三个配置看起来都是“常识”,但每次部署翻车,八成是漏了第二个。用管理身份在命令行执行:
netsh advfirewall firewall add rule name="WAMP_HTTP" dir=in action=allow protocol=TCP localport=80这条命令放行本机 80 端口,dir=in表示入站方向,localport=80指定端口。放行后可以用局域网内另一台机器访问http://服务器IP/验证。注意 Apache 的httpd.conf改完要重启服务,WAMP 里右键托盘图标选择 Restart All Services,改配置不重启等于白改。
3.2 数据库设计:四张核心表撑起一次完整考试
数据库我按最小可用来设计,四张表足够:考生表、题库表、考试场次表、答题明细表,外加一张心跳表用于断点续考。题库表里题目类型用数字区分,选项统一存成 JSON 字符串,答案字段区分单选和判断(单值)与多选(逗号分隔)。建表语句如下:
CREATE DATABASE exam_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE exam_system; CREATE TABLE tb_student ( student_id VARCHAR(20) PRIMARY KEY, student_name VARCHAR(50) NOT NULL, class_name VARCHAR(50) ); CREATE TABLE tb_question ( qid INT AUTO_INCREMENT PRIMARY KEY, qtype TINYINT NOT NULL COMMENT '1单选 2多选 3判断', content TEXT NOT NULL, options VARCHAR(500) COMMENT 'JSON数组,如["A","B","C","D"]', answer VARCHAR(50) NOT NULL COMMENT '多选用逗号分隔,如"A,B"', score DECIMAL(5,2) NOT NULL DEFAULT 2.00, enabled TINYINT DEFAULT 1 ); CREATE TABLE tb_exam_session ( session_id INT AUTO_INCREMENT PRIMARY KEY, session_name VARCHAR(100), start_time DATETIME, end_time DATETIME, status TINYINT DEFAULT 0 COMMENT '0未开始 1进行中 2已结束' ); CREATE TABLE tb_answer_log ( id INT AUTO_INCREMENT PRIMARY KEY, session_id INT NOT NULL, student_id VARCHAR(20) NOT NULL, qid INT NOT NULL, answer VARCHAR(50), is_correct TINYINT DEFAULT 0, earned_score DECIMAL(8,2) DEFAULT 0, submit_time DATETIME, UNIQUE KEY uk_session_student_qid (session_id, student_id, qid) ); CREATE TABLE tb_heartbeat ( session_id INT NOT NULL, student_id VARCHAR(20) NOT NULL, last_seen DATETIME, client_ip VARCHAR(15), PRIMARY KEY (session_id, student_id) );字段说明最关键的是tb_answer_log的唯一约束uk_session_student_qid,它保证同一个考生对同一道题只有一条记录,重复提交走更新而不是插入。earned_score字段在提交答案的同时写入判分结果,这样交卷统计时不需要重新比对题库答案,省去一次全表扫描。tb_heartbeat用于心跳上报,客户端每 30 秒写一次,断网超过 90 秒就判定为异常掉线。
3.3 抽题接口:随机抽题加试卷快照,保证刷新不换题
考生点“开始考试”后,服务端要返回一套试卷。随机抽题直接用ORDER BY RAND()在题库量不大的情况下很干净。但这里有个隐藏坑:考生刷新页面时如果重新抽题,题目顺序和内容可能全变了,所以必须把试卷快照落库。下面是最小实现:
<?php // get_paper.php - 按场次生成试卷并保存快照 header('Content-Type: application/json; charset=utf-8'); $pdo = new PDO('mysql:host=127.0.0.1;dbname=exam_system;charset=utf8mb4', 'exam_user', 'exam_pass'); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $sessionId = intval($_GET['session_id'] ?? 0); $studentId = trim($_GET['student_id'] ?? ''); if ($sessionId <= 0 || $studentId === '') { http_response_code(400); exit(json_encode(['code' => 400, 'msg' => '缺少 session_id 或 student_id'])); } // 检查考试场次状态 $stmt = $pdo->prepare('SELECT start_time, end_time, status FROM tb_exam_session WHERE session_id = ?'); $stmt->execute([$sessionId]); $session = $stmt->fetch(PDO::FETCH_ASSOC); if (!$session || $session['status'] != 1) { exit(json_encode(['code' => 403, 'msg' => '考试未在运行中'])); } $now = new DateTime(); $start = new DateTime($session['start_time']); $end = new DateTime($session['end_time']); if ($now < $start || $now > $end) { exit(json_encode(['code' => 403, 'msg' => '不在考试时间内'])); } // 从题库抽题:单选8题,多选4题,判断4题 $questions = []; $plan = [1 => 8, 2 => 4, 3 => 4]; foreach ($plan as $type => $count) { $stmt = $pdo->prepare('SELECT qid, content, options, score FROM tb_question WHERE qtype = ? AND enabled = 1 ORDER BY RAND() LIMIT ?'); $stmt->bindValue(1, $type, PDO::PARAM_INT); $stmt->bindValue(2, $count, PDO::PARAM_INT); $stmt->execute(); $questions = array_merge($questions, $stmt->fetchAll(PDO::FETCH_ASSOC)); } // 保存试卷快照,刷新时直接读快照,不再重新抽题 $questionIds = json_encode(array_column($questions, 'qid')); $stmt = $pdo->prepare('INSERT INTO tb_paper_snapshot (session_id, student_id, question_ids, created_at) VALUES (?, ?, ?, NOW()) ON DUPLICATE KEY UPDATE question_ids = VALUES(question_ids)'); $stmt->execute([$sessionId, $studentId, $questionIds]); exit(json_encode(['code' => 0, 'questions' => $questions])); ?>逻辑说明:先校验场次状态和时间窗口,防止考生提前或延后访问;抽题按题型分组,保证每种题型数量固定,避免纯随机导致单选 0 题、判断 12 题这种极端情况;最后一次把题目 ID 集合写入tb_paper_snapshot,客户端刷新时如果发现已有快照,直接按快照中的题目顺序从题库读题返回。
参数说明:$plan数组控制各题型抽题数量,实际使用时要和试卷总分对应,比如单选 8 题 × 3 分 + 多选 4 题 × 4 分 + 判断 4 题 × 3 分,合计 52 分,剩余主观题分由人工阅卷补入。ORDER BY RAND()在题库超过两万题时性能明显下降,到时改成JOIN (SELECT FLOOR(RAND() * (SELECT MAX(qid) FROM tb_question)))的随机取数方式。
3.4 交卷接口:单题实时判分加事务,断电也不丢成绩
交卷最容易出的问题是考生点了“交卷”后页面转圈,实际数据没写进去。所以交卷我拆成两级:做题时每答一题立即调用提交接口,交卷按钮只负责把剩余未答的题目补交,然后改状态。这样就算中途断电,已答题目也已经落库。
<?php // submit_answer.php - 提交单题答案,实时判分 header('Content-Type: application/json; charset=utf-8'); $pdo = new PDO('mysql:host=127.0.0.1;dbname=exam_system;charset=utf8mb4', 'exam_user', 'exam_pass'); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pdo->beginTransaction(); try { $sessionId = intval($_POST['session_id'] ?? 0); $studentId = trim($_POST['student_id'] ?? ''); $qid = intval($_POST['qid'] ?? 0); $answer = trim($_POST['answer'] ?? ''); // 查当前题的标准答案和分值 $stmt = $pdo->prepare('SELECT answer, score FROM tb_question WHERE qid = ?'); $stmt->execute([$qid]); $row = $stmt->fetch(PDO::FETCH_ASSOC); if (!$row) { throw new RuntimeException('题目不存在'); } // 判断正误:判断题统一转大写比较,多选题先拆开排序再比较 $standard = strtoupper($row['answer']); $submitted = strtoupper($answer); if (strpos($standard, ',') !== false) { $stdArr = explode(',', $standard); $subArr = explode(',', $submitted); sort($stdArr); sort($subArr); $isCorrect = ($stdArr === $subArr) ? 1 : 0; } else { $isCorrect = ($standard === $submitted) ? 1 : 0; } $earnedScore = $isCorrect ? floatval($row['score']) : 0; // 写入答题明细,已存在则更新答案 $stmt = $pdo->prepare('INSERT INTO tb_answer_log (session_id, student_id, qid, answer, is_correct, earned_score, submit_time) VALUES (?, ?, ?, ?, ?, ?, NOW()) ON DUPLICATE KEY UPDATE answer = VALUES(answer), is_correct = VALUES(is_correct), earned_score = VALUES(earned_score), submit_time = NOW()'); $stmt->execute([$sessionId, $studentId, $qid, $answer, $isCorrect, $earnedScore]); $pdo->commit(); exit(json_encode(['code' => 0, 'correct' => $isCorrect, 'earned_score' => $earnedScore])); } catch (Throwable $e) { $pdo->rollBack(); http_response_code(500); exit(json_encode(['code' => 500, 'msg' => '提交失败,请重试'])); } ?>这个接口把判分逻辑前置到提交时刻,而不是交卷时统一处理,好处是成绩实时可见,考生端能立刻看到本题得分(如果允许的话)。多选题对比时先sort再比较,避免因为选项顺序不同误判。ON DUPLICATE KEY UPDATE配合表里的唯一约束实现幂等,客户端网络抖动重试提交也不会产生重复记录。beginTransaction和rollBack保证写入过程异常时不会留下半条数据。
3.5 心跳与断电续考:用 last_seen 判断掉线时间
心跳接口和断电续考配合使用才有意义。客户端每隔 30 秒调用一次心跳接口,服务端更新tb_heartbeat表。掉线考生重进考场时,服务端先看心跳表,如果last_seen和当前时间差小于 90 秒,直接放行并按快照恢复试卷;超过 90 秒则要求重新进行身份校验。
<?php // heartbeat.php - 心跳上报,更新在线状态 header('Content-Type: application/json; charset=utf-8'); $pdo = new PDO('mysql:host=127.0.0.1;dbname=exam_system;charset=utf8mb4', 'exam_user', 'exam_pass'); $sessionId = intval($_POST['session_id'] ?? 0); $studentId = trim($_POST['student_id'] ?? ''); $stmt = $pdo->prepare('INSERT INTO tb_heartbeat (session_id, student_id, last_seen, client_ip) VALUES (?, ?, NOW(), ?) ON DUPLICATE KEY UPDATE last_seen = NOW(), client_ip = VALUES(client_ip)'); $stmt->execute([$sessionId, $studentId, $_SERVER['REMOTE_ADDR']]); exit(json_encode(['code' => 0, 'server_time' => date('Y-m-d H:i:s')])); ?>心跳接口返回服务器时间也很重要,客户端要用它校准本地时间,避免因为考生机器时间不准导致考试倒计时错乱。这里没有用beginTransaction,因为心跳数据允许轻微丢失。断网恢复后,客户端从本地缓存读已提交的qid列表,再调用一个get_progress.php接口核对哪些题已答,把未答的继续渲染出来,已答的锁定为只读。
4. 局域网考试系统部署细节:IP 规划、防火墙放行与客户端批量分发
4.1 考场网络拓扑:单交换机和跨交换机场景下的 IP 规划
一个普通机房通常是一台主交换机带若干台考生机,服务器直接接在主交换机上。这种场景下 IP 规划最简单:服务器用静态 IP,比如192.168.1.100;考生机用 DHCP 自动获取,但 DHCP 地址池要避开服务器 IP,否则地址冲突会引发一连串奇怪问题。
跨交换机部署时要注意级联端口。常见做法是主交换机和分交换机用网线级联,所有机器保持在同一个网段内,比如都在192.168.1.0/24。如果网络管理员把交换机配置成了多个 VLAN,考生机和服务器之间又没有三层路由,那页面就访问不通。进场前先用局域网 IP 地址已使用查询工具或直接在服务器上ping几台抽样机器确认通断。IP 规划我习惯用下面这张表逐项核对:
| 检查项 | 推荐配置 | 不这样做会怎样 |
|---|---|---|
| 服务器 IP | 静态192.168.1.100,网关指向主交换机 | DHCP 重启后服务器换 IP,全班连不上 |
| DHCP 地址池 | 192.168.1.101到192.168.1.200 | 池子覆盖服务器 IP 会冲突 |
| 考生机掩码 | 统一255.255.255.0 | 静态 IP 机器掩码不一致,跨段不可达 |
| 交换机级联 | 主交换机普通口连分交换机普通口 | 不同 VLAN 之间没有路由,访问被挡 |
4.2 客户端连不上考试服务器的三个高频原因
部署当天最常见的故障就是“网络通的但页面打不开”。按出现频率排序,第一个是防火墙拦截,第二个是网段不一致,第三个是浏览器兼容问题。其中网段不一致最隐蔽——考生机是静态 IP192.168.0.50,服务器是192.168.1.100,两个网段不在同一个网络,互相 ping 不通。排查时先看考生机ipconfig输出的 IP 和掩码,确认和服务器是否同段。
第三个原因在老旧机房特别突出。机房电脑如果是 Win7 加 IE8,访问现代前端框架写的考试页面大概率白屏或布局错乱。解决思路不是去兼容 IE8,而是给浏览器层做降级——机房装 Chrome 离线版,桌面放一个快捷方式指向考试地址,用--kiosk全屏模式打开,顺便禁用地址栏输入,防止考生乱窜。下面这条命令可以创建一个全屏打开考试系统的快捷方式:
"%ProgramFiles%\Google\Chrome\Application\chrome.exe" --kiosk http://192.168.1.100/exam/如果考生机没有 Chrome,先用局域网共享一键通或共享文件夹把离线安装包分发下去,这比一台台拿 U 盘插快得多。注意共享文件夹访问权限要设置成只读,防止考生误删客户端文件或拷贝题目缓存。
4.3 批量开机与一键配置:wake on lan 和开机脚本配合
60 台机器一台台按电源键太不现实,考前用局域网远程开机软件 wake me on lan 批量发送魔术包,能一次性把所有考生机唤醒。前提是考生机 BIOS 里开启了网络唤醒功能,主板型号不同设置位置不一样,但关键词都是 Wake on LAN 或 Power On By PCI-E,进场前一天让机房管理员统一确认。
机器全部唤醒后,用一个开机脚本把环境一次配好。我通常在服务器上放一个共享目录,里面放一个setup.bat,考生机开机后手动或通过计划任务执行一次。脚本内容:
@echo off REM 一键配置考场环境,需要管理员权限运行 set SERVER_IP=192.168.1.100 set SERVER_DOMAIN=exam.local REM 1. 检查 hosts 是否已有考试域名,避免重复追加 findstr /C:"%SERVER_DOMAIN%" C:\Windows\System32\drivers\etc\hosts >nul 2>&1 if errorlevel 1 ( echo %SERVER_IP% %SERVER_DOMAIN%>> C:\Windows\System32\drivers\etc\hosts ) REM 2. 放行到考试服务器 80 端口的出站访问 netsh advfirewall firewall add rule name="AllowExamHTTP" dir=out action=allow protocol=TCP remoteip=%SERVER_IP% remoteport=80 REM 3. 把默认主页设置成考试系统(针对 IE) reg add "HKCU\Software\Microsoft\Internet Explorer\Main" /v "Start Page" /t REG_SZ /d "http://%SERVER_IP%/exam/" /f echo 环境配置完成 pause脚本逻辑分三步:写 hosts 把考试域名指向服务器,避免访问时走 DNS 解析;防火墙放行到服务器的 80 端口出站,防止误拦;设置默认主页方便考生打开浏览器直接进入考试。errorlevel 1判断 hosts 里是否已有该域名,有就跳过,防止重复追加导致越来越长。
5. 局域网考试系统避坑实录:5 个让考场半夜翻车的现场与解法
5.1 开考十分钟考场整体断网:广播风暴让交换机灯狂闪
现象:考试刚开始一切正常,十分钟后全场页面加载缓慢,接着所有考生机 ping 不通服务器,交换机所有端口指示灯都在高频闪烁。
原因:有考生把一根网线两头插在了同一个交换机上,形成了网络环路,广播帧在环路里无限转发,最终演变成广播风暴。交换机没有开启 STP 生成树协议,环路根本没人拦截。
解决:立刻拔掉疑似环路的网线,网络在几十秒内自行恢复。根治办法是把交换机上的 STP/RSTP 打开,让交换机自动检测并阻塞环路端口。现在不少机房维护人员会直接用网路通局域网环路检测 app 这类工具扫描,进场后端口指示灯异常闪动就该警觉,不必等开考了才处理。血泪经验:考试开始前花五分钟看一眼交换机状态,比事后救火省心得多。
5.2 部分机器连不上服务器:静态 IP 网段和服务器不在一个网段
现象:60 台考生机里约 20 台打不开考试页面,ping 服务器丢包率 100%,但这 20 台机器互相之间能 ping 通。
原因:机房管理员之前给部分机器配置过静态 IP,网段是192.168.0.x,服务器是192.168.1.100,两边掩码都是255.255.255.0,跨网段没有路由,自然不通。
解决:把冲突机器的 IP 改成 DHCP 获取,或者统一改到服务器所在网段。进场后要批量核对,不要抽样——漏一台就有一个考生进不了考场。用局域网 IP 地址已使用查询工具先扫一遍现有 IP 占用,再让管理员协助释放冲突地址。这次之后我给所有客户端加了一个启动自检小页面,打开先显示本机 IP,考生和监考老师一眼就能发现问题。
5.3 交卷后成绩凭空少了几道题:事务没提交加断电
现象:考试结束,导出成绩时发现部分考生客观题有空缺,明明试卷快照里包含这些题。
原因:答题明细写入的时候没有实时落库,而是暂存在 PHP 的 Session 里,等交卷一次性提交。考生停电或浏览器崩溃后,Session 丢失,没提交的题目全部丢掉了。另一个原因是数据库表用了 MyISAM 引擎,不支持事务,写了一半断电就回滚不干净。
解决:改成每题提交一次,且将tb_answer_log和tb_question的表引擎改为 InnoDB。交卷接口只负责把剩余题目补交,不再承担全部数据的写库任务。断点续考功能上线后,这类问题再没有复现。
5.4 同一考号两台机器同时登录:缺少会话唯一性校验
现象:监考老师发现一个考号在考场两端的机器上同时在线,两人答题进度还不一致。
原因:登录接口只校验考号是否存在,没限制会话数量。考生把考号告诉同学,两台机器都能登录同一账号。
解决:登录时生成一个随机 token,写入内存缓存并绑定考号和客户端 IP。考生再次用同一考号登录时,把旧 token 作废,同时强制踢掉旧会话。前端 30 秒的心跳里带上 token,服务端校验不通过就跳转回登录页。记住一个原则:同一考号同一时刻只能有一个有效会话,新登录必须让旧的失效,不能让两边同时在线。
5.5 页面白屏但网络通:浏览器缓存和兼容模式在捣乱
现象:考生机网络正常,服务器日志里有请求记录,但浏览器显示白屏,控制台报一堆 JavaScript 错误。
原因:老机房浏览器版本太低,前端代码用了 ES6 语法和新版 CSS 特性,IE 或旧版 Chrome 解析不了。另一个常见原因是浏览器缓存了旧版本页面,服务端更新了代码但客户端还在跑旧的 JS。
解决:前端构建时用 Babel 转译到 ES5,并给静态资源文件名加版本号后缀,比如app.20250610.js,强制浏览器拉新文件。如果机房连安装 Chrome 都困难,就做一个纯静态兼容页面兜底,至少让考生能看到“请使用 Chrome 或 Edge 浏览器打开本系统”的提示,而不是一个无解的白屏。
6. 把局域网考试系统做扎实:成绩校验码、自动判分与上线前压测
客观题自动判分在提交答案时已经完成,成绩落库后还要加一道校验码,防止有人直接改数据库改成绩。我的做法是成绩写入时用 SHA256 生成一个校验值,拼接场次号、考号、原始分数和一个固定盐值,保存进tb_score表。导出成绩时重新计算并比对,不一致的立即标红,这样就算数据库被改动,分数也能被识别出来。
-- 成绩汇总并生成校验码,salt 用固定业务口令,线上部署时单独配置 SELECT session_id, student_id, SUM(earned_score) AS raw_score, SHA2(CONCAT(session_id, '-', student_id, '-', SUM(earned_score), '-exam_salt_2024'), 256) AS verify_code FROM tb_answer_log WHERE session_id = 1 GROUP BY session_id, student_id;这条 SQL 把答题明细里的已得分字段按考生汇总,同时生成校验码。注意SUM(earned_score)在 SELECT 和 SHA2 里都出现,只要明细分值被改动,两边的值都会变化,校验码比对就会失败。主观题部分另建人工阅卷表,阅卷完成后与客观题汇总值合并,这就是最终成绩。判断一道题能不能自动判分,看它有没有明确的标准答案;多选题的判分策略要提前定好——全对才得分还是部分选对给一半分,两种口径差很多,选题型数量时就要先算清楚。
上线前压测我习惯用 WAMP 自带的 Apache ab 工具。在服务器本机执行下面这行,模拟 50 个并发各请求 10 次抽题接口:
ab -n 500 -c 50 "http://127.0.0.1/exam/get_paper.php?session_id=1&student_id=test001"-n 500表示总请求数,-c 50表示同时并发 50 个。看两个指标:Failed requests必须为 0,Requests per second在普通 PC 上能到 50 以上就够用。压测时要在服务器本机跑而不是从考生机跑,否则网络延迟会混进结果里,测不出服务端真实瓶颈。如果Failed requests大量出现,先看 MySQL 的max_connections是否打满,再看 Apache 的 MPM 配置里MaxRequestWorkers是不是太小。
最后说一个我自己的教训:第一版系统上线时我信心满满,结果一场 200 人的模拟考,因为没开 STP 被一根网线打崩全场。从那以后,每次考试前我都按这个顺序自查——交换机指示灯有没有异常闪烁、随机抽三台考生机 ping 服务器、用测试账号完整答一遍题再交卷、看心跳表里所有考号是否都在线。这套动作做下来,后面再没出过大岔子。局域网考试系统不复杂,复杂的是那些你意想不到的物理层故障,提前把网络环境当成考试系统的一部分来对待,才敢放心开考。希望帮到你。
本文还有配套的精品资源,点击获取