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"
|
||||
)
|
||||
@ -37,6 +39,13 @@ type ArticleRepository interface {
|
||||
DeleteRunwayImage(ctx context.Context, imageID uint32) error
|
||||
// GetRunwayImageKey 取单张走秀图片的S4 key(删除前清理用);不存在返回空串。
|
||||
GetRunwayImageKey(ctx context.Context, imageID uint32) (string, error)
|
||||
// GetRunwayDupSources 批量反查重复源:给定一组走秀图片 id(即 dup_of 指向的源图 id),
|
||||
// 返回每张源图及其所属走秀(标题、品牌)的上下文,供编辑页标记重复图时展示来源。
|
||||
GetRunwayDupSources(ctx context.Context, imageIDs []uint32) (map[uint32]dto.DupSource, error)
|
||||
// GetRunwayImageRunwayID 取图片所属 runway_id(不过滤 is_deleted,软删后仍可取,供删图后重算去重)。
|
||||
GetRunwayImageRunwayID(ctx context.Context, imageID uint32) (uint32, error)
|
||||
// ReresolveRunwayDedup 删图后重算本画廊去重,消除陈旧 dup_of(见方法注释)。
|
||||
ReresolveRunwayDedup(ctx context.Context, runwayID uint32) error
|
||||
// SoftDeleteRunwayImages 软删某走秀的全部图片(级联下架用),返回被删图片的 key 列表。
|
||||
SoftDeleteRunwayImages(ctx context.Context, runwayID uint32) ([]string, error)
|
||||
// SetRunwayCover 更新走秀封面(同时回到图片列表首张)。
|
||||
@ -389,6 +398,128 @@ func (r *articleRepository) GetRunwayImageKey(ctx context.Context, imageID uint3
|
||||
return img.Image, nil
|
||||
}
|
||||
|
||||
// GetRunwayImageRunwayID 取图片所属 runway_id(不过滤 is_deleted,软删后仍可取,
|
||||
// 供编辑页删图后重算去重时定位所属画廊)。
|
||||
func (r *articleRepository) GetRunwayImageRunwayID(ctx context.Context, imageID uint32) (uint32, error) {
|
||||
var img model.BrandRunwayImage
|
||||
err := r.db.WithContext(ctx).Select("runway_id").Where("id = ?", imageID).First(&img).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return 0, nil
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return img.RunwayID, nil
|
||||
}
|
||||
|
||||
// nearDupExceptRunway 在 brand_runway_images 内做全局近重复检索(is_deleted=0),
|
||||
// 但排除自身与「同 runway 且 sort_order 在后的图」——后者防止重算时后入者反成源导致互指环。
|
||||
// 命中返回源图 id,无则 0。
|
||||
func nearDupExceptRunway(db *gorm.DB, phashBits string, threshold int, excludeID, runwayID, sortOrder uint32) (uint32, error) {
|
||||
l2Limit := math.Sqrt(float64(threshold))
|
||||
var row struct {
|
||||
ID uint32 `gorm:"column:id"`
|
||||
}
|
||||
if err := db.Table("brand_runway_images").
|
||||
Select("id, (phash <-> ?::vector) AS dist", phashBits).
|
||||
Where("(phash <-> ?::vector) <= ?", phashBits, l2Limit).
|
||||
Where("is_deleted = 0").
|
||||
Where("id <> ?", excludeID).
|
||||
Where("(runway_id <> ? OR sort_order < ?)", runwayID, sortOrder).
|
||||
Order("dist ASC").
|
||||
Limit(1).
|
||||
Scan(&row).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return row.ID, nil
|
||||
}
|
||||
|
||||
// ReresolveRunwayDedup 删除图片后重解本画廊去重:先清空全部存活图的 is_duplicate/dup_of,
|
||||
// 再按 sort_order 逐张以「全局近重复」重新判定(与入库 dedupImage 同口径:先入者为准)。
|
||||
//
|
||||
// 为什么需要:is_duplicate/dup_of 是入库时一次算死的存储标记,编辑页手动删图从不重算;
|
||||
// 删除「源图」后,剩余图仍挂陈旧 dup_of,导致编辑页误显「重复」,且「查看重复来源」指向已删
|
||||
// 源图(其 S4 文件可能已被清理)而打不开。重算后,被删源图不再作为参照,剩余近重复图重新确立
|
||||
// 「先入者为准」的源/副本关系,使「重复」标记始终反映当前真实图片。
|
||||
func (r *articleRepository) ReresolveRunwayDedup(ctx context.Context, runwayID uint32) error {
|
||||
var imgs []model.BrandRunwayImage
|
||||
if err := r.db.WithContext(ctx).
|
||||
Where("runway_id = ? AND is_deleted = 0", runwayID).
|
||||
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.BrandRunwayImage{}).
|
||||
Where("runway_id = ? AND is_deleted = 0", runwayID).
|
||||
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 := nearDupExceptRunway(tx, im.Phash.String, phash.DefaultThreshold, im.ID, runwayID, im.SortOrder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if src != 0 && src != im.ID {
|
||||
if err := tx.Model(&model.BrandRunwayImage{}).
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
// GetRunwayDupSources 批量反查重复源(见接口注释)。一次 IN 查询 + JOIN 走秀与品牌,
|
||||
// 避免逐图 N+1;源图可能已被删(查不到则跳过,不报错)。
|
||||
func (r *articleRepository) GetRunwayDupSources(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
|
||||
RunwayID uint32
|
||||
TitleEn string
|
||||
TitleCn string
|
||||
BrandEn string
|
||||
BrandCn string
|
||||
}
|
||||
var rows []srcRow
|
||||
err := r.db.WithContext(ctx).
|
||||
Table("brand_runway_images bri").
|
||||
Select("bri.id AS image_id, bri.image AS image, bri.runway_id AS runway_id, br.title_en AS title_en, br.title_cn AS title_cn, b.name_en AS brand_en, b.name_cn AS brand_cn").
|
||||
Joins("JOIN brand_runways br ON br.id = bri.runway_id").
|
||||
Joins("LEFT JOIN brands b ON b.id = br.brand_id").
|
||||
Where("bri.id IN ?", imageIDs).
|
||||
Where("bri.is_deleted = 0").
|
||||
Find(&rows).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, rw := range rows {
|
||||
title := rw.TitleEn
|
||||
if title == "" {
|
||||
title = rw.TitleCn
|
||||
}
|
||||
brand := rw.BrandEn
|
||||
if brand == "" {
|
||||
brand = rw.BrandCn
|
||||
}
|
||||
out[rw.ImageID] = dto.DupSource{Image: rw.Image, RecordID: rw.RunwayID, Title: title, Brand: brand}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// SoftDeleteRunwayImages 软删某走秀的全部图片(级联下架用)。
|
||||
// 先取非删除图片的 key 列表返回给调用方(用于后续S4孤儿清理),再统一置 is_deleted=1。
|
||||
func (r *articleRepository) SoftDeleteRunwayImages(ctx context.Context, runwayID uint32) ([]string, error) {
|
||||
|
||||
Reference in New Issue
Block a user