图片恢复:坐标来源全部合并,认不出时说清已查过哪些来源
真数据实测(扫描 40 个对象、只挂回 7 张)暴露了第一版的漏洞: 报错「数据集 #1 里没有编号 389 的地点」——来源能读出来,却不是上传那会儿的编号空间。 根因:srcPKCoords 是「第一个能读到的来源就返回」,而**换基线会覆盖 baselines/<id>.sqlite**, 当前那份未必是当年上传时用的编号;旧编号往往还躺在同一份底座的 .bak 留档里。 教训很直白:**「能读出数据」不等于「读对了那一版」**。 改了三处: 1. 来源全部读入合并(先读的优先):库里 places → entity_raw 里 ZLOCATION 的整行留档 (站点上删掉的地点,places 没了、留档可能还在)→ baselines/<id>.sqlite 及其每一份 .bak-*。 读不动的文件跳过换下一个,不再「第一份成功就返回」。 2. 同一个编号保留多组候选坐标,逐个试到能在当前数据集里对上为止 (站点纠过偏、或不同来源算出的坐标不一样,只认第一组会白白认不出)。 3. 认不出要能排查:理由写明「已查来源 …」与试过的坐标;提示条按**数据集归并**上报 (「数据集 #1:31 张(编号 389、390…),已查来源 …」),不再刷「另有 31 张」; 完整清单(前 30 条)进服务端日志。 新增用例:当前底座缺编号、换基线留档里才有(正是用户遇到的形状)⇒ 必须救得回; 以及「真的认不出」时要带出已查来源。全量验收 PASS=332 FAIL=0 SKIP=0。
This commit is contained in:
@@ -301,8 +301,9 @@ func TestRecoverPlaceImagesFallsBackToBaseline(t *testing.T) {
|
||||
if gotKey != placeKeyOfCoords(lat, lon) {
|
||||
t.Errorf("认出的地点不对:%q", gotKey)
|
||||
}
|
||||
if !strings.Contains(strings.Join(rep.Notes, " "), "底座库") {
|
||||
t.Errorf("应当说明这份坐标是从底座库来的,实际 %+v", rep)
|
||||
// 报告要写明这份坐标是从哪个底座库认出来的(换基线会覆盖底座文件,来源说不清没法排查)
|
||||
if !strings.Contains(strings.Join(rep.Notes, " "), fmt.Sprintf("%d.sqlite", ghost)) {
|
||||
t.Errorf("应当说明这份坐标是从底座库 %d.sqlite 认出来的,实际 %+v", ghost, rep)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,3 +456,84 @@ func TestRecoverPlaceImagesReportsObjectStorageFailure(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestRecoverPlaceImagesFallsBackToSwappedBaselineBackup 钉住「换了底座就认不出编号」这个真实场景。
|
||||
//
|
||||
// 换基线会**覆盖** baselines/<id>.sqlite,当前那份未必是上传那会儿的编号空间。
|
||||
// 用户的实测反馈就是这样:能读出 ZLOCATION,却报「数据集 #1 里没有编号 389 的地点」,
|
||||
// 而旧编号很可能还在换基线留下的 `.bak` 留档里。所以坐标来源必须**全部读进来合并**,
|
||||
// 不能「第一个能读的就算了」。
|
||||
func TestRecoverPlaceImagesFallsBackToSwappedBaselineBackup(t *testing.T) {
|
||||
f := newMovementFixture(t)
|
||||
ctx, srv := f.ctx, f.srv
|
||||
srcPK, _, lat, lon := pickPlaceWithCoords(t, f)
|
||||
isolateImageState(t, f, lat, lon)
|
||||
|
||||
const ghost = 987655
|
||||
base := srv.baselinePath(ghost)
|
||||
if err := os.MkdirAll(filepath.Dir(base), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// 当前底座:换了别的一套编号,里面**没有**我们这个编号
|
||||
writeGhostBaseline(t, base, 1001, 0.1, 0.1)
|
||||
// 换基线留下的留档:才是上传那会儿的编号空间
|
||||
bak := base + ".bak-20260101-000000"
|
||||
writeGhostBaseline(t, bak, srcPK, lat, lon)
|
||||
t.Cleanup(func() { os.Remove(base); os.Remove(bak) })
|
||||
|
||||
put := func(name string, ds int, pk int) string {
|
||||
key := filepath.ToSlash(filepath.Join(strconv.Itoa(ds), strconv.Itoa(pk),
|
||||
fmt.Sprintf("%s-%d.webp", name, time.Now().UnixNano())))
|
||||
dst := filepath.Join(f.cfg.MediaDir(), filepath.FromSlash(key))
|
||||
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(dst, fakeWebP(0x33, 64), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return key
|
||||
}
|
||||
want := put("swapped", ghost, srcPK)
|
||||
// 另一个数据集连留档都没有:应当认不出,但要把「已查过哪些来源」说出来
|
||||
const none = 987656
|
||||
nobase := srv.baselinePath(none)
|
||||
writeGhostBaseline(t, nobase, 2002, 0.2, 0.2)
|
||||
t.Cleanup(func() { os.Remove(nobase) })
|
||||
nope := put("unknown", none, srcPK)
|
||||
|
||||
rep, err := srv.recoverPlaceImages(ctx, true)
|
||||
if err != nil {
|
||||
t.Fatalf("恢复失败: %v", err)
|
||||
}
|
||||
if imageRows(t, f, want) != 1 {
|
||||
t.Fatalf("没从底座留档里认回编号(认不出 %d 张:%+v)", len(rep.Unresolved), rep.Unresolved)
|
||||
}
|
||||
var gotKey string
|
||||
if err := f.pg.QueryRowContext(ctx, `SELECT place_key FROM place_images WHERE object_key=$1`, want).
|
||||
Scan(&gotKey); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if gotKey != rond.PlaceCoordKey(lat, lon) {
|
||||
t.Errorf("认出的地点不对:%q", gotKey)
|
||||
}
|
||||
if !strings.Contains(strings.Join(rep.Notes, " "), filepath.Base(bak)) {
|
||||
t.Errorf("报告里应当写明用到了留档 %s,实际 %v", filepath.Base(bak), rep.Notes)
|
||||
}
|
||||
|
||||
// 认不出的那张:理由要带上「已查来源」,否则站长判断不出该去找哪份包
|
||||
var reason string
|
||||
for _, u := range rep.Unresolved {
|
||||
if u.ObjectKey == nope {
|
||||
reason = u.Reason
|
||||
}
|
||||
}
|
||||
if reason == "" {
|
||||
t.Fatalf("这张本该认不出,实际没有出现在清单里:%+v", rep.Unresolved)
|
||||
}
|
||||
if !strings.Contains(reason, "已查来源") {
|
||||
t.Errorf("认不出的理由里没写已查过哪些来源(没法排查):%q", reason)
|
||||
}
|
||||
if !containsStr(f.imageURLs(t, srcPK), "/media/"+want) {
|
||||
t.Errorf("页面口径看不到这张图:%v", f.imageURLs(t, srcPK))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user