1、引言

搭建用户成长体系的核心目的在于用户的「 留存」和「 促活」,对平台来说,合理的用户成长体系可以提升用户粘性,反之则会让用户觉得繁琐累赘,日渐失去活跃度。本文讲述的是用户成长体系中的核心模块《任务系统》。

2、 系统概述

任务中心分为五大类:每日任务、成长任务、基础任务、试用延期任务、试用达标任务(转正),支持横向扩展 每月任务、新手任务等等。其实每日任务应该叫周期任务,然后再细分成每日、每月等,但是考虑到以后统计查询的方便,故没有设计成二级划分。

2.1、主要功能描述

任务配置设计、用户任务领取、 任务记录进度推送(发送方)、任务记录进度订阅(接收方)、任务记录进度更新和奖励发放、试用期任务清算job。

3、系统结构设计

3.1、用户领取常规任务(非试用期任务)

流程图,本文是登录后任务自动领取,因此放在登录入口里

用户领取任务分为三种情况:

1.领取每日任务:每日任务需要有每日快照,这样配置修改不会影响当天做任务的用户,配置通过redis缓存到当天24点前过期控制。快照的触发由当天第一个领取用户的幸运儿完成。也就是说当天没人领取任务, 系统修改任务配置也是会实时生效的。

2.领取成长任务:成长任务是实时响应的,修改配置会即可生效。领取过一次就一直在了,不会重复领取。不过成长任务的记录是以子任务形式触发的。当第一次领取成长任务,是记录的第一个里程碑指标的任务记录,系统触发任务完成后,会判断该里程碑后面是否有下一个节点了,有的话就新增一条新的子任务记录给用户去完成。当所有节点都完成后,任务停留在最后一个节点上。

每天用户登录的时候判断成长任务是否领取的条件是:有没有领取过这个任务,只要领取过不管是第几个节点的子任务,都算领取了。

考虑到成长任务可能会存在用户全部完成所有里程碑,然后某天产品配置了一个新的里程碑,这时候就需要在登陆上做判断:逻辑是找到当前成长任务记录的最后一个指标值A,如果A已完成并领取了奖励,就判断是否小于该任务配置的最后一个里程碑的指标值B,如果小于 就创建一个新的子任务记录(指标值为配置中大于A的最近一个指标值C,C不一定是B)

3.领取基础任务: 这个就比较简单了,只要领取过该任务,就不能领取了。记录里没有的任务才会领取。

3.2、用户领取试用期任务

用户开通账户时,调用接口CreateTrialRecords来创建试用期任务记录,分为试用达标任务和试用延期任务。(试用期任务领取时会记下试用期截止日期(注意非任务截止日期,任务截止时间可能在试用期结束之前),清算试用任务job会每天0点1分跑试用期任务记录数据,更新试用期试用结果)。

3.3、任务进度更新

流程图(已订单数量达标任务为例):

1.用户下单,订单推送一个mq给任务中心,任务中心处理,根据任务指标类型,找到符合条件的每日任务/成长任务/基础任务,判断完成情况,更新任务进度并发放奖励,如果是成长任务,会在这里创建下一个里程碑的任务记录。

2.推送mq的结构如下:

    /// <summary>
    /// MQ更新任务消息体
    /// </summary>
    public class UpsetMissionRecordRequest : BaseRequestSafety
    {
        /// <summary>
        /// 会员id
        /// </summary>
        public long MemberId { get; set; }
        /// <summary>
        /// 业务端唯一请求标识(标识一次请求)
        /// </summary>
        public string TransGuid { get; set; }
        /// <summary>
        /// 是否是成长任务轮询
        /// </summary>
        public bool IsLoop { get; set; } = false;
        /// <summary>
        /// 信号量集合
        /// </summary>
        public List<SignalInfo> SignalDataList { get; set; }

    }

