All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cn.ps1.soar.engine.FlowEngine Maven / Gradle / Ivy

package cn.ps1.soar.engine;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import cn.ps1.aolai.utils.Const;
import cn.ps1.aolai.utils.FailedException;
import cn.ps1.aolai.utils.SpringContext;
import cn.ps1.soar.entity.Emp;
import cn.ps1.soar.entity.Node;
import cn.ps1.soar.entity.Task;
import cn.ps1.soar.entity.Work;
import cn.ps1.soar.service.EventEmitter;
import cn.ps1.soar.utils.FlowUtil;

/**
 * 工作流程(任务)处理引擎
 * 
 * @author Aolai
 * @version 1.0 $Date: 2023.06.21
 *
 */
public class FlowEngine {

	// 当前的工作流程节点(FLOWNODE)一览表
	// 注意:来自FLOWNODE,与APPRONODE关联
	private List> flowNodes = new ArrayList<>();
	// 当前的审批流中·审批节点一览表
	private List> workNodes = new ArrayList<>();
	// 审批节点上的候选人列表
	private List> candidates = new ArrayList<>();
	// 审批表单内容
	private Map formData = new HashMap<>();

	// 节点的事件驱动服务
	private EventEmitter event = (EventEmitter) SpringContext
			.getBean("eventEmitter");

	// 需要单独处理的节点STATE_COMPLETE
	private boolean isSucceed = false; // 顺利完成全部流程
	private String jointlyNode = null; // 会签父节点

	// 需要跳过忽略的并行节点
	private Set skipNodes = new HashSet<>();

	/** 用户语言 */
	private Object userLang = "ZH";

	/**
	 * 引擎启动后,选出工作流中第一个审批节点上的候选人列表
	 */
	public void findFirstNode() {
		// 新发起的流程,会先确认节点列表是否存在
		if (flowNodes.size() == 0)
			throw new FailedException(FlowUtil.NODE_IS_EMPTY);

		/**
		 * 选出第一个节点的处理过程,默认第一个节点是“串行”节点
		 */
		pickWorkNode(flowNodes.get(0), FlowUtil.STYLE_SERIAL);

	}

