我的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之导航属性

我的BO之状态控制的更多相关文章

  1. 基于 SailingEase WinForm Framework 开发客户端程序(3:实现菜单/工具栏按钮的解耦及状态控制)

    本系列文章将详细阐述客户端应用程序的设计理念,实现方法. 本系列文章以  SailingEase WinForm Framework 为基础进行设计并实现,但其中的设计理念及方法,亦适用于任何类型的客 ...

  2. 线程锁的本质:线程控制、线程状态控制 while if:根据线程的关系(模式)协调线程的执行

    线程锁的本质:线程控制.线程状态控制 while if https://www.cnblogs.com/feng9exe/p/8319000.html https://www.cnblogs.com/ ...

  3. Java多线程 2 线程的生命周期和状态控制

    一.线程的生命周期 线程状态转换图: 1.新建状态 用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态.处于新生状态的线程有自己的内存空间,通过调用start方法进入就 ...

  4. Java多线程——线程的生命周期和状态控制

    一.线程的生命周期 线程状态转换图: 1.新建状态 用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态.处于新生状态的线程有自己的内存空间,通过调用start方法进入就 ...

  5. filter 实现登录状态控制

    每天学习一点点 编程PDF电子书.视频教程免费下载:http://www.shitanlife.com/code 网站需要做用户登录鉴权控制,没有登录的话,不能访问网站,提示需要登录. 实现方式: 使 ...

  6. RxJava异步请求加载状态控制

    在我看来,RxJava最大的特点就是异步,无论你是解析复杂的数据或是IO操作,我们都可以利用它内置的线程池进行线程间的调度,简单的使用 subscribeOn(Schedulers.io()).doO ...

  7. 11月6日下午PHP注册审核(审核状态控制登录、可以更改审核状态)

    1.创建登录界面 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://ww ...

  8. 解密FFmpeg播放状态控制内幕

    上一篇文章(http://my.oschina.net/u/2336532/blog/400790)我们解决了在FFmpeg下如何处理H264和AAC的扩展数据,根据解出的NALU长度恢复了H264的 ...

  9. hdu 4634 Swipe Bo bfs+状态压缩

    题目链接 状态压缩记录当前拿到了哪些钥匙, 然后暴力搜索. 搞了好几个小时, 一开始也不知道哪里错了, 最后A了也不知道一开始哪里有问题. #include <iostream> #inc ...

随机推荐

  1. 炫酷的可视化工具包——cufflinks

    前言 学过Python数据分析的朋友都知道,在可视化的工具中,有很多优秀的三方库,比如matplotlib,seaborn,plotly,Boken,pyecharts等等.这些可视化库都有自己的特点 ...

  2. MutationObserver

    一.MutationObserver Config childList: 添加.删除目标节点的子节点时会收到通知(子节点的后代节点添加或删除时不会收到通知) attribute: 修改目标节点属性时会 ...

  3. Ipython 和 python 的区别

    IPython是一个python交互shell,它比默认的python shell更易于使用.它支持自动变量完成.自动缩进.bash shell命令,并且内置了许多有用的函数和函数. IPython是 ...

  4. shell 大型脚本工具开发实战

    拆分脚本功能,抽象函数 1.function get_all_group 返回进程组列表字符串 2.function get_all_process 返回进程名列表字符串"nginx htt ...

  5. Mysql踩坑 自动更新的时间只允许有一个

    执行如下SQL创建表: CREATE TABLE aa ( a INT, b TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, c TIMESTAMP DEFAULT CU ...

  6. Django之小结

    常用的函数方法与包的调用 # 登陆视图函数 def login(request): if request.method == 'GET': return render(request,'login.h ...

  7. 用js刷剑指offer(二叉搜索树的后序遍历序列)

    题目描述 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果.如果是则输出Yes,否则输出No.假设输入的数组的任意两个数字都互不相同. 牛客网链接 js代码 function Verif ...

  8. 开源框架---tensorflow c++ API中./configure步骤细节

    u@u160406:~/tf1.13/tensorflow$ git checkout r1.13 分支 r1.13 设置为跟踪来自 origin 的远程分支 r1.13.切换到一个新分支 'r1.1 ...

  9. 为RIDE创建桌面快捷方式

    问题场景:默认情况下,RIDE的图标不是自动创建的,需要手动添加.     解决方法: 在桌面上新建"快捷方式" 目标对象的位置:C:\Python27\python2.exe - ...

  10. vueRouter history模式下 nginx配置

    对于VUE的router[mode: history]模式(这里主要是为了去除链接上的"#")开发的时候,一般都不出问题.是因为开发时用的服务器为node,Dev环境中已配置好了, ...