feat(clothes): 支持通过社区公开穿搭只读查看衣物
新增 outfit_uuid 查询参数,非属主可通过社区公开穿搭查看衣物详情。 同时调整点赞通知逻辑,仅在首次点赞时发送通知。
This commit is contained in:
@@ -391,7 +391,8 @@ func (s *ClothesService) ProcessStatusGetByUUID(ctx context.Context, userID int6
|
||||
}
|
||||
|
||||
// GetByUUID 获取衣物详情。
|
||||
// 属主始终可见;非属主仅允许携带有效穿搭分享(share_uuid)时,只读查看该分享穿搭内的衣物。
|
||||
// 属主始终可见;非属主只有两个只读入口:
|
||||
// 携带有效穿搭分享(share_uuid)查看该分享穿搭内的衣物,或携带社区公开穿搭(outfit_uuid)查看已公开可见穿搭内的衣物。
|
||||
func (s *ClothesService) GetByUUID(ctx context.Context, userID int64, request *dto.ClothesGetByUUIDRequest) (*dto.ClothesGetByUUIDResponse, *errcode.Error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "svc.ClothesGetByUUID")
|
||||
defer span.End()
|
||||
@@ -404,18 +405,61 @@ func (s *ClothesService) GetByUUID(ctx context.Context, userID int64, request *d
|
||||
return nil, errcode.DatabaseError
|
||||
}
|
||||
if clothes.UserID != userID {
|
||||
if cerr := s.assertClothesVisibleViaOutfitShare(ctx, userID, request.ShareUUID, clothes.ID); cerr != nil {
|
||||
if cerr := s.assertClothesVisibleToVisitor(ctx, userID, request, clothes.ID); cerr != nil {
|
||||
return nil, cerr
|
||||
}
|
||||
}
|
||||
response := buildClothesResponse(clothes)
|
||||
// 非属主只读查看(穿搭分享):不暴露内部失败原因
|
||||
// 非属主只读查看:不暴露内部失败原因;社区(未携带 share_uuid 的公开穿搭入口)
|
||||
// 是陌生人可见场景,属主私有备注同样不下发。
|
||||
if clothes.UserID != userID {
|
||||
response.FailReason = ""
|
||||
if request.ShareUUID == "" {
|
||||
response.Remark = ""
|
||||
}
|
||||
}
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
// assertClothesVisibleToVisitor 校验非属主可以只读查看该衣物。
|
||||
// 两个入口互斥:优先穿搭分享,其次社区公开穿搭;都未携带则直接拒绝。
|
||||
func (s *ClothesService) assertClothesVisibleToVisitor(ctx context.Context, userID int64, request *dto.ClothesGetByUUIDRequest, clothesID int64) *errcode.Error {
|
||||
if request.ShareUUID != "" {
|
||||
return s.assertClothesVisibleViaOutfitShare(ctx, userID, request.ShareUUID, clothesID)
|
||||
}
|
||||
if request.OutfitUUID != "" {
|
||||
return s.assertClothesVisibleViaPublicOutfit(ctx, request.OutfitUUID, clothesID)
|
||||
}
|
||||
return errcode.ClothesNoPermission
|
||||
}
|
||||
|
||||
// assertClothesVisibleViaPublicOutfit 校验非属主可通过社区公开穿搭只读查看该衣物。
|
||||
// 可见性口径与 OutfitService.GetByUUID 的非属主分支保持一致(公开 + 审核通过 + 正常)。
|
||||
func (s *ClothesService) assertClothesVisibleViaPublicOutfit(ctx context.Context, outfitUUID string, clothesID int64) *errcode.Error {
|
||||
outfit, err := s.outfitDao.OutfitGetByUUID(ctx, outfitUUID)
|
||||
if err != nil {
|
||||
if isNotFound(err) {
|
||||
// 不区分「穿搭不存在」与「穿搭不可见」,统一按无权限返回,避免探测他人穿搭
|
||||
return errcode.ClothesNoPermission
|
||||
}
|
||||
return errcode.DatabaseError
|
||||
}
|
||||
if !isOutfitPublicVisible(outfit) {
|
||||
return errcode.ClothesNoPermission
|
||||
}
|
||||
// 该衣物必须属于该公开穿搭的明细。
|
||||
items, ierr := s.outfitItemDao.OutfitItemListByOutfitID(ctx, outfit.ID)
|
||||
if ierr != nil {
|
||||
return errcode.DatabaseError
|
||||
}
|
||||
for _, it := range items {
|
||||
if it.ClothesID == clothesID {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errcode.ClothesNoPermission
|
||||
}
|
||||
|
||||
// assertClothesVisibleViaOutfitShare 校验非属主可通过某穿搭分享只读查看该衣物。
|
||||
func (s *ClothesService) assertClothesVisibleViaOutfitShare(ctx context.Context, userID int64, shareUUID string, clothesID int64) *errcode.Error {
|
||||
if shareUUID == "" {
|
||||
|
||||
@@ -64,9 +64,10 @@ func (s *LikeService) LikeCreate(ctx context.Context, userID int64, request *dto
|
||||
"target_id", targetID, "user_id", userID, "error", rerr)
|
||||
}
|
||||
|
||||
// 仅在「从取消/无记录变为点赞」时发通知,避免并发/重复点赞重复通知。
|
||||
wasLiked := !notExist && existing.Status == InteractStatusOn
|
||||
if !wasLiked && outfit.UserID != userID {
|
||||
// 仅在「首次点赞」时发通知(likes 表此前无记录)。
|
||||
// 取消点赞只把 status 置为已取消、不删行,因此 notExist 可稳定表达
|
||||
// 「此前从未赞过该穿搭」;取消后再点赞不再重复通知,避免频繁点赞/取消把拥有者刷屏。
|
||||
if notExist && outfit.UserID != userID {
|
||||
sendNotification(ctx, outfit.UserID, userID, NotificationTypeLike, "收到新的点赞", "你的穿搭「"+outfit.OutfitName+"」收到了一个赞")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user