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
+125
View File
@@ -0,0 +1,125 @@
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"
)
// LikeService 点赞业务服务。
type LikeService struct {
likeDao *mysqldao.LikeDao
outfitDao *mysqldao.OutfitDao
}
// NewLikeService 创建点赞服务。
func NewLikeService() *LikeService {
return &LikeService{
likeDao: mysqldao.NewLikeDao(),
outfitDao: mysqldao.NewOutfitDao(),
}
}
// LikeCreate 点赞或取消点赞社区穿搭。
func (s *LikeService) LikeCreate(ctx context.Context, userID int64, request *dto.LikeCreateRequest) (*dto.LikeResponse, *errcode.Error) {
ctx, span := global.Tracer.Start(ctx, "svc.LikeLikeCreate")
defer span.End()
outfit, cerr := s.getPublicOutfit(ctx, userID, request.TargetUUID)
if cerr != nil {
return nil, cerr
}
targetID := outfit.ID
existing, err := s.likeDao.LikeGetByTargetAndUser(ctx, LikeTypeOutfit, 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.likeDao.LikeUpsertOn(ctx, &mysqlmodel.Like{
LikeType: LikeTypeOutfit,
TargetID: targetID,
UserID: userID,
Status: InteractStatusOn,
}); cerr != nil {
return nil, errcode.LikeFailed
}
_ = redisdao.LikeAdd(ctx, LikeTypeOutfit, targetID, userID)
// 仅在「从取消/无记录变为点赞」时发通知,避免并发/重复点赞重复通知。
wasLiked := !notExist && existing.Status == InteractStatusOn
if !wasLiked && outfit.UserID != userID {
sendNotification(ctx, outfit.UserID, userID, NotificationTypeLike, "收到新的点赞", "你的穿搭「"+outfit.OutfitName+"」收到了一个赞")
}
case ActionCancel: // 取消点赞
if !notExist && existing.Status == InteractStatusOn {
if uerr := s.likeDao.LikeUpdateStatus(ctx, existing.ID, InteractStatusOff); uerr != nil {
return nil, errcode.LikeFailed
}
_ = redisdao.LikeRemove(ctx, LikeTypeOutfit, targetID, userID)
}
}
return &dto.LikeResponse{}, nil
}
// GetByTargetID 查询穿搭点赞状态与点赞数(Redis 优先)。
func (s *LikeService) GetByTargetID(ctx context.Context, userID int64, request *dto.LikeGetByTargetIDRequest) (*dto.LikeGetByTargetIDResponse, *errcode.Error) {
ctx, span := global.Tracer.Start(ctx, "svc.LikeGetByTargetID")
defer span.End()
outfit, cerr := s.getPublicOutfit(ctx, userID, request.TargetUUID)
if cerr != nil {
return nil, cerr
}
targetID := outfit.ID
// 是否已点赞:Redis 优先,回落 MySQL。
isLiked, err := redisdao.LikeIsMember(ctx, LikeTypeOutfit, targetID, userID)
if err != nil {
if rec, derr := s.likeDao.LikeGetByTargetAndUser(ctx, LikeTypeOutfit, targetID, userID); derr == nil {
isLiked = rec.Status == InteractStatusOn
}
}
// 点赞总数:Redis 优先,回落 MySQL。
count, err := redisdao.LikeCount(ctx, LikeTypeOutfit, targetID)
if err != nil || count == 0 {
if dbCnt, derr := s.likeDao.LikeCountByTarget(ctx, LikeTypeOutfit, targetID); derr == nil {
count = dbCnt
}
}
return &dto.LikeGetByTargetIDResponse{
UserIsLiked: isLiked,
LikeCount: count,
}, nil
}
// getPublicOutfit 按 UUID 查询社区穿搭(须存在),非本人仅可操作公开可见的穿搭。
func (s *LikeService) getPublicOutfit(ctx context.Context, userID int64, outfitUUID string) (*mysqlmodel.Outfit, *errcode.Error) {
outfit, err := s.outfitDao.OutfitGetByUUID(ctx, outfitUUID)
if err != nil {
if isNotFound(err) {
return nil, errcode.TargetNotFound
}
return nil, errcode.DatabaseError
}
if outfit.UserID != userID && !isOutfitPublicVisible(outfit) {
return nil, errcode.OutfitNoPermission
}
return outfit, nil
}