Version: 0.9.78.dev.260506
This commit is contained in:
145
backend/shared/contracts/taskclassforum/types.go
Normal file
145
backend/shared/contracts/taskclassforum/types.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package taskclassforum
|
||||
|
||||
// PageResult 是计划广场分页响应的跨层契约。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 只描述分页元数据,不负责查询和排序逻辑;
|
||||
// 2. Items 由具体接口决定,避免为了 P0 引入复杂泛型到 RPC 边界;
|
||||
// 3. HTTP 层和 RPC 层需要保持字段语义一致。
|
||||
type PageResult struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
Total int `json:"total"`
|
||||
HasMore bool `json:"has_more"`
|
||||
}
|
||||
|
||||
// UserBrief 是计划广场前端展示作者和评论人的最小用户信息。
|
||||
type UserBrief struct {
|
||||
UserID uint64 `json:"user_id"`
|
||||
Nickname string `json:"nickname"`
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
}
|
||||
|
||||
// TemplateSummary 是列表卡片里的模板摘要。
|
||||
type TemplateSummary struct {
|
||||
TaskCount int `json:"task_count"`
|
||||
Mode string `json:"mode"`
|
||||
StartDate string `json:"start_date"`
|
||||
EndDate string `json:"end_date"`
|
||||
StrategyLabels []string `json:"strategy_labels"`
|
||||
}
|
||||
|
||||
// ForumPostCounters 是帖子计数字段快照。
|
||||
type ForumPostCounters struct {
|
||||
LikeCount int64 `json:"like_count"`
|
||||
CommentCount int64 `json:"comment_count"`
|
||||
ImportCount int64 `json:"import_count"`
|
||||
}
|
||||
|
||||
// ForumPostViewerState 是当前登录用户相对该帖子的状态。
|
||||
type ForumPostViewerState struct {
|
||||
Liked bool `json:"liked"`
|
||||
ImportedOnce bool `json:"imported_once"`
|
||||
}
|
||||
|
||||
// ForumTagItem 是计划广场标签筛选区的最小展示单元。
|
||||
type ForumTagItem struct {
|
||||
Tag string `json:"tag"`
|
||||
PostCount int `json:"post_count"`
|
||||
}
|
||||
|
||||
// ForumPostBrief 是计划列表和详情头部共用的帖子摘要。
|
||||
type ForumPostBrief struct {
|
||||
PostID uint64 `json:"post_id"`
|
||||
Title string `json:"title"`
|
||||
Summary string `json:"summary"`
|
||||
Tags []string `json:"tags"`
|
||||
Author UserBrief `json:"author"`
|
||||
TemplateSummary TemplateSummary `json:"template_summary"`
|
||||
Counters ForumPostCounters `json:"counters"`
|
||||
ViewerState ForumPostViewerState `json:"viewer_state"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
// TemplateItemPreview 是详情页展示的任务条目快照。
|
||||
type TemplateItemPreview struct {
|
||||
ItemID uint64 `json:"item_id"`
|
||||
Order int `json:"order"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
// TemplateDetail 是论坛模板快照的前端展示结构。
|
||||
type TemplateDetail struct {
|
||||
Mode string `json:"mode"`
|
||||
StartDate string `json:"start_date"`
|
||||
EndDate string `json:"end_date"`
|
||||
StrategyLabels []string `json:"strategy_labels"`
|
||||
TaskCount int `json:"task_count"`
|
||||
ItemsPreview []TemplateItemPreview `json:"items_preview"`
|
||||
}
|
||||
|
||||
// ForumPostDetail 是计划详情接口响应主体。
|
||||
type ForumPostDetail struct {
|
||||
Post ForumPostBrief `json:"post"`
|
||||
Template TemplateDetail `json:"template"`
|
||||
}
|
||||
|
||||
// ForumCommentNode 是服务层组装后的多层评论树节点。
|
||||
type ForumCommentNode struct {
|
||||
CommentID uint64 `json:"comment_id"`
|
||||
PostID uint64 `json:"post_id"`
|
||||
ParentCommentID *uint64 `json:"parent_comment_id"`
|
||||
Content string `json:"content"`
|
||||
Status string `json:"status"`
|
||||
Author UserBrief `json:"author"`
|
||||
CanDelete bool `json:"can_delete"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
DeletedAt *string `json:"deleted_at"`
|
||||
Children []ForumCommentNode `json:"children"`
|
||||
}
|
||||
|
||||
// CreateForumPostRequest 是发布计划请求契约。
|
||||
type CreateForumPostRequest struct {
|
||||
ActorUserID uint64 `json:"actor_user_id"`
|
||||
TaskClassID uint64 `json:"task_class_id"`
|
||||
Title string `json:"title"`
|
||||
Summary string `json:"summary"`
|
||||
Tags []string `json:"tags"`
|
||||
IdempotencyKey string `json:"idempotency_key"`
|
||||
}
|
||||
|
||||
// CreateForumCommentRequest 是发表评论或回复请求契约。
|
||||
type CreateForumCommentRequest struct {
|
||||
ActorUserID uint64 `json:"actor_user_id"`
|
||||
PostID uint64 `json:"post_id"`
|
||||
Content string `json:"content"`
|
||||
ParentCommentID *uint64 `json:"parent_comment_id"`
|
||||
IdempotencyKey string `json:"idempotency_key"`
|
||||
}
|
||||
|
||||
// ImportForumPostRequest 是一键导入请求契约。
|
||||
type ImportForumPostRequest struct {
|
||||
ActorUserID uint64 `json:"actor_user_id"`
|
||||
PostID uint64 `json:"post_id"`
|
||||
TargetTitle string `json:"target_title"`
|
||||
IdempotencyKey string `json:"idempotency_key"`
|
||||
}
|
||||
|
||||
// DeleteForumCommentResult 是删除评论后的状态回执。
|
||||
type DeleteForumCommentResult struct {
|
||||
CommentID uint64 `json:"comment_id"`
|
||||
Status string `json:"status"`
|
||||
Content string `json:"content"`
|
||||
DeletedAt *string `json:"deleted_at"`
|
||||
}
|
||||
|
||||
// ImportForumPostResult 是一键导入后的回执。
|
||||
type ImportForumPostResult struct {
|
||||
ImportID uint64 `json:"import_id"`
|
||||
PostID uint64 `json:"post_id"`
|
||||
NewTaskClassID uint64 `json:"new_task_class_id"`
|
||||
TaskClassTitle string `json:"task_class_title"`
|
||||
ImportCount int64 `json:"import_count"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
125
backend/shared/contracts/tokenstore/types.go
Normal file
125
backend/shared/contracts/tokenstore/types.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package tokenstore
|
||||
|
||||
// PageResult 是 token-store 分页响应的跨层契约。
|
||||
type PageResult struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
Total int `json:"total"`
|
||||
HasMore bool `json:"has_more"`
|
||||
}
|
||||
|
||||
// TokenSummary 是 Token 商店概览响应。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. P0 展示 token-store 已记录的获取事实;
|
||||
// 2. 不承诺这些 Token 已经同步到 user/auth 权威额度;
|
||||
// 3. 后续接入 user/auth 后可把 QuotaSyncStatus 调整为 synced。
|
||||
type TokenSummary struct {
|
||||
RecordedTokenTotal int64 `json:"recorded_token_total"`
|
||||
AppliedTokenTotal int64 `json:"applied_token_total"`
|
||||
PendingApplyTokenTotal int64 `json:"pending_apply_token_total"`
|
||||
QuotaSyncStatus string `json:"quota_sync_status"`
|
||||
Tip string `json:"tip"`
|
||||
}
|
||||
|
||||
// TokenProductView 是商品卡片展示结构。
|
||||
type TokenProductView struct {
|
||||
ProductID uint64 `json:"product_id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
TokenAmount int64 `json:"token_amount"`
|
||||
PriceCent int64 `json:"price_cent"`
|
||||
PriceText string `json:"price_text"`
|
||||
Currency string `json:"currency"`
|
||||
Badge string `json:"badge"`
|
||||
Status string `json:"status"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
}
|
||||
|
||||
// TokenGrantView 是 Token 获取记录展示结构。
|
||||
type TokenGrantView struct {
|
||||
GrantID uint64 `json:"grant_id"`
|
||||
EventID string `json:"event_id"`
|
||||
Source string `json:"source"`
|
||||
SourceLabel string `json:"source_label"`
|
||||
Amount int64 `json:"amount"`
|
||||
Status string `json:"status"`
|
||||
QuotaApplied bool `json:"quota_applied"`
|
||||
Description string `json:"description"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
// TokenOrderView 是订单展示结构。
|
||||
type TokenOrderView struct {
|
||||
OrderID uint64 `json:"order_id"`
|
||||
OrderNo string `json:"order_no"`
|
||||
Status string `json:"status"`
|
||||
ProductSnapshot string `json:"product_snapshot"`
|
||||
ProductName string `json:"product_name"`
|
||||
Quantity int `json:"quantity"`
|
||||
TokenAmount int64 `json:"token_amount"`
|
||||
AmountCent int64 `json:"amount_cent"`
|
||||
PriceText string `json:"price_text"`
|
||||
Currency string `json:"currency"`
|
||||
PaymentMode string `json:"payment_mode"`
|
||||
Grant *TokenGrantView `json:"grant"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
PaidAt *string `json:"paid_at"`
|
||||
GrantedAt *string `json:"granted_at"`
|
||||
}
|
||||
|
||||
// CreateTokenOrderRequest 是创建订单请求契约。
|
||||
type CreateTokenOrderRequest struct {
|
||||
ActorUserID uint64 `json:"actor_user_id"`
|
||||
ProductID uint64 `json:"product_id"`
|
||||
Quantity int `json:"quantity"`
|
||||
IdempotencyKey string `json:"idempotency_key"`
|
||||
}
|
||||
|
||||
// ListTokenOrdersRequest 是订单列表查询契约。
|
||||
type ListTokenOrdersRequest struct {
|
||||
ActorUserID uint64 `json:"actor_user_id"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// MockPaidOrderRequest 是 P0 mock paid 请求契约。
|
||||
type MockPaidOrderRequest struct {
|
||||
ActorUserID uint64 `json:"actor_user_id"`
|
||||
OrderID uint64 `json:"order_id"`
|
||||
MockChannel string `json:"mock_channel"`
|
||||
IdempotencyKey string `json:"idempotency_key"`
|
||||
}
|
||||
|
||||
// ListTokenGrantsRequest 是 Token 获取记录列表查询契约。
|
||||
type ListTokenGrantsRequest struct {
|
||||
ActorUserID uint64 `json:"actor_user_id"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
Source string `json:"source"`
|
||||
}
|
||||
|
||||
// RecordForumRewardGrantRequest 是论坛奖励入账的内部 RPC 契约。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 只描述一条待记录到 token_grants 的论坛奖励事实;
|
||||
// 2. 不携带最终奖励金额,金额由 token-store 按 source 和配置解析;
|
||||
// 3. source_ref_id 使用字符串承接 post_id / import_id,服务层再按当前库表结构落成整数。
|
||||
type RecordForumRewardGrantRequest struct {
|
||||
EventID string `json:"event_id"`
|
||||
ReceiverUserID uint64 `json:"receiver_user_id"`
|
||||
Source string `json:"source"`
|
||||
SourceRefID string `json:"source_ref_id"`
|
||||
}
|
||||
|
||||
// TokenGrantRecord 是 token-store 内部发放出口使用的获取事实。
|
||||
type TokenGrantRecord struct {
|
||||
EventID string `json:"event_id"`
|
||||
UserID uint64 `json:"user_id"`
|
||||
Source string `json:"source"`
|
||||
SourceRefID uint64 `json:"source_ref_id"`
|
||||
OrderID uint64 `json:"order_id"`
|
||||
Amount int64 `json:"amount"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
128
backend/shared/events/forum.go
Normal file
128
backend/shared/events/forum.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
ForumPostLikedEventType = "forum.post.liked"
|
||||
ForumPostImportedEventType = "forum.post.imported"
|
||||
ForumRewardEventVersion = "v1"
|
||||
|
||||
ForumRewardSourceLike = "forum_like"
|
||||
ForumRewardSourceImport = "forum_import"
|
||||
)
|
||||
|
||||
// ForumPostRewardPayload 是计划广场作者奖励事件的统一载荷。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 只描述“哪个帖子因什么互动触发了作者奖励”,不直接携带最终 Token 数额;
|
||||
// 2. source 负责表达奖励来源,真正的奖励规则仍由 token-store 自己解析;
|
||||
// 3. event_id 必须稳定,供 outbox 重试和下游记账幂等共同使用。
|
||||
type ForumPostRewardPayload struct {
|
||||
EventID string `json:"event_id"`
|
||||
PostID uint64 `json:"post_id"`
|
||||
ImportID uint64 `json:"import_id"`
|
||||
AuthorUserID uint64 `json:"author_user_id"`
|
||||
ActorUserID uint64 `json:"actor_user_id"`
|
||||
RewardReceiverUserID uint64 `json:"reward_receiver_user_id"`
|
||||
Source string `json:"source"`
|
||||
OccurredAt time.Time `json:"occurred_at"`
|
||||
}
|
||||
|
||||
func NewForumPostLikedPayload(postID uint64, authorUserID uint64, actorUserID uint64, occurredAt time.Time) ForumPostRewardPayload {
|
||||
return newForumPostRewardPayload(
|
||||
ForumPostLikedEventType,
|
||||
ForumRewardSourceLike,
|
||||
postID,
|
||||
0,
|
||||
authorUserID,
|
||||
actorUserID,
|
||||
occurredAt,
|
||||
)
|
||||
}
|
||||
|
||||
func NewForumPostImportedPayload(postID uint64, importID uint64, authorUserID uint64, actorUserID uint64, occurredAt time.Time) ForumPostRewardPayload {
|
||||
return newForumPostRewardPayload(
|
||||
ForumPostImportedEventType,
|
||||
ForumRewardSourceImport,
|
||||
postID,
|
||||
importID,
|
||||
authorUserID,
|
||||
actorUserID,
|
||||
occurredAt,
|
||||
)
|
||||
}
|
||||
|
||||
func newForumPostRewardPayload(
|
||||
eventType string,
|
||||
source string,
|
||||
postID uint64,
|
||||
importID uint64,
|
||||
authorUserID uint64,
|
||||
actorUserID uint64,
|
||||
occurredAt time.Time,
|
||||
) ForumPostRewardPayload {
|
||||
if occurredAt.IsZero() {
|
||||
occurredAt = time.Now()
|
||||
}
|
||||
return ForumPostRewardPayload{
|
||||
EventID: ForumRewardEventID(eventType, postID, actorUserID),
|
||||
PostID: postID,
|
||||
ImportID: importID,
|
||||
AuthorUserID: authorUserID,
|
||||
ActorUserID: actorUserID,
|
||||
RewardReceiverUserID: authorUserID,
|
||||
Source: strings.TrimSpace(source),
|
||||
OccurredAt: occurredAt,
|
||||
}
|
||||
}
|
||||
|
||||
func ForumRewardEventID(eventType string, postID uint64, actorUserID uint64) string {
|
||||
return fmt.Sprintf("%s:%d:%d", strings.TrimSpace(eventType), postID, actorUserID)
|
||||
}
|
||||
|
||||
// EventType 根据 source 反推出当前奖励事件类型。
|
||||
func (p ForumPostRewardPayload) EventType() string {
|
||||
switch strings.TrimSpace(p.Source) {
|
||||
case ForumRewardSourceLike:
|
||||
return ForumPostLikedEventType
|
||||
case ForumRewardSourceImport:
|
||||
return ForumPostImportedEventType
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (p ForumPostRewardPayload) MessageKey() string {
|
||||
return strings.TrimSpace(p.EventID)
|
||||
}
|
||||
|
||||
func (p ForumPostRewardPayload) AggregateID() string {
|
||||
return fmt.Sprintf("post:%d", p.PostID)
|
||||
}
|
||||
|
||||
func (p ForumPostRewardPayload) Validate() error {
|
||||
if strings.TrimSpace(p.EventID) == "" {
|
||||
return errors.New("forum reward event_id 不能为空")
|
||||
}
|
||||
if strings.TrimSpace(p.EventType()) == "" {
|
||||
return errors.New("forum reward source 非法")
|
||||
}
|
||||
if p.PostID == 0 {
|
||||
return errors.New("forum reward post_id 不能为空")
|
||||
}
|
||||
if p.AuthorUserID == 0 || p.ActorUserID == 0 || p.RewardReceiverUserID == 0 {
|
||||
return errors.New("forum reward user_id 不能为空")
|
||||
}
|
||||
if strings.TrimSpace(p.Source) == ForumRewardSourceImport && p.ImportID == 0 {
|
||||
return errors.New("forum import reward import_id 不能为空")
|
||||
}
|
||||
if p.OccurredAt.IsZero() {
|
||||
return errors.New("forum reward occurred_at 不能为空")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
29
backend/shared/infra/outbox/migration.go
Normal file
29
backend/shared/infra/outbox/migration.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package outbox
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
runtimemodel "github.com/LoveLosita/smartflow/backend/services/runtime/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// AutoMigrateServiceTable 按服务目录迁移单个服务拥有的 outbox 表。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 只负责创建或补齐服务级 outbox 物理表,不迁移任何业务表;
|
||||
// 2. table 名统一从 service catalog 解析,避免独立服务和 core 进程各写一份默认值;
|
||||
// 3. 失败时返回带 service/table 的错误,方便启动期直接定位配置漂移。
|
||||
func AutoMigrateServiceTable(db *gorm.DB, serviceName string) error {
|
||||
if db == nil {
|
||||
return fmt.Errorf("auto migrate outbox table failed for %s: db is nil", serviceName)
|
||||
}
|
||||
|
||||
cfg, ok := ResolveServiceConfig(serviceName)
|
||||
if !ok {
|
||||
return fmt.Errorf("resolve outbox config failed for service %s", serviceName)
|
||||
}
|
||||
if err := db.Table(cfg.TableName).AutoMigrate(&runtimemodel.AgentOutboxMessage{}); err != nil {
|
||||
return fmt.Errorf("auto migrate outbox table failed for %s (%s): %w", cfg.Name, cfg.TableName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
103
backend/shared/infra/outbox/repository_publisher.go
Normal file
103
backend/shared/infra/outbox/repository_publisher.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package outbox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// RepositoryPublisher 只负责把事件写入服务级 outbox 表。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 负责复用 Repository 的 eventType -> service -> table 路由能力写入 outbox;
|
||||
// 2. 不启动 Kafka relay / consumer,也不注册任何 handler;
|
||||
// 3. 适合独立 RPC 服务进程只发布事件、统一由 worker 进程消费的迁移期场景。
|
||||
type RepositoryPublisher struct {
|
||||
repo *Repository
|
||||
maxRetry int
|
||||
}
|
||||
|
||||
// NewRepositoryPublisher 基于 outbox 仓储创建轻量发布器。
|
||||
func NewRepositoryPublisher(repo *Repository, maxRetry int) *RepositoryPublisher {
|
||||
return &RepositoryPublisher{
|
||||
repo: repo,
|
||||
maxRetry: maxRetry,
|
||||
}
|
||||
}
|
||||
|
||||
// Publish 写入统一事件外壳,保持与 Engine.Publish 相同的 outbox payload 格式。
|
||||
//
|
||||
// 步骤说明:
|
||||
// 1. 先校验事件类型和业务 payload,明显坏入参直接返回错误,避免写入不可消费消息;
|
||||
// 2. 再把业务 payload 序列化成 RawMessage,并包进统一事件外壳,保证 worker 解析口径一致;
|
||||
// 3. 最后交给 Repository 按事件路由落表;路由缺失时返回错误,由业务侧决定是否降级。
|
||||
func (p *RepositoryPublisher) Publish(ctx context.Context, req PublishRequest) error {
|
||||
if p == nil || p.repo == nil {
|
||||
return errors.New("outbox repository publisher is nil")
|
||||
}
|
||||
|
||||
eventType := strings.TrimSpace(req.EventType)
|
||||
if eventType == "" {
|
||||
return errors.New("eventType is empty")
|
||||
}
|
||||
if req.Payload == nil {
|
||||
return errors.New("payload is nil")
|
||||
}
|
||||
|
||||
payloadJSON, err := json.Marshal(req.Payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
eventVersion := strings.TrimSpace(req.EventVersion)
|
||||
if eventVersion == "" {
|
||||
eventVersion = DefaultEventVersion
|
||||
}
|
||||
|
||||
eventID := strings.TrimSpace(req.EventID)
|
||||
messageKey := strings.TrimSpace(req.MessageKey)
|
||||
if messageKey == "" {
|
||||
messageKey = eventID
|
||||
}
|
||||
if messageKey == "" {
|
||||
messageKey = eventType
|
||||
}
|
||||
|
||||
aggregateID := strings.TrimSpace(req.AggregateID)
|
||||
if aggregateID == "" {
|
||||
aggregateID = messageKey
|
||||
}
|
||||
|
||||
_, err = p.repo.CreateMessage(ctx, eventType, messageKey, OutboxEventPayload{
|
||||
EventID: eventID,
|
||||
EventType: eventType,
|
||||
EventVersion: eventVersion,
|
||||
AggregateID: aggregateID,
|
||||
Payload: payloadJSON,
|
||||
}, p.maxRetry)
|
||||
return err
|
||||
}
|
||||
|
||||
// PublishWithTx 使用外部事务写入 outbox 消息。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 只把底层 Repository 切到调用方传入的事务句柄,事件外壳和路由逻辑仍复用 Publish;
|
||||
// 2. 不提交或回滚事务,事务生命周期由业务用例控制;
|
||||
// 3. 适合“业务表更新 + outbox 入队”必须原子提交的场景。
|
||||
func (p *RepositoryPublisher) PublishWithTx(ctx context.Context, tx *gorm.DB, req PublishRequest) error {
|
||||
if p == nil || p.repo == nil {
|
||||
return errors.New("outbox repository publisher 未初始化")
|
||||
}
|
||||
if tx == nil {
|
||||
return errors.New("gorm 事务句柄为空")
|
||||
}
|
||||
|
||||
txPublisher := &RepositoryPublisher{
|
||||
repo: p.repo.WithTx(tx),
|
||||
maxRetry: p.maxRetry,
|
||||
}
|
||||
return txPublisher.Publish(ctx, req)
|
||||
}
|
||||
@@ -15,6 +15,8 @@ const (
|
||||
ServiceMemory = "memory"
|
||||
ServiceActiveScheduler = "active-scheduler"
|
||||
ServiceNotification = "notification"
|
||||
ServiceTaskClassForum = "taskclass-forum"
|
||||
ServiceTokenStore = "token-store"
|
||||
)
|
||||
|
||||
// ServiceConfig 描述一个服务级 outbox 的固定归属。
|
||||
@@ -83,6 +85,18 @@ func LoadServiceConfigs() map[string]ServiceConfig {
|
||||
GroupID: "smartflow-notification-outbox-consumer",
|
||||
TableName: "notification_outbox_messages",
|
||||
},
|
||||
ServiceTaskClassForum: {
|
||||
Name: ServiceTaskClassForum,
|
||||
Topic: "smartflow.taskclass-forum.outbox",
|
||||
GroupID: "smartflow-taskclass-forum-outbox-consumer",
|
||||
TableName: "taskclass_forum_outbox_messages",
|
||||
},
|
||||
ServiceTokenStore: {
|
||||
Name: ServiceTokenStore,
|
||||
Topic: "smartflow.token-store.outbox",
|
||||
GroupID: "smartflow-token-store-outbox-consumer",
|
||||
TableName: "token_store_outbox_messages",
|
||||
},
|
||||
}
|
||||
|
||||
for name, entry := range entries {
|
||||
|
||||
@@ -10,6 +10,8 @@ const (
|
||||
ServiceNameMemory = "memory"
|
||||
ServiceNameActiveScheduler = "active-scheduler"
|
||||
ServiceNameNotification = "notification"
|
||||
ServiceNameTaskClassForum = "taskclass-forum"
|
||||
ServiceNameTokenStore = "token-store"
|
||||
)
|
||||
|
||||
// ServiceRoute 描述一个 outbox 服务的终态路由信息。
|
||||
@@ -56,6 +58,18 @@ var builtinServiceRoutes = map[string]ServiceRoute{
|
||||
Topic: "smartflow.notification.outbox",
|
||||
GroupID: "smartflow-notification-outbox-consumer",
|
||||
},
|
||||
ServiceNameTaskClassForum: {
|
||||
ServiceName: ServiceNameTaskClassForum,
|
||||
TableName: "taskclass_forum_outbox_messages",
|
||||
Topic: "smartflow.taskclass-forum.outbox",
|
||||
GroupID: "smartflow-taskclass-forum-outbox-consumer",
|
||||
},
|
||||
ServiceNameTokenStore: {
|
||||
ServiceName: ServiceNameTokenStore,
|
||||
TableName: "token_store_outbox_messages",
|
||||
Topic: "smartflow.token-store.outbox",
|
||||
GroupID: "smartflow-token-store-outbox-consumer",
|
||||
},
|
||||
}
|
||||
|
||||
// DefaultServiceRoutes 返回当前已知服务的默认路由清单。
|
||||
@@ -71,6 +85,8 @@ func DefaultServiceRoutes() []ServiceRoute {
|
||||
builtinServiceRoutes[ServiceNameMemory],
|
||||
builtinServiceRoutes[ServiceNameActiveScheduler],
|
||||
builtinServiceRoutes[ServiceNameNotification],
|
||||
builtinServiceRoutes[ServiceNameTaskClassForum],
|
||||
builtinServiceRoutes[ServiceNameTokenStore],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user