我的BO之狀態控制
我的BO
1-我的BO之強類型
2-我的BO之數據保護
3-我的BO之狀態控制
MIS常有狀態
信息管理系統(MIS)常常有流程,一個流程由多個環節構成,不同的環節的流轉通過狀態控制。比如簡單的購物流程:
對應著這樣的狀態:
結合起來就是狀態圖:
狀態的控制在MIS中往往起著舉足輕重的作用,如先付款才能退款。若沒有控制好狀態,未付款就能退款,實在荒唐。
不判斷狀態的做法
此做法比較簡單,前端根據狀態顯示不同的界面和操作,提交時後端也不判斷狀態,直接認為當前肯定處於正確的狀態,否則前端也不會提交此請求。這種做法不安全,攻擊者可以自行發起請求,繞過界面,不顧當前的狀態。本來這麽不靠譜的方案不足拿出來說,但是由於種種“現實原因”現實中時而有見此方案。拿出來說是想說明存在這樣的方案,但要避免這麽幹。
一般的做法
一般的做法,是在業務邏輯中進行判斷。具體來說就是要做具體的業務操作前判斷當前的狀態,只有當前的狀態與預期的一種或多種狀態符合時才進行後續的操作。最後更新狀態。這種做法,軟件寫得對不對,全靠人當時的精神狀態。從工程學來講,人是不可靠的。本做法潛在的風險:
- 漏了檢查狀態。業務環節多的情況下,漏掉一小部分是不足為奇的。
- 檢查了狀態,但沒有檢查全面。有些業務操作可以在N種狀態下操作,少判斷了一些狀態也不難發生。
- 判斷錯了狀態。流程稍微復雜,容易發生。
由於狀態控制分散在各個業務中,沒有集中管理,導致了以上問題。希望有更好的方案。
中等復雜的業務流程的狀態圖:
推薦的做法
集中管理全部狀態。首先把可能的變化做成狀態圖(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之狀態控制