备份与迷雾的差异分析、合并执行,附属数据改挂坐标键

备份(.rondbackup)与迷雾(.fwss)各加一层「先看差异再动手」:

- 迷雾:上传先出差异页,说清叠加能补多少、整体替换会丢多少,并在真实底图上
  用绿/灰/红标出会新增 / 不变 / 会丢的区域
- 备份:与当前数据集按 UUID 逐条比对(rond 每次导出都会重排 Z_PK,编号认不出人),
  新增合入、冲突逐条选、仅站内有逐条勾选删除,全在一个事务里执行;
  合入的行写回 entity_raw,保证再传同一份备份时差异归零
- 地点按坐标对齐:站点的「补全地址」会把地点改名,拿名称当键会让一整个地点的
  到访全变成假冲突
- 附属数据(备注/结论/访客票/图片)从 dataset_id+src_pk 换成坐标键,换数据集不再失联;
  访客票没有 dataset_id,回填按活跃数据集认领
- 修正 contentHash 拿 Z_PK 当指纹的隐患,以及两处「src_pk 跨快照稳定」的错误注释

导出格式(.rondbackup / .fwss)保持原样,往返测试通过。
This commit is contained in:
tamakyi
2026-09-19 16:04:00 +08:00
parent 9e01923244
commit 30c502ce7b
43 changed files with 5968 additions and 170 deletions
+30 -4
View File
@@ -67,15 +67,41 @@ func insertEntityRaw(ctx context.Context, tx *sql.Tx, datasetID int64, rows map[
}
// contentHash 给备份内容算指纹,用来识别「内容没变、只是重新导出了一次」的上传。
// rond 每次导出连 WAL 一起打包,字节必然不同,文件摘要挡不住这种重复;
// 但同一批到访的 Z_PK 是稳定的,所以按到访自然键排序后取摘要即可。
// rond 每次导出连 WAL 一起打包,字节必然不同,文件摘要挡不住这种重复。
//
// 指纹按**到访/活动/地点的稳定标识**算,绝不能掺 Z_PK:rond 每次导出/同步都会重排
// 编号(实测两份备份里 822 条到访的 UUID 完全一致、Z_PK 100% 不同),拿编号当指纹会把
// 「同一批数据重导一次」判成新内容 —— 于是新建数据集,而备注与投票是按 dataset_id
// 隔离的,换了数据集就再也看不见。
//
// 没有 UUID 的老备份退回「到达时间 + 地点坐标」,仍比编号可靠。
func contentHash(b *rond.Backup) string {
keys := make([]string, 0, len(b.Visits))
locCoord := make(map[int]string, len(b.Locations))
keys := make([]string, 0, len(b.Visits)+len(b.Locations)+len(b.Activities))
for _, l := range b.Locations {
locCoord[l.SrcPK] = fmt.Sprintf("%.6f/%.6f", l.Lat, l.Lon)
keys = append(keys, "L"+l.Name+"/"+locCoord[l.SrcPK])
}
for _, v := range b.Visits {
if v.Arrival == nil {
continue // 导入时就被跳过的行,不该影响指纹
}
if u := b.VisitUUID[v.SrcPK]; u != "" {
keys = append(keys, "V"+u)
continue
}
keys = append(keys, fmt.Sprintf("%d|%d", v.SrcPK, v.Arrival.Unix()))
loc := ""
if v.LocationSrcPK != nil {
loc = locCoord[*v.LocationSrcPK]
}
keys = append(keys, fmt.Sprintf("T%d/%s", v.Arrival.Unix(), loc))
}
for _, a := range b.Activities {
if u := b.ActivityUUID[a.SrcPK]; u != "" {
keys = append(keys, "A"+u)
continue
}
keys = append(keys, "A"+a.Name)
}
sort.Strings(keys)
h := sha256.New()