换基线:把「底座换代」做进后台(站点数据一行不动)
导出是「底座为底 + 只覆盖托管表」,所以底座必须是手机导出的包。底座跟着数据集走, 手机重新导出后想换代,以前只能整包重新导入成新数据集(站点补录的行、主题、备注都得重收拾)。 新动作 /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:
@@ -0,0 +1,161 @@
|
||||
{{define "content"}}
|
||||
<div class="wrap">
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1>换基线</h1>
|
||||
<div class="sub">
|
||||
「底座」是导出 rondbackup 时铺数据的底稿:导出只覆盖我们托管的那几张实体表,
|
||||
<strong>其余内容(Core Data 元数据、索引、ZRAWVISIT 原始采样点…)全部从底座原样带出来</strong>。
|
||||
所以底座必须是手机导出的包;换底座<strong>不动站点里的任何数据</strong>,只换底稿。
|
||||
</div>
|
||||
</div>
|
||||
<div class="row"><a class="btn" href="/admin">← 返回后台</a></div>
|
||||
</div>
|
||||
|
||||
{{if not .Dataset}}
|
||||
<div class="card">
|
||||
<div class="empty"><div class="big">📦</div>还没有导入任何数据集,先上传一份备份再说。</div>
|
||||
</div>
|
||||
{{else}}
|
||||
|
||||
<div class="card">
|
||||
<h2>当前底座</h2>
|
||||
<div class="card-sub">数据集「{{trunc .Dataset.Name 40}}」(#{{.Dataset.ID}})的导出底稿</div>
|
||||
<div class="tw">
|
||||
<table class="tb tb-stack">
|
||||
<thead><tr><th>项目</th><th>值</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>状态</td>
|
||||
<td data-label="状态">
|
||||
{{if .Status.Exists}}
|
||||
<span class="tag">已就绪</span>
|
||||
<span class="muted">{{size .Status.Size}} · {{dtt .Status.ModTime}}</span>
|
||||
{{else}}
|
||||
<span class="tag">文件缺失</span>
|
||||
<span class="muted">导出时会按记档自动重建;也可以点下面的「重建」。</span>
|
||||
{{end}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>来源</td>
|
||||
<td data-label="来源">
|
||||
{{if .Status.SHA}}
|
||||
<span class="mono">{{trunc .Status.Name 40}}</span>
|
||||
<span class="muted">· {{dtt .Status.At}}</span>
|
||||
{{if .FromOriginal}}<span class="tag">与数据集原始包同一份</span>{{end}}
|
||||
{{else}}
|
||||
<span>数据集最初上传的那份包</span>
|
||||
<span class="mono muted">{{trunc .OrigName 40}}</span>
|
||||
{{end}}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{if .Status.Exists}}
|
||||
<div class="card-sub" style="margin-top:14px">底座里有什么(换底座前后可以对着看)</div>
|
||||
{{if .CountsErr}}
|
||||
<div class="alert err">{{.CountsErr}}</div>
|
||||
{{else}}
|
||||
<div class="tw">
|
||||
<table class="tb tb-stack">
|
||||
<thead><tr><th>表</th><th>内容</th><th class="num">行数</th></tr></thead>
|
||||
<tbody>
|
||||
{{range .Counts}}
|
||||
<tr>
|
||||
<td><span class="mono">{{.Table}}</span></td>
|
||||
<td data-label="内容">{{.Label}}</td>
|
||||
<td class="num" data-label="行数">{{num .N}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{end}}
|
||||
{{else}}
|
||||
<div class="row" style="margin-top:12px">
|
||||
<form method="post" action="/admin/baseline/rebuild">
|
||||
<button class="btn" type="submit">按记档重建底座</button>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
{{if .PendingSHA}}
|
||||
<div class="card">
|
||||
<div class="alert err">
|
||||
刚上传的 <span class="mono">{{trunc .PendingName 40}}</span> 与当前数据集只对得上
|
||||
<strong>{{num .PendingMatched}}/{{num .PendingWith}}</strong> 条带 UUID 的到访(正常应当有九成以上)。
|
||||
它可能不是同一批数据的手机包——换下去会让导出包整个错位:站点每一行都在新底座里
|
||||
认不出身份,于是全部按「新行」插到最大编号之后,真机上就是「这些到访全变成新的、老记录还在」。
|
||||
</div>
|
||||
<div class="row">
|
||||
<form method="post" action="/admin/baseline/confirm"
|
||||
onsubmit="return confirm('确认这份包确实是同一批数据的手机包?换错会让导出的备份在真机上错位。')">
|
||||
<input type="hidden" name="sha" value="{{.PendingSHA}}">
|
||||
<input type="hidden" name="name" value="{{.PendingName}}">
|
||||
<button class="btn danger" type="submit">我确认,强制替换</button>
|
||||
</form>
|
||||
<a class="btn" href="/admin/baseline">放弃</a>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="card">
|
||||
<h2>换成另一份包当底座</h2>
|
||||
<div class="card-sub">
|
||||
在 rond App 里<strong>重新导出</strong>一份备份,传到这里。换完之后:
|
||||
到访数、地点、备注、主题都不变;导出 rondbackup 立刻以新底座为准。
|
||||
判据有两条——这份包得跟当前数据集是同一批数据(按到访 UUID 认),而且得真的能导出成功。
|
||||
换之前旧底座会留档,随时可以回滚。
|
||||
</div>
|
||||
<form method="post" action="/admin/baseline" enctype="multipart/form-data">
|
||||
<div class="row" style="align-items:center;flex-wrap:wrap">
|
||||
<input type="file" name="backup" accept=".rondbackup,.zip" required>
|
||||
<button class="btn primary" type="submit">上传并换底座</button>
|
||||
</div>
|
||||
<label class="muted" style="display:block;margin-top:10px;font-size:13px">
|
||||
<input type="checkbox" name="force" value="1">
|
||||
即使与当前数据集对不上也强制替换(重合率偏低时会再让你确认一次)
|
||||
</label>
|
||||
<div class="card-sub" style="margin-top:8px">单份上限 {{size .MaxSize}}。</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>旧底座留档</h2>
|
||||
<div class="card-sub">每次替换前都会把旧底座改名留一份(最近 3 份)。换错了就回滚。</div>
|
||||
{{if .Status.Backups}}
|
||||
<div class="tw">
|
||||
<table class="tb tb-stack">
|
||||
<thead><tr><th>留档</th><th>时间</th><th class="num">大小</th></tr></thead>
|
||||
<tbody>
|
||||
{{range .Status.Backups}}
|
||||
<tr>
|
||||
<td><span class="mono">{{trunc .Name 48}}</span></td>
|
||||
<td class="muted" data-label="时间">{{dt .At}}</td>
|
||||
<td class="num" data-label="大小">{{size .Size}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="row" style="margin-top:12px">
|
||||
<form method="post" action="/admin/baseline/rollback"
|
||||
onsubmit="return confirm('回滚到最近一份留档的旧底座?当前这份也会被留档,可以再滚回来。')">
|
||||
<button class="btn" type="submit">回滚到最近一份</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="card-sub" style="margin-top:8px">
|
||||
回滚后底座的「来源」记档会清空(表示它来自数据集最初那份包),导出照常可用。
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="empty"><div class="big">🗂️</div>还没有留档——这个数据集还没换过底座。</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
Reference in New Issue
Block a user