# 08 命运盒子（for AI Agents）

> 关联代码：internal/service/drawbox/、internal/pkg/drawbox/engine.go、internal/model/drawbox*.go（Prize/Record/Idempotency/CostLedger/TaskLog 五表）、internal/response/drawbox_codes.go
> 管理端页面：web/src/views/drawbox/（奖池管理/库存看板/试抽面板/参数配置/中奖记录）；小程序页面：pages/drawbox/*

====================================================================
1. 功能概述
====================================================================

命运盒子是平台自运营的九宫格抽奖（V11 的抽奖引擎核心）：用户用免费次数/任务赚取的次数抽奖，奖池分 L1-L4 层级，奖品类型含实物（购买权/品牌专属价）、红包、优惠券、积分。带完整的成本预算熔断（warn/degrade/breaker 三档）与四层并发防护。是 A/B 实验首批接入的业务（见 18）。

====================================================================
2. 底层逻辑
====================================================================

2.1 次数体系（KV 配置驱动）
- 每日免费次数 drawbox.daily_free_chance、新人免费次数 drawbox.newbie_chance、每日总上限 drawbox.daily_limit。
- 任务赚次数：drawbox.task_{checkin,video,welfare,invite,brand}_{chance,points,daily_limit,interval_sec}（签到/看视频/逛福利/邀请/品牌任务）；部分任务有冷却时间（如视频 1006）。
- MpUser 上的扩展字段：DailyChance/DailyDrawCount/ConsecutiveMiss/LuckValue/RiskScore/TeamLevel/LastDrawDate（懒重置：LastDrawDate 跨天时重置每日计数）。

2.2 抽奖引擎（internal/pkg/drawbox/engine.go）
- 按奖品权重抽取；叠加三层运营策略：
  - luck_value 幸运值加成（行为累积）
  - pity_miss_count 保底：连续未中达到阈值提升中奖权重（drawbox.pity_miss_count）
  - newbie_protection 新人保护：前 N 抽（drawbox.newbie_protection_draws）提高体验
- 结果三态：真中奖（实物/红包）、购买权（品牌专属价，需付费购买）、积分保底。

2.3 成本熔断（KV：drawbox.daily_budget_fen / cost_warn_ratio / cost_degrade_ratio / cost_breaker_ratio）
- 当日累计成本（CostLedger 台账）占日预算比例达到三档阈值：
  - warn：告警（企微 webhook alert.wecom_webhook_url 或日志）
  - degrade：降档（奖池降级，抽中概率/层级下调）
  - breaker：熔断（拒绝新抽奖，返回 1004「好运发完了」）
- 库存看板页实时展示三档进度。

2.4 并发防护四层（勿回退）
- Redis 分布式锁 SETNX（distlock，跨实例同用户互斥；Redis 不可用时退化为进程内）
- DB 条件 UPDATE 防奖品超发
- DrawboxIdempotency 幂等表（RequestID 唯一）——客户端每次 draw 带幂等键 header
- 限流中间件（Limiter/RedisLimiter）挂 draw/claim/task/complete 三个端点

2.5 异步副作用
- Redis 可用时，A/B 转化上报、miss_count 索引更新走 Redis Stream 异步队列（消费者带死信告警）；不可用时同步降级。

2.6 履约（claim）
- claim_type=pay：购买权 → 微信支付（订单类型见 11）
- claim_type=address：实物 → 绑定收货地址
- claim_type=auto：红包/卡券/积分自动入账
- 未领取记录在 history 页可继续领取。

2.7 合伙人（好运合伙人）
- TeamLevel 等级 drawbox.team.lv1/2/3（白银/黄金/钻石），按邀请活跃度升级；小程序 pages/team/index 展示进度；合伙人海报 GET /mp/drawbox/team|poster。

2.8 独立错误码空间（drawbox_codes.go，数值与全局重叠但仅经 DrawboxJSON 返回）
- 1001 无抽奖机会；1002 今日上限；1003 风控拦截；1004 成本熔断；1005 分布式锁繁忙；1006 视频任务冷却中；5000 系统繁忙。

====================================================================
3. 后台操作（命运盒子菜单）
====================================================================

3.1 奖池管理 /drawbox/prizes（views/drawbox/PrizeList.vue）
- 筛选：名称、等级（L1-L4）、启用状态。
- 表格列：ID/名称/等级/类型/权重/成本(元)/幸运价(元)/市场价(元)/总库存/剩余库存/日发上限/排序/启用/操作。
- 「新建/编辑奖品」弹窗：名称、等级、类型（physical 实物/purchase 购买权/points 积分/redpacket 红包/coupon 优惠券）、权重、单份成本（元）、市场价、幸运价（元）、总库存、剩余库存、日发上限、排序、启用开关、描述。
- 行操作：启停切换、删除（软删）。

3.2 库存看板 /drawbox/stock（StockBoard.vue）
- 只读统计卡：当日成本、每日预算、预警比例、熔断比例 + 成本进度条（按档位变色）+ 各奖品库存明细表 + 刷新按钮。运营每日盯板入口。

3.3 试抽面板 /drawbox/trial（TrialPanel.vue）
- 输入用户画像参数（签到天数/团队等级/风险分/总次数/连续未中/是否首抽）→「试抽 1/10/100 次」→ 结果分布条形图 + 当前权重占比。不扣库存不落库，用于验证奖池配置与策略效果。

3.4 参数配置 /drawbox/config（ConfigForm.vue）
- 表单：每日免费次数、新人免费次数、每日上限、每日预算（元）、预警%、降级%、熔断%、新人保护抽数、保底未中次数。保存二次确认（影响线上概率）。

3.5 中奖记录 /drawbox/records（Records.vue）
- 筛选：搜索用户（支持 URL query.user_id 预筛选，从小程序用户列表「更多」跳入）、结果（中奖/购买权/积分）。
- 表格列：用户/结果/奖品/次数/时间。

3.6 后台操作逻辑备注（隐藏约束，UI 不直接体现）
- 奖品删除为软禁用：DELETE 奖品实际设置 is_active=false 而非物理删除；已关联的中奖记录不受影响。
- 参数配置校验：daily_limit 范围 1-100；成本三档比例（warn/degrade/breaker）必须严格递增（warn < degrade < breaker），且每档值在 0-3 之间（即 0%-300%）；保存时后端强校验，不满足直接拒绝。
- 发货记录约束：仅 result_type=win 且 status=claimed 的中奖记录可创建发货记录（ShipRecord）；发货时用户必须已有收货地址。
- 试抽面板隔离：试抽不落库不扣库存，与线上真实概率存在采样误差；验证熔断/降档逻辑要看库存看板与 CostLedger。
- 奖品权重与库存：奖品启停不影响其他奖品权重分配；停用奖品的权重在引擎中自动跳过，不会重新分配给其他奖品（总权重可能不足 100%）。

====================================================================
4. 小程序端操作
====================================================================

- pages/drawbox/index：九宫格主页。onLoad 拉 grid/summary + A/B 分组（GET /mp/ab/assign?variable=drawbox，控制按钮文案/副标题/分享标题/底部规则开关，失败保持默认）。首页命运盒子按钮状态机：draw→进盒子；no_chance→跳任务中心（pages/points/tasks 或 pages/tasks/index）；limit/breaker→Toast 提示。
- 抽奖：POST /mp/drawbox/draw（带幂等键 header）→ 先取结果再播三段动画（进度→旋转→结果）。错误码 1001/1002 引导做任务或回首页；1004 弹 Modal；5000 重试。
- 结果操作：收下/立即领取 → claim 页；继续抽奖/做任务；分享 path 带 invite_code+st；海报 → pages/share/poster?record_id=。
- pages/drawbox/claim：POST /mp/drawbox/claim 按 claim_type 分流（pay→wx.requestPayment；address→选地址；auto→入账提示）。错误：1001 缺地址引导新增；3004 已领取；1004 非本人。
- pages/drawbox/history：我的盒子记录，未领取条目点击进 claim。
- 任务中心：pages/tasks/index（任务 Tab）+ pages/points/tasks 两处入口。

====================================================================
5. API 速查
====================================================================

小程序端（/api/mp，JWT + 限流）：
GET  /api/mp/drawbox/summary             次数/状态汇总
GET  /api/mp/drawbox/grid                九宫格奖池展示
GET  /api/mp/drawbox/history             我的记录
GET  /api/mp/drawbox/tasks               任务列表与完成态
POST /api/mp/drawbox/draw                抽奖（幂等键 header；独立码空间 1001/1002/1003/1004/1005/5000）
POST /api/mp/drawbox/claim               领取/购买履约
POST /api/mp/drawbox/task/complete       任务完成上报（1006 冷却）
GET  /api/mp/drawbox/team|poster         合伙人等级 / 海报

管理端（/api/admin，权限 drawbox:prize:* / drawbox:config:update）：
GET|POST|PUT|DELETE /api/admin/drawbox/prizes(:id)   奖池 CRUD
PUT  /api/admin/drawbox/prizes/:id/toggle-active     启停
GET  /api/admin/drawbox/stock                        库存看板数据
GET  /api/admin/drawbox/trial                        试抽（带画像参数）
GET|PUT /api/admin/drawbox/config                    参数配置
GET  /api/admin/drawbox/records                      中奖记录

====================================================================
6. 注意事项
====================================================================

- 熔断触发后（1004）调参不会自动恢复当日：成本台账按日累计，需评估是否调高预算或等次日重置（drawbox Sweeper 5min 兜底重置）。
- draw 掜幂等键：同一 RequestID 重复请求返回同一结果；排查「扣了次数没出结果」时先查幂等表。
- 试抽面板不落库不扣库存，与线上真实概率存在采样误差；验证熔断/降档逻辑要看库存看板与 CostLedger。
- 奖品「幸运价」与「单份成本」均以元输入，后端以分存储（UnitCostFen）——表单提交时自动 ×100 换算；单份成本必须准确，否则熔断判断失真。
- A/B 实验 drawbox 变量只影响展示文案与规则开关，不影响抽奖数学（见 18）。
