抽奖模块
一、模块定位
抽奖模块是足球季活动的激励模块,用户每日完成投票后获得抽奖机会,可抽取实物奖品和虚拟权益,保持活动期间的持续参与热情。
关于活动整体背景和全局规则,请见 足球季活动全局。
二、功能需求清单
2.1 抽奖机会获取
| 获取方式 | 说明 |
|---|---|
| 每日投票 | 完成当日投票后自动获得 1 次抽奖机会 |
| 分享活动 | 分享活动页面可额外获得抽奖机会,每人每天至多额外获得 5 次 |
- 抽奖机会与投票绑定:必须先完成投票才能抽奖
- 分享额外获得的抽奖机会,不要求当日投票即可使用
抽奖机会生命周期规则
| 机会类型 | 发放触发 | 有效期 | 上限 | 过期处理 |
|---|---|---|---|---|
| 投票机会 | 用户完成当日投票 | 仅当日有效,00:00 重置 | 每日 1 次 | 活动结束后失效 |
| 分享机会 | 用户从活动页面跳转出去 | 仅当日有效,00:00 重置 | 每日最多 5 次 | 活动结束后失效 |
消耗优先级:可用机会 = 投票机会 + 分享机会。扣减时先扣投票机会(当日过期浪费代价小),投票机会为 0 时再扣分享机会。
幂等保证:同一用户当日多次投票只发 1 次机会;当日分享机会达到 5 次上限后不再发放。
2.2 抽奖操作
- 用户进入抽奖区域,消耗 1 次抽奖机会执行抽奖
- 抽奖结果即时展示(中奖/未中奖)
- 中奖后展示奖品信息和领取提示
2.3 奖品类型
| 奖品类型 | 说明 | 数量限制 |
|---|---|---|
| 公仔玩偶 | 限定足球冠军叫叫公仔挂件(实物) | 限量 430 个 |
| 体验课 | 叫叫体验课(虚拟权益) | 不限量 |
| 叫叫口算VIP月卡 | 叫叫口算VIP月卡(虚拟权益) | 激活码池(约 10 万),每用户至多中奖 1 次 |
| 叫叫金片 | 叫叫金片(实物奖品,真金子制作) | 限量(数量待定),每用户至多中奖 1 次 |
- 实物奖品:公仔玩偶(430个)和叫叫金片(数量待定)需严格控制发放节奏,中奖后需收集用户收货地址
- 不限量奖品:体验课无总数限制
- 激活码奖品:叫叫口算VIP月卡通过预置激活码池发放(约 10 万个激活码),激活码耗尽则该奖品不可再中
- 用户维度限制:公仔玩偶、叫叫金片、叫叫口算VIP月卡均为每用户活动期间最多中奖 1 次
2.4 奖品发放
奖品按类型采用不同的发放方式:
| 奖品类型 | 发放方式 | 说明 |
|---|---|---|
| 公仔玩偶(实物) | 中奖后填写收货地址,活动结束后运营人工发货 | 详见 收货地址模块 |
| 叫叫金片(实物) | 中奖后填写收货地址,活动结束后运营人工发货 | 同上 |
| 叫叫口算VIP月卡(虚拟权益) | 中奖后分配激活码 + 自动复制 + 跳转兑换页 | 详见 2.4.1 虚拟权益领取流程 |
| 体验课(虚拟权益) | 用户中奖后点击「立即领取」按钮跳转领取 | 详见 2.4.1 虚拟权益领取流程 |
| 冠军皮肤装扮 | 活动结束后后台手动发放 | 详见 发奖与数据导出模块 |
- 实物奖品不通过系统自动发放,活动结束后运营人员导出数据人工发货
- 虚拟权益奖品由用户自主领取:口算VIP月卡通过激活码发放,体验课通过跳转链接领取
- 详见 发奖与数据导出模块
2.4.1 虚拟权益领取流程
虚拟权益奖品采用用户自主领取方式。口算VIP月卡通过「激活码 + 跳转兑换页」模式发放,体验课通过跳转链接领取。
叫叫口算VIP月卡(激活码模式):
系统预先在数据库中导入约 10 万个激活码。用户中奖后,系统从激活码池中分配一个未使用的激活码给该用户。
领取流程:
- 用户中奖后,在中奖弹窗或「我的奖品」页面看到奖品名称、激活码(可直接复制)及「立即领取」按钮
- 系统自动将激活码复制到用户剪贴板,并提示"激活码已复制"
- 点击「立即领取」按钮,通过 Bridge
openBrowser方法在系统浏览器中打开兑换页面 - 用户在兑换页面中粘贴激活码,自主完成兑换
- 本系统仅负责激活码分配和跳转,兑换流程由目标页面承接,不在本活动范围内
领取链接(固定):
| 奖品 | 领取链接 | 备注 |
|---|---|---|
| 叫叫口算VIP月卡 | https://act.cdssylkj.com/activeCode/index | 已确认 |
技术要点:
- 激活码在中奖时由后端从激活码池原子分配(CAS 取码),确保不重复发放
- 前端收到激活码后自动复制到剪贴板,并通过 Bridge
openBrowser在系统浏览器打开兑换页- 激活码总量约 10 万,耗尽后该奖品自动不可中(概率归入未中奖)
- 已中过口算VIP月卡的用户,其激活码在中奖记录和「我的奖品」中始终可查看和复制
体验课(跳转链接模式):
领取流程:
- 用户中奖后,在中奖弹窗或「我的奖品」页面看到该奖品及「立即领取」按钮
- 点击「立即领取」按钮,跳转到对应的领取页面链接(新页面打开)
- 用户在跳转后的页面中自主完成兑换/领取操作
- 本系统仅负责跳转,后续的兑换流程由目标页面承接,不在本活动范围内
领取链接配置:
| 奖品 | 领取链接 | 备注 |
|---|---|---|
| 体验课 | 待补充 | 链接格式相同,后续确认后填入 |
技术要点:链接通过后台配置,运营可随时修改。前端「立即领取」按钮读取后台返回的链接地址进行跳转即可。
2.5 限量奖品节奏控制
- 公仔玩偶和叫叫金片为限量奖品,核心目标是保证活动全程都有奖品可抽,避免提前抽完
- 不要求绝对均匀出奖,允许参与高的日子多抽、参与低的日子少抽,自然节奏即可
- 未消耗的奖品配额不浪费,累积到后续天数的奖池中继续抽
- 奖池策略需确保:活动最后一天仍有库存,不可提前抽完
- 后台需支持配置奖品总量、每日注入基准、利用率等参数
2.6 抽奖算法
2.6.1 核心思路:滚动奖池 + 参与人数动态概率
抽奖算法采用「滚动奖池 + 参与人数动态概率」策略:
- 滚动奖池:限量奖品每天按计算注入一定数量到「可抽奖池」,当天未抽完的保留在池中,累积到后续天数继续抽
- 动态概率:中奖概率不是写死的,而是根据当前奖池存量和参与抽奖人次实时计算。参与人越多,单次中奖概率自动降低;参与人越少,概率自动提高
- 自然节奏:人多的日子多抽、人少的日子少抽,避免库存过早耗尽,同时减少活动结束后大量奖品剩余
2.6.2 活动参数
| 参数 | 值 |
|---|---|
| 活动期 | 6 月 15 日 - 7 月 20 日(36 天) |
| 公仔玩偶总量 | 430 个 |
| 叫叫金片总量 | 待定(算法相同,以 N 表示) |
| 利用率 R(后台可配) | 0.8(即每天希望消耗当日注入量的 80%) |
| 公仔权重比例(后台可配) | 0.6(公仔占限量奖品概率份额的 60%) |
| 金片权重比例(后台可配) | 0.4(金片占限量奖品概率份额的 40%) |
2.6.3 滚动奖池机制
限量奖品(公仔、金片)各维护一个「当前可抽奖池」,池中的数量 = 累积未抽完的奖品。
关键定义:
- 剩余总库存 = 奖品总配额 - 已被抽走的数量(不含奖池中未抽走的)。即本次活动该奖品尚未发放给用户的剩余额度
每日注入规则:
- 每天 00:00,系统计算当日应注入数量:
当日注入量 = max(1, floor(剩余总库存 / 剩余天数)) max(1, ...)保底注入:即使剩余库存 < 剩余天数(如库存剩 5 个、还剩 10 天),每天也至少注入 1 个,避免奖池长时间为零- 注入后:
当前奖池 = 昨日剩余 + 当日注入量 - 剩余总库存和剩余天数相应更新
- 当剩余总库存为 0 时,不再注入,该奖品永久移除
举例(公仔玩偶,总量 430,36 天):
| 天数 | 剩余库存 | 剩余天数 | 当日注入 | 奖池起始 | 说明 |
|---|---|---|---|---|---|
| 第1天 | 430 | 36 | 11 | 11 | 初始注入 |
| 第2天 | 423 | 35 | 12 | 9 | 第1天抽了5个,剩余4个累积,注入12 |
| 第10天 | 350 | 26 | 13 | 28 | 累积较多,奖池充裕 |
| 第30天 | 80 | 6 | 13 | 22 | 库存减少但天数也少 |
| 第36天 | 12 | 1 | 12 | 12 | 最后一天,剩余全部注入 |
上表为示例,实际数据取决于每天的抽奖消耗。
关键规则:
- 未消耗累积:当天没抽完的奖品留在池中,不会浪费
- 动态注入:注入量 = max(1, 剩余库存 / 剩余天数),自动适应当前消耗节奏,且保底每天至少注入 1 个
- 总库存硬约束:奖池数量永远不超过剩余总库存
- 最后一天保底:最后一天
剩余天数=1,注入量=全部剩余库存,确保最后一天有奖品可抽
2.6.4 参与人数动态概率
中奖概率不是固定值,而是根据当前奖池存量和参与抽奖人次实时计算。
人次基准(双基准取大值):
人次基准 = max(昨日全天抽奖总人次, 今日已抽奖人次)
- 昨日人次作为保底分母,防止凌晨参与人极少时概率畸高
- 今日人次超过昨日时自动切换,保证高峰日概率自适应降低
- 活动第 1 天无昨日数据,使用后台配置的预估日均人次兜底
核心公式:
人次基准 = max(yesterday_draws, today_draws, 保底人次)
目标消耗量 = 当前奖池存量 × 利用率 R(后台可配,默认 0.8)
限量奖品基础概率 = 目标消耗量 / 人次基准
限量奖品最终概率 = min(基础概率 × 奖品权重比例, 单次上限 P_max)
各奖品概率计算规则:
| 奖品类型 | 概率计算方式 | 是否受参与人数影响 |
|---|---|---|
| 公仔玩偶(限量) | min(目标消耗量 × 公仔权重比例 / 人次基准, P_max) | 是:人越多概率越低 |
| 叫叫金片(限量) | min(目标消耗量 × 金片权重比例 / 人次基准, P_max) | 是:人越多概率越低 |
| 体验课(不限量) | 固定概率 W3(后台可配,如 10%) | 否:不限量,概率固定 |
| 叫叫口算VIP月卡(激活码) | 固定概率 W4(后台可配,如 3%);激活码耗尽后概率归零 | 否:固定概率,激活码总量有限 |
| 未中奖(兜底) | 见下方概率归一化规则 | 自动计算,保证总概率 = 100% |
概率归一化(防止概率总和超过 100%):
计算完所有奖品概率后,可能出现概率之和 > 1 的情况(如凌晨参与极少 + 奖池充裕 + 不限量奖品固定概率叠加)。处理规则:
- 计算
所有奖品概率之和 S - 若
S ≤ 1:未中奖概率 =1 - S,正常出奖 - 若
S > 1:采用分步归一化,避免限量奖品被 P_max 截断和缩放双重惩罚:- a. 锁定已达 P_max 上限的限量奖品,其概率固定为 P_max
- b. 计算剩余概率空间:
剩余空间 = 1 - 已锁定奖品概率之和 - c. 未锁定的奖品(含不限量奖品和未达 P_max 的限量奖品)按比例缩放至填满剩余空间
- d. 缩放后若有限量奖品概率超过 P_max,截断至 P_max,超出部分归入未中奖
- e. 未中奖概率 = 0(概率已填满)
此机制确保限量奖品在 P_max 允许范围内优先分配概率份额,不因归一化而被过度压缩。
概率自适应机制说明:
- 参与人少时:人次基准小(由昨日保底),目标消耗量/小 = 概率高 → 鼓励消耗奖池积累
- 参与人多时:人次基准大(今日超过昨日),目标消耗量/大 = 概率低 → 保护库存不被快速抽完
- 奖池充裕时:当前奖池存量大,目标消耗量大 → 概率自然提高
- 奖池紧张时:当前奖池存量小,目标消耗量小 → 概率自然降低
单次抽奖上限 P_max(后台可配,如 5%):
- 防止在参与极少时概率过高(比如凌晨只有 1 人抽奖时不可能 80% 中公仔)
- 限量奖品单次概率不超过 P_max,超出部分自动截断
不限量奖品的概率:
- 口算VIP月卡使用后台配置的固定概率值(如 3%),不受参与人数影响
- 口算VIP月卡通过激活码池发放(约 10 万个),激活码耗尽后该奖品概率自动归零,归零的概率归入未中奖
- 口算VIP月卡受用户维度限制(每用户最多中 1 次),已中过的用户该奖品概率归零,归零的概率归入未中奖(不重新分配给其他奖品)
2.6.5 抽奖流程
用户发起抽奖请求
│
├─ 1. 幂等校验:检查 request_id 是否已处理,是则返回缓存结果
│
├─ 2. 前置校验
│ ├─ 活动是否进行中? → 否:返回"活动已结束"
│ ├─ 用户是否有抽奖机会? → 否:返回"今日已无抽奖机会"
│ └─ 校验通过 → 扣减 1 次抽奖机会
│
├─ 3. 获取抽奖锁(同一用户分布式锁,保证概率计算与扣减原子性)
│
├─ 4. 构建当前可用奖池(根据奖池存量、用户限次动态筛选)
│
├─ 5. 计算各奖品概率(根据 max(昨日人次,今日人次) 实时计算,见 2.6.4)
│
├─ 6. 加权随机抽取(按概率命中奖品或"未中奖")
│
├─ 7. 中奖后处理(仍在锁内)
│ ├─ 限量奖品:原子扣减奖池(CAS 操作,失败则重试步骤 5-7,最多 3 次)
│ ├─ 若重试后仍失败(如 daily_cap 已满或奖池耗尽):视为未中奖,归还抽奖机会
│ ├─ 扣减总库存 1 个
│ ├─ 记录中奖信息(用户ID、奖品类型、时间戳)
│ └─ 更新用户维度中奖记录
│
├─ 8. 释放抽奖锁
│
└─ 9. 返回抽奖结果 + 奖品信息
2.6.6 安全阀:防止库存集中耗尽
滚动奖池可能在极端情况下(如某天参与量突然激增)被快速抽空。安全阀规则:
| 规则 | 说明 |
|---|---|
| 每日抽取上限 | 每个限量奖品每天最多被抽走 daily_cap = max(3, 当日注入量 × 2) 个,超过后该奖品当天不再可抽。活动最后一天特殊处理:daily_cap_last = ceil(当日注入量 / 2),防止剩余库存被一次性抽空 |
| 单次概率上限 | 限量奖品单次中奖概率不超过 P_max(如 5%),防止低参与时概率过高 |
| 总库存硬约束 | 奖池数量永远不会超过剩余总库存,双重保护 |
daily_cap 设
max(3, ...)下限是为了避免注入量较小时 cap 过低(如注入 1 个时 cap=2 太少)。例如某天突然 10 万人参与,即使概率自适应降低,仍可能有较多中奖。daily_cap 作为绝对上限保证不会一天抽光多天的量。最后一天 daily_cap 改用
ceil(注入量 / 2)是因为最后一天注入量 = 全部剩余库存,若沿用注入量 × 2则 cap 远超实际库存,安全阀形同虚设。
2.6.7 用户维度限次逻辑
| 奖品 | 用户限次规则 | 实现方式 |
|---|---|---|
| 公仔玩偶 | 每用户活动期间最多中 1 个 | 查询用户中奖记录,已中过则从奖池移除 |
| 叫叫金片 | 每用户活动期间最多中 1 个 | 查询用户中奖记录,已中过则从奖池移除 |
| 叫叫口算VIP月卡 | 每用户活动期间最多中 1 次 | 查询用户中奖记录,已中过则从奖池移除 |
| 体验课 | 无限制 | 始终参与抽奖 |
2.6.8 防刷与安全
| 措施 | 说明 |
|---|---|
| 服务端校验 | 抽奖机会校验、扣减、开奖全部在服务端完成,前端仅展示结果 |
| 幂等设计 | 每次抽奖请求携带唯一 request_id,防止重复提交 |
| 并发控制 | 同一用户同时只能有 1 个抽奖请求在处理中(加分布式锁) |
| 概率与扣减原子性 | 概率计算、随机抽取、奖池扣减三步在同一把锁内完成,读取加锁后的最新奖池存量;CAS 扣减失败自动重试(最多 3 次) |
| 奖池原子扣减 | 限量奖品从奖池扣减通过原子操作完成,防止超发 |
| 异常回滚 | 抽奖过程中任何环节失败,回滚已扣减的抽奖机会和奖池数量 |
三、业务规则与约束
3.1 抽奖节奏控制
| 规则 | 说明 |
|---|---|
| 活动结束前奖品策略 | 限量奖品(公仔、叫叫金片)必须确保活动结束前不抽完,最后一天仍有库存 |
| 每日抽奖次数 | 每日投票后获得 1 次,分享活动页面最多额外 +5 次(每人每天上限,次日重置) |
| 抽奖机会是否累积 | 未使用的抽奖机会保留至活动结束 |
| 重复中奖 | 公仔每用户限 1 个,金片每用户限 1 个,口算VIP月卡每用户限 1 次,体验课不限 |
3.2 奖品概率与库存
- 限量奖品(公仔、叫叫金片)采用「滚动奖池」机制,每天动态注入,未消耗的累积到后续天数(详见 2.6.3)
- 中奖概率与参与人数动态关联:分母取 max(昨日人次, 今日人次),人越多概率越低(详见 2.6.4)
- 不限量奖品(体验课)使用后台配置的固定权重,不受参与人数影响
- 激活码奖品(口算VIP月卡)使用固定概率,激活码池耗尽后概率归零
- 公仔、金片、口算VIP月卡均受用户维度限制,每用户最多中 1 次(详见 2.6.7)
3.3 前置条件
- 用户必须已完成当日投票,才有抽奖入口
- 分享额外机会无需投票前提
3.4 边界场景
| 场景 | 处理方式 |
|---|---|
| 用户无抽奖机会时点击抽奖 | 提示"今日已无抽奖机会,完成投票可获得" |
| 限量奖品奖池当日耗尽 | 该奖品当天不再可抽;次日注入后恢复(若总库存仍 > 0) |
| 限量奖品触发每日抽取上限 | 该奖品当天不再可抽,次日恢复 |
| 限量奖品总库存耗尽 | 该奖品永久从奖池移除,活动剩余天数内不再出现 |
| 网络中断导致抽奖请求失败 | 提示"抽奖失败,请重试",不消耗抽奖机会 |
| 用户中奖后未查看奖品信息 | 奖品记录保存,后台可查 |
| 活动已结束,用户尝试抽奖 | 入口关闭或提示"活动已结束" |
| 用户已中过叫叫口算VIP月卡 | 该用户后续抽奖不再中此奖品 |
| 用户已中过公仔玩偶 | 该用户后续抽奖不再中公仔(其他奖品不受影响) |
| 用户已中过叫叫金片 | 该用户后续抽奖不再中金片(其他奖品不受影响) |
| 用户当日分享机会已用完 | 提示"今日分享奖励已领完,明天再来吧" |
| 并发重复提交 | 幂等设计 + 分布式锁,只处理一次,返回缓存结果 |
2.6.9 参数配置方式
抽奖模块中所有标注“后台可配”的参数(利用率 R、P_max、公仔/金片权重比例、不限量奖品固定概率、保底人次、体验课/口算VIP月卡概率等)均通过后端配置文件或环境变量设定,不需要管理后台界面。修改参数时由开发人员在后端配置中调整后重新部署即可。