当前位置: 首页 > news >正文

实现复杂状态机的一种思路

一、问题

近期做广告平台,涉及到广告状态转换的问题,将需求抽象之后,发现其实就是要实现一个复杂的广告状态机,状态图如下:
状态跃迁图

广告一个有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;
}

 后续如果要修改状态转换逻辑,只需修改状态映射表就好。

四、总结

通过前后台配合实现复杂状态机是一种思路,并不囿于具体的业务:

通过将状态按照变化的原因进行拆分,将状态的变化进行解耦,这样后台就不需要管状态的具体呈现,只需要关注状态更改的唯一原因,这个原因触发了,就更改这一个状态,其他状态不受影响。具体状态的呈现,由前端通过映射表呈现,映射表将后台状态和前端呈现的状态进行映射,并通过层级对每个状态呈现的优先级进行管理,这样可以大大降低维护成本,无论状态转换的逻辑如何变,只需要修改映射表即可。

转载于:https://www.cnblogs.com/kagol/p/10277910.html

相关文章:

  • 【安全测试自学】初探web安全处测试(二)
  • URAL1966 Cipher Message 3
  • windows 下使用 sc 添加创建exe服务;
  • 【[NOI2018]你的名字】
  • OmniPlan 3 Pro密钥
  • AI书单
  • web通用测试点总结
  • d3生成的树状图
  • Tushare模块
  • 详解Oracle partition分区表
  • centos配置NTP服务器
  • 跟我一起学机器学习:机器学习常用流程-1
  • [Leetcode] 寻找数组的中心索引
  • nginx部署成功却没有办法访问
  • 进程管理
  • 【347天】每日项目总结系列085(2018.01.18)
  • android 一些 utils
  • css系列之关于字体的事
  • docker python 配置
  • exports和module.exports
  • HTTP 简介
  • JavaScript 奇技淫巧
  • javascript数组去重/查找/插入/删除
  • Java比较器对数组,集合排序
  • Linux gpio口使用方法
  • PHP的Ev教程三(Periodic watcher)
  • Python十分钟制作属于你自己的个性logo
  • Spark RDD学习: aggregate函数
  • text-decoration与color属性
  • V4L2视频输入框架概述
  • Webpack入门之遇到的那些坑,系列示例Demo
  • 阿里研究院入选中国企业智库系统影响力榜
  • 初识 webpack
  • 发布国内首个无服务器容器服务,运维效率从未如此高效
  • 复习Javascript专题(四):js中的深浅拷贝
  • 回顾 Swift 多平台移植进度 #2
  • 技术:超级实用的电脑小技巧
  • 将回调地狱按在地上摩擦的Promise
  • 前嗅ForeSpider采集配置界面介绍
  • 浅谈Golang中select的用法
  • 详解移动APP与web APP的区别
  • Python 之网络式编程
  • ​flutter 代码混淆
  • ​queue --- 一个同步的队列类​
  • #FPGA(基础知识)
  • (2020)Java后端开发----(面试题和笔试题)
  • (4)logging(日志模块)
  • (SpringBoot)第二章:Spring创建和使用
  • (附源码)python房屋租赁管理系统 毕业设计 745613
  • (机器学习-深度学习快速入门)第三章机器学习-第二节:机器学习模型之线性回归
  • (十一)图像的罗伯特梯度锐化
  • (五) 一起学 Unix 环境高级编程 (APUE) 之 进程环境
  • (源码版)2024美国大学生数学建模E题财产保险的可持续模型详解思路+具体代码季节性时序预测SARIMA天气预测建模
  • (转)http-server应用
  • .bat批处理(十一):替换字符串中包含百分号%的子串