一、总体设计
积分和积分渠道,实现积分种类和发放的动态配置,是设计中的关键之处。
积分订单表是不必要的,视具体业务需求而定。
积分账户和账户收支是核心的两个表。
后面三个表都有一个school_id, 其实就是租户编号,不同的学校或租户,互不共享。
二、积分表points
用于抽象各种虚拟货币,并通过类别来区分不同的货币体系。
包括主键ID、积分类别type、积分名称name、创建时间、更新时间、创建人员、更新人员、备注
CREATE TABLE `points` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',`type` varchar(32) NOT NULL COMMENT '积分类别',`name` varchar(128) NOT NULL COMMENT '积分名称',`created_date` datetime DEFAULT NULL COMMENT '创建时间',`modified_date` datetime DEFAULT NULL COMMENT '更新时间',`created_by` varchar(64) DEFAULT NULL COMMENT '创建人员',`modified_by` varchar(64) DEFAULT NULL COMMENT '更新人员',`remark` varchar(128) DEFAULT NULL COMMENT '备注',PRIMARY KEY (`id`) USING BTREE,UNIQUE KEY `uk_type` (`type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='积分表';
我这里新增了三种虚拟货币:
三、积分渠道表points_channel
通过有效期的时间区间,控制积分的发放,减少风险。
包括主键ID、渠道编号code、渠道名称name、积分类别(渠道和积分的关联关系)、奖励的积分数reward_points、有效期起始时间、有效期截止时间、创建时间、更新时间、创建人员、更新人员、备注
CREATE TABLE `points_channel` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',`code` varchar(32) NOT NULL COMMENT '渠道编码',`name` varchar(128) NOT NULL COMMENT '渠道名称',`points_type` varchar(32) NOT NULL COMMENT '积分类别',`reward_points` int(10) DEFAULT 0 COMMENT '奖励的积分数',`begin_date` datetime DEFAULT NULL COMMENT '起始时间',`end_date` datetime DEFAULT NULL COMMENT '截止时间',`created_date` datetime DEFAULT NULL COMMENT '创建时间',`modified_date` datetime DEFAULT NULL COMMENT '更新时间',`created_by` varchar(64) DEFAULT NULL COMMENT '创建人员',`modified_by` varchar(64) DEFAULT NULL COMMENT '更新人员',`remark` varchar(128) DEFAULT NULL COMMENT '备注',PRIMARY KEY (`id`) USING BTREE,UNIQUE KEY `uk_code_points_type` (`code`,`points_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='积分渠道表,一类积分对应多个渠道';
比如,邀请他人购买某商品,即可获得虚拟货币类型是key的积分值是4。
再比如,某活动闯关成功,即可获得1个积分。
四、积分账户表points_account
包括主键ID、用户ID、积分类别points_type、积分数points、创建时间、更新时间
CREATE TABLE `points_account` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',`school_id` int(10) DEFAULT NULL COMMENT '学校ID',`user_id` bigint(20) NOT NULL COMMENT '用户ID',`points_type` varchar(32) NOT NULL COMMENT '积分类别',`points` int(10) DEFAULT 0 COMMENT '积分数',`created_date` datetime DEFAULT NULL COMMENT '创建时间',`modified_date` datetime DEFAULT NULL COMMENT '更新时间',`version` bigint(20) DEFAULT 0 COMMENT '版本号',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='积分账户表';
这里多了一个学校ID维度,一个用户可能在多个学校开有账户。
如果你的业务系统没有这么复杂,可去掉该字段。
学校ID,可以进一步抽象为租户编号,隔离性和抽象性更好。意思是,你在不同租户的账户不一样。
这里的余额,就是可用余额,我们没有去细分可用余额和冻结余额。
没有冻结的概念,大大减少了复杂度。
用户在获得积分的时候,增加至账户的余额;同理,在消耗积分的时候,减损的也是账户的余额。
有一种情况,需要考虑的,积分本身的退订。又可以分为两种情况:
-
通过现金方式购买,获得的积分,用户需要退款。(因为积分本身是虚拟商品,大多数平台是不允许退的。 一旦需要退,则应该视积分的使用情况而定,全额退还是部分退,积分已经消耗量是其重要的依据)
-
用户使用积分抵扣购买商品,属于消耗积分,用户需要退该订单。(用户审核退单,同意后,自动发放等量的积分给用户)
五、积分账户的流水表points_account_flow
包括主键ID、积分类别、积分数、类型(增/减)、用户ID、积分渠道编号、积分渠道名称、orderNo、创建时间、更新时间
CREATE TABLE `points_account_flow` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',`school_id` int(10) DEFAULT NULL COMMENT '学校ID',`type` smallint(1) DEFAULT 0 COMMENT '类型:0-增加;1-减少',`user_id` bigint(20) NOT NULL COMMENT '用户ID',`points_type` varchar(32) NOT NULL COMMENT '积分类别',`points` int(10) DEFAULT 0 COMMENT '积分数',`channel_code` varchar(32) DEFAULT NULL COMMENT '渠道编码',`channel_name` varchar(128) DEFAULT NULL COMMENT '渠道名称',`order_no` varchar(32) DEFAULT NULL COMMENT '订单号',`created_date` datetime DEFAULT NULL COMMENT '创建时间',`modified_date` datetime DEFAULT NULL COMMENT '更新时间',`created_by` varchar(64) DEFAULT NULL COMMENT '创建人员',`modified_by` varchar(64) DEFAULT NULL COMMENT '更新人员',`remark` varchar(128) DEFAULT NULL COMMENT '备注',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='积分账户的流水表';
当type类型是0-增加的时候,channel_code和channel_name不能为空;
当type类型是1-减少的时候,order_no不能为空。这里的order_no是业务订单的订单号,记录积分消耗在哪里。
六、积分订单(区别于业务订单)
用户使用现金购买积分(虚拟货币),准确地说,应该叫做积分订单扩展表,用于退款和结算的依据。
记录每个订单的积分数、已使用数、可用、已退、已结算和可结算等。注意:已结算的积分不能退回。(当然积分数不变,其余的都可能会变化)
- 每次积分的抵扣,更新已使用、可用积分、可结算积分。优先抵扣最早的积分订单的可用积分。也就是说,可能会抵扣多个积分订单中的可用积分。
- 积分订单退款,只能退款可用的积分,对于已使用的积分是不能退的。
- 每次结算,把可结算积分加至已结算积分
下面是总结的几个等式:
- 积分数=可用积分数+已退积分数+已使用积分数。
- 积分数=可用积分数+已退积分数+可结算积分数+已结算积分数
- 已使用积分数=可结算积分数+已结算积分数
CREATE TABLE `points_order` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',`order_no` varchar(32) NOT NULL COMMENT '订单号',`user_id` bigint(20) NOT NULL COMMENT '用户ID',`school_id` int(10) NOT NULL COMMENT '学校ID',`points_type` varchar(32) NOT NULL COMMENT '积分类别',`points` int(10) DEFAULT 0 COMMENT '积分数',`used_points` int(10) DEFAULT 0 COMMENT '已使用积分数',`available_points` int(10) DEFAULT 0 COMMENT '可用积分数',`refunded_points` int(10) DEFAULT 0 COMMENT '已退积分数',`settled_points` int(10) DEFAULT 0 COMMENT '已结算积分数',`available_settle_points` int(10) DEFAULT 0 COMMENT '可结算积分数',`created_date` datetime DEFAULT NULL COMMENT '创建时间',`modified_date` datetime DEFAULT NULL COMMENT '更新时间',`version` bigint(20) DEFAULT 0 COMMENT '版本号',PRIMARY KEY (`id`) USING BTREE,UNIQUE KEY `uk_order_no` (`order_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='积分结算表';
如果你的业务不支持积分订单的退款,更无需结算功能,本表无需引入。
最后说一下,业务订单中涉及积分的退款,视这部分的积分已使用,本来是无法逆向退款。
这里采取一个折中的方案,手动给用户发放等量的积分,比如业务订单使用了999个积分,当退款成功时,后台给用户发放999个积分。