115 lines
3.7 KiB
Go
115 lines
3.7 KiB
Go
package service
|
||
|
||
import (
|
||
"context"
|
||
"sort"
|
||
|
||
"fashionapi/internal/dto"
|
||
"fashionapi/internal/model"
|
||
"fashionapi/internal/pkg/hashid"
|
||
"fashionapi/internal/repository"
|
||
)
|
||
|
||
// BrandService 品牌业务接口。
|
||
type BrandService interface {
|
||
List(ctx context.Context, q dto.BrandQuery) ([]dto.PublicBrand, int64, error)
|
||
// Hot 返回热门品牌(按走秀图片数排名前 limit 个)并附带各品牌档案数。
|
||
// 供 SSG 构建期(RunwayLooks 侧栏 / 品牌弹窗 HOT 标签)一次性取前 N 个热门品牌;
|
||
// 复用 List 的 featured 收窄逻辑(FeaturedIDs("images", limit)),与首页精选口径一致。
|
||
Hot(ctx context.Context, limit int, locale string) ([]dto.PublicBrand, error)
|
||
}
|
||
|
||
type brandService struct {
|
||
brands repository.BrandRepository
|
||
}
|
||
|
||
// NewBrandService 创建品牌服务。
|
||
func NewBrandService(brands repository.BrandRepository) BrandService {
|
||
return &brandService{brands: brands}
|
||
}
|
||
|
||
// List 查询品牌列表并附带每个品牌的档案数。
|
||
//
|
||
// featured=1 时先按指标算出"代表品牌"集合,再把列表收敛到该集合内 ——
|
||
// 全库 1600+ 有档案品牌对 A-Z 索引来说过多,精选前 200 更符合导航场景。
|
||
func (s *brandService) List(ctx context.Context, q dto.BrandQuery) ([]dto.PublicBrand, int64, error) {
|
||
q.Normalize()
|
||
|
||
var restrictIDs []uint32
|
||
if q.Featured {
|
||
ids, err := s.brands.FeaturedIDs(ctx, q.Metric, q.Limit)
|
||
if err != nil {
|
||
// 取不到精选集合时交给 repository 收窄为空集,
|
||
// 绝不静默退化成"返回全部品牌"——那会让前端索引突然膨胀。
|
||
restrictIDs = nil
|
||
} else {
|
||
restrictIDs = ids
|
||
}
|
||
}
|
||
|
||
items, total, err := s.brands.List(ctx, q, restrictIDs)
|
||
if err != nil {
|
||
return nil, 0, internalErr(err.Error())
|
||
}
|
||
|
||
ids := make([]uint32, 0, len(items))
|
||
for _, b := range items {
|
||
ids = append(ids, b.ID)
|
||
}
|
||
counts, err := s.brands.ArticleCountByBrandIDs(ctx, ids)
|
||
if err != nil {
|
||
return nil, 0, internalErr(err.Error())
|
||
}
|
||
|
||
list := s.toPublicBrands(items, counts, q.Locale)
|
||
return list, total, nil
|
||
}
|
||
|
||
// Hot 返回热门品牌:按走秀图片总数排名取前 limit 个,结果按热度顺序(而非字母序)排列。
|
||
//
|
||
// 供 SSG 构建期(走秀页侧栏品牌 filter / 品牌弹窗 HOT 标签)一次性取用——
|
||
// 前端不再从全量列表里自行切片,热度排名由后端唯一口径给出。
|
||
func (s *brandService) Hot(ctx context.Context, limit int, locale string) ([]dto.PublicBrand, error) {
|
||
ids, err := s.brands.FeaturedIDs(ctx, "images", limit)
|
||
if err != nil {
|
||
return nil, internalErr(err.Error())
|
||
}
|
||
if len(ids) == 0 {
|
||
return []dto.PublicBrand{}, nil
|
||
}
|
||
|
||
items, err := s.brands.ListByIDs(ctx, ids)
|
||
if err != nil {
|
||
return nil, internalErr(err.Error())
|
||
}
|
||
counts, err := s.brands.ArticleCountByBrandIDs(ctx, ids)
|
||
if err != nil {
|
||
return nil, internalErr(err.Error())
|
||
}
|
||
|
||
// IN 查询不保证顺序,这里重排回 FeaturedIDs 的热度排名顺序。
|
||
pos := make(map[uint32]int, len(ids))
|
||
for i, id := range ids {
|
||
pos[id] = i
|
||
}
|
||
sort.Slice(items, func(i, j int) bool { return pos[items[i].ID] < pos[items[j].ID] })
|
||
|
||
return s.toPublicBrands(items, counts, locale), nil
|
||
}
|
||
|
||
// toPublicBrands 把仓储层品牌行映射为对外精简结构(编码 id + 双语名 + 档案数)。
|
||
func (s *brandService) toPublicBrands(items []model.Brand, counts map[uint32]int, locale string) []dto.PublicBrand {
|
||
list := make([]dto.PublicBrand, 0, len(items))
|
||
for _, b := range items {
|
||
list = append(list, dto.PublicBrand{
|
||
UID: hashid.Encode(b.ID),
|
||
Name: pickLocale(b.NameEn, b.NameCn, locale),
|
||
NameEn: b.NameEn,
|
||
NameCn: b.NameCn,
|
||
ShowName: b.NameCn,
|
||
ArticleCount: counts[b.ID],
|
||
})
|
||
}
|
||
return list
|
||
}
|