Files
rond-with-you/tools/fogprobe/main.go
T
tamakyi 30c502ce7b 备份与迷雾的差异分析、合并执行,附属数据改挂坐标键
备份(.rondbackup)与迷雾(.fwss)各加一层「先看差异再动手」:

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

导出格式(.rondbackup / .fwss)保持原样,往返测试通过。
2026-09-19 16:04:00 +08:00

134 lines
3.0 KiB
Go

// 一次性探针:把一份 .fwss 与 PG 里的 fog_blocks 逐块比对,量化「叠加 / 替换」两种模式的影响。
package main
import (
"database/sql"
"encoding/hex"
"fmt"
"os"
"strconv"
"strings"
_ "github.com/jackc/pgx/v5/stdlib"
"rond-with-you/internal/fog"
)
func main() {
if len(os.Args) < 3 {
fmt.Println("用法: fogprobe <snapshot.fwss> <dsn> [userID]")
os.Exit(2)
}
uid := int64(1)
if len(os.Args) > 3 {
uid, _ = strconv.ParseInt(os.Args[3], 10, 64)
}
data, err := os.ReadFile(os.Args[1])
must(err)
inc, incCells, err := fog.Decode(data)
must(err)
fmt.Printf("快照 %s: %d 块 / %d 格\n", os.Args[1], len(inc), incCells)
pg, err := sql.Open("pgx", os.Args[2])
must(err)
defer pg.Close()
rows, err := pg.Query(`SELECT gbx, gby, bitmap FROM fog_blocks WHERE user_id=$1`, uid)
must(err)
local := map[[2]int][]byte{}
localCells := 0
for rows.Next() {
var gx, gy int
var bm []byte
must(rows.Scan(&gx, &gy, &bm))
local[[2]int{gx, gy}] = bm
localCells += popcount(bm)
}
rows.Close()
fmt.Printf("库内: %d 块 / %d 格\n", len(local), localCells)
var newBlocks, newCells int
var novBlock, overlap int
var overlapGain int
for _, b := range inc {
k := [2]int{b.GX, b.GY}
old, ok := local[k]
if !ok {
newBlocks++
newCells += popcount(b.Bitmap)
continue
}
novBlock++
gain := 0
for i := range b.Bitmap {
gain += poptab[b.Bitmap[i]&^old[i]]
}
if gain > 0 {
overlap++
overlapGain += gain
}
}
var lostBlocks, lostCells, lostFull int
for k, bm := range local {
found := false
for _, b := range inc {
if b.GX == k[0] && b.GY == k[1] {
found = true
break
}
}
if !found {
lostBlocks++
lostCells += popcount(bm)
lostFull++
}
}
fmt.Printf("\n【叠加合并 overlay】\n")
fmt.Printf(" 新增整块: %d 块(+%d 格)\n", newBlocks, newCells)
fmt.Printf(" 与已有块重叠: %d 块,其中 %d 块含新格(+%d 格)\n", novBlock, overlap, overlapGain)
fmt.Printf(" 合计新增格: %d,合并后总量: %d 块 / %d 格\n",
newCells+overlapGain, len(local)+newBlocks, localCells+newCells+overlapGain)
fmt.Printf("\n【整体替换 cover】\n")
fmt.Printf(" 库里现有 %d 块会被丢弃(快照里完全没有这些块),损失 %d 格\n", lostFull, lostCells)
fmt.Printf(" 替换后总量: %d 块 / %d 格\n", len(inc), incCells)
fmt.Printf(" 净变化: %+d 块 / %+d 格\n", len(inc)-len(local), incCells-localCells)
fmt.Printf("\n【其它图层】\n")
layers, err := fog.RawLayers(data)
if err == nil {
for k, v := range layers {
fmt.Printf(" %s (%d 字节, %s)\n", k, len(v), hex.EncodeToString(v[:min(8, len(v))]))
}
}
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
var poptab = func() [256]int {
var t [256]int
for i := range t {
t[i] = strings.Count(strconv.FormatUint(uint64(i), 2), "1")
}
return t
}()
func popcount(b []byte) int {
n := 0
for _, v := range b {
n += poptab[v]
}
return n
}
func must(err error) {
if err != nil {
fmt.Fprintln(os.Stderr, "错误:", err)
os.Exit(1)
}
}