序言:近期业务涉及到各种奇葩的中国式审批,用activiti工作流来实现,网上教程坑太多,故写此文章总结备忘...
一、前期准备
1.maven依赖
5.21.0
org.activiti activiti-engine ${activiti.version} org.activiti activiti-spring ${activiti.version} org.activiti activiti-explorer ${activiti.version} vaadin com.vaadin dcharts-widget org.vaadin.addons activiti-simple-workflow org.activiti org.activiti activiti-modeler ${activiti.version} org.activiti activiti-diagram-rest ${activiti.version}
二、定义流程
1.使用 activiti-modeler 绘制流程图(此插件集成方式请自行百度)
流程图创建参考此文章
2.部署流程
有多种方式(classpath、InputStream、字符串)部署,这里使用InputStream方式。
/**流程引擎(核心对象),默认加载类路径下命名为activiti.cfg.xml*/ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); public void deployementProcessDefinitionByInputStream() throws FileNotFoundException { //获取资源相对路径 String bpmnPath = "diagrams/helloworld.bpmn"; String pngPath = "diagrams/helloworld.png"; //读取资源作为一个输入流 FileInputStream bpmnfileInputStream = new FileInputStream(bpmnPath); FileInputStream pngfileInputStream = new FileInputStream(pngPath); Deployment deployment = processEngine.getRepositoryService()//获取流程定义和部署对象相关的Service .createDeployment()//创建部署对象 .addInputStream("helloworld.bpmn",bpmnfileInputStream) .addInputStream("helloworld.png", pngfileInputStream) .deploy();//完成部署 System.out.println("部署ID:"+deployment.getId()); System.out.println("部署时间:"+deployment.getDeploymentTime()); }
3.使用流程
3.1 启动流程
/** * 启动流程 * * @param procDefKey 流程定义KEY * @param businessTable 业务表表名 * @param businessId 业务表编号 * @param title 流程标题,显示在待办任务标题 * @param vars 流程变量 * @return 流程实例ID */ public String startProcess(String procDefKey, String businessTable, String businessId, String title, Mapvars) { String userId = UserUtils.getUser().getId(); // 用来设置启动流程的人员ID,引擎会自动把用户ID保存到activiti:initiator中 identityService.setAuthenticatedUserId(userId); // 设置流程变量 if (vars == null) { vars = Maps.newHashMap(); } // 设置流程标题 if (StringUtils.isNotBlank(title)) { vars.put("title", title); } // 启动流程 ProcessInstance procIns = runtimeService.startProcessInstanceByKey(procDefKey, businessTable + ":" + businessId, vars); // 更新业务表流程实例ID Act act = new Act(); act.setBusinessTable(businessTable);// 业务表名 act.setBusinessId(businessId); // 业务表ID act.setProcInsId(procIns.getId()); actDao.updateProcInsIdByBusinessId(act); return act.getProcInsId(); }
//启动流程后,将流程实例id (procInsId) 保存到业务表中
//dao层UPDATE ${businessTable} SET proc_ins_id = #{procInsId} WHERE id = #{businessId}
3.2 流程变量
Mapvars = Maps.newHashMap(); vars.put("pass", 1);//是否通过 ...各种参数... 此处省略
3.3 流程是否结束
/** * 判断流程是否结束 * * @param processInstanceId * @return */ public boolean isEnd(String processInstanceId) { ProcessInstance pi = runtimeService.createProcessInstanceQuery()// .processInstanceId(processInstanceId)//使用流程实例ID查询 .singleResult(); if (pi == null) { return true; } else { return false; } }
3.4 撤回流程(删除流程实例)
/** * 删除部署的流程实例 * @param procInsId 流程实例ID * @param deleteReason 删除原因,可为空 */ public void deleteProcIns(String procInsId, String deleteReason) { runtimeService.deleteProcessInstance(procInsId, deleteReason); }
3.5 提交任务
/** * 提交任务, 并保存意见 * * @param taskId 任务ID * @param procInsId 流程实例ID,如果为空,则不保存任务提交意见 * @param comment 任务提交意见的内容 * @param title 流程标题,显示在待办任务标题 * @param vars 任务变量 */ public void complete(String taskId, String procInsId, String comment, String title, Mapvars) { // 添加意见 if (StringUtils.isNotBlank(procInsId) && StringUtils.isNotBlank(comment)) { taskService.addComment(taskId, procInsId, comment); } // 设置流程变量 if (vars == null) { vars = Maps.newHashMap(); } // 设置流程标题 if (StringUtils.isNotBlank(title)) { vars.put("title", title); } // 提交任务 taskService.complete(taskId, vars); }
注:1、通过 taskId 提交任务:complete只执行一次,再次执行时,该任务已不存在,则无法再次提交,提交失败
2、任务已签收时,处理人与签收人需操持一致,否则任务提交失败
3.6 签收任务
/** * 签收任务 * * @param taskId 任务ID * @param userId 签收用户ID(用户登录名) */ public void claim(String taskId, String userId) { taskService.claim(taskId, userId); }
4.设计流程
4.1 用户任务
用户任务,即需要人来参与的任务,具体设计如下:
说明:activiti:assignee:设置任务处理人,指向流程变量“user"
流程变量中放入用户id即可 Mapvars = Maps.newHashMap(); vars.put("user", "userIdxxxxx");
4.2 会签任务
4.2.1 并行任务办理
同一任务多人处理,且所有人处理完成后,流程再跳转到下一环节,具体设计如下:
在userTask节点内,添加multiInstanceLoopCharacteristics节点
说明:1、activiti:collection:设置用户集合,指向流程变量"users"(java中传入list数组)
2、activiti:elementVariable:设置遍历用户集合的单个对象变量名称,注:勿使用流程变量中的常用变更名
3、activiti:assignee:根据 activiti:elementVariable 中的变量名 设置任务的 办理人
4、isSequential = "false",表示任务实例并行执行,“true“,表示顺序执行(一个接一个)
4.2.2 一票否决
用户集合中,有一人不同意,则退出会签,流程实例进入下一环节
说明:1、在multiInstanceLoopCharacteristics节点中,增加completionCondition节点,设置会签退出的条件
2、当流程变量pass 值为 0 时,退出会签环节,流程实例跳转到下一环节
4.2.3 优先处理
用户集合中,达到 指定比例 处理后,则退出会签,流程实例进入下一环节
${nrOfCompletedInstances/nrOfInstances >= 0.6 }
4.2.4 特殊业务
需要用到监听器(暂略)...
5. 获取待办/已办事项
5.1 获取待办
@Autowired private TaskService taskService;
/** * 获取待办列表 ** procDefKey 流程定义标识 * * @return */ public List
todoList(Act act) { String userId = UserUtils.getUser().getId(); List result = new ArrayList (); // =============== 已经签收的任务 =============== TaskQuery todoTaskQuery = taskService.createTaskQuery().taskAssignee(userId).active() .includeProcessVariables().orderByTaskCreateTime().desc(); // 设置查询条件 if (StringUtils.isNotBlank(act.getProcDefKey())) { todoTaskQuery.processDefinitionKey(act.getProcDefKey()); } //开始时间 if (act.getBeginDate() != null) { todoTaskQuery.taskCreatedAfter(act.getBeginDate()); } //结束时间 if (act.getEndDate() != null) { todoTaskQuery.taskCreatedBefore(act.getEndDate()); } // 查询列表 List todoList = todoTaskQuery.list(); for (Task task : todoList) { Act e = new Act(); e.setTask(task); e.setVars(task.getProcessVariables()); e.setProcDef(ProcessDefCache.get(task.getProcessDefinitionId())); e.setStatus("todo"); result.add(e); } // =============== 等待签收的任务 =============== TaskQuery toClaimQuery = taskService.createTaskQuery().taskCandidateUser(userId) .includeProcessVariables().active().orderByTaskCreateTime().desc(); // 设置查询条件 if (StringUtils.isNotBlank(act.getProcDefKey())) { toClaimQuery.processDefinitionKey(act.getProcDefKey()); } //开始时间 if (act.getBeginDate() != null) { toClaimQuery.taskCreatedAfter(act.getBeginDate()); } //结束时间 if (act.getEndDate() != null) { toClaimQuery.taskCreatedBefore(act.getEndDate()); } // 查询列表 List toClaimList = toClaimQuery.list(); for (Task task : toClaimList) { Act e = new Act(); e.setTask(task); e.setVars(task.getProcessVariables()); e.setProcDef(ProcessDefCache.get(task.getProcessDefinitionId())); e.setStatus("claim"); result.add(e); } return result; }
5.2已办任务
/** * 获取已办任务 * * @param page procDefKey 流程定义标识 * @return */ public ListhistoricList(Page page, Act act) { String userId = UserUtils.getUser().getId(); HistoricTaskInstanceQuery histTaskQuery = historyService.createHistoricTaskInstanceQuery().taskAssignee(userId).finished() .includeProcessVariables().orderByHistoricTaskInstanceEndTime().desc(); // 设置查询条件 if (StringUtils.isNotBlank(act.getProcDefKey())) { histTaskQuery.processDefinitionKey(act.getProcDefKey()); } if (act.getBeginDate() != null) { histTaskQuery.taskCompletedAfter(act.getBeginDate()); } if (act.getEndDate() != null) { histTaskQuery.taskCompletedBefore(act.getEndDate()); } // 查询列表 List histList = histTaskQuery.listPage(page.getFirstResult(), page.getMaxResults()); //处理分页问题 List actList = Lists.newArrayList(); for (HistoricTaskInstance histTask : histList) { Act e = new Act(); e.setHistTask(histTask); e.setVars(histTask.getProcessVariables()); //e.setProcDef(ProcessDefCache.get(histTask.getProcessDefinitionId())); e.setStatus("finish"); actList.add(e); } return actList; }
6. 流程图查看
生成png格式图片
public void graphHistoryProcessInstance(String processInstanceId, HttpServletResponse response) throws Exception { Commandcmd = new HistoryProcessInstanceDiagramCmd(processInstanceId); InputStream is = managementService.executeCommand(cmd); response.setContentType("image/png"); int len = 0; byte[] b = new byte[1024]; while ((len = is.read(b, 0, 1024)) != -1) { response.getOutputStream().write(b, 0, len); } is.close(); }
7. 流转历史查看
/** * 获取流转历史列表 * * @param procInsId 流程实例 * @param startAct 开始活动节点名称 * @param endAct 结束活动节点名称 */ public ListhistoicFlowList(String procInsId, String startAct, String endAct) { List actList = Lists.newArrayList(); List list = historyService.createHistoricActivityInstanceQuery().processInstanceId(procInsId) .orderByHistoricActivityInstanceStartTime().asc().orderByHistoricActivityInstanceEndTime().asc().list(); boolean start = false; Map actMap = Maps.newHashMap(); for (int i = 0; i < list.size(); i++) { HistoricActivityInstance histIns = list.get(i); // 过滤开始节点前的节点 if (StringUtils.isNotBlank(startAct) && startAct.equals(histIns.getActivityId())) { start = true; } if (StringUtils.isNotBlank(startAct) && !start) { continue; } // 只显示开始节点和结束节点,并且执行环节不为空的任务 if (StringUtils.isNotBlank(histIns.getActivityName()) || "startEvent".equals(histIns.getActivityType()) || "endEvent".equals(histIns.getActivityType())) { // 给节点增加一个序号 Integer actNum = actMap.get(histIns.getActivityId()); if (actNum == null) { actMap.put(histIns.getActivityId(), actMap.size()); } Act e = new Act(); e.setHistIns(histIns); // 获取流程发起人名称 if ("startEvent".equals(histIns.getActivityType())) { List il = historyService.createHistoricProcessInstanceQuery().processInstanceId(procInsId).orderByProcessInstanceStartTime().asc().list(); if (il.size() > 0) { if (StringUtils.isNotBlank(il.get(0).getStartUserId())) { User user = UserUtils.get(il.get(0).getStartUserId()); if (user != null) { e.setAssignee(histIns.getAssignee()); e.setAssigneeName(user.getName()); } } } } // 获取任务执行人名称 if (StringUtils.isNotEmpty(histIns.getAssignee())) { User user = UserUtils.get(histIns.getAssignee()); if (user != null) { e.setAssignee(histIns.getAssignee()); e.setAssigneeName(user.getName()); } } // 获取意见评论内容 if (StringUtils.isNotBlank(histIns.getTaskId())) { List commentList = taskService.getTaskComments(histIns.getTaskId()); if (commentList.size() > 0) { e.setComment(commentList.get(0).getFullMessage()); } } actList.add(e); } // 过滤结束节点后的节点 if (StringUtils.isNotBlank(endAct) && endAct.equals(histIns.getActivityId())) { boolean bl = false; Integer actNum = actMap.get(histIns.getActivityId()); // 该活动节点,后续节点是否在结束节点之前,在后续节点中是否存在 for (int j = i + 1; j < list.size(); j++) { HistoricActivityInstance hi = list.get(j); Integer actNumA = actMap.get(hi.getActivityId()); if ((actNumA != null && actNumA < actNum) || StringUtils.equals(hi.getActivityId(), histIns.getActivityId())) { bl = true; } } if (!bl) { break; } } } return actList; }
import java.util.Date; import java.util.List; import java.util.Map; import org.activiti.engine.history.HistoricActivityInstance; import org.activiti.engine.history.HistoricTaskInstance; import org.activiti.engine.repository.ProcessDefinition; import org.activiti.engine.runtime.ProcessInstance; import org.activiti.engine.task.Task; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnore; import com.*.common.persistence.BaseEntity; import com.*.common.utils.StringUtils; import com.*.common.utils.TimeUtils; import com.*.modules.act.utils.Variable; /** * 工作流Entity * */ public class Act extends BaseEntity{ private static final long serialVersionUID = 1L; private String taskId; // 任务编号 private String taskName; // 任务名称 private String taskDefKey; // 任务定义Key(任务环节标识) private String procInsId; // 流程实例ID private String procDefId; // 流程定义ID private String procDefKey; // 流程定义Key(流程定义标识) private String businessTable; // 业务绑定Table private String businessId; // 业务绑定ID private String title; // 任务标题 private String status; // 任务状态(todo/claim/finish) // private String procExecUrl; // 流程执行(办理)RUL private String comment; // 任务意见 private String flag; // 意见状态 private Task task; // 任务对象 private ProcessDefinition procDef; // 流程定义对象 private ProcessInstance procIns; // 流程实例对象 private HistoricTaskInstance histTask; // 历史任务 private HistoricActivityInstance histIns; //历史活动任务 private String assignee; // 任务执行人编号 private String assigneeName; // 任务执行人名称 private Variable vars; // 流程变量 // private Variable taskVars; // 流程任务变量 private Date beginDate; // 开始查询日期 private Date endDate; // 结束查询日期 private List list; // 任务列表 private int isView;//是否是查看已办任务 public Act() { super(); } public String getTaskId() { if (taskId == null && task != null) { taskId = task.getId(); } return taskId; } public void setTaskId(String taskId) { this.taskId = taskId; } public String getTaskName() { if (taskName == null && task != null) { taskName = task.getName(); } return taskName; } public void setTaskName(String taskName) { this.taskName = taskName; } public String getTaskDefKey() { if (taskDefKey == null && task != null) { taskDefKey = task.getTaskDefinitionKey(); } return taskDefKey; } public void setTaskDefKey(String taskDefKey) { this.taskDefKey = taskDefKey; } @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") public Date getTaskCreateDate() { if (task != null) { return task.getCreateTime(); } return null; } @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") public Date getTaskEndDate() { if (histTask != null) { return histTask.getEndTime(); } return null; } @JsonIgnore public Task getTask() { return task; } public void setTask(Task task) { this.task = task; } @JsonIgnore public ProcessDefinition getProcDef() { return procDef; } public void setProcDef(ProcessDefinition procDef) { this.procDef = procDef; } public String getProcDefName() { return procDef.getName(); } @JsonIgnore public ProcessInstance getProcIns() { return procIns; } public void setProcIns(ProcessInstance procIns) { this.procIns = procIns; if (procIns != null && procIns.getBusinessKey() != null) { String[] ss = procIns.getBusinessKey().split(":"); setBusinessTable(ss[0]); setBusinessId(ss[1]); } } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } @JsonIgnore public HistoricTaskInstance getHistTask() { return histTask; } public void setHistTask(HistoricTaskInstance histTask) { this.histTask = histTask; } @JsonIgnore public HistoricActivityInstance getHistIns() { return histIns; } public void setHistIns(HistoricActivityInstance histIns) { this.histIns = histIns; } @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") public Date getBeginDate() { return beginDate; } public void setBeginDate(Date beginDate) { this.beginDate = beginDate; } @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") public Date getEndDate() { return endDate; } public void setEndDate(Date endDate) { this.endDate = endDate; } public String getComment() { return comment; } public void setComment(String comment) { this.comment = comment; } public String getFlag() { return flag; } public void setFlag(String flag) { this.flag = flag; } public String getProcDefId() { if (procDefId == null && task != null) { procDefId = task.getProcessDefinitionId(); } return procDefId; } public void setProcDefId(String procDefId) { this.procDefId = procDefId; } public String getProcInsId() { if (procInsId == null && task != null) { procInsId = task.getProcessInstanceId(); } return procInsId; } public void setProcInsId(String procInsId) { this.procInsId = procInsId; } public String getBusinessId() { return businessId; } public void setBusinessId(String businessId) { this.businessId = businessId; } public String getBusinessTable() { return businessTable; } public void setBusinessTable(String businessTable) { this.businessTable = businessTable; } public String getAssigneeName() { return assigneeName; } public void setAssigneeName(String assigneeName) { this.assigneeName = assigneeName; } public String getAssignee() { if (assignee == null && task != null) { assignee = task.getAssignee(); } return assignee; } public void setAssignee(String assignee) { this.assignee = assignee; } public List getList() { return list; } public void setList(List list) { this.list = list; } public Variable getVars() { return vars; } public void setVars(Variable vars) { this.vars = vars; } /** * 通过Map设置流程变量值 * * @param map */ public void setVars(Map map) { this.vars = new Variable(map); } /** * 获取流程定义KEY * * @return */ public String getProcDefKey() { if (StringUtils.isBlank(procDefKey) && StringUtils.isNotBlank(procDefId)) { procDefKey = StringUtils.split(procDefId, ":")[0]; } return procDefKey; } public void setProcDefKey(String procDefKey) { this.procDefKey = procDefKey; } /** * 获取过去的任务历时 * * @return */ public String getDurationTime() { if (histIns != null && histIns.getDurationInMillis() != null) { return TimeUtils.toTimeString(histIns.getDurationInMillis()); } return ""; } /** * 是否是一个待办任务 * * @return */ public boolean isTodoTask() { return "todo".equals(status) || "claim".equals(status); } /** * 是否是已完成任务 * * @return */ public boolean isFinishTask() { return "finish".equals(status) || StringUtils.isBlank(taskId); } @Override public void preInsert() { } @Override public void preUpdate() { } public int getIsView() { return isView; } public void setIsView(int isView) { this.isView = isView; } }