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
+30 -21
View File
@@ -28,28 +28,37 @@ $err = '';
$ok = false;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$user = trim((string)($_POST['username'] ?? $cfg['username']));
$pwd = (string)($_POST['password'] ?? '');
// 允许用户在本页调整账号;密码必须输入
if ($user === '' || $pwd === '') {
$err = '请输入账号与密码。';
// 登录失败限流:5 次失败锁 10 分钟,防止暴力破解
$throttle = ny_login_throttle_check();
if ($throttle['until'] > time()) {
$wait = (int)(($throttle['until'] - time()) / 60) + 1;
$err = '尝试次数过多,请 ' . $wait . ' 分钟后再试。';
} else {
// 用当前配置的连接信息 + 本页输入的账密做一次卷王登录
$test = $cfg;
$test['username'] = $user;
$test['password'] = $pwd;
try {
$client = new SurveyKingClient($test);
$client->getToken(true);
// 登录成功:把账号与密码写回配置(本地单用户工具,持久化密码以便 token
// 失效时平台能自动重新登录卷王,避免后台刷新陷入"未配置账号"的死状态)
$cfg['username'] = $user;
$cfg['password'] = $pwd;
ny_save_config($cfg);
ny_login();
$ok = true;
} catch (Throwable $e) {
$err = '登录失败:' . $e->getMessage();
$user = trim((string)($_POST['username'] ?? $cfg['username']));
$pwd = (string)($_POST['password'] ?? '');
// 允许用户在本页调整账号;密码必须输入
if ($user === '' || $pwd === '') {
$err = '请输入账号与密码。';
} else {
// 用当前配置的连接信息 + 本页输入的账密做一次卷王登录
$test = $cfg;
$test['username'] = $user;
$test['password'] = $pwd;
try {
$client = new SurveyKingClient($test);
$client->getToken(true);
// 登录成功:把账号与密码写回配置(本地单用户工具,持久化密码以便 token
// 失效时平台能自动重新登录卷王,避免后台刷新陷入"未配置账号"的死状态)
$cfg['username'] = $user;
$cfg['password'] = $pwd;
ny_save_config($cfg);
ny_login();
ny_login_throttle_reset();
$ok = true;
} catch (Throwable $e) {
ny_login_throttle_hit();
$err = '登录失败:' . $e->getMessage();
}
}
}
}