我的BO之状态控制

我的BO
1-我的BO之强类型
2-我的BO之数据保护
3-我的BO之状态控制
4-我的BO之导航属性

MIS常有状态

信息管理系统(MIS)常常有流程,一个流程由多个环节构成,不同的环节的流转通过状态控制。比如简单的购物流程:

对应着这样的状态:

结合起来就是状态图:

状态的控制在MIS中往往起着举足轻重的作用,如先付款才能退款。若没有控制好状态,未付款就能退款,实在荒唐。

不判断状态的做法

此做法比较简单,前端根据状态显示不同的界面和操作,提交时后端也不判断状态,直接认为当前肯定处于正确的状态,否则前端也不会提交此请求。这种做法不安全,攻击者可以自行发起请求,绕过界面,不顾当前的状态。本来这么不靠谱的方案不足拿出来说,但是由于种种“现实原因”现实中时而有见此方案。拿出来说是想说明存在这样的方案,但要避免这么干。

一般的做法

一般的做法,是在业务逻辑中进行判断。具体来说就是要做具体的业务操作前判断当前的状态,只有当前的状态与预期的一种或多种状态符合时才进行后续的操作。最后更新状态。这种做法,软件写得对不对,全靠人当时的精神状态。从工程学来讲,人是不可靠的。本做法潜在的风险:

  1. 漏了检查状态。业务环节多的情况下,漏掉一小部分是不足为奇的。
  2. 检查了状态,但没有检查全面。有些业务操作可以在N种状态下操作,少判断了一些状态也不难发生。
  3. 判断错了状态。流程稍微复杂,容易发生。
    由于状态控制分散在各个业务中,没有集中管理,导致了以上问题。希望有更好的方案。

中等复杂的业务流程的状态图:

推荐的做法

集中管理全部状态。首先把可能的变化做成状态图(State Diagram/state transition diagram/状态转移图,这几个名是否指同个东西?请懂的人赐教), 凡是状态图没有出现的都是非法。当状态值变化时,在状态图中查找旧值能否直接到达新值。若旧值无法直达新值,说明不是非法调用,就是程序写错了。
状态图需要转化成数据结构才方便计算机的处理,一般是多个二元组,
每个二元组是这样的:(旧状态值,新状态值),意思是 旧状态值允许直接变成新状态值。比如简单的购物流程,即转化为这样的结构:

(开始, 已下单)
(已下单, 已付款)
(已付款, 已发货)
(已发货, 已确认收货)
(已确认收货, 已评价)

这个状态图我是写死在代码中,也可以存储在任何位置,只要运行时能装载到内存即可。

无论当前做什么具体的业务操作,只要状态发生变化,就调用检查,从而能防止非法的状态变化。结合前面介绍过的我的BO之数据保护就能全面地防止非法的流程的出现。

示例代码

Java

// 订单
public class OrderBo extends BoBase {
    Order order;

    public Long getId() {
        return order.getId();
    }

    protected void setId(Long id) {  /* 每个 set 都不是 public */
        order.setId(id);
        setTrackUpdate();    /* 父类方法,后续文章会介绍 */
    }

    // 这里省略若干属性

    // 订单状态
    public OrderStatus getStatus() {
        String sStatus = order.getStatus();
        return OrderStatus.valueOf(sStatus);
    }

    protected void setStatus(OrderStatus status) {
        OrderStatus.statusChanging(this.getStatus(), status);
        String sStatus = status.toString();
        order.setStatus(sStatus);
        setTrackUpdate();
    }

    // 发货
    public void delivery(String expressCompanyName, String  expressNumber) {
        this.setStatus(OrderStatus.已发货);  // 你没有看错,这里可以不判断当前的状态
        this.setExpressCompanyName(expressCompanyName);
        this.setExpressNumber(expressNumber);
        // 其它各种操作
        this.save();    /* 父类方法,后续文章会介绍 */
    }
}

public enum OrderStatus{
    开始,
    已下单,
    已付款,
    已发货,
    已确认收货,
    已评价;

    public static void statusChanging(OrderStatus oldStatus, OrderStatus newStatus) {
        switch (oldStatus) {
            case 开始:
                switch (newStatus) {
                    case 已下单:
                        return;
                }
                break;
            case 已下单:
                switch (newStatus) {
                    case 已付款:
                        return;
                }
                break;
            case 已付款:
                switch (newStatus) {
                    case 已发货:
                        return;
                }
                break;
            case 已发货:
                switch (newStatus) {
                    case 已确认收货:
                        return;
                }
                break;
            case 已确认收货:
                switch (newStatus) {
                    case 已评价:
                        return;
                }
                break;
            case 已评价:
                break;
        }
        throw CommonException.invalidStatus("订单" + this.getEvaluateStatus() + ",不允许此操作");
    }
}

未来可能在二元组中加入“动作”,变成三元组:(旧状态值, 动作, 新状态值)。能在状态变化时加上动作一起判断,效果更好。

感谢 螺丝钉 协助阅稿并提出修改建议。

系列导航

1-我的BO之强类型
2-我的BO之数据保护
3-我的BO之状态控制
4-我的BO之导航属性

posted @ 2019-05-11 16:42  BillySir  阅读(422)  评论(0编辑  收藏  举报