feat(clothes): 支持通过社区公开穿搭只读查看衣物
新增 outfit_uuid 查询参数,非属主可通过社区公开穿搭查看衣物详情。 同时调整点赞通知逻辑,仅在首次点赞时发送通知。
This commit is contained in:
@@ -53,6 +53,7 @@ type ClothesUpdateByUUIDResponse struct{}
|
||||
type ClothesGetByUUIDRequest struct {
|
||||
ClothesUUID string `json:"clothes_uuid" binding:"required"` // 衣物UUID
|
||||
ShareUUID string `json:"share_uuid"` // 可选:通过穿搭分享只读查看时携带(非属主)
|
||||
OutfitUUID string `json:"outfit_uuid"` // 可选:通过社区公开穿搭只读查看时携带(非属主)
|
||||
}
|
||||
|
||||
// ClothesGetByUUIDResponse 衣物信息
|
||||
|
||||
@@ -76,7 +76,12 @@ func (h *ClothesHandler) Get(c *gin.Context) {
|
||||
response.ToErrorResponse(errcode.Unauthorized)
|
||||
return
|
||||
}
|
||||
request := dto.ClothesGetByUUIDRequest{ClothesUUID: c.Param("uuid"), ShareUUID: c.Query("share_uuid")}
|
||||
request := dto.ClothesGetByUUIDRequest{
|
||||
ClothesUUID: c.Param("uuid"),
|
||||
// 非属主只读查看的两个入口:穿搭分享(share_uuid)与社区公开穿搭(outfit_uuid)
|
||||
ShareUUID: c.Query("share_uuid"),
|
||||
OutfitUUID: c.Query("outfit_uuid"),
|
||||
}
|
||||
if request.ClothesUUID == "" {
|
||||
response.ToErrorResponse(errcode.InvalidParams)
|
||||
return
|
||||
|
||||
@@ -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+"」收到了一个赞")
|
||||
}
|
||||
|
||||
|
||||
@@ -817,15 +817,19 @@ Authorization: Bearer <access_token>
|
||||
| --- | --- | --- | --- |
|
||||
| uuid | string | ✅ | 衣物 UUID |
|
||||
| share_uuid | string | 否 | 穿搭分享 UUID:非属主通过穿搭分享只读查看该分享穿搭内的衣物时必传 |
|
||||
| outfit_uuid | string | 否 | 穿搭 UUID:非属主通过**社区公开穿搭**只读查看该穿搭内的衣物时必传 |
|
||||
|
||||
**响应** `data` = [衣物对象](#42-衣物对象-clothesitem)
|
||||
|
||||
**业务逻辑(可见性)**
|
||||
|
||||
- 属主:始终可见。
|
||||
- 非属主:**仅当携带有效 `share_uuid`**(该分享正常、穿搭未被删除 / 封禁、且该衣物属于该分享的穿搭明细)时返回**只读**衣物信息;否则 `50005`。
|
||||
- 非属主:两个只读入口,**二选一携带即可**,都不携带则 `50005`。
|
||||
1. `share_uuid`:该分享正常、穿搭未被删除 / 封禁、且该衣物属于该分享的穿搭明细 → 返回**只读**衣物信息。
|
||||
2. `outfit_uuid`:该穿搭**公开可见**(`is_public=2` 且 `audit_status=2` 且 `status=1`,与 `GET /outfit/:uuid` 非属主口径一致)且该衣物属于该穿搭明细 → 返回**只读**衣物信息。
|
||||
- 只读场景下响应不含失败原因(`fail_reason` 恒为空);通过 `outfit_uuid`(社区公开穿搭)进入时,属主**私有备注 `remark` 也不下发**,避免暴露给陌生人(`share_uuid` 好友分享场景保持原样)。
|
||||
|
||||
**错误码**:`10000` 参数错误(uuid 为空)· `50000` 衣物不存在 · `50005` 无权操作该衣物 · `90103` 穿搭分享不存在(含穿搭已删除 / 已封禁)· `90104` 穿搭分享已取消
|
||||
**错误码**:`10000` 参数错误(uuid 为空)· `50000` 衣物不存在 · `50005` 无权操作该衣物(含未携带凭证 / 穿搭不可见 / 衣物不属于该穿搭)· `90103` 穿搭分享不存在(含穿搭已删除 / 已封禁)· `90104` 穿搭分享已取消
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user