掘金 后端 ( ) • 2024-03-27 16:27

引言:

“集卡”活动是由支付宝兴起的“集五福”系列活动衍生的一系列引导用户自主进行分享的裂变活动,虽然乍一看会觉得这套系统非常复杂,但其实只要拆分其核心功能,并保留一定的业务抽象,就可以落地一套通用的“集卡”后端服务

从业务角度看集卡

假设要做一个简单的集五福的活动,运营一般会设计5类“福”字卡片,且设计1类“万能卡”来作为稀有卡片增加活动噱头

用户进入集卡页面后可以从一系列“任务”:比如登录完成/浏览页面/打卡签到/抽奖转盘等玩法获取到各个类型的卡片

当用户获取某类卡片过多时,可以主动分享给其他缺这类型的用户来“共同促进”整体用户的收集进度

当用户集齐5“福”后,可以“瓜分”一个更大的奖励:比如红包/礼包/游戏周边等奖品

从设计角度看集卡

(本篇不会过多阐述“任务”和“抽奖”系统,只需要将集卡服务当成一个外部服务提供给上述系统接入即可)

为了抽象简单并符合面向对象思维,我们接口设计时尽量从单用户角度来设计接口和协议,将“参与集卡的用户”作为逻辑对象,这样我们就可以将一些很过程化的逻辑,变成一个对象的成员函数,保持接口的简洁性

比如:

“发放奖品”可以设计成“用户从某场景获取到卡片”:

“A用户分享卡片给B用户”可以设计成“用户从某用户获取卡片”

这样简单的角度转换,自然而然的就成型了一个很明确的实体核心类 -- CollectCardActUser

它的成员函数就可以设计成

  • GetCollectCardInfo: 获取活动卡片信息
  • GrantCard: 获取卡片奖励
  • GrantCardFromOtherUser: 从其他玩家获取卡片
  • ConsumeCard: 消耗卡片

当把用户核心逻辑行为抽象好后,我们会发现集卡的“卡片”实体也被自然而然显现出来 -- CollectCard

至此,50%的代码块和协议已经基本成型

func NewCollectCardActUser(baseUserInfo BaseUserInfo, actID string) CollectCardActUser {
	return CollectCardActUser{
		BaseUserInfo: baseUserInfo,
	}
}

type BaseUserInfo struct {
}

type CollectCardActUser struct {
	BaseUserInfo
	ActID string
}

func (c CollectCardActUser) GetCollectCardInfo(ctx context.Context, param GetCollectCardInfoParam) (res GetCollectCardInfoRes, err error) {
	// ...implement me
}

func (c CollectCardActUser) GrantCard(ctx context.Context, param GrantCardParam) (res GrantCardRes, err error) {
	// ...implement me
}

func (c CollectCardActUser) GrantCardFromOtherUserCard(ctx context.Context, param GrantCardFromOtherUserCardParam) (res GrantCardFromOtherUserCardRes, err error) {
	// ...implement me
}

func (c CollectCardActUser) ConsumeCard(ctx context.Context, param ConsumeCardParam) (res ConsumeCardRes, err error) {
	// ...implement me
}

接着开始分析CollectCard并着手设计细节:

首先卡片信息的起始来源其实是“运营的活动设计” -- 也就是活动配置信息,按照业务视角的语义可以见大分析出一张卡片在活动配置阶段至少有以下基础属性:

  • 卡片标识:card_key
  • 卡片名称:card_name
  • 卡片类型:card_type,用于区分“万能卡”这种特殊业务场景

当玩家获取到某一张卡片之后成为了玩家的一份权益,会对应新生成出卡片的唯一标识类似我们数据库某条数据唯一性的uuid

  • 卡片唯一id:card_uuid

对于用户需要分享卡片赠送给其他用户这个场景,需要增加一个“来源卡片”字段和一个“卡片状态”字段来扩展卡片的额外含义

  • 来源卡片:from_card_uuid
  • 卡片状态:status,用于标识卡片是否可用或者被转增等状态

至此,一个卡片的基本属性和数据结构已经显现出来

此外,除了用户卡片权益信息,我们另外需要冗余一张日志表来记录卡片的流转信息,方便用户进行查看和后续权益核销行为记录