SignalInfo实体

    /// <summary>
    /// 信号量信息
    /// </summary>
    public class SignalInfo
    {
        /// <summary>
        /// 指标类型
        /// </summary>
        public MissionTarget TargetId { get; set; }

        /// <summary>
        /// 当前指标本次增益的数值 比如下了一单付款16.88元, 就传16.88
        /// 当bool类型的返回值时,返回1为成功, 其他为失败。比如绑定手机号 1代表绑定成功
        /// 比如访客数 就传访客id
        /// </summary>
        public decimal IncrementValue { get; set; }
    }

3.任务进度推送(发送)和订阅(接收)方法

继承接口IMissionEventRegister

实现IMissionEventRegister.SignalSubscribe方法:订阅任务进度并处理

实现IMissionEventRegister.GetMissionResult方法:返回这个任务指标对应的最新进度值(即SignalSubscribe方法产生的结论)

     /// <summary>
    /// Todo  确保子类单例
    /// </summary>
    public interface IMissionEventRegister
    {
        /// <summary>
        /// 返回任务最新进度值decimal的方法。
        /// 当bool类型的返回值时,返回1为成功, 其他为失败。
        /// 比如绑定手机号 1代表绑定成功
        /// 比如当前下单量为10,则返回10,后面增加到20了,则返回20,而不是增加的区间值
        /// </summary>
        /// <param name="request">请求调解</param>
        /// <returns></returns>
        ResultMessage<decimal> GetMissionResult(MissionCurrentDataRequest request);

        /// <summary>
        /// 信号量订阅服务
        /// 处理业务发送过来的信号转换成任务中心的数据
        /// </summary>
        /// <param name="memberId"></param>
        /// <param name="incrementValue">增量值</param>
        void SignalSubscribe(long memberId, decimal incrementValue);

    }
     /// <summary>
    /// 
    /// </summary>
    public class MissionCurrentDataRequest
    {
        /// <summary>
        /// 会员id
        /// </summary>
        public long MemberId { get; set; }
        /// <summary>
        /// 任务分类
        /// </summary>
        public MissionClassify MissionClassifyId { get; set; }

        /// <summary>
        /// 任务指标
        /// </summary>
        public MissionTarget MissionTargetId { get; set; }
        /// <summary>
        /// 任务结算周期
        /// </summary>
        public SettleCycle SettleCycle { get; set; }
    }

事件注册入口,采用简单工厂模式(MissionEventRegisterFactory的GetInstance):

    /// <summary>
    /// 任务事件注册服务入口
    /// </summary>
    public class MissionEventRegisterFactory
    {

        #region 任务事件注册
        /// <summary>
        /// 
        /// </summary>
        /// <param name="missionTarget"></param>
        /// <returns></returns>
        public static IMissionEventRegister GetInstance(MissionTarget missionTarget)
        {
            IMissionEventRegister _instance = null;
            switch (missionTarget)
            {
                case MissionTarget.BindPhone: _instance = null; break;
                case MissionTarget.Visitors: _instance = new VisitiorCountRegister(); break;
                case MissionTarget.EnteredCommission: _instance = new EnterCommisitionRegister(); break;
                case MissionTarget.NetBills: _instance = new NetBillRegister(); break;
                case MissionTarget.PayedOrders: _instance = new PayOrdersRegister(); break;
                case MissionTarget.PayMBills: _instance = new PayBillRegister(); break;
                case MissionTarget.ShareCount: _instance = new ShareCountRegister(); break;
                case MissionTarget.TaobaoAuthorize: _instance = new TaobaoAuthRegister(); break;
                case MissionTarget.OrderPeoples: _instance = new OrderPeopleRegister(); break;
                case MissionTarget.PredictCommission: _instance = new PredictCommissionRegister(); break;
                default:
                    _instance = null; break;
            }
            return _instance;
        }
        #endregion
    }

由于任务功能上线前,有的指标其实用户已经完成,这时候就需要在领取任务的逻辑后调用发送更新任务信号量的接口。

3.4、试用期任务清算job

每天0点1分跑试用期任务记录数据,将到期的任务依次判断是否达标,则转正;其次判断是否延期,则试用期延期一个月,并领取下一轮的试用期任务;否则试用期结束不通过。更新试用期任务记录为已清算。

