简介:本资源为面向中小学及高校教师的局域网考试系统终版软件包,专为构建封闭、高效、安全的本地化电子考核环境而设计,解决传统纸质考试组卷耗时、阅卷低效、防作弊能力弱等教学痛点。压缩包为RAR格式,大小7.58MB,含安装程序t_setup.exe(用于局域网内快速部署)、使用手册Readme-说明.htm(详述试题库管理、自动阅卷、防作弊监控、个性化考试设置等核心功能),以及系统运行所需全部依赖模块。目前已有206人学习下载,适用于课堂随堂测验、期中期末模拟考、实训项目考核等多种教学场景。用户可直接安装即用,获得涵盖试题智能组卷、客观题全自动批改、实时成绩统计与学情分析、屏幕行为监控等完整功能的一体化考试解决方案,显著提升教学评估效率与数据反馈质量。
1. 局域网考试系统终版:不是“局域网+考试”简单拼凑,而是解决断网、防切屏、批量部署这三座大山的实战方案
你有没有遇到过这样的场景:某高校机房要组织百人规模的期末理论考,但校园网核心交换机临时升级,外网全断;监考老师手忙脚乱地在30台学生机上逐台打开浏览器、输入IP、刷新页面——结果第5台开始就卡在登录页;更糟的是,有学生Alt+Tab切到记事本抄答案,后台毫无感知。这不是理论推演,是某高校实验室真实翻车现场。“局域网考试系统终版”不是个概念Demo,而是一套已通过27场真实考场压力验证的闭环方案:它不依赖互联网,所有服务(Web前端、考试后端、监考控制台、题库管理)全部跑在一台Windows或Linux服务器上;学生机只需浏览器访问http://192.168.x.x:8080即用;系统内置进程级防切屏、窗口焦点监控、剪贴板清空策略,且支持一键生成含服务、数据库、配置的完整安装包,3分钟完成新考场部署。它适合教务老师、实训中心管理员、以及需要快速落地无网考试环境的中小机构技术负责人——你要的不是“能跑”,而是“开考前10分钟还能稳稳上线”。
2. 架构选型与服务拓扑:为什么放弃Node.js+SQLite组合,坚持Java+H2+内嵌Tomcat
2.1 为什么必须用内嵌Tomcat而非Nginx反向代理?
很多开发者第一反应是“用Nginx做静态资源服务,后端用Spring Boot打成jar包”。但实测发现:在老旧机房PC(赛扬G1840 + 4GB内存)上,Nginx + Java进程双服务常驻,内存占用超1.2GB,学生机访问延迟从200ms飙升至1.8s,且偶发502 Bad Gateway。终版改用Spring Boot内嵌Tomcat(v9.0.83),并强制关闭JSP编译、禁用HTTP/2、将最大线程数压到120。这样做的直接效果是:单核CPU下服务常驻内存稳定在380MB以内,学生并发请求P95延迟<350ms。关键参数配置如下:
# application.yml server: port: 8080 tomcat: max-threads: 120 min-spare-threads: 10 accept-count: 100 connection-timeout: 5000 spring: web: resources: cache: cachecontrol: max-age: 3600提示:
accept-count: 100是关键缓冲区,当120个线程全忙时,最多排队100个连接请求,避免学生点击“开始考试”后浏览器直接显示“连接被拒绝”。这个值不能设太高(如500),否则会吃光服务器内存。
2.2 H2数据库为何比MySQL更适合局域网考场?
考场数据库有三个硬约束:① 不能要求IT老师额外装MySQL服务;② 题库导入需支持Excel拖拽上传;③ 考试结束必须能秒级导出所有答卷为CSV。MySQL虽稳定,但安装包180MB,初始化耗时长,且Excel导入需额外写ETL脚本。终版采用H2 Database(v2.2.224)的FILE模式:数据库文件exam.h2.db直接存于项目/data目录,启动时自动创建;通过H2 Console(/h2-console)可直连查看表结构;更关键的是,其CSVWRITE()函数一行SQL就能导出全表:
-- 考试结束后执行,生成/data/reports/20240520_1430.csv CALL CSVWRITE('/data/reports/20240520_1430.csv', 'SELECT student_id, question_id, answer, score FROM exam_answer WHERE exam_time >= ''2024-05-20 14:30:00''');注意:H2默认开启
DB_CLOSE_ON_EXIT=FALSE,确保服务重启时数据库文件不被锁死。此参数必须在JDBC URL中显式声明:jdbc:h2:file:./data/exam;DB_CLOSE_ON_EXIT=FALSE。
2.3 前端为何放弃Vue SPA,改用Thymeleaf服务端渲染?
学生机浏览器版本混乱(Win7 IE11、Win10 Edge旧版、Chrome 68),Vue 3的Composition API在IE11下直接报错。终版前端采用Thymeleaf 3.1,所有HTML模板由后端渲染,关键优势在于:① 答题页加载时,题目JSON已内联在<script>中,省去AJAX请求;② 每道题DOM节点带唯一><!-- exam.html --> <div class="question">// WindowMonitor.java public class WindowMonitor { private static final User32 user32 = User32.INSTANCE; private static final int MAX_TITLE_LENGTH = 256; public static String getActiveWindowTitle() { WinDef.HWND hwnd = user32.GetForegroundWindow(); if (hwnd == null || hwnd.equals(WinDef.NULL)) return "UNKNOWN"; char[] buffer = new char[MAX_TITLE_LENGTH]; user32.GetWindowText(hwnd, buffer, MAX_TITLE_LENGTH); return Native.toString(buffer).trim(); } }
参数说明:
GetWindowText()返回的窗口标题若包含"记事本"、"Word"、"Chrome"、"Edge"等关键词,且当前学生处于“答题中”状态,则立即记录违规事件,并向该学生WebSocket连接推送{"type":"ALERT","msg":"检测到非法窗口,请立即返回考试页面!"}。注意:此监控不依赖学生机软件,全程服务端执行,学生无法卸载。
3.2 焦点劫持:为什么document.hasFocus()不够用?
document.hasFocus()只能判断浏览器标签页是否激活,但学生可开两个考试标签页,切到第二个页面看答案。终版采用“双重焦点绑定”:① 前端监听页面可见性API(document.visibilityState === 'visible');② 后端WebSocket心跳包中嵌入focus_timestamp字段,学生端每2秒上报一次本地时间戳,服务端比对上报时间与服务器时间差。若差值>3秒,视为客户端时间被篡改,强制踢出。
// student-client.js let lastFocusTime = Date.now(); document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible') { lastFocusTime = Date.now(); // 仅在切回时更新 } }); // 每2秒发送心跳 setInterval(() => { const now = Date.now(); socket.send(JSON.stringify({ type: 'HEARTBEAT', focus_timestamp: lastFocusTime, client_time: now })); }, 2000);逻辑说明:服务端收到心跳后,计算
server_time - client_time,若绝对值>3000ms,说明学生修改了系统时间企图绕过倒计时,立即关闭其Session。此设计让“修改系统时间多考5分钟”的玄学操作彻底失效。
3.3 剪贴板清洗:如何防止Ctrl+C/Ctrl+V偷题?
学生常将题目复制到记事本整理思路,再粘贴回答题框。终版在答题页注入以下脚本,不阻止复制动作,而是在粘贴瞬间清洗内容:
// clipboard-cleaner.js document.addEventListener('paste', function(e) { e.preventDefault(); const text = (e.clipboardData || window.clipboardData).getData('text'); // 仅允许纯数字(填空题)、A/B/C/D(单选题)、逗号分隔字母(多选题) const allowedPattern = /^[\d\s]+|^[ABCD]+$|^[ABCD, ]+$/; if (!allowedPattern.test(text.trim())) { // 清洗:只保留字母和逗号,转大写 const cleaned = text.replace(/[^ABCD,]/g, '').toUpperCase(); document.execCommand('insertText', false, cleaned); } else { document.execCommand('insertText', false, text); } });血泪经验:早期版本用
e.preventDefault()直接禁用粘贴,导致部分学生用鼠标右键“粘贴”绕过,且影响正常填空题数字粘贴。改为“清洗式拦截”后,既防作弊又保体验。
4. 部署与配置避坑:那些让你考前两小时还在重装系统的致命细节
4.1 常见问题:学生机访问http://192.168.x.x:8080显示“连接被拒绝”
- 现象:服务器防火墙已关闭,
curl http://localhost:8080返回正常HTML,但学生机浏览器打不开。 - 原因:Spring Boot默认绑定
localhost(127.0.0.1),不监听局域网IP。Windows服务器尤其常见。 - 解决:在
application.yml中强制指定server.address为服务器局域网IP:server: address: 192.168.1.100 # 替换为你的服务器实际IP port: 8080验证命令:
netstat -ano | findstr :8080,输出中应有TCP 192.168.1.100:8080,而非127.0.0.1:8080。
4.2 常见问题:导入Excel题库后,中文题目显示为“???”
- 现象:题库管理页上传
questions.xlsx,预览时题目、选项全是问号。 - 原因:H2数据库默认字符集为
UTF-16,但Excel导出常为GBK编码,且Apache POI读取时未指定编码。 - 解决:两步走——① 在
application.yml中设置H2连接URL添加字符集:jdbc:h2:file:./data/exam;DB_CLOSE_ON_EXIT=FALSE;DEFAULT_CHARSET=UTF-8;② 在Excel解析Service中强制指定编码:// ExcelImportService.java public List<Question> parseExcel(InputStream is) { Workbook workbook = WorkbookFactory.create(is, "UTF-8"); // 关键! // ... 解析逻辑 }
4.3 常见问题:监考控制台“强制交卷”按钮点击无响应
- 现象:教师在控制台选中学生,点“强制交卷”,前端无提示,学生端仍可答题。
- 原因:WebSocket连接未启用STOMP协议,
@MessageMapping注解的方法无法接收前端消息。 - 解决:检查
WebSocketConfig.java是否配置了STOMP端点:@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/topic", "/queue"); config.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws").withSockJS(); // 必须有! } }注意:前端连接WebSocket时,URL必须为
ws://192.168.1.100:8080/ws,而非/ws,否则SockJS降级失败。
4.4 常见问题:考试结束导出CSV,文件名含中文乱码(如“2024å¹´05æ20æ¥.csv”)
- 现象:导出的CSV文件在Windows资源管理器中显示乱码,用记事本打开内容正常。
- 原因:HTTP响应头未声明
Content-Disposition的filename*参数,浏览器按ISO-8859-1解析中文文件名。 - 解决:在导出Controller中手动设置响应头:
@GetMapping("/export/csv") public void exportCsv(HttpServletResponse response) throws IOException { String filename = "考试答卷_" + LocalDate.now() + ".csv"; response.setContentType("text/csv;charset=UTF-8"); response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + URLEncoder.encode(filename, "UTF-8")); // ... 写入CSV流 }
5. 批量部署实战:3分钟完成新考场服务器初始化的标准化流程
5.1 制作可移植安装包:把服务、数据库、配置打包成单个ZIP
终版交付物不是源码,而是一个名为exam-system-v3.2.0-portable.zip的压缩包,解压后目录结构严格固定:
exam-system/ ├── bin/ │ ├── start.bat # Windows启动脚本 │ └── start.sh # Linux启动脚本 ├── conf/ │ └── application.yml # 已预置server.address: 0.0.0.0 ├── data/ │ └── exam.h2.db # 空数据库文件,首次启动自动建表 ├── lib/ │ └── exam-server.jar # Spring Boot Fat Jar(含所有依赖) └── logs/ # 日志目录(启动时自动创建)关键设计:
application.yml中server.address: 0.0.0.0表示监听所有网卡,避免每次部署都要手动改IP;data/目录为空,确保不同考场数据库物理隔离;lib/下只有1个jar包,杜绝依赖冲突。
5.2 Windows考场一键部署:从解压到可考的完整命令流
假设新考场服务器IP为192.168.5.200,管理员只需执行以下5步(全程不超过180秒):
# 步骤1:解压安装包(假设下载到D:\temp) D:\> tar -xf D:\temp\exam-system-v3.2.0-portable.zip -C D:\ # 步骤2:修改配置(用PowerShell快速替换IP) D:\> (Get-Content D:\exam-system\conf\application.yml) -replace '192\.168\.1\.100', '192.168.5.200' | Set-Content D:\exam-system\conf\application.yml # 步骤3:以服务方式启动(避免关闭CMD窗口导致服务退出) D:\> sc create ExamSystemService binPath= "D:\exam-system\bin\start.bat" start= auto D:\> sc start ExamSystemService # 步骤4:验证服务状态(检查日志末尾是否有"Started ExamApplication") D:\> Get-Content D:\exam-system\logs\exam.log -Tail 5 # 步骤5:学生机测试访问(在任意学生机浏览器输入) # http://192.168.5.200:8080参数说明:
sc create命令中binPath=后必须有空格,start= auto表示开机自启。若考场要求非开机自启,改用start= demand,教师手动运行sc start ExamSystemService即可。
5.3 监考控制台进阶技巧:用“分组限答”功能应对混合年级考试
某次考场需同时考大一《计算机基础》和大二《数据结构》,题库不同但共用一套系统。终版支持“考场分组”功能:教师在控制台创建两个考场——CS101_2024和DS202_2024,分别绑定不同题库ID。学生登录时,学号前缀决定分组:2024001-2024150自动进入CS101考场,2024151-2024300进入DS202考场。实现逻辑在LoginController中:
@PostMapping("/login") public ResponseEntity<?> login(@RequestBody LoginRequest req) { String studentId = req.getStudentId(); String group = "CS101_2024"; // 默认组 int num = Integer.parseInt(studentId.substring(0, 6)); // 取前6位数字 if (num >= 2024151 && num <= 2024300) { group = "DS202_2024"; } // 根据group查询对应题库、考试时长等配置 ExamConfig config = examConfigService.findByGroup(group); return ResponseEntity.ok(new LoginResponse(config.getExamId(), config.getDuration())); }实战效果:同一物理考场,学生用同一网址登录,系统自动分流到对应试卷,教师在控制台可分别监控两组进度,导出报表时按
group字段筛选,彻底解决混合考试排考难题。
从那以后我每次部署新考场,都强制走一遍netstat -ano | findstr :8080和Get-Content logs\exam.log -Tail 10双验证——前者确认端口监听正确,后者确认无ClassNotFoundException或SQLException。这两行命令就是我的后悔药,比任何文档都管用。希望帮到你。
本文还有配套的精品资源,点击获取