1. 程式人生 > >我的BO之狀態控制

我的BO之狀態控制

什麽 退款 數據結構 信息管理 比較 技術 rbo number 全面

我的BO
1-我的BO之強類型
2-我的BO之數據保護
3-我的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之狀態控制

我的BO之狀態控制