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