feat: 接入论坛奖励 outbox 链路

This commit is contained in:
Losita
2026-05-05 10:44:33 +08:00
parent 4fc6c0cac3
commit c42f0c5b8c
31 changed files with 1381 additions and 101 deletions

View File

@@ -2,21 +2,21 @@ package sv
import (
"context"
"fmt"
"time"
"github.com/LoveLosita/smartflow/backend/respond"
forumdao "github.com/LoveLosita/smartflow/backend/services/taskclassforum/dao"
forummodel "github.com/LoveLosita/smartflow/backend/services/taskclassforum/model"
forumcontracts "github.com/LoveLosita/smartflow/backend/shared/contracts/taskclassforum"
sharedevents "github.com/LoveLosita/smartflow/backend/shared/events"
)
// LikePost 点赞计划帖子。
//
// 职责边界:
// 1. 负责保证同一用户同一帖子只有一个 active 点赞状态;
// 2. 负责维护帖子 like_count 计数字段;
// 3. 不直接发放 Token只写稳定 event_id后续奖励链路可基于该 ID 幂等消费
// 1. 保证同一用户同一帖子只有一个 active 点赞状态;
// 2. 维护帖子 like_count 计数字段;
// 3. 只在首次创建 like 记录时补发 outbox 事件,取消后重新激活旧记录不重复发奖励
func (s *Service) LikePost(ctx context.Context, actorUserID uint64, postID uint64) (forumcontracts.ForumPostCounters, forumcontracts.ForumPostViewerState, error) {
if err := s.Ready(); err != nil {
return forumcontracts.ForumPostCounters{}, forumcontracts.ForumPostViewerState{}, err
@@ -25,6 +25,7 @@ func (s *Service) LikePost(ctx context.Context, actorUserID uint64, postID uint6
return forumcontracts.ForumPostCounters{}, forumcontracts.ForumPostViewerState{}, respond.MissingParam
}
var rewardPayload *sharedevents.ForumPostRewardPayload
if err := s.forumDAO.Transaction(ctx, func(txDAO *forumdao.ForumDAO) error {
post, err := txDAO.LockPublishedPost(ctx, postID)
if err != nil {
@@ -35,7 +36,19 @@ func (s *Service) LikePost(ctx context.Context, actorUserID uint64, postID uint6
return err
}
if like == nil {
return createActiveLike(ctx, txDAO, post, actorUserID)
payload, createErr := createActiveLike(ctx, txDAO, post, actorUserID)
if createErr != nil {
return createErr
}
// 调用目的:优先把首次点赞奖励事件写入当前事务,保证点赞记录和 outbox 入队原子提交。
handled, publishErr := s.publishForumRewardEventInTx(ctx, txDAO.GormDB(), payload)
if publishErr != nil {
return publishErr
}
if !handled {
rewardPayload = &payload
}
return nil
}
if like.Status == forummodel.ForumLikeStatusActive {
return nil
@@ -47,6 +60,10 @@ func (s *Service) LikePost(ctx context.Context, actorUserID uint64, postID uint6
}); err != nil {
return forumcontracts.ForumPostCounters{}, forumcontracts.ForumPostViewerState{}, err
}
if rewardPayload != nil {
s.publishForumRewardEventBestEffort(*rewardPayload)
}
return s.postInteractionState(ctx, actorUserID, postID)
}
@@ -80,7 +97,7 @@ func (s *Service) UnlikePost(ctx context.Context, actorUserID uint64, postID uin
return s.postInteractionState(ctx, actorUserID, postID)
}
func createActiveLike(ctx context.Context, txDAO *forumdao.ForumDAO, post *forummodel.ForumPost, actorUserID uint64) error {
func createActiveLike(ctx context.Context, txDAO *forumdao.ForumDAO, post *forummodel.ForumPost, actorUserID uint64) (sharedevents.ForumPostRewardPayload, error) {
like := &forummodel.ForumLike{
PostID: post.ID,
UserID: actorUserID,
@@ -89,9 +106,21 @@ func createActiveLike(ctx context.Context, txDAO *forumdao.ForumDAO, post *forum
EventID: forumLikeEventID(post.ID, actorUserID),
}
if err := txDAO.CreateLike(ctx, like); err != nil {
return err
return sharedevents.ForumPostRewardPayload{}, err
}
return txDAO.AddPostCounter(ctx, post.ID, "like_count", 1)
if err := txDAO.AddPostCounter(ctx, post.ID, "like_count", 1); err != nil {
return sharedevents.ForumPostRewardPayload{}, err
}
likedAt := like.LikedAt
if likedAt.IsZero() {
likedAt = time.Now()
}
payload := sharedevents.NewForumPostLikedPayload(post.ID, post.AuthorUserID, actorUserID, likedAt)
if like.EventID != "" {
payload.EventID = like.EventID
}
return payload, nil
}
func (s *Service) postInteractionState(ctx context.Context, actorUserID uint64, postID uint64) (forumcontracts.ForumPostCounters, forumcontracts.ForumPostViewerState, error) {
@@ -107,5 +136,5 @@ func (s *Service) postInteractionState(ctx context.Context, actorUserID uint64,
}
func forumLikeEventID(postID uint64, userID uint64) string {
return fmt.Sprintf("forum.post.liked:%d:%d", postID, userID)
return sharedevents.ForumRewardEventID(sharedevents.ForumPostLikedEventType, postID, userID)
}