换基线:把「底座换代」做进后台(站点数据一行不动)
导出是「底座为底 + 只覆盖托管表」,所以底座必须是手机导出的包。底座跟着数据集走, 手机重新导出后想换代,以前只能整包重新导入成新数据集(站点补录的行、主题、备注都得重收拾)。 新动作 /admin/baseline(后台首页加入口)就是给同一份数据的底座换代用的。 三道闸,顺序不能挪: 1. 重合判据:新包里的到访 UUID 与站点侧带 UUID 的到访比。一条都对不上直接拒 (连 force 也不给过)——干跑只验结构,一份不相干的包照样导得出,但换下去站点 每行都在新底座里认不出身份,全按新行插到最大编号之后,真机上就是「这些到访 全变成新的」。重合率不到一半要显式确认;判据没过不当场报错了事:包已经收进 uploads,页面给一个「我确认,强制替换」按钮,不必重传几十 MB。 2. 干跑导出:底座缺表/缺实体编号在这步挡住,不会出现「换完了才发现导不出、 原来那份已经不知去哪」。 3. 旧底座留档 + 记档,换错了可一键回滚。 两个坑: - 留档名不能只用秒级时间戳。「替换后紧接着回滚」落在同一秒时,回滚那次 rename 会把 刚生成的留档覆盖掉,于是回滚回了个寂寞(TestRollbackBaselineRestoresPrevious 当场红)。 改用 baselineBackupName():20060102-150405 + 冲突自增。 - ensureBaseline 必须先看记档。只认 datasets.sha256(= 最初那份包)的话,底座一丢就静默 重建回旧包,上一次换基线白做且毫无提示。抽出 ensureBaselineTo(ctx, dsID, dst) 共用; 「重建」入口写临时文件再 rename,不做破坏性删除。 回归用例 7 条(internal/web/baseline_swap_test.go):成功路径断言「换底座前后导出包里 ZVISIT 的 UUID 集合逐条一致」——编号会换、身份不该换,谁在底座里没对上立刻亮; 记档优先重建带反证(清掉记档后同一条路必须走不通);部分重合需确认、完全对不上 force 也不给过、非包文件第一步就挡住、回滚还原且当前那份也留档、页面渲染到底。 验收:./scripts/verify.sh 全量 PASS=318 FAIL=0 SKIP=0。
This commit is contained in:
@@ -534,3 +534,46 @@ zip 包,**条目内容本身是 zlib 流**;`Model/*/` = 头 32768B + 每块
|
||||
- 验收四件套 `ROND_TEST_CONF`+`ROND_SRC`+`FOG_SRC`+`ROND_DSN`(须指 `rond_fog_test`);`-count=1 -v ./...` 要 **0 skip**。两个假绿坑:缺 `ROND_SRC` 静默 Skip;共用库 `Import` 让 `internal/stats` 假 skip ⇒ 用专用库,`t.Cleanup` 在 `defer` **之后**才跑。
|
||||
- 静态守卫走 `moduleRoot(t)`/`pkgFile(t,name)`;`-race` 需 mingw 在 PATH。纪律:只走 IPv4、303 不跟随;**同一文件绝不并行发多条 Edit**;新回归测试做**变异验证**。
|
||||
- 📐 覆盖率**必须 `-coverpkg=./internal/...`**(`-cover` 只算本包 ⇒ rond/geo 假报 0%,真值 82.2%/92.9%);解析 profile 按 `(文件,行段)` 取 **max 别求和**;**死代码删掉、别给它补测试**。关键页冒烟 `scripts/smoke.py`(`verify.sh --smoke`/`--all`):关键区块不能用 `nav.nav`、用 `.card` 不用 `div.card`。`details 廿九`
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 三十三、「换基线」(底座换代)做进后台(2026-09-30)
|
||||
|
||||
### 是什么 / 为什么要
|
||||
导出的实现是「**底座为底 + 只覆盖托管表**」(见 `internal/rond/export.go` 头部注释):
|
||||
Core Data 元数据、索引、持久化历史、`ZRAWVISIT` 采样点、`ZTRIP*` 全从底座原样带出来。
|
||||
所以**底座必须是手机导出的包**。底座跟着数据集走,手机重新导出后想换代,
|
||||
以前唯一办法是整包重新导入成新数据集(站点补录的行、主题、备注全要重收拾)。
|
||||
新动作:**同一份数据的底座换代,站点数据一行不动**。
|
||||
|
||||
### 实现要点(`internal/web/baseline.go` + `handlers_export.go` + `templates/admin/baseline.html`)
|
||||
- 顺序不可挪:写临时底座 → ①重合判据 → ②干跑导出 → 旧底座改名留档 → `rename` 就位 → 记档 → prune。
|
||||
干跑(`rond.ExportRondbackup(..., tmp, io.Discard)`)把「底座缺表/缺实体编号」这类失败挡在替换之前,
|
||||
否则会出现「底座换了、才发现导不出、原来那份已经不知去哪」。
|
||||
- ①重合判据(唯一能挡住「换错包」的闸):分母是**站点侧带 UUID 的到访**(站点补录行本来就没有 UUID,
|
||||
不计入);`minOverlapVisits=20` 以下不判;`matched==0` ⇒ 硬拒(**force 也不给过**);
|
||||
`matched*2 < withUUID` ⇒ 要显式确认。理由:干跑只验结构,一份不相干的包照样导得出,
|
||||
但换下去站点每行都在新底座里认不出身份 ⇒ 全按新行插到最大编号之后 ⇒ 真机上「这些到访全变成新的」。
|
||||
- ⚠️ **留档名必须防重名**:`fmt.Sprintf("%s.bak-%d", dst, time.Now().Unix())` 是秒级;
|
||||
「替换后紧接着回滚」两个操作落在同一秒时,回滚那次 `rename` 会把刚生成的留档**覆盖掉**,
|
||||
于是回滚拿到的还是新底座——实测 `TestRollbackBaselineRestoresPrevious` 就是这么红的。
|
||||
改用 `baselineBackupName()`(`20060102-150405` + 冲突自增)。
|
||||
- 记档三列 `datasets.baseline_name/baseline_sha256/baseline_at`。
|
||||
**`ensureBaseline` 必须先看记档**:只认 `datasets.sha256`(= 最初那份包)的话,
|
||||
底座一丢就静默重建回旧包,上一次换基线白做。共用实现 `ensureBaselineTo(ctx, dsID, dst)`;
|
||||
「重建」入口写临时文件再 `rename`,不做破坏性 `os.Remove`(重建失败不能让数据集没底座)。
|
||||
- 上传件要落 `uploads`(`stashUpload`,按 sha 去重后复用已有那份),否则底座丢了按记档找不回包。
|
||||
⚠️ 算摘要的落盘必须在 `uploads` **之外**(`ingest.TempDir` 下),否则 `findUploadBySha`
|
||||
会把自己认成「已存在」。
|
||||
- 判据没过不当场报错了事:包已经收好了,页面用 `?pending=<sha>&pname=&pm=&pw=` 渲染一个
|
||||
「我确认,强制替换」按钮(不必重传几十 MB);`POST /admin/baseline/confirm` 走同一条路。
|
||||
- 导出内容在换底座前后**逐条不变**(`ZVISIT` 的 UUID 集合):编号会换、身份不该换,
|
||||
这条是回归用例最锋利的断言(谁在底座里没对上,就会多出一条派生 UUID 的行)。
|
||||
- 路由:`GET /admin/baseline`、`POST /admin/baseline`(上传并换)、`/confirm`、`/rollback`、`/rebuild`。
|
||||
|
||||
### 用例 `internal/web/baseline_swap_test.go`(7 条)
|
||||
成功路径(文件真换/旧底座留档 sha 一致/记档写入/站点计数与样本时间不变/导出 UUID 集合一致)、
|
||||
记档优先重建(**带反证**:清掉记档后同一条路必须走不通,防止用例被别的兜底弄绿)、
|
||||
部分重合需确认、一条都对不上 force 也不给过、非包文件第一步就挡住、回滚还原且当前那份也留档、
|
||||
页面渲染到底(尾部 `</html>`)。
|
||||
|
||||
Reference in New Issue
Block a user