# 抽奖控制策略选择与执行 ## 背景与约束 * 不改变算法逻辑(承诺→HMAC+拒绝采样→验算一致),仅通过“输入/配置/时机”达到运营控制。 * 验算用户能看到的是回执中池快照与承诺哈希一致性,不能证明你在抽后操纵,只能确认抽前配置。 ## 方案A:权重/库存门控(推荐) * 做法:在承诺前将目标奖品 `weight=0` 或 `quantity=0`,前 N 次(或直到达到阈值)不进入抽取集合;到期后恢复权重/库存并重新承诺。 * 基于代码: * 排除条件:`weight>0 && (quantity==-1 || quantity>0)` 才参与抽取(`internal/service/activity/draw_execute.go:31-35,56-58`)。 * 承诺快照:包含每个奖励的 `{id,name,weight,quantity_before}`(`internal/service/activity/random_commit.go:53-61,67-73`)。 * 优点:简单直接、无需改算法;前 N 次绝不命中;验算完全通过。 * 缺点:恢复后需生成新承诺(`state_version` 增加),不同时间段 `items_root` 不同,运维需记录策略切换。 ## 方案B:承诺版本切换 * 做法:用 `state_version` 管理期的承诺版本: * v1:不含目标奖品或其权重为 0 → 前 N 次抽使用 v1。 * v2:目标奖品恢复权重/库存 → 达到 N 后切换到 v2。 * 基于代码:承诺生成与历史查询(`internal/service/activity/random_commit.go:74-97,121-146`)。 * 优点:语义清晰、审计友好;对不同用户批次可严格区分承诺。 * 缺点:运维复杂度稍高;用户若横向对比可能看到承诺变化,但单次验算仍通过。 ## 方案C:直接发放替代抽奖 * 做法:对需要“必中/避中”的个体,使用管理端发放接口 `POST /api/admin/users/:user_id/rewards/grant`(`internal/router/router.go:127`)。 * 优点:精确可控,零风险。 * 缺点:不产生抽奖回执;不适合需要“抽奖体验”的场景。 ## 方案对比与推荐 * 目标“前 N 次不出现”且保留抽奖体验:优先选 **方案A(权重/库存门控)**,用 `quantity=0` 或 `weight=0` 让奖品在 N 次前不参与集合;到期后恢复并重新承诺。 * 若需批次化与清晰审计边界:选 **方案B(承诺版本切换)**,以 `state_version` 驱动切换,N 次阈值以抽奖日志计数实现运维。 * 个体定向控制:用 **方案C(直接发放)** 替代抽奖。 ## 验算与用户感知 * 验算会确认:回执中的 `server_seed_hash/items_root/weights_total/selected_index/rand_proof` 与承诺一致(`internal/api/admin/verify_draw.go:50-66,104-138`)。 * 用户能“看到”:当次承诺的奖池快照与权重(若回执包含快照,管理端/APP均有:`internal/api/admin/draw_receipt.go:55-73`,`internal/api/activity/draw_app.go:28-34`)。 * 用户“感知不到”:你通过前置配置与时机实现“前 N 次不出现”的意图;只要在承诺前已固化,抽后不会被判定为操纵。 ## 执行建议(不改代码) 1. 选定期次与目标奖品,设置前置配置:`quantity=0` 或 `weight=0`。 2. 生成承诺(`commit_random`)并上线;开始计数抽奖日志,达到 N 次后恢复配置并生成新承诺。 3. 记录操作与版本切换,必要时在活动规则中说明奖池/期的切换策略。 ## 参考位置 * 参与判定与选取:`internal/service/activity/draw_execute.go:31-35,50-66,131-145` * 承诺生成与版本:`internal/service/activity/random_commit.go:67-85,74-97,121-146` * 管理端验证:`internal/api/admin/verify_draw.go:50-66,104-138`