Files
AskaEth 6841cf49aa fix(p2): 按审查清单修 103 条「建议修」+ 奖牌榜改名 + 两个守卫(dev_0.2.0)
## 奖牌榜改名(主人要求)
「团体奖牌榜」→「单位奖牌榜」,界面 / 大屏渲染页 / 导出件(工作表名与
下载文件名 `单位奖牌明细.xlsx`)/ 用户文档 全部统一;`DISPLAY_PAGES` 是
大屏控制页与大屏页的共同来源,改一处两边同时生效。旧名保留为别名,
老链接与老配置(`display.pages`)照常有效。真 Chromium 验证:14 个管理端
路由 + 7 个基础数据分区 0 报错,大屏页标题已是「单位奖牌榜」。

## P2 清单(123 条,103 条标「建议修」)
按 A/B/C/D 分组、以「文件所有权互斥」分 10 个代理并行推进,另有独立复核
代理逐条核对:61 条声称已修 / 55 条证实 / 4 条部分 / 0 条不成立 / 0 垃圾。

主要交付(按风险从高到低):
- 写入侧校验:update 路径补必填与 TrimSpace、模板 rules 重复 rank、
  「小项不存在」的四种回答、`defaultProfileId` 外键、decodeJSON 的 8MB
  上限与 413(A01–A06、D02、D03 批量上限 500)
- 危险操作:一键恢复加互斥(第二个请求当场 409)、恢复超时不再说
  「未执行」、`ConfirmDialog` pending 时锁死 ESC/遮罩/关闭
- 认证:改密与撤会话进同一事务、滑动续期写库失败不再假装成功、
  CSRF Cookie 丢失自动补发、密码长度按字符(原按字节)、
  反代 403 只在可信代理透传原 Host 时采信 `X-Forwarded-Host`(全等比较)、
  过期会话定期清理、登出补审计、登录限流可配
- 数据可见性:导出「已发布未结算」行改为 `名次 — / 积分 — / 状态未结算`、
  单位积分明细成绩列与其它导出件逐字一致、空单位也打合计行、
  基础数据包命中「已彻底删除的单位」整行跳过并报界面路径、
  报名导入换单位给明确提示、勾选报名有冲突不再假报成功
- 导入/导出:赛事包上限 512MB(**它是唯一带字体/图片的包**,字体上限
  20MB)而 multipart 内存档位独立钉在 16MB、工作簿表名尾随空格、
  导出件路由与文件名契约补全
- 前端:一次写请求只生成一个 requestId(幂等缓存此前从未命中)、
  基础数据包补 `X-Request-Id`、示例数据模式禁写扩到撤销发布/存草稿、
  访客 401 不再是裸错误、模板 `{{else if}}` 不再静默吞内容
- Console:按设计退出(切赛事/恢复)不再判成崩溃、旧数据目录读不出来
  会记日志、渲染层日志按上限截断、`service.js` 补 17 例测试

## 测试
服务端用例 610 → 674(13 包全绿,新增老库跨九条迁移升级、panic 形状、
429 落 interceptions、三态对照、avg_mode 分母、会话失效、请求体上限等)
前端 449 → 531(vue-tsc + build 通过);Console 28 → 54(19 个文件)。
另修复一条会让整包挂死 600s 的用例(测试主协程堵在 client.Do,defer
轮不到执行 → 改为可取消 + 探针超时,改坏也 7.5s 快速失败)。

## 两个新守卫(防止再出现「组件 setup 抛异常」这类盲区)
- `web/scripts/check-declaration-order.mjs` 已进测试命令:沿 watch/
  watchEffect 的 const 依赖链查 TDZ(正是「运动员页整块空白」的根因)
- `web/scripts/smoke-admin.mjs`(`pnpm smoke`):真 Chromium 点遍 14 个
  管理端路由 + 7 个基础数据分区 + 大屏页,任何 console 错误即失败

## 文档
- `docs/升级说明.md` 新增 dev_0.2.0 一节(三件升级必知:改名、审计前缀
  与历史记录、密码口径;现场变化;赛事包上限;反代与 CSRF)
