個人博客:無奈何楊(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<PointRecord> 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