背景:積分項目,每次添加的積分都有一個有效期,有效期爲一年,如2017-01-02添加了一條積分記錄,到2018-01-02這條記錄應該是過時的。當前項目設計有兩張表:積分明細表(存放積分添加、使用明細)、積分總額表(用戶當前的積分額度)。因爲每條積分的過時時間各不相同,如何正確地將過時的積分做廢?消費時,如何優先使用即將過時的積分?java
剛開始系統有兩張表:積分明細表、積分總額表,積分總額直接由積分明細表統計。app
如下是積分明細表,須要將內容展現給用戶,讓用戶知道本身積分的添加、消耗狀況:性能
記錄id | 會員id | 積分值 | 添加時間 | 過時時間 | 備註 |
---|---|---|---|---|---|
1 | 1 | 10 | 2017-01-02 | 2018-01-02 | 新增積分 |
2 | 1 | 20 | 2017-01-04 | 2018-01-04 | 新增積分 |
3 | 1 | -30 | 2017-01-08 | 2018-01-08 | 使用積分 |
假設id爲1的會在2017-01-08後,再無積分添加與消耗的狀況。設計
如今有一張積分總額表,記錄的了會員的積分額度,因爲積分是過時的,那麼按不一樣時間來統計,就會出現如下狀況:code
在2018-01-01統計時,id爲1的會員積分額度是0,這沒問題。排序
會員id | 積分總值 | 更新時間 |
---|---|---|
1 | 0 | 2018-01-01 |
那在2018-01-02統計時呢,記錄id爲1的記錄已通過期了,只統計記錄id爲2與3的記錄,會出現如下狀況:get
會員id | 積分總值 | 更新時間 |
---|---|---|
1 | -10 | 2018-01-02 |
在2018-01-04統計時,記錄id爲1與3的記錄已通過期了,只統計記錄id爲3的記錄,差額就更大了:it
會員id | 積分總值 | 更新時間 |
---|---|---|
1 | -30 | 2018-01-04 |
只有在2018-01-08統計時,記錄id爲一、二、3的記錄已通過期了,積分值纔會正常:io
會員id | 積分總值 | 更新時間 |
---|---|---|
1 | 0 | 2018-01-08 |
從上面的狀況來看,單純從積分明細表來統計,會出現積分值爲負數的狀況。table
項目當前的方案中,積分明細表記錄了用戶使用添加與使用狀況,須要展示給用戶,因此沒法在這上面下手,爲此新添加了一張表來記錄當前可用積分明細。該表的關鍵字段以下:
字段 | 含義 |
---|---|
積分值 | 該記錄的積分值 |
添加時間 | 該記錄的添加時間 |
過時時間 | 該記錄的過時時間 |
如,在2017-01-02該會員的可用積分記錄爲
記錄id | 會員id | 積分值 | 添加時間 | 過時時間 |
---|---|---|---|---|
100 | 1 | 10 | 2017-01-02 | 2018-01-02 |
在2017-01-04該會員的可用積分記錄爲
記錄id | 會員id | 積分值 | 添加時間 | 過時時間 |
---|---|---|---|---|
100 | 1 | 10 | 2017-01-02 | 2018-01-02 |
101 | 1 | 20 | 2017-01-04 | 2018-01-04 |
在2018-01-08該會員的可用積分記錄爲
記錄id | 會員id | 積分值 | 添加時間 | 過時時間 |
---|---|---|---|---|
因爲當前會員無可用積分,因此表中記錄爲空。 |
新加了可用積分表後,後續用戶的積分總額就能夠直接從該表中統計了。
明白了大體原理後,接下來解決如下幾個問題。
以會員id爲2的用戶爲例
可用積分表以下:
記錄id | 會員id | 積分值 | 添加時間 | 過時時間 |
---|---|---|---|---|
103 | 2 | 10 | 2017-01-02 | 2018-01-02 |
104 | 2 | 20 | 2017-01-04 | 2018-01-04 |
105 | 2 | 20 | 2017-01-06 | 2018-01-06 |
積分明細表以下:
記錄id | 會員id | 積分值 | 添加時間 | 過時時間 | 備註 |
---|---|---|---|---|---|
4 | 2 | 10 | 2017-01-02 | 2018-01-02 | 新增積分 |
5 | 2 | 20 | 2017-01-04 | 2018-01-04 | 新增積分 |
6 | 2 | 20 | 2017-01-06 | 2018-01-06 | 新增積分 |
積分總額表以下:
會員id | 積分總值 | 更新時間 |
---|---|---|
2 | 50 | 2018-01-01 |
在2018-01-02統計該用戶的可用積分時,發現有一條記錄id爲103的記錄已過時,那麼統計完以後,各表變化以下:
可用積分表及時刪除過時記錄:
記錄id | 會員id | 積分值 | 添加時間 | 過時時間 |
---|---|---|---|---|
104 | 2 | 20 | 2017-01-04 | 2018-01-04 |
105 | 2 | 20 | 2017-01-06 | 2018-01-06 |
積分明細表添加一條過時明細:
記錄id | 會員id | 積分值 | 添加時間 | 過時時間 | 備註 |
---|---|---|---|---|---|
4 | 2 | 10 | 2017-01-02 | 2018-01-02 | 新增積分 |
5 | 2 | 20 | 2017-01-04 | 2018-01-04 | 新增積分 |
6 | 2 | 20 | 2017-01-06 | 2018-01-06 | 新增積分 |
7 | 2 | -10 | 2018-01-02 | - | 積分過時 |
積分總額表更新爲正確的積分總額:
會員id | 積分總值 | 更新時間 |
---|---|---|
2 | 40 | 2018-01-04 |
小結:積分過時時,可用積分表刪除過時記錄,積分明細表添加一條過時明細,積分總額表更新爲當前積分總額。
那麼積分過時在何時觸發呢?能夠有如下方案:
第一種方案須要所有遍歷會員表,若是會員表太大,效率低下,並且若是會員並無過時積分,會作許多無效的統計。第二種方案採用被動觸發的形式,雖然也會有無效的積分統計,未能及時清理過時積分,但避免了積分的會員表的全表掃描,性能略高。第三種方案從過時積分入手,主動觸發,能及時清理過時積分,只針對過時積分,沒有無效的統計,性能優於前兩種。
積分使用策略以下:
可用積分表
的可用積分記錄按過時時間升序排列,依次累加積分額度sumPoint直到sumPoint>=point
或記錄所有遍歷完,用recordIds記錄符合要求的積分id,用targetRecord記錄最後一條積分記錄。(sumPoint-point)
),另外一條記錄積分剩餘(額度爲最後一條記錄的額度-(sumPoint-point)
,過時時間爲最後一條記錄的過時時間)。仍是以會員id爲2的用戶爲例,用戶各表以下:
可用積分表以下:
記錄id | 會員id | 積分值 | 添加時間 | 過時時間 |
---|---|---|---|---|
103 | 2 | 10 | 2017-01-02 | 2018-01-02 |
104 | 2 | 20 | 2017-01-04 | 2018-01-04 |
105 | 2 | 20 | 2017-01-06 | 2018-01-06 |
積分明細表以下:
記錄id | 會員id | 積分值 | 添加時間 | 過時時間 | 備註 |
---|---|---|---|---|---|
4 | 2 | 10 | 2017-01-02 | 2018-01-02 | 新增積分 |
5 | 2 | 20 | 2017-01-04 | 2018-01-04 | 新增積分 |
6 | 2 | 20 | 2017-01-06 | 2018-01-06 | 新增積分 |
積分總額表以下:
會員id | 積分總值 | 更新時間 |
---|---|---|
2 | 50 | 2017-12-01 |
假設該用戶在2017-12-01須要使用40積分,使用過程以下:
記錄id | 會員id | 積分值 | 添加時間 | 過時時間 |
---|---|---|---|---|
103 | 2 | 10 | 2017-01-02 | 2018-01-02 |
104 | 2 | 20 | 2017-01-04 | 2018-01-04 |
105 | 2 | 10 | 2017-01-06 | 2018-01-06 |
106 | 2 | 10 | 2017-01-06 | 2018-01-06 |
id爲106的記錄由105拆出來的,過時時間與105相同,如今105的積分值(10)+如今的106的積分值(10)=原來的105的積分值(20)
記錄id | 會員id | 積分值 | 添加時間 | 過時時間 |
---|---|---|---|---|
106 | 2 | 10 | 2017-01-06 | 2018-01-06 |
積分明細表以下:
記錄id | 會員id | 積分值 | 添加時間 | 過時時間 | 備註 |
---|---|---|---|---|---|
4 | 2 | 10 | 2017-01-02 | 2018-01-02 | 新增積分 |
5 | 2 | 20 | 2017-01-04 | 2018-01-04 | 新增積分 |
6 | 2 | 20 | 2017-01-06 | 2018-01-06 | 新增積分 |
7 | 2 | -40 | 2017-12-01 | - | 使用積分 |
積分總額表以下:
會員id | 積分總值 | 更新時間 |
---|---|---|
2 | 10 | 2017-12-01 |
相關的java代碼以下:
int pageNo = 0; int pageSize = 10; int sumPoint = 0; //目標記錄 PointRecord targetRecord = null; //保存要更新的recordId List<Long> recordIds = new ArrayList<>(); //查詢可用積分記錄 PointRecordSearch search = new PointRecordSearch(); search.setMemberId(memberId); search.setOrderFields(" expire_time ASC "); while(sumPoint < point) { search.setStart(pageNo * pageSize); search.setLimit(pageSize); List<PointRecord> pointRecords = pointRecordMapper.queryForPages(search); if(CollectionUtils.isEmpty(pointRecords)) { break; } for(PointRecord pointRecord : pointRecords) { //累加,直到值大於等於point sumPoint += pointRecord.getPoint(); if(sumPoint >= point) { targetRecord = pointRecord; break; } //記錄相應的id值 recordIds.add(pointRecord.getRecordId()); } pageNo++; } if(sumPoint < point) { //當前積分不足以消耗 return false; } else if(sumPoint == point) { //加上最後一條記錄的值後,總額恰好等於使用額度 recordIds.add(targetRecord.getRecordId()); } else { // 加上最後一條記錄的值後,總額恰好大於使用額度,此時須要將最後一條記錄拆成兩條記錄 int leftPoint = sumPoint - point; int usePoint = targetRecord.getPoint() - leftPoint; //添加一條記錄 PointRecord newPointRecord = new PointRecord(); newPointRecord.setCreateTime(targetRecord.getCreateTime()); newPointRecord.setExpireTime(targetRecord.getExpireTime()); newPointRecord.setMemberId(targetRecord.getMemberId()); newPointRecord.setPoint(leftPoint); newPointRecord.setUpdateTime(new Date()); pointRecordMapper.insertValues(newPointRecord); //修改最後一條記錄的積分值 PointRecord updateModel = new PointRecord(); updateModel.setRecordId(targetRecord.getRecordId()); updateModel.setPoint(usePoint); pointRecordMapper.updateById(updateModel); } //按id刪除記錄 pointRecordMapper.deleteByIds(recordIds); //處理積分明細 PointDetail detail = new PointDetail(); detail.setCreateTime(new Date()); detail.setMemberId(memberId); detail.setPoint(-point); detail.setDesc("使用積分"); pointDetailMapper.add(detail); //處理積分總額 PointTotal pointTotal = new PointTotal(); pointTotal.setId(memberId); pointTotal.setPoint(pointRecordMapper.sumByMemberId(memberId)); pointTotalMapper.updateById();
總結:積分過時問題的難點在於每條積分都有過時時間,很差把控。引入了一張新表後,使積分明細與可用積分得以分離,明細表僅展現用戶的積分添加與使用記錄,用戶的可用積分額度由該表進行統計,這樣積分的過時問題就完美獲得瞭解決。