所以,权益操作日志表其实只是在卡片实体的数据结构基础上增加了操作类型:

  • 卡片操作类型:opertion_type,用于区分正常获取/赠予/消耗等事件
type Card struct {
	UUID          string `json:"uuid"`
	Name          string `json:"name"`
	Key           string `json:"key"`
	Type          int64  `json:"type"`
	CreatedAtTime int64  `json:"created_at_time"`
	Status        int64  `json:"status"`
}

接下来就可以开始我们的逻辑运行细节实现

从上面的示例中可以看到,当我们实例化好一个CollectCardActUser的对象后,拥有了一些基础的成员属性信息

比如:

用户信息:user_id, user_name等基础用户信息参数

活动信息:act_id, act_name等基础活动信息,实例化前获取好活动信息,并做好校验

CollectCard实体对象作为成员函数的入参/出参来流转

(以下主要用mysql+redis组件作为数据存储示例)

  • 获取活动卡片信息
    • 入参:无,对象成员信息数据已满足查询
    • 通过user_id, act_id匹配用户已有卡片集合
    • 通过user_id, act_id匹配用户的一系列卡片流转记录
    • 出参:用户获取的卡片信息和操作记录
  • 获取卡片奖励
    • 入参:待获取的卡片类型标识、来源唯一src_uuid(避免重放导致重复发奖)
    • 通过act_id从活动配置服务获取卡片的活动配置信息
    • 随机生成一个新的card_uuid作为卡片权益标识唯一id
    • 开启事务:
      • 新增用户卡片信息记录,状态默认为有效
      • 新增卡片操作日志,操作类型为新增
    • 出参:新生成的卡片
  • 从其他玩家获取卡片
    • 入参:分享者的卡片唯一标识from_card_uuid、来源唯一src_uuid
    • 增加全局分布式业务并发锁,避免异常情况并发导致重复录入或并行操作:
      • 当前user_id+act_id维度并发锁
      • 当前from_card_uuid+act_id维度并发锁
    • 校验数据:
      • 赠送的卡片是否存在和相关持卡人信息正确
      • 赠送人不可等于接收人
      • 赠送的卡片状态是否有效
    • 开启事务:
      • 更新赠送的卡片的卡片状态为失效状态
      • 接受人新增一条卡片记录状态默认为有效,并记录from_card_uuid来源字段用于回溯信息
      • 新增接收人的卡片操作日志,操作类型为赠送
      • 新增赠送人的卡片操作日志,操作类型为新增
    • 释放上述全局分布式锁
    • 出参:赠送的卡片信息
  • 消耗卡片
    • 入参:无,对象成员信息数据已满足查询

    • 增加全局分布式业务并发锁:当前user_id+act_id维度并发锁

    • 通过user_id, act_id匹配用户已有卡片集合

    • 校验用户的消耗次数是否满足活动配置

    • 筛选用户有效卡片信息

    • 计算用户待消耗卡片的集合

      • 将有效集合按照卡片类型+卡片标识分组
      • 按照活动配置抽选每一种非万能卡标识的其中一张卡片作为待消耗卡片
      • 当用户非万能卡数量不足时,遍历万能卡类型补足数量
    • 抽选卡片匹配后,遍历每一张card_uuid进行设置业务全局锁,避免相互竞争

    • 开启事务:

      • 更新所有匹配的卡片的状态为已消耗状态
      • 遍历增加每张卡片的操作日志,操作类型为消耗
    • 释放上述全局分布式锁

    • 出参:是否成功

结合如上所述,

"集卡"活动后端服务的设计旨在提供一套稳定、高效的系统以管理用户的交互与卡片的流转。系统通过一系列精心设计的API接口,让用户能在多元的任务中获得、分享及消耗卡片,

而运营团队能够灵活配置活动并追踪其进展。核心实体类 'CollectCardActUser' 和 'CollectCard' 的定义及操作逻辑设计是确保整个服务顺畅实施的基础。此外,系统采用了如分布式锁这样的并发控制机制以保持数据完整性,并通过仔细规划的数据库事务处理,确保了每一步操作的稳定性。

结果是一个可扩展、低冗余、易于维护的后台服务,能够支持复杂且富有引导性的集卡式营销活动,为用户带来互动的愉悦同时为商家创造价值。