feat(import): 报名模板按「小项 × 组别」分工作表;号码布判重改认证件号;备份改原子落盘

三件事合成一次提交(其中两件都落在 handlers_config.go,无法按文件拆开):

一、分工作表报名模板(新增能力)
- 新增 GET /api/entries/template.xlsx:按当前赛事的「小项 × 组别」生成模板,
  一个小项组别一张工作表,工作表名 = <小项>@<组别>(31 字符上限、重名自动去重)。
- 导入解析器认工作表名还原小项 + 组别;若与表内列冲突则报错,不静默取其一。
- 小项名与组别名禁用 @,基础数据界面 / 规则包导入 / 服务端校验三处一并拦截。
- 单表 CSV/XLSX 导入路径不回归。

二、号码布判重(修误判)
- 旧逻辑依赖证件号本身,而证件号是可选字段 → 同一个人报多个项目会被整块拦下。
- 改为:号码布相同且证件号相同 → 判定为同一人报多项,放行;
  号码布相同且证件号都能填且不同 → 仍报错(真抢号);一方证件号为空 → 放行并给提示。
- 团体行的号码布一并纳入查重。

三、自动备份的关停与落盘(修数据安全隐患)
- export.Service 增加 Wait():StartAuto 的协程进 WaitGroup,关停时 join(10 秒上限,
  超时只记 WARN 不拖死关闭),不再在备份写到一半时退出。
- vacuumInto 改为先写 <最终名>.tmp-<随机> 再 rename,失败则删临时文件;
  Backup / SafetySnapshot / ExportProjectZip 三个调用方一并受益。
- 新增 isFinalBackupName,ListBackups / safetyCopies / KindOfBackupName 统一过滤中间文件,
  避免半截 .db 被列进备份列表、进而被「一键恢复」选中。

文档:docs/ 五份(使用手册 / 报名名单导入规范 / 升级说明 / 部署指南 / 上线检查清单)同步。
This commit is contained in:
AskaEth committed 2026-09-14 01:10:16 +08:00
1 parent b74768b7d5
commit 1b8a37ff8c
32 files changed
+3302 -233

No files matched your search