	/**
	 * 根据当前节点5种类型分别处理,直到递归找到第一个普通节点
	 * 

* 0.普通 1.串行(子流程) 2.条件 3.会签 5.并行 *

* 当前节点全部信息: * nodeComp,nodeNo,nodeFlowNo,nodeName,nodeParent,nodeStyle,nodeTier * ,nodeType,nodeStep,nodeRules,nodeEvent,taskComp,taskId,taskFlowNo * ,workComp,workTaskId,workNodeNo,workState,workComment * ,workESign,workUpdate,workIdx.. */ private void pickWorkNode(Map theNode, String pStyle) { // 审批节点会根据当前节点类型去分别处理 String style = theNode.get(Node.STYLE); // 当前节点是普通节点=“0”时,选出所有候选人列表 if (FlowUtil.STYLE_NORMAL.equals(style)) { // 这里先设置审批状态为“就绪”=“0”,并缓存当前“审批中”的节点 addWorkNode(theNode, FlowUtil.STATE_READY); /** * 选出当前节点审批候选人列表(这里也可能是“死胡同”) */ pickCandidates(theNode, pStyle); return; } // 当前节点不是普通节点时,暂时置为“处理中”=“1” addWorkNode(theNode, FlowUtil.STATE_WAITING); // 再获取下一级所有子节点 List> childs = getChildNode(theNode); // 如果分支节点下无子节点,则流程有问题 if (childs.size() == 0) isLostWay(); // 遇到“死胡同”了 // 如果当前节点是串行节点“1”,则跳转到下级第一个子节点 if (FlowUtil.STYLE_SERIAL.equals(style)) { // 当前节点是串行节点“1”,则跳转到下级第一个子节点 pickWorkNode(childs.get(0), style); } else if (FlowUtil.STYLE_CONDITION.equals(style)) { // 如果当前节点是条件节点“2”,根据条件分支处理 int actionIdx = event.getActionIdx(theNode, formData); // 如果“条件”节点无法跳转,则流程有问题 if (actionIdx == -1 || actionIdx >= childs.size()) isLostWay(); // 遇到“死胡同”了 // 分支“子节点”数量必须大于返回值才行,根据返回值跳转不同分支 pickWorkNode(childs.get(actionIdx), style); } else if (FlowUtil.STYLE_JOINTLY.equals(style) || FlowUtil.STYLE_PARALLEL.equals(style)) { // 如果当前节点是会签节点“3” or 并行节点“5”,则遍历所有子节点 for (Map child : childs) { pickWorkNode(child, style); } } } /** * 当前“普通”节点的审批候选人,参数pStyle为父节点的处理模式 *

* 当前节点全部信息: * nodeComp,nodeNo,nodeFlowNo,nodeName,nodeParent,nodeStyle,nodeTier * ,nodeType,nodeStep,nodeRules,nodeEvent,taskComp,taskId,taskFlowNo * ,workComp,workTaskId,workNodeNo,workState,workComment * ,workESign,workUpdate,workIdx.. */ private void pickCandidates(Map node, String pStyle) { List> candidateList; /** * 当前审批节点的所有审批“候选人” */ candidateList = event.pickCandidates(node, formData, flowNodes); // 如果一个节点仅一个候选人时,直接推给此人待审核 if (candidateList.size() == 1) { /** * 新设候选人:workEmpId,workEmpName,workAgent,workOpUid */ setNodeApprover(node, candidateList.get(0)); } else if (candidateList.size() > 1) { // 有多个候选人时,返回前台节点信息以及候选人列表 // TODO:亦可考虑扩展是否支持推送多人,也可首选第一个人 Map candidate = new HashMap<>(); candidate.put(FlowUtil.MORM_NODE, node); candidate.put("parentType", pStyle); candidate.put("candidates", candidateList); candidates.add(candidate); } else { // 遇到“死胡同”了 isLostWay(); } } /** * 设置节点上的审批(候选)人,还需在addWorkNodes()克隆一次 *

* 新设候选人数据:workEmpId,workEmpName,workAgent,workOpUid */ public void setNodeApprover(Map node, Map emp) { node.put(Work.EMPID, emp.get(Emp.ID)); node.put(Work.EMPNAME, emp.get(Emp.NAME)); // 这里必须填empUid,用于审批人登录后校验权限 node.put(Work.OPUID, emp.get(Emp.UID)); /** * 如果设置了“代理人”,则推送“代理人”
* 另外根据三种消息通知方式:邮件、手机、微信,推送提醒消息 */ String[] keys = { "Agent", "Mobi", "Mail", "Wechat" }; for (String key : keys) node.put(Work.KEY + key, emp.get(Emp.KEY + key)); } /** * 寻找工作流程的下一个节点,这里一定是flowNodes.size()>0 */ public void findNextNode(Map params) { // 已是“审批中”的流程,不再确认节点是否存在 // 新发起的流程,会先确认节点列表是否存在 Object workNodeNo = params.get(Work.NODENO); for (Map node : flowNodes) { // 定位当前的“审批节点”,状态“就绪”且分配给当前用户审批的节点 // 校验workEmpId以防止越权操作 // 前面getReadyTask()中已经验证了workNodeNo(workEmpId\workState) if (node.get(Node.NO) != null && node.get(Node.NO).equals(workNodeNo)) { // && params.get(Work.EMPID).equals(node.get(Work.EMPID)) // && FlowUtil.STATE_READY.equals(node.get(Work.STATE))) { // 提交选中“审批人”时,已在setNodeApprover()设置了: // workEmpId,workEmpName,workAgent,workOpUid // 非“委托代理人”审批时,暂时清空“代理人” event.setAgentNull(params, node.get(Work.OPUID)); // 不需要再设置workOpUid,继承setNodeApprover()中的设置即可 // 设置当前审批节点workOpUid,在addWorkNodes()中用 // params.put(Work.OPUID, node.get(Work.OPUID)); /** * 判断选中合适的“待审批”节点 */ pickNextNode(node); } } // 至少应该有一个当前节点,否则提示“无效参数”("invalidParams") if (workNodes.size() == 0) throw new FailedException(); } /** * 遍历节点列表,从当前节点开始找出下一个流程节点 */ private void pickNextNode(Map theNode) { // 当前节点完成审批,并缓存当前“审批中”的节点 addWorkNode(theNode, FlowUtil.STATE_COMPLETE); // 根据“父节点”类型,跳转到下一步待处理的节点 Map parent = getParentNode(theNode); if (parent == null) return; // 无父节点时,处理完毕 String pStyle = parent.get(Node.STYLE); if (FlowUtil.STYLE_SERIAL.equals(pStyle)) { // “父节点”是“串行节点”时,需要遍历兄弟节点 List> nodes = getChildNode(parent); // 找到当前节点的下个“兄弟”节点 for (int i = 0; i < nodes.size() - 1; i++) { // 只有一个workIdx=0的节点 String brotherNo = nodes.get(i).get(Node.NO); if (brotherNo != null && brotherNo.equals(theNode.get(Node.NO))) { /** * 选择下一个串行兄弟节点,且只有一个workIdx=0的节点 */ pickWorkNode(nodes.get(i + 1), pStyle); return; } } // 没找到下一个“兄弟”审批节点,则需要回归“父节点”找“兄弟” // 如果“父节点”是超级节点,则整个工作流程结束 isSucceed = Const.STR_0.equals(parent.get(Node.PARENT)); // 完成或继续下一个 if (isSucceed) { addWorkNode(parent, FlowUtil.STATE_COMPLETE); } else { // 再找“父节点”的“兄弟”节点处理 pickNextNode(parent); } } else if (FlowUtil.STYLE_CONDITION.equals(pStyle)) { // 父节点是“条件”节点,由系统自动完成 pickNextNode(parent); } else if (FlowUtil.STYLE_JOINTLY.equals(pStyle)) { // 如果“父节点”是“会签节点”时,系统自动判断子节点是否都完成审批 if (jointlyApproved(parent, theNode)) { // 所有子节点都完成,则父节点也自动完成后,再找“父节点”的“兄弟”节点 pickNextNode(parent); } else { // 未完成 // 但有可能出现多人“并发”,这里实现幂等性,需要单独处理 jointlyNode = parent.get(Node.NO); } } else if (FlowUtil.STYLE_PARALLEL.equals(pStyle)) { // 并行节点:考虑可能并发,需要实现幂等性处理 if (FlowUtil.STATE_WAITING.equals(parent.get(Work.STATE))) { // 当前父节点下,需忽略(跳过)未处理的节点 addSkipNode(parent.get(Node.NO)); // “并行节点”时,系统自动完成 pickNextNode(parent); } // 注意:其他并发节点完成后,只完成当前节点即可,无需再处理父节点 } } /** * 判断会签节点“父节点”下的每个节点是否都审批完毕 */ private boolean jointlyApproved(Map parent, Map theNode) { // 当前节点 String nodeNo = theNode.get(Node.NO); List> nodes = getChildNode(parent); // 遍历同父“兄弟”节点 for (Map node : nodes) { // “兄弟”已批完,或遇到当前节点 if (FlowUtil.STATE_COMPLETE.equals(node.get(Work.STATE)) || node.get(Node.NO).equals(nodeNo)) continue; // 只要一个“兄弟”节点未完成,则未完成 return false; } // 全部审批完(含当前节点) return true; } /** * 根据当前节点,找出下级子节点 *

* 当前节点全部信息: * nodeComp,nodeNo,nodeFlowNo,nodeName,nodeParent,nodeStyle,nodeTier * ,nodeType,nodeStep,nodeRules,nodeEvent,taskComp,taskId,taskFlowNo * ,workComp,workTaskId,workNodeNo,workState,workComment * ,workESign,workUpdate,workIdx.. */ private List> getChildNode(Map theNode) { // 根据当前节点,从工作流程中选出子节点 List> list = new ArrayList<>(); // 这里需判断空值“” String nodeNo = theNode.get(Node.NO); for (Map node : flowNodes) { if (node.get(Node.PARENT).equals(nodeNo)) list.add(node); } return list; } /** * 根据当前节点,找出上级父节点 */ private Map getParentNode(Map theNode) { // 从工作节点列表定位父节点,这里不需要判断空值 String nodeNo = theNode.get(Node.PARENT); for (Map node : flowNodes) { if (node.get(Node.NO).equals(nodeNo)) return node; } return null; } /** * 设置当前选择节点的审批状态,并缓存当前审批节点 */ public void addWorkNode(Map theNode, String workState) { // 更新当前节点的审批状态 theNode.put(Work.STATE, workState); // 暂时缓存当前审批节点 workNodes.add(theNode); } /** * 在流程中暂存“待审批”或“就绪”的中途节点 * params={taskFlowNo,taskDept,taskTitle,taskFormData,..candidates} */ public void setWaitingNode(Map params) { /** * 当前节点全部信息: 数据源:getFlowNodes */ Map theNode = workNodes.get(0); for (Map node : workNodes) { // 遍历每个“待审批”节点状态,要么“就绪”=“0”,要么“处理中”=“1” String state = node.get(Work.STATE); // 找出第一个就绪节点 // 改为找出最后一个,兼容发起人第一次发起时去除自身节点 if (FlowUtil.STATE_READY.equals(state) || FlowUtil.STATE_WAITING.equals(state)) { theNode = node; // break; } } // 如果状态已经完成 if (isSucceed) params.put(Task.STATE, FlowUtil.STATE_COMPLETE); params.put(Task.NODENO, theNode.get(Node.NO)); } /** 构造函数 */ public FlowEngine(Map formData, List> flowNodes) { this.formData = formData; this.flowNodes = flowNodes; } /** 遇到死胡同了 */ public void isLostWay() { // 如果走到“死胡同”迷路了,说明该“流程”有问题,需要维护完善流程 throw new FailedException(FlowUtil.IS_LOST_WAY); // 迷路了 } /** 已完成全部流程 */ public boolean isSucceed() { return isSucceed; } /** * 需忽略(跳过)未处理的节点 */ public boolean addSkipNode(String nodeNo) { return skipNodes.add(nodeNo); } /** 需要跳过(忽略)的节点集合 */ public Set getSkipNodes() { return skipNodes; } /** 当前工作流的全部节点 */ public List> getFlowNodes() { return flowNodes; } /** 当前审批节点一览表 */ public List> getWorkNodes() { return workNodes; } /** 审批候选人列表 */ public List> getCandidates() { return candidates; } /** 需要重复检查的会签“父”节点 */ public String getJointlyNode() { return jointlyNode; } /** 当前用户语言 */ public Object getUserLang() { return userLang; } /** 设置用户语言 */ public void setUserLang(Object userLang) { this.userLang = userLang; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy