update
This commit is contained in:
@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -63,8 +62,6 @@ type IngestRepository interface {
|
||||
CreateStreetSnapWithImages(ctx context.Context, snap *model.StreetSnap, imgs []model.StreetSnapImage) (uint32, error)
|
||||
// ReuseRejectedStreetSnap 复用一条 rejected 街拍记录(事务内完成)。
|
||||
ReuseRejectedStreetSnap(ctx context.Context, id uint32, snap *model.StreetSnap, imgs []model.StreetSnapImage) error
|
||||
// FindNearDuplicateImage 在给定图片表中按 dHash 汉明距离检索近重复,返回命中行 id(仅取最近一条)。
|
||||
FindNearDuplicateImage(ctx context.Context, tables []string, phashBits string, threshold int) (dupID uint32, found bool, err error)
|
||||
// ListJobs 按 id 倒序分页列出入库任务(用于后台监控页)。offset/limit 控制分页。
|
||||
ListJobs(ctx context.Context, offset, limit int) ([]model.IngestJob, error)
|
||||
// CountJobs 返回 ingest_jobs 总条数(用于分页计算总页数)。
|
||||
@ -409,13 +406,16 @@ func (r *ingestRepository) BackfillRunwayPublishedAt(ctx context.Context, runway
|
||||
return nil
|
||||
}
|
||||
// 仅当目标 season_code 不与同品牌其它行冲突时才补全 year / season_code。
|
||||
// 注意:占位符顺序必须是 (runwayID, targetSeasonCode) —— 本 Where 有两个 ?,
|
||||
// 漏传 runwayID 会让 season_code 落到 id 槽位,生成 `WHERE id = 'FW27'`
|
||||
// 这种必然语法错误的 SQL(该缺陷曾导致「重爬已存在走秀」的任务无限重试)。
|
||||
return r.db.WithContext(ctx).
|
||||
Model(&model.BrandRunway{}).
|
||||
Where("id = ? AND year = 0 AND is_deleted = 0 "+
|
||||
"AND NOT EXISTS (SELECT 1 FROM brand_runways b "+
|
||||
"WHERE b.brand_id = brand_runways.brand_id AND b.season_code = ? "+
|
||||
"AND b.collection_type = brand_runways.collection_type AND b.id <> brand_runways.id AND b.is_deleted = 0)",
|
||||
targetSeasonCode).
|
||||
runwayID, targetSeasonCode).
|
||||
Updates(map[string]any{
|
||||
"year": year,
|
||||
"season_code": targetSeasonCode,
|
||||
@ -570,39 +570,6 @@ func (r *ingestRepository) RetryJob(ctx context.Context, id uint32) error {
|
||||
}).Error
|
||||
}
|
||||
|
||||
// FindNearDuplicateImage 在给定图片表中按 dHash 汉明距离检索近重复,取距离 ≤ threshold 的最近一条。
|
||||
// 算子用 pgvector 的 L2(<->);因 phash 是 {0,1}^64 向量,L2² == 汉明距离,故 L2 阈值 = sqrt(threshold)。
|
||||
// phashBits 为 vector(64) 二进制向量串;NULL 的 phash 不参与比较。
|
||||
//
|
||||
// 只比对未软删的行(is_deleted = 0):已被软删(审核剔除 / 复用重审)的旧图不应再作为
|
||||
// 「重复」的参照。注意复用驳回行的时序 —— 去重发生在软删旧图**之前**,那一刻旧行仍是
|
||||
// is_deleted = 0、会被正常命中;dup_of 只是人工留痕,不承担可见性语义。
|
||||
// 本方法被 runway / street 两条入库路径共用,过滤对两者语义一致。
|
||||
func (r *ingestRepository) FindNearDuplicateImage(ctx context.Context, tables []string, phashBits string, threshold int) (uint32, bool, error) {
|
||||
// 汉明阈值转 L2 阈值:phash 为 {0,1}^64 向量,L2² == 汉明距离,故 L2 阈值 = sqrt(汉明阈值)。
|
||||
l2Limit := math.Sqrt(float64(threshold))
|
||||
for _, t := range tables {
|
||||
var row struct {
|
||||
ID uint32 `gorm:"column:id"`
|
||||
Dist float64 `gorm:"column:dist"`
|
||||
}
|
||||
err := r.db.WithContext(ctx).Table(t).
|
||||
Select("id, (phash <-> ?::vector) AS dist", phashBits).
|
||||
Where("(phash <-> ?::vector) <= ?", phashBits, l2Limit).
|
||||
Where("is_deleted = 0").
|
||||
Order("dist ASC").
|
||||
Limit(1).
|
||||
Scan(&row).Error
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
if row.ID != 0 {
|
||||
return row.ID, true, nil
|
||||
}
|
||||
}
|
||||
return 0, false, nil
|
||||
}
|
||||
|
||||
// isDuplicateKey 兜底:gorm 的 ErrDuplicatedKey 在不同驱动下的封装不一定一致,
|
||||
// 命中驱动返回的唯一约束冲突错误信息更稳(兼容 MySQL/PostgreSQL/SQLite 不同措辞)。
|
||||
func isDuplicateKey(err error) bool {
|
||||
|
||||
Reference in New Issue
Block a user