备份与迷雾的差异分析、合并执行,附属数据改挂坐标键
备份(.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:
@@ -3,6 +3,7 @@ package web
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"database/sql"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -26,6 +27,11 @@ const (
|
||||
imageUploadMem = 8 << 20 // multipart 解析用的内存上限
|
||||
)
|
||||
|
||||
// placeKeyExpr 把 places p 那一行的坐标换算成 place_key,图片、备注、结论共用这个键。
|
||||
// **必须与 rond.PlaceCoordKey 以及 notes/votes 包里的同款表达式逐位一致**:
|
||||
// fmt.Sprintf("%.6f|%.6f") ↔ to_char(x,'FM999990.000000')||'|'||to_char(y,'FM999990.000000')
|
||||
const placeKeyExpr = `to_char(p.lat,'FM999990.000000')||'|'||to_char(p.lon,'FM999990.000000')`
|
||||
|
||||
// PlaceImage 是模板里用的地点图片。
|
||||
type PlaceImage struct {
|
||||
ID int64
|
||||
@@ -57,14 +63,18 @@ func (s *Server) savePlaceImage(ctx context.Context, datasetID int64, srcPK int,
|
||||
if err := s.media.Save(ctx, backend, key, data, "image/webp"); err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
pkey, err := s.placeKeyOf(ctx, datasetID, srcPK)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
var id int64
|
||||
if err := s.db.QueryRowContext(ctx, `INSERT INTO place_images
|
||||
(dataset_id, src_pk, storage, object_key, width, height, bytes, position)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,
|
||||
COALESCE((SELECT max(position)+1 FROM place_images WHERE dataset_id=$1 AND src_pk=$2), 0))
|
||||
(dataset_id, src_pk, place_key, storage, object_key, width, height, bytes, position)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,
|
||||
COALESCE((SELECT max(position)+1 FROM place_images WHERE place_key=$3), 0))
|
||||
ON CONFLICT (dataset_id, src_pk, object_key) DO UPDATE SET bytes=EXCLUDED.bytes
|
||||
RETURNING id`,
|
||||
datasetID, srcPK, backend, key, w, h, int64(len(data))).Scan(&id); err != nil {
|
||||
datasetID, srcPK, pkey, backend, key, w, h, int64(len(data))).Scan(&id); err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
return id, s.media.PublicURL(backend, key), nil
|
||||
@@ -95,9 +105,15 @@ func (s *Server) adminPlaceImageUpload(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
pkey, err := s.placeKeyOf(ctx, ds.ID, src)
|
||||
if err != nil {
|
||||
s.serverError(w, r, err)
|
||||
return
|
||||
}
|
||||
var count int
|
||||
// 按坐标键数:换数据集后图片仍跟着同一个地点,配额也要跟着
|
||||
if err := s.db.QueryRowContext(ctx, `SELECT count(*) FROM place_images
|
||||
WHERE dataset_id=$1 AND src_pk=$2`, ds.ID, src).Scan(&count); err != nil {
|
||||
WHERE place_key=$1`, pkey).Scan(&count); err != nil {
|
||||
s.serverError(w, r, err)
|
||||
return
|
||||
}
|
||||
@@ -196,10 +212,28 @@ func (s *Server) imageBackend(ctx context.Context) (string, error) {
|
||||
return setting.MediaBackendLocal, nil
|
||||
}
|
||||
|
||||
// placeKeyOf 把某个数据集里的地点编号换算成坐标键。
|
||||
// 换数据集之后仍靠它认出「还是同一个地点」,所以图片、备注、结论都挂在这上面。
|
||||
func (s *Server) placeKeyOf(ctx context.Context, datasetID int64, srcPK int) (string, error) {
|
||||
var key string
|
||||
err := s.db.QueryRowContext(ctx, `SELECT `+placeKeyExpr+` FROM places p
|
||||
WHERE p.dataset_id=$1 AND p.src_pk=$2 AND p.lat IS NOT NULL AND p.lon IS NOT NULL`,
|
||||
datasetID, srcPK).Scan(&key)
|
||||
if err == sql.ErrNoRows {
|
||||
return "", fmt.Errorf("地点不存在或没有坐标")
|
||||
}
|
||||
return key, err
|
||||
}
|
||||
|
||||
// placeImagesOf 读某个地点的图片,按 position 排序。
|
||||
// 按坐标键查而不是 (dataset_id, src_pk):换一份备份之后图片还跟着同一个地点。
|
||||
func (s *Server) placeImagesOf(ctx context.Context, datasetID int64, srcPK int) ([]PlaceImage, error) {
|
||||
pkey, err := s.placeKeyOf(ctx, datasetID, srcPK)
|
||||
if err != nil {
|
||||
return nil, nil // 地点没有坐标就当作没有图
|
||||
}
|
||||
rows, err := s.db.QueryContext(ctx, `SELECT id, storage, object_key, width, height, bytes
|
||||
FROM place_images WHERE dataset_id=$1 AND src_pk=$2 ORDER BY position, id`, datasetID, srcPK)
|
||||
FROM place_images WHERE place_key=$1 ORDER BY position, id`, pkey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user