个人博客:无奈何杨(wnhyang)
个人语雀:wnhyang
共享语雀:在线知识共享
Github:wnhyang - Overview
声明
本篇文章纯粹抛砖引玉!
需求说明
开门见山,业务背景直接跳过。
类比支付宝会员积分,支付宝APP-我的-支付宝会员。
支付宝会员-XXX积分-积分规则,可以看到具体的积分规则,本篇文章类比于此积分业务场景,做简单的设计。
积分说明
积分不具有货币或现金价值,不可兑现,不可转让。用户可以通过支付、账户服务、金融理财和积分奖励活动等方式来获取积分。
积分可以兑换各类权益、参与各种积分活动等,具体以权益兑换及活动页面展示为准。
积分领取规则
积分发放后,用户可前往“我的”-“支付宝会员”,点击领取积分球,或者在支付成功页面、服务消息提醒、账单点击领取,积分方可到账。积分自产生之时起,领取有效期7天(168小时),逾期不领则作废,不予补发。
积分获取方式
1、支付
2、账户服务
3、金融理财
4、扫一扫
5、等
积分有效期
用户获得的积分有效期为自获得当月起的12个自然月,有效期内未使用的积分到期将自动清零,不予补发。
温馨提示:
1、用户日常使用的积分,将优先使用即将过期的积分。
2、每月,用户可在“我的”-“支付宝会员”-“收支/订单/规则”页查看当月月底即将过期的积分。
3、已使用的积分若发生退还,积分有效期不变,仍以该笔积分原获取时间计算有效期。
积分权益兑换
积分可以兑换各类权益、参与各种积分活动等,具体以权益兑换及活动页面展示为准。
积分与成长体系
略
需求分析
因为上面几乎都是粘贴于支付宝对外展示的积分规则,而这篇文章涉及范围并没有那么广,所以这里要声明一下需求范围。
积分获取方式
支付、账户服务、金融理财、扫一扫等。
同步方案:这个就涉及到AOP
的思想,换言之“广义AOP
”。这个不是单个系统能实现的,需要多方系统共同确认,而本系统需要提供可靠的积分累计接口。如:用户支付完成后应该累计积分的,通过什么方式与积分系统交互增加积分呢?这样的接口如何设计呢?除了用户唯一标识、积分值、交易时间、交易类型还有什么必要字段呢?需不需要重试?如何做幂等?分布式事务如何考虑?等等,还有很多要素要考虑。
异步方案:有时我们对于积分累积的时效性并不高,就可以考虑异步方案。异步无非就是消息队列类的异步任务,或是有数据团队来做数据的分析处理。异步任务方式不管是消息队列或是事件驱动都大概可行,当然这中间也有很多地方要注意。数据分析处理有点像是定时任务,描述可能不太准确,就是通过分析用户实际数据来具体分析处理,如:每天2点,数据团队有多个数据模型针对不同的数据表(账户存款、理财等等)对用户前一天所有交易进行分析,计算积分然后累计前一天产生的积分。
关于积分获取方式,本次就讨论到这了。其实上面简单分析已经包含了很多系统设计的必要考虑条件了,而且很多都可以类比的,比如积分累计不就是类似购物产生订单,或是记录操作日志吗。有很多相似之处的,可以类比思考一下。
积分领取规则
从前面积分领取规则可以确定,积分领取涉及消息通知和过期。
消息通知肯定是成体系的,可参考其他消息类系统设计。过期可以参考下面的积分过期。
积分有效期
这个是本篇文章中我想要突出的重点。积分产生、积分兑换、积分过期还是需要好好思考一下的,如若不然,甚至不单单是产生系统bug,甚至会有业务bug,这是难以接受的。
既然上面需求说明了积分有效期是12个自然月,那么就可以考虑设计一张按月存储的表来存储积分。
如5.1-5.31的产生的积分存储为一条可用积分记录,过期时间为5.31 23:59:59,总积分值为最近12个月积分之和。
用户ID | 累计积分 | 可用积分值 | 过期时间 |
---|---|---|---|
1 | 300 | 300 | 5.31 23:59:59 |
1 | 500 | 500 | 6.30 23:59:59 |
1 | 800 | 700 | 7.31 23:59:59 |
1 | 500 | 400 | 8.31 23:59:59 |
1 | 300 | 200 | 9.30 23:59:59 |
1 | 300 | 100 | 10.31 23:59:59 |
1 | 500 | 500 | 11.30 23:59:59 |
1 | 600 | 600 | 12.31 23:59:59 |
可能有人在接到积分过期需求时就考虑到定时任务扫表的方式,虽然可能也是没问题的,但细节其实挺多的,这里就不做过多讨论了。
积分权益兑换
用户日常使用的积分,将优先使用即将过期的积分。针对于上面的月度积分表,在积分兑奖时从最早的未过期的月开始计算扣减积分。
设计方案
数据表
简单设计如下,月度积分关联多条积分明细记录,这样可以方便查询月度积分获取和消耗明细。
月度积分
- 月度积分id
- 用户id
- 年月(参考2024-01之类的)
- 累计积分
- 可用积分
- 过期时间
积分明细
- 月度积分id
- 明细id
- 用户id
- 积分值
- 类型(如:消耗,获取等)
- 操作
- 时间
兑奖明细
- 兑奖明细id
- 用户id
- 奖品消耗积分
- 奖品名称
- 时间
流程
积分获取
积分由用户操作生成,同时存储于月度积分表和积分明细表。
积分兑奖
其他
除了上面这些,还有
- 总积分查询,最近12个月的月度积分-可用积分之和
- 积分明细查询,倒序查询月度积分和月度积分明细
- 积分兑奖明细,等,简单的就不多提了
参考代码
积分兑奖
import java.time.LocalDate;
import java.util.*;
class PointRecord {
private int points;
private LocalDate expirationDate;
// 构造方法、getters和setters省略
public boolean canConsume(int requiredPoints) {
return this.points >= requiredPoints;
}
public void consume(int requiredPoints) {
if (canConsume(requiredPoints)) {
this.points -= requiredPoints;
} else {
throw new IllegalArgumentException("Insufficient points to consume.");
}
}
}
public class PointService {
public void redeemReward(User user, int rewardPointCost) {
List sortedRecords = getSortedUnexpiredPointRecords(user);
for (PointRecord record : sortedRecords) {
if (record.canConsume(rewardPointCost)) {
record.consume(rewardPointCost);
rewardPointCost = 0; // 已经满足消耗条件,跳出循环
break;
} else {
rewardPointCost -= record.getPoints();
record.setPoints(0); // 消耗完该记录所有积分
}
}
// 更新数据库中的积分明细表
updateDatabase(sortedRecords);
// 如果rewardPointCost仍然大于0,说明积分不足,需要在此处处理异常情况
if (rewardPointCost > 0) {
throw new InsufficientPointsException("Not enough valid points to redeem the reward.");
}
}
// 省略了获取用户未过期且已排序的积分记录列表的方法(getSortedUnexpiredPointRecords)以及更新数据库的方法(updateDatabase)
}
在这个示例中,PointRecord
类代表单个积分记录,包含积分值和过期日期。PointService
中的redeemReward
方法首先获取用户的所有未过期且按过期时间排序的积分记录,然后遍历这些记录,优先扣除最早到期的积分。当所需兑换积分数量减为0时停止扣除。如果最后仍有剩余的兑换积分需求,则抛出积分不足的异常。
实际应用中,你需要根据数据库查询结果填充sortedRecords
列表,并在扣除积分后更新数据库以反映新的积分状态。
小结
其实还有很多内容没有细究,这部分就留给认真看的你们了。哈哈哈
积分补偿、退货等场景,分布式事务如何考虑,留给你们了。
最后的最后,其实在系统设计之前,需求分析是重中之重,一定要讲需求看透,尤其是多考虑一些场景。
下面有一张关于积分过期和兑奖的场景草图,是我在思考时画的,不想多解释了,自己看吧
最后四个字:跳出时间。
写在最后
拙作艰辛,字句心血,望诸君垂青,多予支持,不胜感激。
个人博客:无奈何杨(wnhyang)
个人语雀:wnhyang
共享语雀:在线知识共享
Github:wnhyang - Overview