- 使用手册 / 常见问题 / 部署指南 / 上线检查清单按实现同步
2026-09-11 23:46:59 +08:00

164 lines
7.3 KiB
Batchfile

@echo off
REM ============================================================================
REM Scoreboard one-click backup (Windows)
REM
REM Calls scoreboard.exe's backup subcommand (VACUUM INTO), so it is safe to
REM run while the service is up and does not need an external sqlite3.exe.
REM Output goes to <data dir>\backups\; the server keeps the newest 20 per DB
REM (system / each event).
REM The data dir is the one the Console uses, i.e. %LOCALAPPDATA%\ScoreboardConsole\data
REM (falling back to <root>\data when that does not exist yet). It lives outside
REM the install dir on purpose: reinstalling over an existing install wipes the
REM install dir, so data must not be kept there.
REM NOTE: the .db snapshots below are not the whole event. Uploaded image assets
REM (event logo, display background/logo, team logos) and uploaded fonts
REM live as files under <data dir>\event_projs\<project>\uploads\ and are
REM referenced from the DB by path, so this script also zips each
REM project's uploads\ into <data dir>\backups\ next to its snapshots.
REM Logs under <data dir>\logs\ are NOT archived: the server rotates them
REM (log.* in config.yaml), they can grow without bound, and they are
REM diagnostics rather than data -- copy that directory by hand when a
REM post-mortem needs it.
REM The uploads zips are NOT pruned by the server (its 20-file rotation
REM only counts .db files), so delete old *-uploads-*.zip by hand when
REM they pile up -- keep at least the newest one per project.
REM
REM Usage: deploy\backup.bat manual backup (reason=manual)
REM deploy\backup.bat publish pre-publish backup (reason=publish)
REM
REM Keep this file ASCII-only. cmd.exe mis-parses UTF-8 non-ASCII text inside
REM batch files (especially inside if(...) blocks). See deploy\build.bat.
REM ============================================================================
setlocal enabledelayedexpansion
chcp 65001 >nul 2>&1
cd /d "%~dp0.."
set "ROOT=%CD%"
REM Same data dir the Console hands to the server via -data.
set "DATA=%ROOT%\data"
if defined LOCALAPPDATA if exist "%LOCALAPPDATA%\ScoreboardConsole\data" set "DATA=%LOCALAPPDATA%\ScoreboardConsole\data"
REM Same config.yaml the Console hands to the server via -config: it lives in the
REM per-user dir now, and the copy next to this script is only the shipped template
REM (the uninstaller deletes the install dir, so edits there are lost).
set "CONFIG=%ROOT%\config.yaml"
if defined LOCALAPPDATA if exist "%LOCALAPPDATA%\ScoreboardConsole\config.yaml" set "CONFIG=%LOCALAPPDATA%\ScoreboardConsole\config.yaml"
set "REASON=manual"
if not "%~1"=="" set "REASON=%~1"
set "REASON=%REASON: =_%"
set "REASON=%REASON:/=-%"
set "REASON=%REASON:\=-%"
set "REASON=%REASON:"=%"
if not exist "%DATA%" (
echo [ERROR] data directory not found: %DATA%
echo Start Scoreboard_Console.exe once so the service creates it,
echo or point this script at the data dir via -data manually.
exit /b 1
)
REM ---- locate scoreboard.exe -------------------------------------------------
set "EXE="
if exist "%~dp0..\dist\scoreboard.exe" set "EXE=%~dp0..\dist\scoreboard.exe"
if not defined EXE if exist "%ROOT%\scoreboard.exe" set "EXE=%ROOT%\scoreboard.exe"
if not defined EXE if exist "%~dp0scoreboard.exe" set "EXE=%~dp0scoreboard.exe"
if not defined EXE for %%I in (scoreboard.exe) do if exist "%%~$PATH:I" set "EXE=%%~$PATH:I"
if not defined EXE (
echo [ERROR] scoreboard.exe not found.
echo Run deploy\build.bat first, or put it in the project root / PATH.
exit /b 1
)
REM config.yaml is still needed for port / backup settings; -data below picks the
REM data dir explicitly, so the current directory no longer decides it.
if not exist "%CONFIG%" (
echo [ERROR] config.yaml not found: %CONFIG%
exit /b 1
)
echo === Scoreboard backup ===
echo Data dir : %DATA%
echo Config : %CONFIG%
echo Server : %EXE%
echo Reason : %REASON%
echo.
set /a OK=0
set /a FAIL=0
REM The server backs up one event DB at a time (system.db is always included),
REM so loop over the project directories. A typical deployment has one project.
REM Project names come from `dir /b /ad` (names only, no paths) rather than from
REM `for /d %%D in ("%DATA%\event_projs\*")`: cmd runs that FOR body once with the
REM unexpanded pattern when the wildcard matches nothing, which set FOUND, handed
REM "-project *" to the server (illegal name -> exit 1) and made the fallback below
REM dead code. `dir /b` prints nothing for an empty or missing folder, so the
REM fallback is reachable by construction. "eol=|" only disables the default ";"
REM end-of-line comment so a project name starting with ; is not skipped.
set "FOUND="
for /f "delims= eol=|" %%D in ('dir /b /ad "%DATA%\event_projs" 2^>nul') do (
set "FOUND=1"
echo backup event %%D
"%EXE%" backup -config "%CONFIG%" -project "%%D" -data "%DATA%" -reason "%REASON%"
if errorlevel 1 (set /a FAIL+=1) else (set /a OK+=1)
)
if not defined FOUND (
echo backup default event
"%EXE%" backup -config "%CONFIG%" -data "%DATA%" -reason "%REASON%"
if errorlevel 1 (set /a FAIL+=1) else (set /a OK+=1)
)
REM Archive each project's uploads\ (images + uploaded fonts under
REM uploads\fonts\). One timestamp is reused for every project so the snapshots
REM of a single run sort together. Same iteration as above (see the note there).
set /a ASSETS=0
set /a ASSETS_FAIL=0
for /f %%T in ('powershell -NoProfile -NonInteractive -Command "Get-Date -Format yyyyMMdd-HHmmss"') do set "STAMP=%%T"
if not defined STAMP set "STAMP=unknown"
for /f "delims= eol=|" %%D in ('dir /b /ad "%DATA%\event_projs" 2^>nul') do call :archive_uploads "%%D" "%DATA%\event_projs\%%D"
echo.
echo === backup done: ok=!OK! fail=!FAIL! assets=!ASSETS! assets_fail=!ASSETS_FAIL! ===
dir /b "%DATA%\backups\*.db" 2>nul
dir /b "%DATA%\backups\*-uploads-*.zip" 2>nul
if !FAIL! GTR 0 (
endlocal
exit /b 1
)
if !ASSETS_FAIL! GTR 0 (
endlocal
exit /b 1
)
endlocal
exit /b 0
REM ==== subroutines ===========================================================
REM %1 = project name, %2 = project dir. The archive keeps the top-level
REM "uploads" folder name, so unzipping into a project dir puts the files back
REM exactly where the server reads them from.
:archive_uploads
set "HASFILES="
for /f "delims=" %%F in ('dir /b /s /a-d "%~2\uploads" 2^>nul') do set "HASFILES=1"
if not defined HASFILES (
echo no assets %~1\uploads (empty, skipped)
exit /b 0
)
echo archive assets %~1\uploads
REM The paths go through the environment instead of being spliced into the
REM PowerShell source: project names are user-supplied (the server only rejects
REM / \ : * ? " < > | and control chars, so a single quote is allowed), and a
REM single quote would close the -Path string, make PowerShell fail to parse the
REM command and leave the whole script exiting 1. Environment values need no
REM quoting at all inside the command string.
set "SCOREBOARD_UPLOADS=%~2\uploads"
set "SCOREBOARD_ZIP=%DATA%\backups\%~1-uploads-%STAMP%.zip"
powershell -NoProfile -NonInteractive -Command "$ErrorActionPreference='Stop'; Compress-Archive -Path $env:SCOREBOARD_UPLOADS -DestinationPath $env:SCOREBOARD_ZIP -Force"
if errorlevel 1 (
echo [ERROR] archiving %~1\uploads failed
set /a ASSETS_FAIL+=1
) else (
set /a ASSETS+=1
)
exit /b 0