+76 -4
View File
@@ -4,6 +4,7 @@
> 给负责收表、填表的同志看即可,不需要懂系统内部。
>
> 配套模板可在系统里直接下载:**基础数据 → 报名导入 →「中文表头」/「英文表头」**(两个文件列数与列顺序完全一样,只有表头语言不同,按手上表格的样子选)。
> 小项与组别很多时,同一处还有**「分工作表 · 中文表头 / 分工作表 · 英文表头」**:一个小项组别再抄串——**一个小项 × 一个组别一张工作表**,表名写成「小项名称@组别名称」,详见第八节。
---
@@ -19,12 +20,14 @@
一共 12 列。系统**同时认两套表头**——英文一套、中文一套,**两套都能导入**,也可以**中英混着写**(每一列各自认名字,例如英文 `event_code` 配中文「姓名」)。**英文是规范列名**:老的英文模板一个字都不用改,照常导入;中文只是给习惯看中文的人用的同义写法。
下载模板时有两个按钮,按手上表格的样子选:
下载模板时按手上表格的样子选:
- 「**中文表头**」→ 文件 `entries-template-zh.csv`
- 「**英文表头**」→ 文件 `entries-template-en.csv`
- 「**分工作表 · 中文表头**」/「**分工作表 · 英文表头**」→ 文件 `entries-template-zh.xlsx` / `entries-template-en.xlsx`
(一个小项组别一张工作表,见第八节;表头语言与上面两个按钮同一套口径)
两个文件**列数与列顺序完全一样,只有表头语言不同**,示例行也一样。列的**先后顺序可以随便调**,多出来的列系统会忽略。
单表模板的两个文件**列数与列顺序完全一样,只有表头语言不同**,示例行也一样。列的**先后顺序可以随便调**,多出来的列系统会忽略。
| 英文表头(规范列名) | 中文表头(同义写法) | 这一列是 |
|---|---|---|
@@ -208,6 +211,7 @@
- [ ] `status` 只用那 4 个英文词(大小写都行)或第三节列的中文别名,不写别的词;拿不准就留空
- [ ] `heat` 要么留空,要么是 1–999 的整数(不参与算分,只是录入分组)
- [ ] 全表没有因为"怕麻烦"把某些列删掉——**表头 8 个必须列一个都不能少**(哪怕整列是空的)
- [ ] 用的是**分工作表模板**时:每张工作表的名字都是「小项名称@组别名称」、没有多余的工作表;「项目编码 / 组别」两列要么整列留空、要么与表名一字不差(见第八节)
---
@@ -233,7 +237,9 @@
| 第 N 行:heat 需要是 1-999 的整数(留空表示未分批) | `heat` 填了非整数、0、负数或超过 999 的值(如 `第1批`、`一组`、`1000`) | 改成 1–999 的整数,或者整格留空(= 未分批);批次只影响录入分组,不参与算分 |
| 提示「冲突」,人员没有写入 | 按证件号(或号码布、「姓名 + 单位」唯一命中)的人在系统里已有记录,但姓名 / 证件号 / 性别 / 年龄 / 单位 / 号码布跟表里**对不上** | 在导入结果里逐条选「保留已有数据」或「使用新数据」,选完再提交 |
| 文件传不上去 | 超过 32MB | 拆分上传,或改用 xlsx |
| 只导入了第一张表 | xlsx 只读第一个工作表 | 把要导入的数据放在第一个工作表里 |
| 工作表「XXX」没能定出是哪个小项、哪个组别 | 这是一份**分工作表模板**(有工作表名里带 `@`),但工作簿里混进了别的表(备注表、说明表),或者表名写得不合规 | 删掉多余的工作表,或把表名改成「小项名称@组别名称」(见第八节);只有一张表、数据都在里面时,把它存成 CSV 用单表模板导 |
| 本行项目编码 XXX 不是工作表名里那个小项 / 本行组别 XXX 与工作表名里的组别不一致 | 分工作表模板里范围由**工作表名**决定,而这一行的「项目编码 / 组别」两格又填了别的值 | 把那两格**清空**(留空即按工作表名走),或改成与工作表名一致的值 |
| 工作表「XXX」是隐藏表,按约定不参与导入 | 工作簿里有隐藏工作表 | 要在它里面导数据就先取消隐藏并改名成「小项名称@组别名称」,否则忽略这条提示即可 |
---
@@ -254,7 +260,73 @@
---
## 八、给填写人的最短提示(一页版)
## 八、分工作表模板(一个小项组别一张表)
前面几节讲的都是**单表模板**:一份表装下全部报名,「项目编码」与「组别」两列每行都要抄一遍。
小项一多(几十个小项 × 几个组别),抄这两列又累又容易串行——所以系统还提供一种
**分工作表模板(xlsx)**:**一个小项 × 一个组别 = 一张工作表**,范围写在**表名**上,数据区只填人。
> 两种模板**都能导入**,谁也替代不了谁:单表 CSV 老填表人照旧用,分工作表模板给「一个小项组别再抄串了」的场合。
> 「基础数据 → 报名导入」里四个下载按钮分别对应:**中文表头 / 英文表头**(单表 CSV)、
> **分工作表 · 中文表头 / 分工作表 · 英文表头**(xlsx)。
### 8.1 工作表怎么命名
| 规则 | 说明 |
|---|---|
| 表名 = **小项名称** `@` **组别名称** | 例如小项「100 米跑」+ 组别「男子组 30岁以下」→ 工作表名 `100 米跑@男子组 30岁以下` |
| 用的是**名称**,不是编码 | 就是「基础数据 → 大项 / 小项」里的小项名、「组别」里的组别名,一字不差 |
| 一张表只能对一个组合 | 一个小项组别一张表;两列都不用填——**范围由表名说了算** |
| 表名里**不能有** `@` 以外的花样 | `:` `\` `/` `?` `*` `[` `]` 会被系统自动换成同形的全角字符(Excel 不允许工作表名里有它们) |
**小项名称与组别名称里禁止使用 `@`(全角 `@` 也不行)。** 理由是它就是表名里的分隔符:
名字里再出现一个 `@`,系统就分不清哪个 `@` 是分隔符、哪一个是名字的一部分。
- 界面(新建 / 编辑小项、编辑组别)会当场拦下并说明原因;
- 规则包里的小项名同样拦;服务端接口也拦(改接口、导 Excel 都绕不过去);
- **已经存在**的历史名字里若带 `@`:不影响任何报名、成绩与积分,但**分工作表模板会生成不出来**——
下载时会明确告诉你「哪一个小项 / 组别带 @,请先改名」,改名后再下载即可(改名不丢数据)。
### 8.2 数据怎么填
- 每张工作表**第 1 行就是表头**(与单表模板同样的 12 列、中英两套都认),**从第 2 行开始填人**;
- **「项目编码」「组别」两列可以整列留空**——表名已经说明了是哪个小项、哪个组别;
一旦填了,就**必须与表名一致**,不一致的那一行会报错并跳过(系统不猜、也不会挑一个用);
- 其余列与单表模板完全一样(第三节逐列说明照样适用);
- **只填了表头、没填人的那张表**会被跳过并给一条提示(= 这个组别没人报名),其余表照常导入;
- 不要在工作簿里加「说明」之类的工作表:**只要工作簿里有一张表名带 `@`**,整份文件就按分工作表模板处理,
其它表名(例如 `Sheet1`、`备注`)必须也是「小项名称@组别名称」的形状,否则**整份拒绝**并点名是哪张表——
静默跳过意味着你填在第 3 张表里的几十行数据无声消失,结果面板上却还显示「导入成功」。
### 8.3 表名太长、或者两个组合撞名了怎么办
Excel 对工作表名有硬限制:**最长 31 个字符**,同一份工作簿里**不能重名**。系统的处理是确定的,不用你操心:
- **太长**:小项名与组别名各保留开头的一段(小项名优先保留,组别名至少留 8 个字符),拼起来不超过 31 字符。
截断后的表名仍是原名的一段开头(保留头部、去掉尾巴),人一眼能认出是哪一项:小项名
`第三届全国职工运动会田径比赛男子甲组100米跑决赛` + 组别名 `30岁以下公开组`,算出来是
`第三届全国职工运动会田径比赛男子甲组100米@30岁以下公开组`(小项名留下 22 个字符,组别名留下 8 个);
- **撞名**:截断后如果两张表名字一样,后一张会自动加 `(2)`、`(3)` 后缀(Excel 里重名会让后一张把前一张**覆盖掉**,数据无声消失,所以这一步必须做);
- 下载下来的模板里这些名字**是最终名字**,照原样填、原样传回来即可;**不要手工改工作表名**,改了就对不上号了。
> 小项或组别**改过名字**(或改了截断规则之后重新下载)时:旧模板里的表名对不上新名字,导入会报
> 「工作表『XXX』没能定出是哪个小项、哪个组别」。这时**重新下载一份模板**、把数据粘过去最稳。
### 8.4 与单表模板的关系
| | 单表模板(CSV / XLSX) | 分工作表模板(XLSX) |
|---|---|---|
| 文件形态 | 一张表装全部报名 | 一个小项组别一张工作表 |
| 范围从哪来 | 每行的「项目编码 / 组别」两列 | **工作表名**(「项目编码 / 组别」可留空) |
| 表名限制 | 随便(`Sheet1`、`报名表` 都行) | 必须「小项名称@组别名称」 |
| 多张工作表的文件 | 只读第一张,其余在导入结果里提示「没有读」 | 每张表各导各的 |
| 适合 | 零散补报、小规模、沿用老表格 | 小项组别多、按项目分工填表 |
两种模板**解析口径完全一致**:表头、认人顺序、冲突处理、行级报错都共用同一套代码,
只有「范围从哪来」这一点不同。
---
## 九、给填写人的最短提示(一页版)
> 1. 用系统里下载的模板(**中文表头 / 英文表头都能用**,见第二节),**第一行不要动**、同一列不要写两遍;
> 2. **一个人一行**;一个人报几项就写几行;