备份与迷雾的差异分析、合并执行,附属数据改挂坐标键
备份(.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:
@@ -0,0 +1,94 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image/png"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"rond-with-you/internal/fog"
|
||||
)
|
||||
|
||||
// 差异瓦片要反复读同一份快照、同一份库内迷雾,单槽缓存住两边的块集即可——
|
||||
// 站长一次只会看一份预览。缓存里的 map 加载完就只读,渲染时并发遍历是安全的。
|
||||
var (
|
||||
diffSetsMu sync.Mutex
|
||||
diffSetsToken string
|
||||
diffSetsAt time.Time
|
||||
diffSetsCur map[[2]int][]byte
|
||||
diffSetsInc map[[2]int][]byte
|
||||
)
|
||||
|
||||
// diffSetsTTL 短于暂存的 2 小时:库内迷雾随时可能被别的操作改掉,
|
||||
// 缓存太久会让差异图与页面上那组数字对不上。
|
||||
const diffSetsTTL = 10 * time.Minute
|
||||
|
||||
func (s *Server) fogDiffSets(token string) (cur, inc map[[2]int][]byte, err error) {
|
||||
diffSetsMu.Lock()
|
||||
defer diffSetsMu.Unlock()
|
||||
if token == diffSetsToken && time.Since(diffSetsAt) < diffSetsTTL {
|
||||
return diffSetsCur, diffSetsInc, nil
|
||||
}
|
||||
data, err := s.loadStashedFog(token)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
blocks, _, err := fog.Decode(data)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
ix, err := s.fogIndex()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
cur = map[[2]int][]byte{}
|
||||
ix.View(func(m map[[2]int][]byte) {
|
||||
for k, bm := range m {
|
||||
cur[k] = bm
|
||||
}
|
||||
})
|
||||
inc = make(map[[2]int][]byte, len(blocks))
|
||||
for _, b := range blocks {
|
||||
inc[[2]int{b.GX, b.GY}] = b.Bitmap
|
||||
}
|
||||
diffSetsToken, diffSetsCur, diffSetsInc, diffSetsAt = token, cur, inc, time.Now()
|
||||
return cur, inc, nil
|
||||
}
|
||||
|
||||
// adminFogDiffTile 渲染一张差异瓦片:绿=叠加会新增、灰=两边都有、红=整体替换会丢。
|
||||
// 只给站长看,不涉及遮罩。
|
||||
func (s *Server) adminFogDiffTile(w http.ResponseWriter, r *http.Request) {
|
||||
z, err1 := strconv.Atoi(r.PathValue("z"))
|
||||
x, err2 := strconv.Atoi(r.PathValue("x"))
|
||||
y, err3 := strconv.Atoi(r.PathValue("y"))
|
||||
if err1 != nil || err2 != nil || err3 != nil || z < 0 || z > 22 ||
|
||||
x < 0 || y < 0 || x >= 1<<uint(z) || y >= 1<<uint(z) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
cur, inc, err := s.fogDiffSets(r.PathValue("token"))
|
||||
if err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
crs := r.URL.Query().Get("crs")
|
||||
if crs != "gcj02" && crs != "wgs84" {
|
||||
crs = s.cfg.MapCRS
|
||||
}
|
||||
img := fog.RenderDiffTile(cur, inc, z, x, y, crs)
|
||||
if img == nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := png.Encode(&buf, img); err != nil {
|
||||
s.serverError(w, r, err)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "image/png")
|
||||
// 令牌是一次性的,内容也只给站长看,不落共享缓存
|
||||
w.Header().Set("Cache-Control", "private, max-age=300")
|
||||
w.Write(buf.Bytes())
|
||||
}
|
||||
Reference in New Issue
Block a user