first commit

This commit is contained in:
2026-09-11 01:41:52 +08:00
commit 9f22e6ccae
263 changed files with 28935 additions and 0 deletions
+183
View File
@@ -0,0 +1,183 @@
package service
import (
"context"
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/global"
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/dto"
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/mysqldao"
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/mysqlmodel"
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/redisdao"
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/pkg/errcode"
)
// CollectService 收藏业务服务。
type CollectService struct {
collectDao *mysqldao.CollectDao
outfitDao *mysqldao.OutfitDao
outfitItemDao *mysqldao.OutfitItemDao
clothesDao *mysqldao.ClothesDao
}
// NewCollectService 创建收藏服务。
func NewCollectService() *CollectService {
return &CollectService{
collectDao: mysqldao.NewCollectDao(),
outfitDao: mysqldao.NewOutfitDao(),
outfitItemDao: mysqldao.NewOutfitItemDao(),
clothesDao: mysqldao.NewClothesDao(),
}
}
// Collect 收藏或取消收藏社区穿搭。
func (s *CollectService) Collect(ctx context.Context, userID int64, request *dto.CollectRequest) (*dto.CollectResponse, *errcode.Error) {
ctx, span := global.Tracer.Start(ctx, "svc.CollectCollect")
defer span.End()
outfit, err := s.outfitDao.OutfitGetByUUID(ctx, request.TargetUUID)
if err != nil {
if isNotFound(err) {
return nil, errcode.TargetNotFound
}
return nil, errcode.DatabaseError
}
// 非本人仅可收藏公开可见的穿搭。
if outfit.UserID != userID && !isOutfitPublicVisible(outfit) {
return nil, errcode.OutfitNoPermission
}
targetID := outfit.ID
existing, err := s.collectDao.CollectGetByTargetAndUser(ctx, CollectTypeOutfit, targetID, userID)
notExist := false
if err != nil {
if isNotFound(err) {
notExist = true
} else {
return nil, errcode.DatabaseError
}
}
switch request.Action {
case ActionDo: // 收藏
if cerr := s.collectDao.CollectUpsertOn(ctx, &mysqlmodel.Collect{
CollectType: CollectTypeOutfit,
TargetID: targetID,
UserID: userID,
Status: InteractStatusOn,
}); cerr != nil {
return nil, errcode.CollectFailed
}
_ = redisdao.CollectAdd(ctx, CollectTypeOutfit, targetID, userID)
case ActionCancel: // 取消收藏
if !notExist && existing.Status == InteractStatusOn {
if uerr := s.collectDao.CollectUpdateStatus(ctx, existing.ID, InteractStatusOff); uerr != nil {
return nil, errcode.CollectFailed
}
_ = redisdao.CollectRemove(ctx, CollectTypeOutfit, targetID, userID)
}
}
return &dto.CollectResponse{}, nil
}
// List 分页查询我收藏的穿搭。
func (s *CollectService) List(ctx context.Context, userID int64, request *dto.CollectListRequest) (*dto.CollectListResponse, *errcode.Error) {
ctx, span := global.Tracer.Start(ctx, "svc.CollectList")
defer span.End()
page, pageSize := normalizePage(request.Page, request.PageSize)
pageIDs, total, err := s.collectDao.CollectListTargetIDsByUserPage(ctx, CollectTypeOutfit, userID, page, pageSize)
if err != nil {
return nil, errcode.DatabaseError
}
items := make([]dto.OutfitGetByUUIDResponse, 0, len(pageIDs))
for _, oid := range pageIDs {
outfit, gerr := s.outfitDao.OutfitGetByID(ctx, oid)
if gerr != nil {
// 穿搭可能已删除,跳过。
continue
}
outfitItems, uerr := s.itemResponses(ctx, outfit.ID)
if uerr != nil {
continue
}
items = append(items, dto.OutfitGetByUUIDResponse{
OutfitCreateResponse: buildOutfitCreateResponse(outfit, outfitItems),
})
}
return &dto.CollectListResponse{
PageResponse: dto.PageResponse{Total: total, Page: page, PageSize: pageSize},
List: items,
}, nil
}
// GetByTarget 查询穿搭收藏状态与收藏数(Redis 优先,回落 MySQL)。
func (s *CollectService) GetByTarget(ctx context.Context, userID int64, request *dto.CollectGetByTargetRequest) (*dto.CollectGetByTargetResponse, *errcode.Error) {
ctx, span := global.Tracer.Start(ctx, "svc.CollectGetByTarget")
defer span.End()
outfit, err := s.outfitDao.OutfitGetByUUID(ctx, request.TargetUUID)
if err != nil {
if isNotFound(err) {
return nil, errcode.TargetNotFound
}
return nil, errcode.DatabaseError
}
// 非本人仅可查询公开可见穿搭的收藏状态。
if outfit.UserID != userID && !isOutfitPublicVisible(outfit) {
return nil, errcode.OutfitNoPermission
}
targetID := outfit.ID
// 是否已收藏:Redis 优先,回落 MySQL。
isCollected, err := redisdao.CollectIsMember(ctx, CollectTypeOutfit, targetID, userID)
if err != nil {
if rec, derr := s.collectDao.CollectGetByTargetAndUser(ctx, CollectTypeOutfit, targetID, userID); derr == nil {
isCollected = rec.Status == InteractStatusOn
}
}
// 收藏总数:Redis 优先,回落 MySQL。
count, err := redisdao.CollectCount(ctx, CollectTypeOutfit, targetID)
if err != nil || count == 0 {
if dbCnt, derr := s.collectDao.CollectCountByTarget(ctx, CollectTypeOutfit, targetID); derr == nil {
count = dbCnt
}
}
return &dto.CollectGetByTargetResponse{
UserIsCollected: isCollected,
CollectCount: count,
}, nil
}
// itemResponses 获取某穿搭的衣物明细(含图片 URL)。
func (s *CollectService) itemResponses(ctx context.Context, outfitID int64) ([]dto.OutfitItemResponse, error) {
its, err := s.outfitItemDao.OutfitItemListByOutfitID(ctx, outfitID)
if err != nil {
return nil, err
}
ids := make([]int64, 0, len(its))
for _, it := range its {
ids = append(ids, it.ClothesID)
}
clothesList, err := s.clothesDao.ClothesGetByIDs(ctx, ids)
if err != nil {
return nil, err
}
clothesByID := make(map[int64]mysqlmodel.Clothes, len(clothesList))
for _, c := range clothesList {
clothesByID[c.ID] = c
}
resps := make([]dto.OutfitItemResponse, 0, len(its))
for _, it := range its {
if c, ok := clothesByID[it.ClothesID]; ok {
resps = append(resps, buildOutfitItemResponse(&c))
}
}
return resps, nil
}