4、程序调用说明:

4.1、领取常规任务接口(会员登录时领取任务):

方法名:MemberLoginFilterAttribute类的OnActionExecutedAsync方法 (核心方法是CreateMissionRecords接口创建任务)

public override Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)

4.2、试用期任务领取接口:

方法名:CreateTrialRecords

4.3、业务发送信号量更新任务记录进度接口

方法名:UpsetMissionRecordMQProvider

public ResultMessage<bool> UpsetMissionRecordMQProvider(UpsetMissionRecordRequest request)

4.4、任务中心订阅信号量处理接口(需要实现接口)

方法名:IMissionEventRegister类的SignalSubscribe方法

处理信号为任务中心的需要的数据

4.5、任务中心实时获取任务进度情况接口(需要实现接口)

方法名:IMissionEventRegister类的GetMissionResult方法

4.6、注册任务模块接口

方法名:MissionEventRegisterFactory的GetInstance方法

注册任务模块实例

4.7、试用期任务清算job

方法名:SettleTrialMissionJob

配置页面设计

1.列表查询页面:

2.配置页面—基础任务

配置页面-每日任务

配置任务-成长任务:

配置页面-表单型任务(用户上传图片,后台审核图片的方式,或者其他表单型的)

配置页面:试用期延期任务、试用达标任务 (配置页面一致)

数据库设计

1.任务配置主表(每日任务、基础任务只用到这张表)

