update
This commit is contained in:
@ -3,11 +3,13 @@ package repository
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"fashionapi/internal/dto"
|
||||
"fashionapi/internal/model"
|
||||
"fashionapi/internal/pkg/phash"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@ -39,6 +41,13 @@ type StreetSnapRepository interface {
|
||||
DeleteSnapImage(ctx context.Context, imageID uint32) error
|
||||
// GetSnapImageKey 取单张街拍图片的S4 key(删除前清理用);不存在返回空串。
|
||||
GetSnapImageKey(ctx context.Context, imageID uint32) (string, error)
|
||||
// GetStreetSnapDupSources 批量反查重复源:给定一组街拍图片 id(dup_of 指向的源图 id),
|
||||
// 返回每张源图及其所属街拍的上下文,供编辑页标记重复图时展示来源。
|
||||
GetStreetSnapDupSources(ctx context.Context, imageIDs []uint32) (map[uint32]dto.DupSource, error)
|
||||
// GetSnapImageRunwayID 取图片所属 snap_id(不过滤 is_deleted,软删后仍可取,供删图后重算去重)。
|
||||
GetSnapImageRunwayID(ctx context.Context, imageID uint32) (uint32, error)
|
||||
// ReresolveSnapDedup 删图后重算本专辑去重,消除陈旧 dup_of(见方法注释)。
|
||||
ReresolveSnapDedup(ctx context.Context, snapID uint32) error
|
||||
// SoftDeleteSnapImages 软删某街拍的全部图片(级联下架用),返回被删图片的 key 列表。
|
||||
SoftDeleteSnapImages(ctx context.Context, snapID uint32) ([]string, error)
|
||||
// SetSnapCover 更新街拍封面。
|
||||
@ -333,6 +342,117 @@ func (r *streetSnapRepository) GetSnapImageKey(ctx context.Context, imageID uint
|
||||
return img.Image, nil
|
||||
}
|
||||
|
||||
// GetSnapImageRunwayID 取图片所属 snap_id(不过滤 is_deleted,软删后仍可取,供编辑页删图后重算去重)。
|
||||
func (r *streetSnapRepository) GetSnapImageRunwayID(ctx context.Context, imageID uint32) (uint32, error) {
|
||||
var img model.StreetSnapImage
|
||||
err := r.db.WithContext(ctx).Select("snap_id").Where("id = ?", imageID).First(&img).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return 0, nil
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return img.SnapID, nil
|
||||
}
|
||||
|
||||
// nearDupExceptSnap 在 street_snap_images 内做全局近重复检索(is_deleted=0),但排除自身与
|
||||
// 「同 snap 且 sort_order 在后的图」——防止重算时后入者反成源导致互指环。命中返回源图 id,无则 0。
|
||||
func nearDupExceptSnap(db *gorm.DB, phashBits string, threshold int, excludeID, snapID, sortOrder uint32) (uint32, error) {
|
||||
l2Limit := math.Sqrt(float64(threshold))
|
||||
var row struct {
|
||||
ID uint32 `gorm:"column:id"`
|
||||
}
|
||||
if err := db.Table("street_snap_images").
|
||||
Select("id, (phash <-> ?::vector) AS dist", phashBits).
|
||||
Where("(phash <-> ?::vector) <= ?", phashBits, l2Limit).
|
||||
Where("is_deleted = 0").
|
||||
Where("id <> ?", excludeID).
|
||||
Where("(snap_id <> ? OR sort_order < ?)", snapID, sortOrder).
|
||||
Order("dist ASC").
|
||||
Limit(1).
|
||||
Scan(&row).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return row.ID, nil
|
||||
}
|
||||
|
||||
// ReresolveSnapDedup 删除图片后重解本专辑去重:先清空全部存活图的 is_duplicate/dup_of,
|
||||
// 再按 sort_order 逐张以「全局近重复」重新判定(与入库 dedupImage 同口径:先入者为准)。
|
||||
//
|
||||
// 为什么需要:is_duplicate/dup_of 是入库时一次算死的存储标记,编辑页手动删图从不重算;删除「源图」
|
||||
// 后剩余图仍挂陈旧 dup_of,导致编辑页误显「重复」且「查看重复来源」指向已删源图而打不开。重算后
|
||||
// 被删源图不再作为参照,剩余近重复图重新确立「先入者为准」的源/副本关系。
|
||||
func (r *streetSnapRepository) ReresolveSnapDedup(ctx context.Context, snapID uint32) error {
|
||||
var imgs []model.StreetSnapImage
|
||||
if err := r.db.WithContext(ctx).
|
||||
Where("snap_id = ? AND is_deleted = 0", snapID).
|
||||
Order("sort_order ASC, id ASC").
|
||||
Find(&imgs).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if len(imgs) == 0 {
|
||||
return nil
|
||||
}
|
||||
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Model(&model.StreetSnapImage{}).
|
||||
Where("snap_id = ? AND is_deleted = 0", snapID).
|
||||
Updates(map[string]any{"is_duplicate": 0, "dup_of": ""}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
for _, im := range imgs {
|
||||
if !im.Phash.Valid || im.Phash.String == "" {
|
||||
continue
|
||||
}
|
||||
src, err := nearDupExceptSnap(tx, im.Phash.String, phash.DefaultThreshold, im.ID, snapID, im.SortOrder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if src != 0 && src != im.ID {
|
||||
if err := tx.Model(&model.StreetSnapImage{}).
|
||||
Where("id = ? AND is_deleted = 0", im.ID).
|
||||
Updates(map[string]any{"is_duplicate": 1, "dup_of": strconv.FormatUint(uint64(src), 10)}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// GetStreetSnapDupSources 批量反查重复源(见接口注释)。街拍无品牌关联;一次 IN 查询 + JOIN。
|
||||
func (r *streetSnapRepository) GetStreetSnapDupSources(ctx context.Context, imageIDs []uint32) (map[uint32]dto.DupSource, error) {
|
||||
out := make(map[uint32]dto.DupSource, len(imageIDs))
|
||||
if len(imageIDs) == 0 {
|
||||
return out, nil
|
||||
}
|
||||
type srcRow struct {
|
||||
ImageID uint32
|
||||
Image string
|
||||
SnapID uint32
|
||||
Title string
|
||||
TitleCn string
|
||||
}
|
||||
var rows []srcRow
|
||||
err := r.db.WithContext(ctx).
|
||||
Table("street_snap_images ssi").
|
||||
Select("ssi.id AS image_id, ssi.image AS image, ssi.snap_id AS snap_id, ss.title AS title, ss.title_cn AS title_cn").
|
||||
Joins("JOIN street_snaps ss ON ss.id = ssi.snap_id").
|
||||
Where("ssi.id IN ?", imageIDs).
|
||||
Where("ssi.is_deleted = 0").
|
||||
Find(&rows).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, rw := range rows {
|
||||
title := rw.Title
|
||||
if title == "" {
|
||||
title = rw.TitleCn
|
||||
}
|
||||
out[rw.ImageID] = dto.DupSource{Image: rw.Image, RecordID: rw.SnapID, Title: title}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// SoftDeleteSnapImages 软删某街拍的全部图片(级联下架用)。
|
||||
// 先取非删除图片的 key 列表返回给调用方(用于后续S4孤儿清理),再统一置 is_deleted=1。
|
||||
func (r *streetSnapRepository) SoftDeleteSnapImages(ctx context.Context, snapID uint32) ([]string, error) {
|
||||
|
||||
Reference in New Issue
Block a user