新功能(#4): 抽奖 Stage 2 人工奖领奖工单(crypto / physical / manual_other)
Closes HIF-4 Stage 2 交付:人工奖领奖工单完整闭环。crypto / physical / manual_other 三类奖品从抽中到 mark-paid 的全流程可用。 - 迁移 02159_lottery_claim:UNIQUE(draw_id) + 3 支持索引,状态机 pending_claim→reviewing→paying→paid,rejected 可复活,超时 expired - 3 个 PrizeHandler:Dispatch→ErrDispatchNotSupported 兜底、ClaimSchema 各自形态、ValidateClaim 表驱动 - BuildCryptoClaimSchema:抽中时按奖品 config.networks 注入 enum,前端下拉直接可用 - Draw service dispatchOrEnqueueClaim:人工奖同 tx 插 pending_claim(回滚双清),nonce 重放回读 ExpiresAt + ClaimFormSchema - POST /claim 实装:ownership 校验 → prize 类型校验 → handler.ValidateClaim → crypto network 白名单二次校验 → tx CAS status IN (pending_claim, rejected) AND expires_at > now - Admin CRUD 5 接口:list(IN 批拉 snap + user,无 N+1)、summary(GROUP BY 一次拿计数 + overdue 单查)、approve/reject/mark-paid 全走 CAS + audit - Scheduler @every 1h 扫过期,级联 lottery_draw.dispatch_state → expired - 新增错误码 100005-100011(already_submitted / invalid_claim_data / draw_not_found / not_your_draw / claim_expired / claim_state_invalid) - Rebase 后 Stage 2 测试主动 reuse PR E 的 unmetReasonsNotEmpty + evaluatedAtNotZero matcher,人工奖分支若绕过守卫会立即挂 - Stage 1 全部 4 处 guardrail 后端 rebase 时自检过:UnmetReasons、EvaluatedAt、GrantLedger.Payload、AdminMetaMiddleware 全保留 CI 全绿;28 files, +2442/-126;覆盖率 handler 78.9% / model.lottery 74.3% / draw 68.7% / queue/lottery 76.9%
This commit is contained in:
@@ -7,6 +7,7 @@ package lottery
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"gorm.io/gorm"
|
||||
@@ -95,7 +96,7 @@ type DispatchResult struct {
|
||||
|
||||
// PrizeHandler 是一种奖品类型的发奖策略。Type 是注册键;IsAuto=true 表示
|
||||
// 抽奖事务内立刻发放,false 表示挂 pending_claim 等人工发(Stage 1 只实现
|
||||
// IsAuto=true 的三种)。
|
||||
// IsAuto=true 的三种,Stage 2 补齐 crypto/physical/manual_other)。
|
||||
//
|
||||
// 幂等:所有实现必须以 lottery_draw.id 为外部 ref 做 check-before-write,
|
||||
// 避免重试重复发放。见 doc/lottery-stage1-plan.md 的"发奖账本"章节。
|
||||
@@ -104,12 +105,24 @@ type PrizeHandler interface {
|
||||
IsAuto() bool
|
||||
// Dispatch 在调用方的事务内执行;返回结果或错误。
|
||||
// 错误会导致抽奖事务回滚(次数不扣、draw 不落库),由用户侧重新发起。
|
||||
//
|
||||
// 对 IsAuto()=false 的人工奖 handler,Dispatch 不会被抽奖服务调用;
|
||||
// 实现返回 ErrDispatchNotSupported 即可。
|
||||
Dispatch(ctx context.Context, tx *gorm.DB, req DispatchRequest) (DispatchResult, error)
|
||||
// ValidateClaim 是人工领奖时校验用户输入(Stage 2 才用);Stage 1 的
|
||||
// auto handler 直接返回 nil 即可。
|
||||
// ValidateClaim 是人工领奖时校验用户输入(Stage 2 才用);auto handler
|
||||
// 直接返回 nil 即可(默认 noopHandler / vpn_duration / commission 都不用)。
|
||||
ValidateClaim(raw []byte) error
|
||||
// ClaimSchema 返回该奖品的领奖表单 JSON Schema(Stage 2 才用)。
|
||||
// - auto handler 返回 nil(前端拿到 nil / null 就知道不用弹表单)。
|
||||
// - 人工奖 handler 返回一段合法 JSON Schema,前端据此动态渲染表单。
|
||||
ClaimSchema() json.RawMessage
|
||||
}
|
||||
|
||||
// ErrDispatchNotSupported 是 IsAuto()=false handler 的 Dispatch 占位错误:
|
||||
// 抽奖服务命中人工奖时不应调用 Dispatch,理论上永远不会返回给用户,仅供
|
||||
// 单测断言与防御性编程使用。
|
||||
var ErrDispatchNotSupported = errors.New("lottery: dispatch not supported for manual claim handler")
|
||||
|
||||
// ErrNotImplemented 是 Stage 1 骨架里 handler 的占位错误:抽奖流程接入前
|
||||
// 若不慎命中真实 handler 会立即失败,避免误发。
|
||||
var ErrNotImplemented = errors.New("lottery: handler not yet wired to real business")
|
||||
|
||||
Reference in New Issue
Block a user