feat: 完善/优化/新增大批次 + 安全加固 + 性能优化

- 修复再次提交不置顶(排序键改用 last_submit_ts)
- 后端结果缓存 + 并发锁(DataCache / ny_build_data / Roster 指纹失效)
- 导出 xlsx 多表:按最新成绩 / 按班级分表 / 可选列
- 名单管理:新增改弹窗、删除多选+二次确认、登陆账号列
- 安全:CSRF token + 登录失败限流(5次锁10分钟)
- 未匹配答卷一键加入名单(unmatched_map)
- 缺考名单增强:复制学号/姓名、按班统计、打印
- 成绩快照 + 跨卷趋势(Snapshot)
- 异常检测线索(短时长高分/高频重考/雷同成绩)
- 前端性能:虚拟滚动、搜索防抖、后台暂停、失败退避
- 测试沉淀:test-new-features.php + run-all.sh
This commit is contained in:
2026-09-10 17:59:45 +08:00
parent 87750b20c4
commit 0fd880624f
14 changed files with 1784 additions and 196 deletions
+17
View File
@@ -98,6 +98,23 @@ class Roster
return count(self::all());
}
/**
* 名单指纹:名单内容一变就变,用于数据缓存 key。
* 只取「人数 + 文件修改时间 + 内容的轻量哈希」,避免每次全量序列化。
*/
public static function fingerprint(): string
{
if (self::useDb()) {
return 'db:' . self::count();
}
$f = self::file();
$mt = is_file($f) ? (string)filemtime($f) : '0';
$sz = is_file($f) ? (string)filesize($f) : '0';
$d = ny_read_json($f, null);
$hash = is_array($d) ? md5(json_encode($d['students'] ?? [], JSON_UNESCAPED_UNICODE)) : '0';
return $mt . ':' . $sz . ':' . $hash;
}
/**
* 首次使用时,若名单为空且存在 导入模板.xlsx,自动导入
*/