Files
ny-exam-platform/docs/guide/snapshot.md
T
nxxy335 a3be81b261 fix(security): 修复上传名单文件可被 URL 直连访问(nginx 下可 getshell)
严重漏洞:roster_import 使用 $_FILES 的原始扩展名拼 roster_YYYYMMDD_HHMMSS.<ext>
落在 Web 根目录内的 data/uploads/,既无扩展名白名单(可上传 .php),文件名又
可按秒枚举;而 bootstrap 只写了 data/.htaccess,nginx 不读 .htaccess,
生产环境(nginx + PHP-FPM)下可直接远程代码执行。

修复(api.php roster_import 四层防护):
- 扩展名白名单:仅 xlsx / xlsm / csv / txt
- 随机文件名:bin2hex(random_bytes(16)),杜绝枚举
- 解析完成后 @unlink 立即删除,Web 目录下不留存任何上传文件
- 20MB 大小限制 + UPLOAD_ERR 检查

加固(bootstrap.php):data 及 cache/uploads/snapshots 每个目录都写
.htaccess(Deny from all)+ index.html 占位;README 与文档补充 nginx
location ^~ /data/ { deny all; return 404; } 规则。

测试:新增 e2e tools/test-upload-guard.js(11 断言,覆盖 .php/.phtml 被拒、
无残留、合法 csv 导入成功且即删、无可枚举文件名),并入 tests/run-all.sh。
全套 90 项通过。

docs: 新增 VitePress 文档站(18 页)
- 使用指南 12 页:简介/部署/快速上手/系统设置/名单管理/统计口径/试卷设置/
  快照趋势异常/导出打印/数据看板/随机抽取/常见问题
- 安全 2 页:安全总览(含本次漏洞复盘)与部署加固清单(P0-P2)
- 开发 4 页:架构与目录/接口一览/SQLite 存储/测试
- 配图迁移至 docs/public/images/;.gitignore 忽略文档构建产物
2026-09-10 22:42:13 +08:00

2.2 KiB
Raw Blame History

快照、趋势与异常检测

这一组功能用于跨时间看成绩,而不是只看当前这一刻。

快照

工具栏 存快照 打开快照面板,填写标签(留空自动用「快照 MM-DD HH:II」)后保存。

一份快照包含:

  • 保存时间与标签;
  • 试卷 ID 与名称;
  • 当时的整体统计(总人数、已提交、平均分、合格率……);
  • 每名学生的 学号 / 姓名 / 班级 / 组别 / 成绩 / 最高分 / 考试次数 / 最近提交时间(按学号排序)。

存储位置:data/snapshots/<projectId>/<unix秒>_<md5(标签)>.json。

快照按试卷分目录,所以不同试卷的快照互不干扰。

操作 位置
保存当前快照 快照弹窗 → 保存当前快照
刷新列表 快照弹窗 → 刷新列表
删除某份 列表每行的删除按钮

趋势

趋势 面板把同一试卷的多份快照横向铺开,按学号对齐,可以直接看出每名学生 在几个时间点上的分数变化(上升 / 下降 / 缺考)。

典型用法:

  1. 月考卷 A 考完 → 存快照「10月月考」;
  2. 期中卷 B 考完 → 存快照「期中」;
  3. 打开趋势,逐人对比两次成绩。

异常检测

工具栏 异常 打开异常线索面板。它不是判定作弊,只是把值得人工复核的对象挑出来, 避免在上千行数据里肉眼找。

短时长高分

命中条件:

有时长记录
且 时长 < max(60秒, 平均时长 × 40%)
且 成绩 ≥ 满分提示值 × 85%

「满分提示值」取当前数据中的最高分(max_score),至少为 1。

高频重考

命中条件:考试次数 ≥ max(4, ceil(平均考试次数 + 3))

即:至少 4 次才算高频,且要明显高于整体平均水平。

雷同成绩

同一分数出现 ≥ 3 人 时列为疑似,按人数降序排列并列出成员。

::: warning 请人工复核 异常检测基于统计特征,误报很正常(例如题目全是选择题时同分很常见)。 请把它当作"值得看一眼"的线索,不要直接作为处理依据。 :::

相关