版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/kmyhy/article/details/84784479
以一个简单的二级审批流程(请假)为例,研究一下 StateMachine 的使用。该流程设计如下:
目标:通过 StateMachine 实现该二级审批流程,提供 API 给第三方调用。
将流程图上的 5 个状态(不包括开始和结束)定义如下:
public enum States { WAITING_FOR_SUBMIT, // 等待提交 WAITING_FOR_TL_APPROVE, // 等待 TL 审批 WAITING_FOR_DM_APPROVE, // 等待 DM 审批 WAITING_FOR_HR_RECORD, // 等待 HR 备案 END, // 流程结束 }
将流程图上的 6 个事件(不括开始和结束)定义如下:
public enum Events { SUBMIT, // 提交申请 TL_AGREE, // WAITING_FOR_TL_APPROVE 审批 TL_REJECT, // WAITING_FOR_TL_APPROVE 驳回 DM_AGREE, // 部门经理审批 DM_REJECT, // 部门经理驳回 HR_RECORD, // WAITING_FOR_HR_RECORD 备案 }
主要是配置状态、事件和迁移。
@Configuration @EnableStateMachine public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<States, Events> { @Autowired private LeaveStateMachinePersist leaveStateMachinePersist; @Bean public StateMachinePersister<States,Events,String> stateMachinePersist(){ return new DefaultStateMachinePersister<>(leaveStateMachinePersist); } @Override public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception { states .withStates() .initial(States.WAITING_FOR_SUBMIT) .states(EnumSet.allOf(States.class)); } @Override public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception { transitions .withExternal() .source(States.WAITING_FOR_SUBMIT).target(States.WAITING_FOR_TL_APPROVE) .event(Events.SUBMIT) .and() .withExternal() .source(States.WAITING_FOR_TL_APPROVE).target(States.WAITING_FOR_DM_APPROVE) .event(Events.TL_AGREE) .and() .withExternal() .source(States.WAITING_FOR_TL_APPROVE).target(States.WAITING_FOR_SUBMIT) .event(Events.TL_REJECT) .and() .withExternal() .source(States.WAITING_FOR_DM_APPROVE).target(States.WAITING_FOR_HR_RECORD) .event(Events.DM_AGREE) .and() .withExternal() .source(States.WAITING_FOR_DM_APPROVE).target(States.WAITING_FOR_SUBMIT) .event(Events.DM_REJECT) .and() .withExternal() .source(States.WAITING_FOR_HR_RECORD).target(States.END) .event(Events.HR_RECORD); }
其中 LeaveStateMachinePersist 是自定义的持久化对象,用于恢复状态机的状态,因为不同的业务对象共用同一个状态机,状态机的状态根据业务 id 来缓存。LeaveStateMachinePersist 目前很简单,仅仅是一个 HashMap 来保存业务 id 和对于的 StateMachineContext:
@Component public class LeaveStateMachinePersist implements StateMachinePersist<States,Events,String> { // 用 map 来模拟持久化存储,可替换成数据库 static Map<String, States> cache = new HashMap<>(16); @Override public void write(StateMachineContext<States, Events> stateMachineContext, String s) { cache.put(s, stateMachineContext.getState()); } @Override public StateMachineContext<States, Events> read(String s) { return cache.containsKey(s) ? new DefaultStateMachineContext<>(cache.get(s),null,null,null,null,"请假流程") : new DefaultStateMachineContext<>(States.WAITING_FOR_SUBMIT,null,null,null,null,"请假流程"); } }
控制器负责对外提供 RESTFul 接口。有 6 个 RequestMapping,分别执行新建请假条、提交、tl审批、dm审批、hr备案和获取状态机当前状态 6 个操作。
@RestController public class StateMachineController { @Autowired private StateMachinePersister<States, Events, String> persister; @Autowired private StateMachine<States, Events> stateMachine; @RequestMapping("/new") @ResponseBody public BaseResponse newLeave(@RequestBody LeaveRequest leave){ BaseResponse result = new BaseResponse(); stateMachine.start(); result.message = "新建请假申请成功"; result.success = true; result.data = leave; return result; } @RequestMapping("/apply") @ResponseBody public BaseResponse apply(@RequestBody JSONObject params){ String leaveId = params.getAsString("leaveId"); return sendEvent(Events.SUBMIT,leaveId); } @RequestMapping("/tlApprove") @ResponseBody public BaseResponse tlApprove(@RequestBody JSONObject params) { String id = params.getAsString("leaveId"); boolean agree = params.getAsNumber("agree").intValue() != 0; return sendEvent(agree ? Events.TL_AGREE : Events.TL_REJECT, id); } @RequestMapping("/dmApprove") @ResponseBody public BaseResponse dmApprove(@RequestBody JSONObject params) { String id = params.getAsString("leaveId"); boolean agree = params.getAsNumber("agree").intValue() != 0; return sendEvent(agree ? Events.DM_AGREE : Events.DM_REJECT, id); } @RequestMapping("/hrRecord") @ResponseBody public BaseResponse hrRecord(@RequestBody JSONObject params) { String id = params.getAsString("leaveId"); return sendEvent(Events.HR_RECORD,id); } @RequestMapping("/getState") @ResponseBody public BaseResponse getState(@RequestBody JSONObject params){ String leaveId = params.getAsString("leaveId"); BaseResponse result = new BaseResponse(); try{ persister.restore(stateMachine,leaveId); result.success = true; States state = stateMachine.getState().getId(); result.data = state; }catch (Exception e){ e.printStackTrace(); }finally { stateMachine.stop(); return result; } } private BaseResponse sendEvent(Events event,String leaveId){ BaseResponse result = new BaseResponse(); if(leaveId == null || leaveId.length()==0){ result.success = false; result.message = "leaveId 不能为空"; return result; } try { // 根据业务 id 获取状态 persister.restore(stateMachine,leaveId); result.success = stateMachine.sendEvent(event); // 持久化状态机 if (result.success) { persister.persist(stateMachine, leaveId); } JSONObject data = new JSONObject(); result.message = result.success ? "执行成功":"执行失败"; result.message = result.message + ",当前状态为:"+stateMachine.getState().getId(); data.put("leaveId",leaveId); data.put("event",event.toString()); data.put("state",stateMachine.getState().getId()); result.data = data; } catch (Exception e) { e.printStackTrace(); result.message = e.getMessage(); }finally { stateMachine.stop(); return result; } } }
注意 leaveId 是业务 id,代表了一个业务对象(比如请假条 LeaveRequest)。leaveId 是一个 UUID,保证不会重复。
运行程序,会自动在 8080 端口上运行 tomcat。然后就可以用 postman 调用各个接口进行测试了: