一、問題
近期做廣告平台,涉及到廣告狀態轉換的問題,將需求抽象之后,發現其實就是要實現一個復雜的廣告狀態機,狀態圖如下:
廣告一個有7種狀態(如上圖),其中”Not delivering”包含4種子狀態。
10種狀態(state),理論上最多可能有90種躍遷(transition),狀態之間的轉化極其復雜,如果只是用條件分支的方式來展示廣告的狀態,不夠優雅。
二、解決方案
於是將整個狀態轉換邏輯進行抽象和簡化,具體做法如下:
1.后台將廣告狀態進行拆分
將推廣計划和廣告的狀態拆分成系統狀態(system_status)和用戶狀態(configure_status),用戶狀態是廣告主可以手動開啟和關閉的,賬戶余額狀態放在賬戶層級。
后台不做狀態的聯動,這意味着:后台所有狀態的改變互不影響,例如推廣計划被暫停了,廣告並不會跟着暫停;賬戶余額不足,廣告的系統狀態也不會受到影響。
具體拆分邏輯見下表:
財務狀態(fund_status) | 系統狀態(system_status) | 用戶配置狀態(configured_status) | |
---|---|---|---|
賬戶層級 | 余額情況 | -- | -- |
推廣計划層級 | -- | 是否達到日限額 | 是否開啟 |
廣告層級 | -- | 是否達到日限額 審核狀態 | 是否開啟 |
2.前端設計廣告狀態流轉的映射表
用狀態映射表將前端展示狀態和后台狀態關聯起來,這樣如果增加新狀態或狀態轉換邏輯改變,都只需要改狀態映射表就好,修改成本非常低。
這樣廣告的前端展示狀態由以下6個后台狀態共同決定:
- (1)賬戶余額狀態
- (2)推廣計划系統狀態
- (3)推廣計划用戶狀態
- (4)廣告系統狀態
- (5)廣告用戶狀態
- (6)廣告投放時間
三、具體實現
1.狀態映射表的設計
狀態映射表就是一個JSON結構,其設計非常簡單:
{//ad configure status
"STATUS_SUSPEND": 'Paused',
"STATUS_NORMAL": {//ad system status
"STATUS_PENDING": 'Pending for review',//待審核
"STATUS_DENIED": 'Denied',//審核不通過
"STATUS_DAILY_LIMIT": 'Not delivering,Reach ad limit',
"STATUS_NORMAL": {//date range
"BEFORE_DATE_RANGE": 'Prepare for delivery',
"AFTER_DATE_RANGE": 'End of delivery',//超過投放時間
"BETWEEN_DATE_RANGE": {//campaign configure status
"STATUS_SUSPEND": 'Not delivering,Campaign is paused',
"STATUS_NORMAL": {//campaign system status
"STATUS_DAILY_LIMIT": 'Not delivering,Reach campaign daily limit',
"STATUS_NORMAL": {//account system status
"FUND_STATUS_NOT_ENOUGH": 'Not delivering,Low balance',
"FUND_STATUS_NORMAL": 'In delivery'
}
}
}
}
}
}
JSON中的key是后台各個層級狀態的值,value是前端廣告的展示狀態。
需要注意的是投放時間需要手動轉換好才能進行映射,轉換的邏輯抽離成一個工具函數getDateStatus,后面談具體實現時會提及。
2.映射廣告狀態
為了將廣告狀態映射表與后台字段關聯起來,寫了一個工具函數:
var getEffectStatus = function({
first_level_status,
second_level_status,
third_level_status,
fourth_level_status,
fifth_level_status,
sixth_level_status}) {
var firstLevel = status_map[first_level_status];
var secondLevel = firstLevel && firstLevel[second_level_status];
var thirdLevel = secondLevel && secondLevel[third_level_status];
var fourthLevel = thirdLevel && thirdLevel[fourth_level_status];
var fifthLevel = fourthLevel && fourthLevel[fifth_level_status];
var sixthLevel = fifthLevel && fifthLevel[sixth_level_status];
var effect_status = (isString(firstLevel) && firstLevel)
|| (isString(secondLevel) && secondLevel)
|| (isString(thirdLevel) && thirdLevel)
|| (isString(fourthLevel) && fourthLevel)
|| (isString(fifthLevel) && fifthLevel)
|| (isString(sixthLevel) && sixthLevel)
|| '';
return effect_status;
}
層級代表各種狀態的優先級,淺層的狀態會覆蓋深層的狀態。
具體哪一層是哪個狀態,由調用者自己決定,保證了靈活性和可擴展性。
3.測試
var ad_configure_status = 'STATUS_NORMAL';//STATUS_SUSPEND STATUS_NORMAL
var ad_system_status = 'STATUS_NORMAL';//STATUS_PENDING STATUS_DENIED STATUS_DAILY_LIMIT STATUS_NORMAL
var date_range = {
start: '2018-01-02',
end: '2018-01-07'
};
var campaign_configure_status = 'STATUS_NORMAL';//STATUS_SUSPEND STATUS_NORMAL
var campaign_system_status = 'STATUS_NORMAL';//STATUS_DAILY_LIMIT STATUS_NORMAL
var account_fund_status = 'FUND_STATUS_NORMAL';//FUND_STATUS_NOT_ENOUGH FUND_STATUS_NORMAL
var effect_status = getEffectStatus({
first_level_status: ad_configure_status,
second_level_status: ad_system_status,
third_level_status: getDateStatus(date_range),
fourth_level_status: campaign_configure_status,
fifth_level_status: campaign_system_status,
sixth_level_status: account_fund_status
});
console.log('effect status:', effect_status);
用到的轉換投放時間狀態的工具函數(引入了moment.js日期處理庫):
var getDateStatus = function(date_range){
var today = moment().format('YYYY-MM-DD');
var start = date_range['start'];
var end = date_range['end'];
let date_status = '';
if(moment(today).isBefore(start)){
date_status = 'BEFORE_DATE_RANGE';
}
if(moment(today).isAfter(end)){
date_status = 'AFTER_DATE_RANGE';
}
if(moment(today).isSame(start) || moment(today).isBetween(start, end) || moment(today).isSame(end)){
date_status = 'BETWEEN_DATE_RANGE';
}
return date_status;
}
后續如果要修改狀態轉換邏輯,只需修改狀態映射表就好。
四、總結
通過前后台配合實現復雜狀態機是一種思路,並不囿於具體的業務:
通過將狀態按照變化的原因進行拆分,將狀態的變化進行解耦,這樣后台就不需要管狀態的具體呈現,只需要關注狀態更改的唯一原因,這個原因觸發了,就更改這一個狀態,其他狀態不受影響。具體狀態的呈現,由前端通過映射表呈現,映射表將后台狀態和前端呈現的狀態進行映射,並通過層級對每個狀態呈現的優先級進行管理,這樣可以大大降低維護成本,無論狀態轉換的邏輯如何變,只需要修改映射表即可。