package service import ( "context" "errors" "net/http" "fashionapi/internal/dto" "fashionapi/internal/model" "fashionapi/internal/pkg/hashid" "fashionapi/internal/repository" ) // StreetSnapService 街拍业务接口。 type StreetSnapService interface { List(ctx context.Context, q dto.StreetSnapQuery) ([]dto.PublicStreetSnap, int64, error) Detail(ctx context.Context, id string) (*dto.PublicStreetSnapDetail, error) // Popular 返回热度前 N 个街拍(按图片数降序),供 SSG 首页热门街拍展示。 Popular(ctx context.Context, limit int) ([]dto.PublicStreetSnap, error) } type streetSnapService struct { repo repository.StreetSnapRepository } // NewStreetSnapService 创建街拍服务。 func NewStreetSnapService(repo repository.StreetSnapRepository) StreetSnapService { return &streetSnapService{repo: repo} } // ErrStreetSnapNotFound 街拍不存在。 var ErrStreetSnapNotFound = NewError(http.StatusNotFound, "street snap not found") // List 查询街拍列表,并按需附带每条的前 N 张图。 func (s *streetSnapService) List(ctx context.Context, q dto.StreetSnapQuery) ([]dto.PublicStreetSnap, int64, error) { q.Normalize() rows, total, err := s.repo.List(ctx, q) if err != nil { return nil, 0, internalErr(err.Error()) } list := make([]dto.PublicStreetSnap, 0, len(rows)) ids := make([]uint32, 0, len(rows)) for _, r := range rows { ids = append(ids, r.ID) list = append(list, dto.PublicStreetSnap{ UID: hashid.Encode(r.ID), Title: r.Title, Cover: r.Cover, ImageCount: r.ImageCount, }) } if q.Images > 0 && len(list) > 0 { if err := s.attachImages(ctx, list, ids, q.Images); err != nil { return nil, 0, internalErr(err.Error()) } } return list, total, nil } // attachImages 用单次 IN 查询为列表批量补齐图片,避免前端逐条请求详情。 func (s *streetSnapService) attachImages(ctx context.Context, list []dto.PublicStreetSnap, ids []uint32, limit int) error { grouped, err := s.repo.ImagesBySnapIDs(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 = toPublicSnapImages(imgs) } return nil } // Detail 查询街拍详情,含完整图片集。 func (s *streetSnapService) Detail(ctx context.Context, id string) (*dto.PublicStreetSnapDetail, error) { snap, err := s.repo.FindByID(ctx, id) if err != nil { if errors.Is(err, repository.ErrNotFound) { return nil, ErrStreetSnapNotFound } return nil, internalErr(err.Error()) } imgs, err := s.repo.ListImages(ctx, id) if err != nil { return nil, internalErr(err.Error()) } return &dto.PublicStreetSnapDetail{ UID: hashid.Encode(snap.ID), Title: snap.Title, Cover: snap.Cover, Images: toPublicSnapImages(imgs), }, nil } // IDs 返回全部街拍的编码 uid(供 SSG 枚举详情页路径使用,对外暴露的即编码串)。 // Popular 返回热度前 N 个街拍(按图片数降序),供 SSG 首页热门街拍展示。 func (s *streetSnapService) Popular(ctx context.Context, limit int) ([]dto.PublicStreetSnap, error) { rows, err := s.repo.Popular(ctx, limit) if err != nil { return nil, internalErr(err.Error()) } list := make([]dto.PublicStreetSnap, 0, len(rows)) for _, r := range rows { list = append(list, dto.PublicStreetSnap{ UID: hashid.Encode(r.ID), Title: r.Title, Cover: r.Cover, ImageCount: r.ImageCount, }) } return list, nil } // toPublicSnapImages 把图片实体映射为对外结构(只保留 url 与名称)。 func toPublicSnapImages(imgs []model.StreetSnapImage) []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 }