This commit is contained in:
toom1996
2026-10-18 17:42:10 +08:00
parent 1b06bd5bda
commit 1de1ec46a6
58 changed files with 5281 additions and 19191 deletions

View File

@ -37,6 +37,11 @@ type BrandRepository interface {
Create(ctx context.Context, b *model.Brand) (uint32, error)
// Update 按数字主键更新品牌中英文名。
Update(ctx context.Context, id uint32, nameEn, nameCn string) error
// CountRunwaysByBrand 统计该品牌下「未删除」的走秀档案数(品牌删除的护栏依据)。
CountRunwaysByBrand(ctx context.Context, brandID uint32) (int64, error)
// SetDeleted 软删 / 恢复品牌(只改 is_deleted 与 updated_at,不动其他列)。
// 用软删而非物理删除:品牌可能被历史数据引用,且需要可恢复。不存在返回 ErrNotFound。
SetDeleted(ctx context.Context, id uint32, deleted uint8) error
// CrawlBrands 返回所有可被爬虫抓取的品牌(is_deleted=0 且 name_en 非空)。
// 供爬虫取任务接口使用:不限制"是否已有走秀档案",因为爬虫本就要去补全尚无档案的品牌。
@ -111,13 +116,13 @@ func (r *brandRepository) brandFilterScope(q dto.BrandQuery, restrictIDs []uint3
db = db.Where("name_en ~ ?", latinPrefixRegexp)
}
// 字母索引始终只返回"拥有走秀档案"的品牌(库中有 2 万+ 无档案品牌,
// 不过滤会污染 A-Z 索引)。原 only_with_articles 参数已删除,此过滤无条件生效;
// 唯一区别是"档案"的口径:公开=已发布(视图),后台=未删除(基表,含待审)。
// 例外:关键词搜索时不限制"必须有走秀档案"(用户按名字找品牌,
// 即使该品牌暂无走秀档案也应被命中,如 A Bathing Ape / 猿人头);
// 中文分桶(OTHER)同样列出全部中文名品牌便于检索。
if q.Keyword == "" && q.Letter != "OTHER" {
// 字母索引默认只返回"拥有走秀档案"的品牌(库中有 2 万+ 无档案品牌,
// 不过滤会污染 A-Z 索引)。两个例外:
// 1) 关键词搜索时不限制(用户按名字找品牌,即使暂无档案也应命中);
// 2) 中文分桶(OTHER)同样列出全部中文名品牌便于检索;
// 3) 后台显式开 All(q.All):品牌目录有 2 万+ 条,后台需要能翻到任意品牌
// (否则导入的品牌在界面上等于"不存在")。公开侧永远忽略 All。
if q.Keyword == "" && q.Letter != "OTHER" && !(admin && q.All) {
if admin {
// 后台:只要求「存在未删除走秀档案」,待审 / 驳回的品牌也带出来。
db = db.Where("id IN (?)", r.hasArticlesSubQueryBase())
@ -247,6 +252,39 @@ func (r *brandRepository) Create(ctx context.Context, b *model.Brand) (uint32, e
return b.ID, nil
}
// CountRunwaysByBrand 统计该品牌下「未删除」的走秀档案数。
// 品牌删除的护栏依据:有档案时禁止删除,否则会留下一批 brand_id 指不到品牌的图集
// (brand_runways.brand_id 无外键级联),前台品牌页 / 热门榜都会异常。
func (r *brandRepository) CountRunwaysByBrand(ctx context.Context, brandID uint32) (int64, error) {
var n int64
if err := r.db.WithContext(ctx).
Model(&model.BrandRunway{}).
Where("brand_id = ? AND is_deleted = 0", brandID).
Count(&n).Error; err != nil {
return 0, err
}
return n, nil
}
// SetDeleted 软删 / 恢复品牌:只改 is_deleted 与 updated_at。
// 不存在返回 ErrNotFound(而不是静默成功),避免「点了删除但其实没删」。
func (r *brandRepository) SetDeleted(ctx context.Context, id uint32, deleted uint8) error {
res := r.db.WithContext(ctx).
Model(&model.Brand{}).
Where("id = ?", id).
Updates(map[string]any{
"is_deleted": deleted,
"updated_at": uint32(time.Now().Unix()),
})
if res.Error != nil {
return res.Error
}
if res.RowsAffected == 0 {
return ErrNotFound
}
return nil
}
// Update 按数字主键更新品牌中英文名(同时刷新 updated_at)。
func (r *brandRepository) Update(ctx context.Context, id uint32, nameEn, nameCn string) error {
return r.db.WithContext(ctx).