a46fb83054
Closes HIF-3 (阶段 PR B) PR B:handler 真实业务对接 + 邀请钩子(迭代含 R1 修复) - 迁移 02157_lottery_grant_ledger:external_ref UNIQUE 作为发奖幂等键 - log.CommissionTypeLottery=339(架构师批准的新常量) - DispatchRequest.IdempotencyKey(架构师 review 建议第 2 条) - GrantLedger + LedgerService.Reserve:INSERT ON CONFLICT DO NOTHING 幂等 upsert - VPNDurationHandler:ResolveEffectiveUser 归位家庭 owner + UpdateSubscribe,ExpireTime 三分支对齐 grantGiftDays - CommissionHandler:UpdateCommission + WriteCommissionLog(339),发给中奖者本人,不做家庭组归位 - Handler 从 model 层迁到 logic 层(避免 model → logic 反向依赖);noop 保留在 model 层 - InviteHook:fire-and-forget 独立 goroutine + 10s timeout,扫 running 活动的 invite_success 源 - ServiceContext 新增 LotteryChance / LotteryLedger / LotteryInviteHook - activateOrderLogic.handleCommission 两条分支通过 invokeInviteHookIfEligible 助手触发,助手内统一 gate IsNew(架构师 R1 打回后的修复) 架构师 R1 打回:branch B 未按"首次付款激活"gate,续费也会给 referer 发抽奖机会 → 已通过助手函数集中收拢,避免 branch A/B 判断漂移。 测试覆盖率:model 76.5% / handler 73.2% / hook 91.7%;补 4 个回归测试覆盖 IsNew 门槛(含关键的 DoesNotFireOnRenewal)。 线上仍零可见变更:无对外路由,钩子仅在活动 status=running 时生效,Stage 1 全流程无活动记录时 loadRunningActivities 返回空。
56 lines
1.7 KiB
Go
56 lines
1.7 KiB
Go
package lottery
|
||
|
||
import (
|
||
"context"
|
||
"sync"
|
||
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
// defaultRegistry 是并发安全的默认 Registry。启动时一次性注册,运行时只读。
|
||
type defaultRegistry struct {
|
||
mu sync.RWMutex
|
||
handlers map[string]PrizeHandler
|
||
}
|
||
|
||
// NewRegistry 返回空的默认注册表。使用 Register 挂接实现。
|
||
// 实际业务 handler(vpn_duration / commission)在 internal/logic/lottery/handler 里,
|
||
// 由 initialize 阶段注入(避免 model 层反向依赖 logic 层)。
|
||
func NewRegistry() *defaultRegistry {
|
||
return &defaultRegistry{handlers: make(map[string]PrizeHandler, 8)}
|
||
}
|
||
|
||
// Register 挂接一个 handler;重复注册会覆盖(初始化阶段可控,不额外拦截)。
|
||
func (r *defaultRegistry) Register(h PrizeHandler) {
|
||
r.mu.Lock()
|
||
defer r.mu.Unlock()
|
||
r.handlers[h.Type()] = h
|
||
}
|
||
|
||
func (r *defaultRegistry) Get(prizeType string) (PrizeHandler, bool) {
|
||
r.mu.RLock()
|
||
defer r.mu.RUnlock()
|
||
h, ok := r.handlers[prizeType]
|
||
return h, ok
|
||
}
|
||
|
||
func (r *defaultRegistry) MustGet(prizeType string) (PrizeHandler, error) {
|
||
if h, ok := r.Get(prizeType); ok {
|
||
return h, nil
|
||
}
|
||
return nil, ErrHandlerNotRegistered
|
||
}
|
||
|
||
// noopHandler 是"谢谢参与",永远归 model 层:不依赖任何业务,最小占位。
|
||
type noopHandler struct{}
|
||
|
||
// NewNoopHandler 返回 PrizeTypeNone 的 handler。
|
||
func NewNoopHandler() PrizeHandler { return &noopHandler{} }
|
||
|
||
func (noopHandler) Type() string { return PrizeTypeNone }
|
||
func (noopHandler) IsAuto() bool { return true }
|
||
func (noopHandler) Dispatch(_ context.Context, _ *gorm.DB, _ DispatchRequest) (DispatchResult, error) {
|
||
return DispatchResult{State: DispatchStateAutoClaimed, Message: "谢谢参与"}, nil
|
||
}
|
||
func (noopHandler) ValidateClaim(_ []byte) error { return nil }
|