feat(post): add idempotency support for post creation with client request ID
Add client_request_id field to post creation endpoints (CreateVotePost and CreatePostWithSegments). When provided, the server uses Redis SetNX-based idempotency to prevent duplicate posts from double-clicks or network retries within a 24-hour TTL. Duplicate requests return the originally created post; concurrent in-flight requests return 429 "正在发布中,请稍候".
This commit is contained in:
@@ -66,6 +66,23 @@ func (s *voteService) CreateVotePost(ctx context.Context, userID string, req *dt
|
||||
return nil, errors.New("投票选项最多10个")
|
||||
}
|
||||
|
||||
// 幂等检查:同一 userID + clientRequestID 在 TTL 内只创建一次。
|
||||
// 重复请求(多次点击、网络重试)会命中已回填的 postID,直接返回首次创建的帖子。
|
||||
if req.ClientRequestID != "" && s.cache != nil {
|
||||
idemKey := cache.PostCreateIdempotentKey(userID, req.ClientRequestID)
|
||||
result := cache.TryAcquireIdempotency(ctx, s.cache, idemKey, PostCreateIdempotentTTL)
|
||||
if !result.Acquired {
|
||||
// 命中已完成的请求:返回首次创建的帖子。
|
||||
if result.ExistingValue != "" {
|
||||
if existing, err := s.postRepo.GetByID(result.ExistingValue); err == nil && existing != nil {
|
||||
return s.convertToPostResponse(existing, userID), nil
|
||||
}
|
||||
}
|
||||
// 命中"进行中"占位:前一个请求还在处理,拒绝重复提交。
|
||||
return nil, ErrDuplicatePostRequest
|
||||
}
|
||||
}
|
||||
|
||||
// 构建 vote segment
|
||||
voteOptions := make([]dto.VoteOptionData, len(req.VoteOptions))
|
||||
for i, opt := range req.VoteOptions {
|
||||
@@ -103,9 +120,18 @@ func (s *voteService) CreateVotePost(ctx context.Context, userID string, req *dt
|
||||
|
||||
err := s.postRepo.Create(post, req.Images)
|
||||
if err != nil {
|
||||
// 创建失败:释放幂等占位,允许用户重试。
|
||||
if req.ClientRequestID != "" && s.cache != nil {
|
||||
cache.ReleaseIdempotency(ctx, s.cache, cache.PostCreateIdempotentKey(userID, req.ClientRequestID))
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 创建成功:回填幂等键为 postID,使后续重复请求返回同一帖子。
|
||||
if req.ClientRequestID != "" && s.cache != nil {
|
||||
cache.CompleteIdempotency(ctx, s.cache, cache.PostCreateIdempotentKey(userID, req.ClientRequestID), post.ID, PostCreateIdempotentTTL)
|
||||
}
|
||||
|
||||
// 异步审核
|
||||
go s.reviewVotePostAsync(post.ID, userID, req.Title, content, req.Images)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user