Version: 0.9.78.dev.260506

This commit is contained in:
Losita
2026-05-06 00:30:08 +08:00
parent 3b6fca44a6
commit 33227e48a7
71 changed files with 13137 additions and 62 deletions

View File

@@ -0,0 +1,157 @@
package sv
import (
"context"
"encoding/json"
"errors"
"fmt"
"log"
"strconv"
"strings"
tokencontracts "github.com/LoveLosita/smartflow/backend/shared/contracts/tokenstore"
sharedevents "github.com/LoveLosita/smartflow/backend/shared/events"
kafkabus "github.com/LoveLosita/smartflow/backend/shared/infra/kafka"
outboxinfra "github.com/LoveLosita/smartflow/backend/shared/infra/outbox"
)
// OutboxBus 是 token-store 注册论坛奖励 handler 需要的最小总线接口。
type OutboxBus interface {
RegisterEventHandler(eventType string, handler outboxinfra.MessageHandler) error
}
// RegisterForumRewardRoutes 只登记 token-store 负责消费的论坛奖励事件归属。
//
// 职责边界:
// 1. 只负责把事件类型路由到 token-store 的服务级 outbox
// 2. 不注册 handler也不启动 consumer
// 3. 供发布方和消费方在不同进程内共享同一份事件归属映射。
func RegisterForumRewardRoutes() error {
if err := outboxinfra.RegisterEventService(sharedevents.ForumPostLikedEventType, outboxinfra.ServiceTokenStore); err != nil {
return err
}
return outboxinfra.RegisterEventService(sharedevents.ForumPostImportedEventType, outboxinfra.ServiceTokenStore)
}
// RegisterForumRewardHandlers 注册 token-store 对论坛奖励事件的消费处理器。
//
// 职责边界:
// 1. 只消费 forum.post.liked / forum.post.imported 两类事件;
// 2. 论坛奖励账本写入由 token-store Service 负责handler 不自行计算金额;
// 3. grant 写入成功后再标记 consumed若标记失败可依赖 event_id 幂等安全重试。
func RegisterForumRewardHandlers(bus OutboxBus, outboxRepo *outboxinfra.Repository, svc *Service) error {
if bus == nil {
return errors.New("event bus is nil")
}
if outboxRepo == nil {
return errors.New("outbox repository is nil")
}
if svc == nil {
return errors.New("tokenstore service is nil")
}
if err := RegisterForumRewardRoutes(); err != nil {
return err
}
if err := registerForumRewardHandler(bus, outboxRepo, svc, sharedevents.ForumPostLikedEventType, sharedevents.ForumRewardSourceLike); err != nil {
return err
}
return registerForumRewardHandler(bus, outboxRepo, svc, sharedevents.ForumPostImportedEventType, sharedevents.ForumRewardSourceImport)
}
func registerForumRewardHandler(
bus OutboxBus,
outboxRepo *outboxinfra.Repository,
svc *Service,
eventType string,
source string,
) error {
route, ok := outboxinfra.ResolveEventRoute(eventType)
if !ok {
return fmt.Errorf("forum reward outbox route is missing: eventType=%s", eventType)
}
eventOutboxRepo := outboxRepo.WithRoute(route)
handler := func(ctx context.Context, envelope kafkabus.Envelope) error {
if !isAllowedForumRewardEventVersion(envelope.EventVersion) {
if err := eventOutboxRepo.MarkDead(ctx, envelope.OutboxID, fmt.Sprintf("论坛奖励事件版本不受支持: %s", envelope.EventVersion)); err != nil {
return err
}
return nil
}
var payload sharedevents.ForumPostRewardPayload
if err := json.Unmarshal(envelope.Payload, &payload); err != nil {
if markErr := eventOutboxRepo.MarkDead(ctx, envelope.OutboxID, "解析论坛奖励载荷失败: "+err.Error()); markErr != nil {
return markErr
}
return nil
}
if err := payload.Validate(); err != nil {
if markErr := eventOutboxRepo.MarkDead(ctx, envelope.OutboxID, "论坛奖励载荷非法: "+err.Error()); markErr != nil {
return markErr
}
return nil
}
if payload.EventType() != eventType {
if markErr := eventOutboxRepo.MarkDead(ctx, envelope.OutboxID, fmt.Sprintf("论坛奖励事件类型不匹配: envelope=%s payload=%s", eventType, payload.EventType())); markErr != nil {
return markErr
}
return nil
}
eventID := strings.TrimSpace(envelope.EventID)
if eventID == "" {
eventID = strings.TrimSpace(payload.EventID)
}
if eventID == "" {
if markErr := eventOutboxRepo.MarkDead(ctx, envelope.OutboxID, "论坛奖励 event_id 为空"); markErr != nil {
return markErr
}
return nil
}
grant, err := svc.RecordForumRewardGrant(ctx, tokencontracts.RecordForumRewardGrantRequest{
EventID: eventID,
ReceiverUserID: payload.RewardReceiverUserID,
Source: forumRewardSource(payload, source),
SourceRefID: forumRewardSourceRefID(payload, source),
})
if err != nil {
return err
}
if err := eventOutboxRepo.MarkConsumed(ctx, envelope.OutboxID); err != nil {
return err
}
log.Printf(
"forum reward event consumed by tokenstore: event_type=%s event_id=%s grant_id=%d outbox_id=%d",
eventType,
eventID,
grant.GrantID,
envelope.OutboxID,
)
return nil
}
return bus.RegisterEventHandler(eventType, handler)
}
func isAllowedForumRewardEventVersion(version string) bool {
version = strings.TrimSpace(version)
return version == "" || version == sharedevents.ForumRewardEventVersion
}
func forumRewardSource(payload sharedevents.ForumPostRewardPayload, fallback string) string {
source := strings.TrimSpace(payload.Source)
if source != "" {
return source
}
return fallback
}
func forumRewardSourceRefID(payload sharedevents.ForumPostRewardPayload, source string) string {
if source == sharedevents.ForumRewardSourceImport && payload.ImportID > 0 {
return strconv.FormatUint(payload.ImportID, 10)
}
return strconv.FormatUint(payload.PostID, 10)
}