CREATE TABLE `TCDAILYSURPRISE`.`MissionConfig`  (
  `MCId` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `MCMissionClassifyId` int(11) NOT NULL DEFAULT 0 COMMENT '任务类别(每日、成长、基础)',
  `MCMissionName` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '任务名称',
  `MCDescription` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '任务简介',
  `MCIcon` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '任务图标',
  `MCTargetId` int(11) NOT NULL DEFAULT 0 COMMENT '任务指标Id(签到、订单数等)',
  `MCTargetName` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '任务指标名称(签到、订单数等)',
  `MCTargetValue` int(11) NOT NULL DEFAULT 0 COMMENT '指标值',
  `MCMoneyNumber` int(11) NOT NULL DEFAULT 0 COMMENT '奖励配置红包编号',
  `MCRewardValue` decimal(18, 2) NOT NULL DEFAULT 0.00 COMMENT '奖励值',
  `MCRuleExplain` varchar(300) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '任务规则说明(步骤描述)',
  `MCOfficialStory` varchar(300) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '任务推荐话术',
  `MCPhraseExplain` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '名词解释',
  `MCIsForm` int(11) NOT NULL DEFAULT 2 COMMENT '是否表单任务 1是 2不是 默认2',
  `MCIsAutoReceive` int(11) NOT NULL DEFAULT 0 COMMENT '任务是否自动领取 (1是 2否)',
  `MCIsManualAudit` int(11) NOT NULL DEFAULT 2 COMMENT '是否人工审核 1是 2否',
  `MCIsAutoReward` int(11) NOT NULL DEFAULT 0 COMMENT '是否自动发放奖励(1是 2否)',
  `MCSortBy` int(11) NOT NULL DEFAULT 0 COMMENT '排序字段(倒序模式)',
  `MCRowStatus` int(11) NOT NULL DEFAULT 1 COMMENT '有效性(1有效 2无效)',
  `MCCreateTime` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
  `MCCreateUser` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '创建人',
  `MCUpdateTime` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '修改时间',
  `MCUpdateUser` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '修改人',
  `MCProjectCode` int(11) NOT NULL DEFAULT 0 COMMENT '项目类型',
  `MCSettleCycle` int(11) NOT NULL DEFAULT 0 COMMENT '结算周期 1.当天 2.7天 3.31天 4.永久',
  PRIMARY KEY (`MCId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 46 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '任务配置表' ROW_FORMAT = Compact;

2.任务配置明细表(成长任务用到)

CREATE TABLE `TCDAILYSURPRISE`.`MissionConfigDetail`  (
  `MCDId` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `MCDConfigId` bigint(20) NOT NULL DEFAULT 0 COMMENT '任务主键Id',
  `MCDTargetValue` int(11) NOT NULL DEFAULT 0 COMMENT '指标值',
  `MCDRewardValue` decimal(18, 2) NOT NULL DEFAULT 0.00 COMMENT '达标奖励值',
  `MCDRowStatus` int(11) NOT NULL DEFAULT 1 COMMENT '有效性 1有效 2无效',
  `MCDCreateTime` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
  `MCDCreateUser` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '创建人',
  `MCDUpdateTime` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '修改时间',
  `MCDUpdateUser` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '修改人',
  PRIMARY KEY (`MCDId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 56 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '任务配置明细表' ROW_FORMAT = Compact;

3.用户记录表

CREATE TABLE `TCSURPRISEGAMELOG`.`MissionRecord`  (
  `MRId` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `MRConfigId` bigint(20) NOT NULL DEFAULT 0 COMMENT '任务配置表主键',
  `MRMIssionClassifyId` int(11) NOT NULL DEFAULT 0 COMMENT '任务类别(每日、成长、基础)',
  `MRMissionName` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '任务名称',
  `MRUserId` bigint(20) NOT NULL DEFAULT 0 COMMENT '用户Id',
  `MRNickName` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '用户昵称',
  `MRSOP` bigint(20) NOT NULL DEFAULT 0 COMMENT '归属SOP',
  `MRTargetId` int(11) NOT NULL DEFAULT 0 COMMENT '任务指标Id(字典值)',
  `MRTargetValue` int(11) NOT NULL DEFAULT 0 COMMENT '任务指标值',
  `MRCurrentValue` decimal(18, 2) NOT NULL DEFAULT 0.00 COMMENT '当前完成量',
  `MRRewardValue` decimal(18, 2) NOT NULL DEFAULT 0.00 COMMENT '奖励值',
  `MRFormValue1` varchar(300) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '用户录入信息1',
  `MRFormValue2` varchar(300) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '用户录入信息2',
  `MRFormValue3` varchar(300) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '用户录入信息3',
  `MRCreateTime` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
  `MRFinishTime` datetime(0) NOT NULL DEFAULT '1900-01-01 00:00:00' COMMENT '任务完成提交时间',
  `MRIsForm` int(11) NOT NULL DEFAULT 2 COMMENT '是否表单任务 1是 2否 默认2',
  `MRIsFinish` int(11) NOT NULL DEFAULT 0 COMMENT '任务完成情况 1已完成 2未完成 默认2',
  `MRIsManualAudit` int(11) NOT NULL DEFAULT 0 COMMENT '是否人工审核(1是 2否 默认0)',
  `MRAuditStatus` int(11) NOT NULL DEFAULT 0 COMMENT '审核状态(0默认 1待审核 2通过 3驳回)',
  `MRReceiveStatus` int(11) NOT NULL DEFAULT 0 COMMENT '奖励领取状态 1已领取 2未领取 默认',
  `MRReason` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '驳回原因',
  `MRRowStatus` int(11) NOT NULL DEFAULT 1 COMMENT '有效性 1有效 2无效',
  `MRUpdateTime` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '修改时间',
  `MRSettleCycle` int(11) NOT NULL DEFAULT 0 COMMENT '结算周期类型',
  `MRTrialTimes` int(11) NOT NULL DEFAULT 0 COMMENT '试用期第几次',
  `MRExpireTime` datetime(0) NOT NULL DEFAULT '1900-01-01 00:00:00' COMMENT '任务过期时间',
  `MRTrialDeadline` datetime(0) NOT NULL DEFAULT '1900-01-01 00:00:00' COMMENT '试用期到期时间(清算时间)',
  `MRIsSettleTrial` int(11) NOT NULL DEFAULT 2 COMMENT '试用期是否清算 1结算 2未结算',
  PRIMARY KEY (`MRId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 823 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户任务记录表' ROW_FORMAT = Compact;