1.背景
在很多APP中,我们经常可以看到很多九宫格或者砸金蛋的抽奖活动,例如掘金的签到抽奖。
这个需求目的相对,主要是为了形成内部积分闭环,每日免费赠送一次抽奖机会不仅可以促进产品日活,让用户养成习惯(好的用户是靠养出来的),
用户打开app或者网页端,签到免费获得一次抽奖。
2.抽奖活动业务分析
这里以简单的举例进行业务分析,并不是以掘金抽奖具体业务分析:
- 动态展示奖品。
- 动态展示抽奖需要消耗的积分数。
- 展示当前剩余积分。
- 中奖记录分类展示,分为大奖和小奖,并已跑马灯形式展示。
- 每个奖品可以配置中奖权重,奖品个数。
如下图所示(来自deepin画图软件随便画了一下)
3.方案规划
- 主要方案设计
- 抽奖活动系统与积分业务系统相关,单抽奖系统是相对独立的。
- 后台可以配置中奖奖品与权重,以及每个奖品个数。
- 保证抽奖奖品个数有限制的不可多送
- 前端抽奖架构样例
当客户端访问活动抽奖系统时,查询出配置的奖品列表,用户可用积分,活动时间段及其他相关配置,抽奖页面跑马灯等。
在这里访问奖品列表以及跑马灯(抽奖记录日志数据)可以进行缓存处理,因为不涉及时效性问题,图上只需要在活动系统与数据库查询之间进行数据缓存。
- 解决方案
如何保证抽奖奖品个数有限制的不可多送?其实这种问题如今已经很常见了,具体可以在网上可以寻找很多思路,我这里使用了中间件redis 进行标记奖品处理,第一是为了方便服务器水平扩展,第二不使用太多中间件(例如不涉及zk,以及消息队列等):我的做法是后台每次上奖品时候把奖品缓存进redis,进行预加载处理,利用redis inc 以及数据库的锁去保证库存问题。
当后台服务器通过修改奖品配置,例如奖品权重,去覆盖缓存中的抽奖数据,客户端最主要是和redis打交道,当用户中间,比如奖品类型是需要判断库存的,那么我们就通过数据比较,最后修改数据库,当数据库执行成功时候才认为用户中奖。当redis inc后的数值大于或者等于该奖品库存数量(当然,也可以再次抽奖时候该奖品不混入奖池中)。相信优秀的产品经理一定会考虑到程序猿会写bug,所以顺水推舟时候,会设计一个安慰奖作为抽奖意外,比如设计一个10积分,当某用户抽奖抽到了“大奖”,由于发大奖逻辑异常,这个时候就可以返回一个默认奖
- 发奖逻辑方案
对于发奖,这个就很简单了,根据中奖类型,走不同的逻辑,典型的if...else if..,这里可以使用 策略模式 优化代码。
4.数据库设计
- 活动配置表
字段 | 数据类型 | 描述 |
---|---|---|
id | bigint(19) | 主键 |
image | varchar(512) | 活动大图 |
name | varchar(128) | 活动名称 |
start_time | datetime | 活动开始时间 |
end_time | datetime | 活动结束时间 |
consume_type | varchar(20) | 消耗类型(integral积分,jewel宝石) |
consume_num | bigint(19) | 消耗数量(单位个) |
status | tinyint(2) | 活动状态(1开启,2关闭) |
create_time | datetime | 创建时间 |
create_user | varchar(20) | 创建用户 |
update_time | datetime | 修改时间 |
update_user | varchar(20) | 修改用户 |
is_delete | tinyint(2) | 是否删除 |
- 奖品表
字段 | 数据类型 | 描述 |
---|---|---|
id | bigint(19) | 主键 |
icon | varchar(512) | 奖品的icon |
name | varchar(64) | 奖品名称 |
type | tinyint(2) | 奖品类型(1积分,2会员,3实物大奖) |
stock_count | int(11) | 库存(单位个) |
enable_stock | tinyint(2) | 开启库存校验(0关闭,1开启) |
weight | double(2,4) | 奖品权重 |
activity_id | bigInt(19) | 绑定活动的id |
create_time | datetime | 创建时间 |
create_user | varchar(20) | 创建用户 |
update_time | datetime | 修改时间 |
update_user | varchar(20) | 修改用户 |
is_delete | tinyint(2) | 是否删除 |
- 抽奖日志表(哪个用户什么时间抽中了什么奖品)
字段 | 数据类型 | 描述 |
---|---|---|
id | bigint(19) | 主键 |
user_id | bigint(19) | 用户id |
prize_id | bigint(19) | 奖品id |
create_time | datetime | 创建时间 |
... |
5.后台样例
6.尾言
大多数情况下,抽奖活动的实行是为了平衡产品内部 数据流水问题,比如积分,当用户积分过多,会造成积分的“通胀”,也就是没有花出去的地方,比如王者荣耀的钻石抽奖,就是为了平衡钻石,当大量的用户拥有过多的,而没有花出去的地方,这就导致了钻石特别不值钱,例如天天酷跑。在掘金中,设计一个每日签到抽奖的,每日签到作用是促进日活,签到和抽奖结合,也是促进日活的方案,可以增加APP的打开频次和网页的点击量(流量),并且容易养成一批忠实用户,接下来的日子里,掘金的兑换商店会做出来,钻石是产品内部唯一通用货币,送的钻石如此之多,我猜,屯钻石的用户应该会很快兑换到奖品,从而慢慢弱化抽奖,作为一个钻石消耗口,后面钻石可能不会有这么多出现。
不想了,老实打代码吧,剩下的交个数据分析的人,我们只要正常造就行~(希望掘金不要揍我 _)