feat: 分级拦截日志 + 两类日志文件;成绩录入防呆/解析/宽度修复
## 日志(用户要求:分级 + 每个拦截都要有指出代码行的调试日志 + 两类文件)
- 新增 internal/logging:拦截点捕获(file:line + 函数名,相对路径)、两类文件轮转、
按级别/类别分流。httpapi 与 middleware 各自薄封装,无循环依赖。
- 两类目录(数据目录下):
operations/ 放行类(INFO「操作已放行」+ 常规运行日志,按 log.level)
interceptions/ 拦截类(WARN 中文原因 + 拦截点 + method/path/actor/role/requestId、
DEBUG 上下文、ERROR 服务端异常)—— **固定 debug 采集**,
否则默认 info 下"每次拦截的调试日志"根本看不见
- 命名 operations-YYYYMMDD[.N].log,按天+按大小轮转,每类保留 keepFiles 份;写盘失败
降级只写控制台且只 Warn 一次。
- 所有业务错误统一走 newAPIError 捕获拦截点(11 处内联 &apiError{} 已收进构造函数,
防止绕过);中间件层拦截(401/403/CSRF/未改初始密码/429)也留痕,响应体与状态码一字未变。
- config.yaml 的 log 段新增 dir / maxSizeMB / keepFiles(留空即默认)。
- 服务端包数 12 → 13(新增 internal/logging)。
## 成绩录入(用户实机踩坑)
- 真因一:成绩格 placeholder 是光秃秃的 `1:06.33`,而全仓库没有任何 ::placeholder 样式,
示例文本与真实输入长得一样 → 用户把灰字当成已填内容,那 13 格其实是空的。
- 真因二:2 条报错是秒位 ≥ 60(1:70)或全角冒号(1:06);1:06 与 1:06.00 在解析器里
本来就等价(都 = 66),格式不是问题。
- 真因三:成绩格没有宽度定义(旁边名次 72px、状态 110px 都有),被挤到第 4 位数字看不见。
修复:parseSecondsDetail 区分 empty/seconds-overflow/unrecognized;新增 entryGuards.ts
纯函数(录入页与审核页共用),报错**打出那一格的实际内容**并说明原因;解析器新认全角
冒号/全角数字/1分06秒 等写法;成绩格 min-width 112px(按 fontTools 实测文本宽度定);
示例文本改 `例 1:06.33` 等并给录入表格内的 ::placeholder 加淡色斜体;新增
onBeforeRouteLeave 离开守卫(原来切菜单会静默丢未保存成绩)。
测试:服务端 13 包全绿(372 个测试函数);前端 vue-tsc 通过、205 例全过;Console 18 文件。
docs/ 与 devdocs/ 同步(FAQ 到 Q76,部署指南新增第 10 节「服务端日志与拦截复盘」)。
This commit is contained in:
1 parent
8e707e1e9b
commit
e5e0944d26
32 files changed
+2017
-138
No files matched your search
+37
-2
@@ -37,7 +37,7 @@
|
||||
撤销发布 → 修改 → 重新发布。撤销会作废该组积分流水,大屏回退到发布前状态,过程可在「变更历史」查看。
|
||||
|
||||
**Q9:为什么「发布」是灰的?**
|
||||
常见原因:未登录、服务端断开、示例数据模式,或存在阻止发布的校验错误(缺少成绩、格式无法解析、手动名次项目缺名次)。
|
||||
常见原因:未登录、服务端断开、示例数据模式,或存在阻止发布的校验错误(成绩为空、秒位 ≥ 60、成绩写法无法识别、手动名次项目缺名次)。成绩格的报错现在会把**那一格的实际内容**打出来并对症给改法,照着提示改即可(见 Q75、Q76)。
|
||||
|
||||
**Q10:并列名次怎么处理?**
|
||||
成绩相同(或手动名次相同)自动按并列处理,显示同一名次。默认口径是「顺延」:并列第 3 各得第 3 名的分,无第 4 名,下一名第 5 名;奖牌同样并列(并列第 1 各记 1 金、无银)。具体口径由规则包配置决定。
|
||||
@@ -47,7 +47,7 @@
|
||||
唯一例外:「个人+团体」小项若把平均值口径选成「全部报名人员都计入」,这些人在队内平均里会按 0 分占一个分母名额(见 Q15)。
|
||||
|
||||
**Q12:时间类成绩怎么写?**
|
||||
按 `分:秒.毫秒` 形式输入,例如 `1:06.33`;工具栏会显示当前项目的单位。
|
||||
按 `分:秒.毫秒` 形式输入最直观,例如 `1:06.33`;工具栏会显示当前项目的单位。实际上能接受的写法比过去多,中文输入法下的全角冒号 `1:06`、全角数字也认,完整清单见 Q76。成绩格里的 `例 1:06.33` 是**更淡的斜体灰字**,只是示例、不是已填内容(见 Q75)。
|
||||
|
||||
**Q13:能批量录入吗?**
|
||||
可以。支持从 Excel 复制后粘贴:两列及以上时第一列按单位名称 / 简称匹配行、最后一列作为成绩;单列时从当前行向下依次填充。也可以用 `Tab` / `Enter` 在格子间移动,`Ctrl+Enter` 存草稿。
|
||||
@@ -407,3 +407,38 @@ Console 启动的多屏窗口默认每屏锁定一页。到「大屏控制」点
|
||||
|
||||
**Q72:个人积分明细导出的数字跟个人积分榜对得上吗?会不会受榜单口径系数影响?**
|
||||
对得上,而且**不受系数影响**。「导出个人明细」(`个人积分总明细.xlsx`)里的 `得分` 是**记到个人的原始分**,与「个人积分榜」逐个数字一致;「基础数据 → 积分模板 → 榜单口径」里的个人项目得分折算系数(键名 `standings.individual_to_team_weight`,默认 1)只在汇总环节把个人分折算进单位总分,**不会改变个人明细里的数字**——比如把系数改成 0.5,个人明细的得分仍是原始分,只有单位总分变小(见 Q18)。
|
||||
|
||||
**Q73:操作被拒绝了,怎么查是谁在什么时候、因为什么被拦的?**
|
||||
服务端把「主动拦截」单独记到数据目录的拦截日志里(默认 `%LOCALAPPDATA%\ScoreboardConsole\data\logs\interceptions\`):
|
||||
|
||||
1. 找当天的 `interceptions-YYYYMMDD.log`(同一天有续写文件时按 `.1`、`.2` 往后看);
|
||||
2. 搜 `请求被拦截`(WARN)那一行,它已经带齐:`reason` 中文原因、`origin` **拦截点**(形如 `internal/httpapi/repo_events.go:231 normalizeAvgMode`,直接看出是哪个函数的哪一行拦下的)、`code` / `method` / `path`,以及 `actor`(`用户名#id`,未登录是 `guest`)/ `role` / `requestId`;
|
||||
3. 想定位得更准:浏览器开发者工具 **Network** 面板点开那条失败请求,复制响应头 `X-Request-Id`,拿它去日志文件里搜;同值的 DEBUG 行「拦截上下文」还会补 `detail` 与来源 IP;
|
||||
4. 想对照同一时间有没有成功的操作,去 `operations\` 里搜同一个 `requestId`(放行日志带 action / 实体 / 操作人)。
|
||||
|
||||
中间件层的拦截(未登录 401、无权限 403、CSRF 校验失败、未改初始密码、操作过于频繁 429)同样记在这里。完整说明见部署指南第 10 节。
|
||||
|
||||
**Q74:日志文件在哪?会不会把盘写满?**
|
||||
服务端日志在**数据目录**下的 `logs\`,分两类:
|
||||
|
||||
- `operations\`(放行类):放行的写操作(`操作已放行`,含 action / 实体 / 操作人 / requestId)+ 启动、数据库迁移等常规运行日志,按 `log.level` 过滤(默认 `info`);
|
||||
- `interceptions\`(拦截类):我们主动拦下的报错 + 服务端异常(5xx),**固定按 debug 采集,不受 `log.level` 影响**。
|
||||
|
||||
不会写满:文件名按天(`interceptions-20260911.log`),同一天写满一个文件后续写 `.1`、`.2`…;每类保留份数**含当前文件**,超出自动删最旧的。单文件上限与保留份数由 `config.yaml` 的 `log.maxSizeMB`(默认 20 MB)、`log.keepFiles`(默认 10)决定,两类合计最多约 `2 × keepFiles × maxSizeMB`(默认约 400 MB);目录可用 `log.dir` 改(留空 = `<数据目录>\logs`)。日志目录不可写时,服务端只告警一次并降级为只写控制台,不影响业务。
|
||||
|
||||
别和 Console 自己的 `logs\console.log`(在安装目录里)混淆:那个装的是 Console 与服务端的控制台输出;服务端日志文件在数据目录。
|
||||
|
||||
**Q75:为什么我明明填了成绩,发布却说缺少成绩?**
|
||||
最可能是把格子里的**灰字示例文本**当成了已填内容。过去成绩格的示例是 `1:06.33`,没有样式区分,看起来就像已经填好;现在示例统一带「例」字(时间 `例 1:06.33`、距离 `例 12.34`、计数 `例 12`、得分 `例 96.5`,备注格是 `可留空`),并且是**更淡的斜体灰字**,和真实输入明显不一样。灰字不会被提交,那一格仍然是空的。
|
||||
|
||||
新版报错也会自证:会写 `第 3 行 张三:成绩为空(当前「(空)」),请填写成绩`,把那一格的实际内容原样打出来。填了但写法不认的是另外两条:`秒位不能 ≥ 60(当前「1:70」)…` 或 `成绩写法无法识别(当前「约1分06秒」)…`,各自带内容与可用写法(见 Q76)。
|
||||
|
||||
**Q76:成绩能怎么填?**
|
||||
按项目单位填(工具栏会显示当前单位:秒 / 米 / 次 / 分):
|
||||
|
||||
- **时间类(秒)**:`66`、`66.33`、`1:06`、`1:06.33`、`1:6`、`1分06秒`、`1分6秒`、`1分06`、`1小时2分3秒` 都认;中文输入法下的**全角冒号 `1:06` 与全角数字**也认;
|
||||
- **距离 / 计数 / 得分类**:按提示填数字,例如米 `12.34`、次 `12`、分 `96.5`;无法识别时的提示会换成对应的数字说明,不会拿时间格式去教距离项目;
|
||||
- **秒位 ≥ 60 会被拒**(`1:70` 不是 70 秒),但原因写明:`秒位不能 ≥ 60(当前「1:70」),请写成 1:06 或 66 这类`;
|
||||
- 成绩格已经加宽,四位数也能看全。
|
||||
|
||||
有未保存的成绩时,切到别的菜单页面会先弹确认框,不会静默丢弃。
|
||||
Reference in new issue
Block a user