新功能(#3): 抽奖 Stage 1 handler 真实业务对接 + 邀请钩子
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 返回空。
This commit is contained in:
@@ -14,6 +14,8 @@ type defaultRegistry struct {
|
||||
}
|
||||
|
||||
// NewRegistry 返回空的默认注册表。使用 Register 挂接实现。
|
||||
// 实际业务 handler(vpn_duration / commission)在 internal/logic/lottery/handler 里,
|
||||
// 由 initialize 阶段注入(避免 model 层反向依赖 logic 层)。
|
||||
func NewRegistry() *defaultRegistry {
|
||||
return &defaultRegistry{handlers: make(map[string]PrizeHandler, 8)}
|
||||
}
|
||||
@@ -39,11 +41,7 @@ func (r *defaultRegistry) MustGet(prizeType string) (PrizeHandler, error) {
|
||||
return nil, ErrHandlerNotRegistered
|
||||
}
|
||||
|
||||
// ---- Stage 1 骨架 handler --------------------------------------------------
|
||||
// 真实业务对接在架构师 review 骨架 PR 之后 Day 2-3 补齐。此时命中真实
|
||||
// handler 会返回 ErrNotImplemented,让抽奖流程整笔事务回滚,避免误发。
|
||||
|
||||
// noopHandler 是"谢谢参与"。写记录、不发放,不需要真实业务对接。
|
||||
// noopHandler 是"谢谢参与",永远归 model 层:不依赖任何业务,最小占位。
|
||||
type noopHandler struct{}
|
||||
|
||||
// NewNoopHandler 返回 PrizeTypeNone 的 handler。
|
||||
@@ -55,40 +53,3 @@ func (noopHandler) Dispatch(_ context.Context, _ *gorm.DB, _ DispatchRequest) (D
|
||||
return DispatchResult{State: DispatchStateAutoClaimed, Message: "谢谢参与"}, nil
|
||||
}
|
||||
func (noopHandler) ValidateClaim(_ []byte) error { return nil }
|
||||
|
||||
// vpnDurationStubHandler 是订阅时长发放的占位 handler。
|
||||
// Day 2-3 需替换为真实实现:
|
||||
// 1. tx 内 SELECT ... FOR UPDATE 锁 lottery_grant_ledger UNIQUE(external_ref)
|
||||
// 2. 命中 → 幂等返回;未命中 → INSERT ledger + 调用 UserModel.UpdateSubscribe/InsertSubscribe
|
||||
// 3. external_ref 用 fmt.Sprintf("lottery:%d:%d", req.ActivityId, req.DrawId)
|
||||
// 4. 家庭组场景通过 commonLogic.ResolveEntitlementUser 归位到 owner
|
||||
// 5. 成功后 best-effort 调 NodeModel.ClearServerAllCache + 用户组重算
|
||||
type vpnDurationStubHandler struct{}
|
||||
|
||||
// NewVPNDurationStubHandler 返回 PrizeTypeVPNDuration 的骨架 handler。
|
||||
func NewVPNDurationStubHandler() PrizeHandler { return &vpnDurationStubHandler{} }
|
||||
|
||||
func (vpnDurationStubHandler) Type() string { return PrizeTypeVPNDuration }
|
||||
func (vpnDurationStubHandler) IsAuto() bool { return true }
|
||||
func (vpnDurationStubHandler) Dispatch(_ context.Context, _ *gorm.DB, _ DispatchRequest) (DispatchResult, error) {
|
||||
return DispatchResult{}, ErrNotImplemented
|
||||
}
|
||||
func (vpnDurationStubHandler) ValidateClaim(_ []byte) error { return nil }
|
||||
|
||||
// commissionStubHandler 是佣金入账的占位 handler。
|
||||
// Day 2-3 需替换为真实实现:
|
||||
// 1. tx 内 lottery_grant_ledger 幂等检查
|
||||
// 2. 调 UserModel.UpdateCommission + common.WriteCommissionLog(同 tx)
|
||||
// 3. 建议新增 log.CommissionTypeLottery 常量(数值待架构师批),
|
||||
// 避免把抽奖入账混进 Purchase/Renewal 类型统计
|
||||
type commissionStubHandler struct{}
|
||||
|
||||
// NewCommissionStubHandler 返回 PrizeTypeCommission 的骨架 handler。
|
||||
func NewCommissionStubHandler() PrizeHandler { return &commissionStubHandler{} }
|
||||
|
||||
func (commissionStubHandler) Type() string { return PrizeTypeCommission }
|
||||
func (commissionStubHandler) IsAuto() bool { return true }
|
||||
func (commissionStubHandler) Dispatch(_ context.Context, _ *gorm.DB, _ DispatchRequest) (DispatchResult, error) {
|
||||
return DispatchResult{}, ErrNotImplemented
|
||||
}
|
||||
func (commissionStubHandler) ValidateClaim(_ []byte) error { return nil }
|
||||
|
||||
Reference in New Issue
Block a user