update
This commit is contained in:
@ -35,19 +35,30 @@ type IngestRepository interface {
|
||||
MarkFailed(ctx context.Context, id uint32, errMsg string) error
|
||||
// ReserveNonce 写入一次性随机串;若已存在(重放)返回 ok=false。
|
||||
ReserveNonce(ctx context.Context, nonce string) (ok bool, err error)
|
||||
// RunwayEntityState 按实体键(brand_id + season_code + collection_type)查正式表既有行,返回其状态。
|
||||
// worker 据此分三支:pending/published → 直接放弃本次入库;rejected → 复用该行重审;未命中 → 新建。
|
||||
RunwayEntityState(ctx context.Context, brandID uint32, seasonCode, collectionType string) (id uint32, status string, found bool, err error)
|
||||
// RunwayEntityState 按实体键(brand_id + season_code + collection_type)查正式表既有行,
|
||||
// 返回其状态与是否已被软删(isDeleted)。worker 据此分四支:
|
||||
// - 命中 pending/published → 直接放弃本次入库(ingestSkip)
|
||||
// - 命中 rejected → 复用该行重审(ingestReuseRejected)
|
||||
// - 命中但已软删 → 跳过重爬(删除即永久,不再因重爬复活)
|
||||
// - 未命中 → 新建(ingestCreate)
|
||||
// 注意:本查询不再以 is_deleted = 0 为过滤条件(旧版口径),否则软删行会被当成「未命中」
|
||||
// 而重新入库,违背「删除即永久」。优先取活着行(ORDER BY is_deleted ASC, id DESC)。
|
||||
RunwayEntityState(ctx context.Context, brandID uint32, seasonCode, collectionType string) (id uint32, status string, found bool, isDeleted bool, err error)
|
||||
// CreateRunwayWithImages 新建走秀正式行(status=pending)并写入图片,返回记录主键(事务内完成)。
|
||||
CreateRunwayWithImages(ctx context.Context, rw *model.BrandRunway, imgs []model.BrandRunwayImage) (uint32, error)
|
||||
// ReuseRejectedRunway 复用一条 rejected 走秀:覆盖内容字段、软删旧图、写入新图、置回 pending(事务内完成)。
|
||||
// 驳回因此不是永久黑名单:重爬同一实体即重新送审,且始终「一个实体一行」。
|
||||
ReuseRejectedRunway(ctx context.Context, id uint32, rw *model.BrandRunway, imgs []model.BrandRunwayImage) error
|
||||
// BackfillRunwayPublishedAt 临时回填:给已存在走秀补 published_at,并在标题缺年份时
|
||||
// 用发布日期年份补全 year / season_code(不重下图片)。回填为一次性操作,按主键 id 更新。
|
||||
// publishedAt 为 Unix 时间戳(秒),0 表示未抽取。
|
||||
BackfillRunwayPublishedAt(ctx context.Context, runwayID uint32, publishedAt uint32, year uint16, targetSeasonCode string) error
|
||||
// RepairRunwayDetailParents 入库后按 sort_order 回填走秀细节图的 parent_image_id(细节图挂到最近主图)。
|
||||
RepairRunwayDetailParents(ctx context.Context, runwayID uint32) error
|
||||
// StreetSnapEntityState 按实体键(city + year + title)查正式表既有行,返回其状态。
|
||||
// StreetSnapEntityState 按实体键(city + year + title)查正式表既有行,返回其状态与是否已被软删。
|
||||
// 与 RunwayEntityState 同源:不再以 is_deleted = 0 过滤,软删实体跳过重爬(删除即永久)。
|
||||
// title 用 COALESCE 与唯一索引 uq_ss_entity 的表达式保持一致。
|
||||
StreetSnapEntityState(ctx context.Context, city string, year uint16, title string) (id uint32, status string, found bool, err error)
|
||||
StreetSnapEntityState(ctx context.Context, city string, year uint16, title string) (id uint32, status string, found bool, isDeleted bool, err error)
|
||||
// CreateStreetSnapWithImages 新建街拍正式行(status=pending)并写入图片(事务内完成)。
|
||||
CreateStreetSnapWithImages(ctx context.Context, snap *model.StreetSnap, imgs []model.StreetSnapImage) (uint32, error)
|
||||
// ReuseRejectedStreetSnap 复用一条 rejected 街拍记录(事务内完成)。
|
||||
@ -248,24 +259,29 @@ func (r *ingestRepository) ReserveNonce(ctx context.Context, nonce string) (bool
|
||||
|
||||
// ---- 入库直写正式表(单表发布模型)----
|
||||
|
||||
func (r *ingestRepository) RunwayEntityState(ctx context.Context, brandID uint32, seasonCode, collectionType string) (uint32, string, bool, error) {
|
||||
// RunwayEntityState 按实体键(brand_id + season_code + collection_type)查正式表既有行。
|
||||
// 不再过滤 is_deleted:软删行也会被查到,由调用方据此跳过重爬(删除即永久)。
|
||||
// 优先取活着行(is_deleted ASC),保证「活着行 + 软删行同键共存」的存量数据下也返回活行。
|
||||
func (r *ingestRepository) RunwayEntityState(ctx context.Context, brandID uint32, seasonCode, collectionType string) (uint32, string, bool, bool, error) {
|
||||
var row struct {
|
||||
ID uint32 `gorm:"column:id"`
|
||||
Status string `gorm:"column:status"`
|
||||
ID uint32 `gorm:"column:id"`
|
||||
Status string `gorm:"column:status"`
|
||||
IsDeleted uint8 `gorm:"column:is_deleted"`
|
||||
}
|
||||
err := r.db.WithContext(ctx).
|
||||
Model(&model.BrandRunway{}).
|
||||
Select("id, status").
|
||||
Where("brand_id = ? AND season_code = ? AND collection_type = ? AND is_deleted = 0", brandID, seasonCode, collectionType).
|
||||
Select("id, status, is_deleted").
|
||||
Where("brand_id = ? AND season_code = ? AND collection_type = ?", brandID, seasonCode, collectionType).
|
||||
Order("is_deleted ASC, id DESC").
|
||||
Limit(1).
|
||||
Scan(&row).Error
|
||||
if err != nil {
|
||||
return 0, "", false, err
|
||||
return 0, "", false, false, err
|
||||
}
|
||||
if row.ID == 0 {
|
||||
return 0, "", false, nil
|
||||
return 0, "", false, false, nil
|
||||
}
|
||||
return row.ID, row.Status, true, nil
|
||||
return row.ID, row.Status, true, row.IsDeleted == 1, nil
|
||||
}
|
||||
|
||||
func (r *ingestRepository) CreateRunwayWithImages(ctx context.Context, rw *model.BrandRunway, imgs []model.BrandRunwayImage) (uint32, error) {
|
||||
@ -372,32 +388,72 @@ func (r *ingestRepository) RepairRunwayDetailParents(ctx context.Context, runway
|
||||
return nil
|
||||
}
|
||||
|
||||
// StreetSnapEntityState 按实体键(city + year + title)查正式表既有行,返回其状态。
|
||||
// BackfillRunwayPublishedAt 临时回填(一次性):按主键 id 给已存在走秀补 published_at;
|
||||
// 并在标题缺年份(year=0)时,用发布日期年份补全 year / season_code。
|
||||
//
|
||||
// 设计要点:
|
||||
// - published_at 总是写入(按 id,幂等,不会触犯唯一索引)。
|
||||
// - year / season_code 仅在「当前 year=0」且「目标 season_code 不与同品牌其它行冲突」时补全,
|
||||
// 避免触犯 (brand_id, season_code, collection_type) 唯一索引;冲突时只补 published_at,留待人工处理。
|
||||
// - 不重下图片。
|
||||
// - publishedAt 为 Unix 时间戳(秒),0 表示未抽取。
|
||||
func (r *ingestRepository) BackfillRunwayPublishedAt(ctx context.Context, runwayID uint32, publishedAt uint32, year uint16, targetSeasonCode string) error {
|
||||
now := time.Now().Unix()
|
||||
if err := r.db.WithContext(ctx).
|
||||
Model(&model.BrandRunway{}).
|
||||
Where("id = ? AND is_deleted = 0", runwayID).
|
||||
Updates(map[string]any{"published_at": publishedAt, "updated_at": now}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if year == 0 {
|
||||
return nil
|
||||
}
|
||||
// 仅当目标 season_code 不与同品牌其它行冲突时才补全 year / season_code。
|
||||
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).
|
||||
Updates(map[string]any{
|
||||
"year": year,
|
||||
"season_code": targetSeasonCode,
|
||||
"updated_at": now,
|
||||
}).Error
|
||||
}
|
||||
|
||||
// StreetSnapEntityState 按实体键(city + year + title)查正式表既有行,返回其状态与是否已被软删。
|
||||
// title 用 COALESCE 与唯一索引 uq_ss_entity 的表达式保持一致:唯一索引里 NULL 互不冲突,
|
||||
// 若这里写成裸 title,查重口径会比索引更严,导致重爬同城同年(title 为 NULL 的历史行)时误判未命中、
|
||||
// 进而插入一行注定被索引拒绝的记录。
|
||||
//
|
||||
// 入参 title 会先经 NormalizeStreetTitle 归一化:与落库口径(processStreet 同样归一化)保持一致,
|
||||
// 否则「空白 / 多空格」微调会被判为新实体,同城同年多出重复行(见设计规格 §12.4)。
|
||||
func (r *ingestRepository) StreetSnapEntityState(ctx context.Context, city string, year uint16, title string) (uint32, string, bool, error) {
|
||||
//
|
||||
// 不再过滤 is_deleted:软删行也会被查到,由调用方据此跳过重爬(删除即永久)。
|
||||
// 与 RunwayEntityState 同源,优先取活着行(ORDER BY is_deleted ASC, id DESC)。
|
||||
func (r *ingestRepository) StreetSnapEntityState(ctx context.Context, city string, year uint16, title string) (uint32, string, bool, bool, error) {
|
||||
title = NormalizeStreetTitle(title)
|
||||
var row struct {
|
||||
ID uint32 `gorm:"column:id"`
|
||||
Status string `gorm:"column:status"`
|
||||
ID uint32 `gorm:"column:id"`
|
||||
Status string `gorm:"column:status"`
|
||||
IsDeleted uint8 `gorm:"column:is_deleted"`
|
||||
}
|
||||
err := r.db.WithContext(ctx).
|
||||
Model(&model.StreetSnap{}).
|
||||
Select("id, status").
|
||||
Where("city = ? AND year = ? AND COALESCE(title, '') = ? AND is_deleted = 0", city, year, title).
|
||||
Select("id, status, is_deleted").
|
||||
Where("city = ? AND year = ? AND COALESCE(title, '') = ?", city, year, title).
|
||||
Order("is_deleted ASC, id DESC").
|
||||
Limit(1).
|
||||
Scan(&row).Error
|
||||
if err != nil {
|
||||
return 0, "", false, err
|
||||
return 0, "", false, false, err
|
||||
}
|
||||
if row.ID == 0 {
|
||||
return 0, "", false, nil
|
||||
return 0, "", false, false, nil
|
||||
}
|
||||
return row.ID, row.Status, true, nil
|
||||
return row.ID, row.Status, true, row.IsDeleted == 1, nil
|
||||
}
|
||||
|
||||
// NormalizeStreetTitle 街拍实体键标题归一化:去除首尾空白,并把内部任意空白(空格 / 制表 / 换行)
|
||||
|
||||
Reference in New Issue
Block a user