Files
backend_v2/internal/service/article_service.go
toom1996 30c9f21da4 update
2026-08-26 10:45:21 +08:00

147 lines
4.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"context"
"errors"
"net/http"
"fashionapi/internal/dto"
"fashionapi/internal/model"
"fashionapi/internal/pkg/hashid"
"fashionapi/internal/pkg/textutil"
"fashionapi/internal/repository"
)
// summaryLength 对外摘要的截断长度(rune 数),与原项目一致。
const summaryLength = 140
// ArticleService 文章业务接口。
type ArticleService interface {
List(ctx context.Context, q dto.ArticleQuery) ([]dto.PublicArticle, int64, error)
Detail(ctx context.Context, id string, locale string) (*dto.PublicArticleDetail, error)
// IDs 返回全部文章的编码 uid,供 SSG 路径枚举(getStaticPaths)。
IDs(ctx context.Context) ([]string, error)
}
type articleService struct {
articles repository.ArticleRepository
}
// NewArticleService 创建文章服务。
func NewArticleService(articles repository.ArticleRepository) ArticleService {
return &articleService{articles: articles}
}
// List 查询文章列表,并按需附带每篇的前 N 张图。
func (s *articleService) List(ctx context.Context, q dto.ArticleQuery) ([]dto.PublicArticle, int64, error) {
q.Normalize()
rows, total, err := s.articles.List(ctx, q)
if err != nil {
return nil, 0, internalErr(err.Error())
}
list := make([]dto.PublicArticle, 0, len(rows))
ids := make([]uint32, 0, len(rows))
for _, r := range rows {
ids = append(ids, r.ID)
list = append(list, dto.PublicArticle{
UID: hashid.Encode(r.ID),
BrandUID: hashid.Encode(r.BrandID),
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),
Year: r.Year,
ImageCount: r.ImageCount,
CollectionType: r.CollectionType,
Season: r.Season,
SeasonCode: r.SeasonCode,
PublishedAt: r.CreatedAt,
})
}
if q.WithImages > 0 && len(list) > 0 {
if err := s.attachImages(ctx, list, ids, q.WithImages); err != nil {
return nil, 0, internalErr(err.Error())
}
}
return list, total, nil
}
// attachImages 用单次 IN 查询为列表批量补齐图片,避免前端逐篇请求详情。
// ids 是与之对应的数字主键(对外已编码为 UID,此处用数字主键查库)。
func (s *articleService) attachImages(ctx context.Context, list []dto.PublicArticle, ids []uint32, limit int) error {
grouped, err := s.articles.ImagesByRunwayIDs(ctx, ids)
if err != nil {
return err
}
for i := range list {
imgs, ok := grouped[ids[i]]
if !ok {
continue
}
if len(imgs) > limit {
imgs = imgs[:limit]
}
list[i].Images = toPublicImages(imgs)
}
return nil
}
// Detail 查询文章详情,含完整图片集。locale 决定标题/描述/品牌名的展示语言。
func (s *articleService) Detail(ctx context.Context, id string, locale string) (*dto.PublicArticleDetail, error) {
row, err := s.articles.FindByID(ctx, id)
if err != nil {
if errors.Is(err, repository.ErrNotFound) {
return nil, ErrArticleNotFound
}
return nil, NewError(http.StatusInternalServerError, err.Error())
}
imgs, err := s.articles.ListImages(ctx, id)
if err != nil {
return nil, internalErr(err.Error())
}
return &dto.PublicArticleDetail{
UID: hashid.Encode(row.ID),
BrandUID: hashid.Encode(row.BrandID),
Title: pickLocale(row.TitleEn, row.TitleCn, locale),
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),
Year: row.Year,
ImageCount: row.ImageCount,
SourceURL: row.SourceURL,
PublishedAt: row.CreatedAt,
Images: toPublicImages(imgs),
}, nil
}
// IDs 返回全部文章的编码 uid(供 SSG 枚举详情页路径使用,对外暴露的即编码串)。
func (s *articleService) IDs(ctx context.Context) ([]string, error) {
ids, err := s.articles.IDs(ctx)
if err != nil {
return nil, internalErr(err.Error())
}
out := make([]string, 0, len(ids))
for _, id := range ids {
out = append(out, hashid.Encode(id))
}
return out, nil
}
// toPublicImages 把图片实体映射为对外结构(只保留 url 与名称)。
func toPublicImages(imgs []model.BrandRunwayImage) []dto.PublicArticleImage {
if len(imgs) == 0 {
return nil
}
out := make([]dto.PublicArticleImage, 0, len(imgs))
for _, im := range imgs {
out = append(out, dto.PublicArticleImage{Image: im.Image, Name: im.Name})
}
return out
}