個人BO之狀態控制

個人BO
1-個人BO之強類型
2-個人BO之數據保護
3-個人BO之狀態控制
4-個人BO之導航屬性html

MIS常有狀態

信息管理系統(MIS)經常有流程,一個流程由多個環節構成,不一樣的環節的流轉經過狀態控制。好比簡單的購物流程:

對應着這樣的狀態:前端

結合起來就是狀態圖:
java

狀態的控制在MIS中每每起着舉足輕重的做用,如先付款才能退款。若沒有控制好狀態,未付款就能退款,實在荒唐。express

不判斷狀態的作法

此作法比較簡單,前端根據狀態顯示不一樣的界面和操做,提交時後端也不判斷狀態,直接認爲當前確定處於正確的狀態,不然前端也不會提交此請求。這種作法不安全,攻擊者能夠自行發起請求,繞過界面,不顧當前的狀態。原本這麼不靠譜的方案不足拿出來講,可是因爲種種「現實緣由」現實中時而有見此方案。拿出來講是想說明存在這樣的方案,但要避免這麼幹。後端

通常的作法

通常的作法,是在業務邏輯中進行判斷。具體來講就是要作具體的業務操做前判斷當前的狀態,只有當前的狀態與預期的一種或多種狀態符合時才進行後續的操做。最後更新狀態。這種作法,軟件寫得對不對,全靠人當時的精神狀態。從工程學來說,人是不可靠的。本作法潛在的風險:安全

  1. 漏了檢查狀態。業務環節多的狀況下,漏掉一小部分是不足爲奇的。
  2. 檢查了狀態,但沒有檢查全面。有些業務操做能夠在N種狀態下操做,少判斷了一些狀態也不難發生。
  3. 判斷錯了狀態。流程稍微複雜,容易發生。
    因爲狀態控制分散在各個業務中,沒有集中管理,致使了以上問題。但願有更好的方案。

中等複雜的業務流程的狀態圖:
數據結構

推薦的作法

集中管理所有狀態。首先把可能的變化作成狀態圖(State Diagram/state transition diagram/狀態轉移圖,這幾個名是否指同個東西?請懂的人賜教), 凡是狀態圖沒有出現的都是非法。當狀態值變化時,在狀態圖中查找舊值可否直接到達新值。若舊值沒法直達新值,說明不是非法調用,就是程序寫錯了。
狀態圖須要轉化成數據結構才方便計算機的處理,通常是多個二元組,
每一個二元組是這樣的:(舊狀態值,新狀態值),意思是 舊狀態值容許直接變成新狀態值。好比簡單的購物流程,即轉化爲這樣的結構:this

(開始, 已下單)
(已下單, 已付款)
(已付款, 已發貨)
(已發貨, 已確認收貨)
(已確認收貨, 已評價)

這個狀態圖我是寫死在代碼中,也能夠存儲在任何位置,只要運行時能裝載到內存便可。lua

不管當前作什麼具體的業務操做,只要狀態發生變化,就調用檢查,從而能防止非法的狀態變化。結合前面介紹過的個人BO之數據保護就能全面地防止非法的流程的出現。code

示例代碼

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之導航屬性

相關文章
相關標籤/搜索