This commit is contained in:
toom1996
2026-08-26 19:40:16 +08:00
parent 30c9f21da4
commit a6724265ad
14 changed files with 400 additions and 101 deletions

View File

@ -51,7 +51,9 @@ func (s *articleService) List(ctx context.Context, q dto.ArticleQuery) ([]dto.Pu
Title: pickLocale(r.TitleEn, r.TitleCn, q.Locale),
Summary: textutil.Summarize(pickLocale(r.DescriptionEn, r.DescriptionCn, q.Locale), summaryLength),
Cover: r.Cover,
BrandName: pickLocale(r.BrandNameEn, r.BrandNameCn, q.Locale),
// 品牌名强制优先英文("en"),仅在英文缺失时回落中文,避免留空;
// 与 title/description 不同,品牌名不随 locale 切换,保持英文观感。
BrandName: pickLocale(r.BrandNameEn, r.BrandNameCn, "en"),
Year: r.Year,
ImageCount: r.ImageCount,
CollectionType: r.CollectionType,
@ -111,7 +113,8 @@ func (s *articleService) Detail(ctx context.Context, id string, locale string) (
Summary: textutil.Summarize(pickLocale(row.DescriptionEn, row.DescriptionCn, locale), summaryLength),
Description: pickLocale(row.DescriptionEn, row.DescriptionCn, locale),
Cover: row.Cover,
BrandName: pickLocale(row.BrandNameEn, row.BrandNameCn, locale),
// 品牌名强制优先英文("en"),不随 locale 切换,保持英文观感。
BrandName: pickLocale(row.BrandNameEn, row.BrandNameCn, "en"),
Year: row.Year,
ImageCount: row.ImageCount,
SourceURL: row.SourceURL,

View File

@ -2,8 +2,10 @@ package service
import (
"context"
"sort"
"fashionapi/internal/dto"
"fashionapi/internal/model"
"fashionapi/internal/pkg/hashid"
"fashionapi/internal/repository"
)
@ -11,9 +13,10 @@ import (
// BrandService 品牌业务接口。
type BrandService interface {
List(ctx context.Context, q dto.BrandQuery) ([]dto.PublicBrand, int64, error)
// Popular 首页"热门品牌"区块:按图片数排名的前 limit 个品牌,附带代表走秀封面与标题。
// locale 决定品牌名/标题的展示语言(cn/en)。
Popular(ctx context.Context, limit int, locale string) ([]dto.PopularBrand, 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 {
@ -58,34 +61,54 @@ func (s *brandService) List(ctx context.Context, q dto.BrandQuery) ([]dto.Public
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, q.Locale),
Name: pickLocale(b.NameEn, b.NameCn, locale),
NameEn: b.NameEn,
NameCn: b.NameCn,
ShowName: b.NameCn,
ArticleCount: counts[b.ID],
})
}
return list, total, nil
}
// Popular 热门品牌(首页区块用):按图片数排名的前 limit 个品牌,附带代表走秀封面与标题。
// 复用仓储层的 FeaturedIDs 排名逻辑,保持与"代表品牌"一致的"热门"定义。locale 选展示语言。
func (s *brandService) Popular(ctx context.Context, limit int, locale string) ([]dto.PopularBrand, error) {
rows, err := s.brands.PopularWithCover(ctx, limit)
if err != nil {
return nil, internalErr(err.Error())
}
out := make([]dto.PopularBrand, 0, len(rows))
for _, b := range rows {
out = append(out, dto.PopularBrand{
UID: hashid.Encode(b.ID),
ArticleUID: hashid.Encode(b.ArticleID),
Brand: pickLocale(b.BrandEn, b.BrandCn, locale),
Cover: b.Cover,
Title: pickLocale(b.TitleEn, b.TitleCn, locale),
})
}
return out, nil
return list
}

View File

@ -0,0 +1,60 @@
package service
import (
"context"
"fashionapi/internal/dto"
"fashionapi/internal/pkg/hashid"
"fashionapi/internal/repository"
)
// IndexService 首页(/index/)各区块数据的聚合服务。
//
// 首页由多个独立区块(runway 等)组成,每个区块的数据各自组合、互不干扰。
// 该服务专管「首页区块数据」这一领域,避免把首页数据拼装逻辑散落到 BrandService 等其它服务里。
type IndexService interface {
// Runway 首页 runway 区块数据:包含 hotBrand(热门品牌,固定 20 条)等多个模块。
// 服务端写死各模块数量,不接收任何筛选参数;locale 决定标题展示语言。
Runway(ctx context.Context, locale string) (IndexRunwayData, error)
}
// IndexRunwayData 首页 runway 区块数据,按模块拆分;hotBrand 为其中之一。
//
// 后续新增首页 runway 模块时,在此结构体追加字段即可,前端按需读取,互不影响。
type IndexRunwayData struct {
HotBrand []dto.PopularBrand `json:"hotBrand"`
}
type indexService struct {
brands repository.BrandRepository
}
// NewIndexService 创建首页数据服务。
func NewIndexService(brands repository.BrandRepository) IndexService {
return &indexService{brands: brands}
}
// Runway 首页 runway 区块数据。
//
// 复用仓储层 PopularWithCover 的「按图片数排名取热门品牌 + 代表走秀封面/标题」逻辑,
// 保持与品牌「代表品牌」一致的"热门"定义;品牌名强制英文观感,标题按 locale 选列。
// hotBrand 数量写死 20,接口不接收任何参数。
func (s *indexService) Runway(ctx context.Context, locale string) (IndexRunwayData, error) {
const hotBrandLimit = 20
rows, err := s.brands.PopularWithCover(ctx, hotBrandLimit)
if err != nil {
return IndexRunwayData{}, internalErr(err.Error())
}
brands := make([]dto.PopularBrand, 0, len(rows))
for _, b := range rows {
brands = append(brands, dto.PopularBrand{
UID: hashid.Encode(b.ID),
ArticleUID: hashid.Encode(b.ArticleID),
// 品牌名强制优先英文("en"),不随 locale 切换,保持英文观感。
Brand: pickLocale(b.BrandEn, b.BrandCn, "en"),
Cover: b.Cover,
Title: pickLocale(b.TitleEn, b.TitleCn, locale),
})
}
return IndexRunwayData{HotBrand: brands}, nil
}