工作流的定义(解决什么问题?)
工作流(Workflow),就是“业务过程的部分或整体在计算机应用环境下的自动化”,它主要解决的是“使在多个参与者之间按照某种预定义的规则传递文档、信息或任务的过程自动进行,从而实现某个
预期的业务目标,或者促使此目标的实现”。
我的理解:工作流将一套大的业务逻辑分解成业务逻辑段, 并统一控制这些业务逻辑段的执行条件,执行顺序以及相互通信。 实现业务逻辑的分解和解耦。
什么是Activiti?
官方介绍 Activiti是一个轻量级的工作流程和业务流程管理(BPM)平台,面向业务人员,开发人员和系统管理员。它的核心是使用Java开发的极速且稳定的的BPMN 2流程引擎。它是开源的,并在
Apache许可下分发。Activiti可在任何Java应用程序,服务器,群集或云中运行。它与Spring完美集成,非常轻巧。
Activiti与JPBM比较
都是BPMN2过程建模和执行环境。 都是BPM系统(符合BPM规范)。 都是开源项目-遵循ASL协议( Apache的 软件许可)。 都源自JBoss(Activiti5是jBPM4的衍生,jBPM5则基于Drools Flow)。
都很成熟,从无到有,双方开始约始于2年半前。 都有对人工任务的生命周期管理。 Activiti5和jBPM5唯一的区别是jBPM5基于WebService - HumanTask标准来描述人工任务和管理生命周期。 如有
兴趣了解这方面的标准及其优点,可参阅WS - HT规范介绍 。 都使用了不同风格的 Oryx 流程编辑器对BPMN2建模。 jBPM5采用的是 Intalio 维护的开源项目分支。 Activiti5则使用了Signavio维护的
分支。
Activiti与JPBM技术选型比较
总结:
虽然是比较,但不一定要有胜负,只有适合自己的才是最好的,要针对具体的项目区别对待。对我们自己的项目,其实我更关注的是我们项目未来的流程引擎的执行效率以及性能,每小时几十万甚至上百万的
流程需要执行,需要多少个服务,集群、负载的策略是什么,会不会有冲突?目前这方面的资料还是比较少的,很多问题只有实际遇用到的时候才会去想办法解决。不过就我个人的感觉而言,Activiti上手比较
快,界面也比较简洁、直观,值得一试,同时activiti的社区更新与支持也更及时,暂时推荐使用activiti。
Activiti开发环境搭建
1.Eclipse插件安装
>在线安装
>离线安装
2.Idea插件安装
IDEA 在插件仓库搜索actiBPM安装即可(这个插件,我非常想吐槽,画个直线是真的难,即便在编辑器里面是直线,后面用Activiti的API生成的图也是弯的。)
Activiti为我们做了什么?
1.第一次启动流程引擎的时候由Activiti自动创建28张数据表(不同版本表单数量,可能略有差异,本教程使用的是activiti6.0);
2.提供核心7大接口调用;
3.提供工作流API,简化流程操作;
4.提供全面的可视化流程管理服务,使流程更加直观。
activiti表单介绍
1、act_ge_ 通用数据表,ge是general的缩写
2、act_hi_ 历史数据表,hi是history的缩写,对应HistoryService接口
3、act_id_ 身份数据表,id是identity的缩写,对应IdentityService接口
4、act_re_ 流程存储表,re是repository的缩写,对应RepositoryService接口,存储流程部署和流程定义等静态数据
5、act_ru_ 运行时数据表,ru是runtime的缩写,对应RuntimeService接口和TaskService接口,存储流程实例和用户任务等动态数据
Activiti核心7大API接口
1.RepositoryService:提供一系列管理流程部署和流程定义的API。
2.RuntimeService:在流程运行时对流程实例进行管理与控制。
3.TaskService:对流程任务进行管理,例如任务提醒、任务完成和创建任务等。
4.IdentityService:提供对流程角色数据进行管理的API,这些角色数据包括用户组、用户及它们之间的关系。
5.ManagementService:提供对流程引擎进行管理和维护的服务。
6.HistoryService:对流程的历史数据进行操作,包括查询、删除这些历史数据。
7.FormService:表单服务。
使用eclipse插件 新建流程定义文件
<process id="activitiInsurancePlanProcess" name="activitiInsurancePlanProcess" isExecutable="true" isClosed="false" processType="None">
<startEvent id="startEvent" name="开始流程"></startEvent>
<sequenceFlow id="flow2" sourceRef="startEvent" targetRef="usertask2"></sequenceFlow>
<userTask id="usertask2" name="二级子公司员工" activiti:assignee="${userId}" activiti:candidateGroups="22"></userTask>
<userTask id="usertask4" name="二级子公司领导" activiti:candidateGroups="21"></userTask>
<sequenceFlow id="flow4" sourceRef="usertask2" targetRef="usertask4"></sequenceFlow>
<userTask id="usertask5" name="财务专员" activiti:candidateGroups="32"></userTask>
<sequenceFlow id="flow5" sourceRef="usertask4" targetRef="usertask5"></sequenceFlow>
<userTask id="usertask6" name="财务领导" activiti:candidateGroups="31"></userTask>
<sequenceFlow id="flow6" sourceRef="usertask5" targetRef="usertask6"></sequenceFlow>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow7" sourceRef="usertask6" targetRef="endevent1"></sequenceFlow>
</process>
流程定义的xml文件只贴出process 定义部分,坐标未列出;
需要注意的几个关键参数有:
id:在撤回、驳回时,会经常用到每个节点的ID;
assignee:受理人,表示当前节点由谁处理或者受理;
candidateGroups:分配任务负责的组,查询待办任务时,相应的组所有成员均可以看到此组的待办任务,但是只有一个人能处理
以上三个参数是最基础最常见使用的最多的参数。
测试代码:
package com.dd.activiti.admin.test; import com.huatonghh.AdminApplication; import com.huatonghh.activiti.util.Jump2TargetFlowNodeCommand; import com.huatonghh.common.util.SpringUtil; import org.activiti.engine.*; import org.activiti.engine.history.HistoricActivityInstance; import org.activiti.engine.history.HistoricActivityInstanceQuery; import org.activiti.engine.history.HistoricDetail; import org.activiti.engine.history.HistoricFormProperty; import org.activiti.engine.history.HistoricVariableUpdate; import org.activiti.engine.identity.Group; import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl; import org.activiti.engine.impl.persistence.entity.HistoricDetailVariableInstanceUpdateEntity; import org.activiti.engine.repository.Deployment; import org.activiti.engine.repository.ProcessDefinition; import org.activiti.engine.repository.ProcessDefinitionQuery; import org.activiti.engine.runtime.ProcessInstance; import org.activiti.engine.task.Task; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.HashMap; import java.util.List; import java.util.Map; import static com.huatonghh.common.util.SpringUtil.getBean; //@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = AdminApplication.class) //@WebAppConfiguration public class ActivitiResourceIntTest { @Autowired private RuntimeService runtimeService; @Autowired private TaskService taskService; @Autowired private HistoryService historyService; @Autowired private RepositoryService repositoryService; @Autowired private ProcessEngine processEngine; @Autowired IdentityService identityService; @Autowired ManagementService managementService; // @Autowired // private ProcessRuntime processRuntime; @BeforeEach public void setup() { if(repositoryService ==null){ repositoryService = (RepositoryService)SpringUtil.getBean("RepositoryService"); } } /** * 流程定义的部署 * 影响的activiti表有哪些 * act_re_deployment 部署信息 * act_re_procdef 流程定义的一些信息 * act_ge_bytearray 流程定义的bpmn文件以及png文件 */ // @Test public void ActivitiDeployment(){ //1.创建ProcessEngine对象 // ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); // // //2.得到RepositoryService实例 // RepositoryService repositoryService = processEngine.getRepositoryService(); //3.进行部署 Deployment deployment = repositoryService.createDeployment()//创建Deployment对象 .addClasspathResource("processes/activitiUserRoleProcess.bpmn")//添加bpmn文件 .addClasspathResource("processes/activitiUserRoleProcess.png")//添加png文件 .name("请假申请单流程") .deploy();//部署 //4.输出部署的一些信息 System.out.println(deployment.getName()); System.out.println(deployment.getId()); } /** * Zip文件部署流程 * 影响的activiti表有哪些 * act_re_deployment 部署信息 * act_re_procdef 流程定义的一些信息 * act_ge_bytearray 流程定义的bpmn文件以及png文件 */ public void ActivitiZipDeployment (){ //先将bpmn文件和png文件压缩成zip文件。但是activiti最终也是以单个文件形式保存,说明activiti进行了解压工作。 //1.创建ProcessEngine对象 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); //2.得到RepositoryService实例 RepositoryService repositoryService = processEngine.getRepositoryService(); //3.进行部署 Deployment deployment = repositoryService.createDeployment()//创建Deployment对象 .addClasspathResource("diagram/holiday.bpmn")//添加bpmn文件 .addClasspathResource("diagram/holiday.png")//添加png文件 .name("请假申请单流程") .deploy();//部署 //4.输出部署的一些信息 System.out.println(deployment.getName()); System.out.println(deployment.getId()); //用户信息初始化 //初始化4级子公司角色及以下的员工 Group group1 = identityService.newGroup("threelevel"); group1.setName("三级及以下子公司"); group1.setType("assignment"); identityService.saveGroup(group1); Group group2 = identityService.newGroup("secondlevel"); group2.setName("二级子公司"); group2.setType("assignment"); identityService.saveGroup(group2); Group group3 = identityService.newGroup("financelevel"); group3.setName("财务公司"); group3.setType("assignment"); identityService.saveGroup(group3); Group group4 = identityService.newGroup("insurancelevel"); group4.setName("保险公司"); group4.setType("assignment"); identityService.saveGroup(group4); } /** * 启动一个工作流流程进行流程测试 */ @Test public void ActivitiStartInstance() { //1.得到ProcessEngine对象 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); //2.得到RunService对象 RuntimeService runtimeService = processEngine.getRuntimeService(); //3.创建流程实例(关键步骤)即 启动流程实例 //需要知道流程定义的Key:holiday(找key的方法 1:bpmn文件中的id,它对应的值就是key // 2:直接看数据库中流程定义表act_re_procdet的key值) String userId= "sjb"; Map<String,Object> map=new HashMap<String,Object>(); map.put("userId", userId);//标识为工作流程的工作,区别其他单独使用的流程 ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("activitiUserRoleProcess",map); runtimeService.addUserIdentityLink(processInstance.getId(), "w6", "participant"); runtimeService.addGroupIdentityLink(processInstance.getId(), "gly", "candidate"); //4.输出实例的相关信息 System.out.println("流程部署ID="+processInstance.getDeploymentId());//null System.out.println("流程定义ID="+processInstance.getProcessDefinitionId());//holiday:1:4 System.out.println("流程实例ID="+processInstance.getId());//2501 System.out.println("流程活动ID="+processInstance.getActivityId());//获取当前具体执行的某一个节点的ID(null) } /** * 查询当前用户的任务列表 */ public void ActivitiTaskQuery () { //lisi完成自己任务列表的查询 //1.得到ProcessEngine对象 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); //2.得到TaskService对象 TaskService taskService = processEngine.getTaskService(); //3.根据流程定义的key以及负责人assignee来实现当前用户的任务列表查询 List<Task> taskList = taskService.createTaskQuery() .processDefinitionKey("holiday") .taskAssignee("lisi") .list();//这里还有一个查询唯一结果的方法:singleResult();、还有分页查询listPage(index,limit); //4.任务列表展示 for (Task task : taskList) { //查的act_hi_procinst表的id System.out.println("流程实例ID="+task.getProcessInstanceId()); //查的act_hi_taskinst表的id System.out.println("任务ID="+task.getId()); //查的act_hi_taskinst表的Assignee_ System.out.println("任务负责人名称="+task.getAssignee()); //查的act_hi_taskinst表的NAME_ System.out.println("任务名称="+task.getName()); } } /** * 处理当前用户的任务列表 * 背后操作到的表: * act_hi_actinst * act_hi_identitylink * act_hi_taskinst * act_ru_execution * act_ru_identitylink * act_ru_task //只放当前要执行的任务 */ public void ActivitiCompleteTask (){ /** * 李四完成自己的任务 */ //1.得到ProcessEngine对象 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); //2.得到TaskService对象 TaskService taskService = processEngine.getTaskService(); //3.处理任务,结合当前用户任务列表的查询操作的话,可以知道任务ID=5002(实际操作中应该与查询写在一起) taskService.complete("5002"); } /** * 查询流程定义信息 **/ public void QueryProcessDefinition (){ //1.创建ProcessEngine对象 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); //2.创建RepositoryService对象 RepositoryService repositoryService = processEngine.getRepositoryService(); //3.得到ProcessDefinitionQuery对象,可以认为它就是一个查询器 ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery(); //4.设置条件,并查询出当前的所有流程定义 查询条件:流程定义的key=holiday //.orderByProcessDefinitionVersion() 设置排序方式,根据流程定义的版本号进行排序。 List<ProcessDefinition> list = processDefinitionQuery.processDefinitionKey("holiday") .orderByProcessDefinitionVersion() .desc().list(); //5.输出流程定义信息 for (ProcessDefinition processDefinition : list) { System.out.println("流程定义ID" + processDefinition.getId()); System.out.println("流程定义名称" + processDefinition.getName()); System.out.println("流程定义Key" + processDefinition.getKey()); System.out.println("流程定义的版本号" + processDefinition.getVersion()); } } /** * 删除已经部署的流程定义 * 影响的activiti表有哪些 * act_re_deployment 部署信息 * act_re_procdef 流程定义的一些信息 * act_ge_bytearray 流程定义的bpmn文件以及png文件 **/ public void DeleteProcessDefinition (){ //1.创建ProcessEngine对象 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); //2.创建RepositoryService对象 RepositoryService repositoryService = processEngine.getRepositoryService(); //3.执行删除流程定义,参数代表流程部署的id //参数true代表级联删除,此时就会先删除没有完成的流程节点,最后就可以删除流程定义信息,false代表不级联 repositoryService.deleteDeployment("1"); } /** * 需求 * 历史数据的查看 **/ public void HistoryQuery (){ //1.得到ProcessEngine对象 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); //2.得到HistoryService HistoryService historyService = processEngine.getHistoryService(); //3.得到HistoricActivitiInstanceQuery对象 HistoricActivityInstanceQuery historicActivityInstanceQuery = historyService.createHistoricActivityInstanceQuery(); historicActivityInstanceQuery.processInstanceId("2501");//设置流程实例的id //4.执行查询 List<HistoricActivityInstance> list = historicActivityInstanceQuery .orderByHistoricActivityInstanceStartTime() .asc()//根据流程开始进行的时间来排序 .list(); //5.遍历查询结果 for (HistoricActivityInstance instance : list) { System.out.println("节点ID:" + instance.getActivityId()); System.out.println("节点名称:" + instance.getActivityName()); System.out.println("流程定义ID:" + instance.getProcessDefinitionId()); System.out.println("流程实例ID:" + instance.getProcessInstanceId()); System.out.println("================================="); } } /** * 工作流的挂起与激活; */ // @Test public void ActivitiSuspendAndActivate() { // 通过流程实例ID来挂起流程实例 runtimeService.suspendProcessInstanceById("activitiInsurancePlanProcess:1:42504"); // 通过流程实例ID来激活流程实例 runtimeService.activateProcessInstanceById("42504"); } /* * 查询组用户的任务 */ //@Test public void ActivitiGroupTaskQuery() { String candidateUser = "user_secondlevel"; List<Task> list = processEngine.getTaskService()// 与正在执行的任务管理相关的Service .createTaskQuery()// 创建任务查询对象 /** 查询条件(where部分) */ // .taskAssignee(assignee)//指定个人任务查询,指定办理人 .taskCandidateUser(candidateUser)// 组任务的办理人查询 // .processDefinitionId(processDefinitionId)//使用流程定义ID查询 // .processInstanceId(processInstanceId)//使用流程实例ID查询 // .executionId(executionId)//使用执行对象ID查询 /** 排序 */ .orderByTaskCreateTime().asc()// 使用创建时间的升序排列 /** 返回结果集 */ // .singleResult()//返回惟一结果集 // .count()//返回结果集的数量 // .listPage(firstResult, maxResults);//分页查询 .list();// 返回列表 if (list != null && list.size() > 0) { for (Task task : list) { System.out.println("任务ID:" + task.getId()); System.out.println("任务名称:" + task.getName()); System.out.println("任务的创建时间:" + task.getCreateTime()); System.out.println("任务的办理人:" + task.getAssignee()); System.out.println("流程实例ID:" + task.getProcessInstanceId()); System.out.println("执行对象ID:" + task.getExecutionId()); System.out.println("流程定义ID:" + task.getProcessDefinitionId()); System.out.println("########################################################"); } } else { System.out.println("未查询到组用户的任务"); } } //驳回至 上一节点 private void jumpBeforeNode(String taskId,String msg){ Task task = taskService.createTaskQuery().taskId(taskId).singleResult(); String processInstanceId = task.getProcessInstanceId(); // 1、首先是根据流程ID获取当前任务: List<Task> tasks = processEngine.getTaskService().createTaskQuery().processInstanceId(processInstanceId).list(); // 获取流程中已经执行的节点,按照执行先后顺序排序 List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId) .orderByHistoricActivityInstanceId().asc().list(); //上一节点 节点ID HistoricActivityInstance historicActivityInstance = historicActivityInstances.get(historicActivityInstances.size()-2); managementService.executeCommand(new Jump2TargetFlowNodeCommand(taskId, historicActivityInstance.getActivityId(),msg)); tasks = taskService.createTaskQuery().processInstanceId(processInstanceId).list(); //同步计划信息 for (Task ts : tasks) { ts.setDescription(task.getDescription()); taskService.saveTask(ts); } } /** * 查询 每个节点的备注信息 * @param processInstanceId * @return */ private Map<String,Object> packetVariables(String processInstanceId){ Map<String,Object> historyVariables=new HashMap<String,Object>(); //查询act_hi_detail表中proc_inst_id为processInstance.getId() 的所有数据并返回 List<HistoricDetail> list=historyService.createHistoricDetailQuery().processInstanceId(processInstanceId).list(); for(HistoricDetail historicDetail:list){ if(historicDetail instanceof HistoricFormProperty){ HistoricFormProperty field=(HistoricFormProperty)historicDetail; historyVariables.put(field.getPropertyId(), field.getPropertyValue()); System.out.println("form field:taskId="+field.getTaskId()+",="+field.getPropertyId()+"="+field.getPropertyValue()); }else if (historicDetail instanceof HistoricVariableUpdate){ HistoricDetailVariableInstanceUpdateEntity variable=(HistoricDetailVariableInstanceUpdateEntity)historicDetail; historyVariables.put(variable.getName(), variable.getValue()); System.out.println("variable:"+variable.getVariableName()+",="+variable.getValue()); } } return historyVariables; } }
驳回代码
1 package com.huatonghh.activiti.util; 2 import org.activiti.bpmn.model.FlowElement; 3 import org.activiti.bpmn.model.Process; 4 import org.activiti.engine.ActivitiEngineAgenda; 5 import org.activiti.engine.impl.interceptor.Command; 6 import org.activiti.engine.impl.interceptor.CommandContext; 7 import org.activiti.engine.impl.persistence.entity.ExecutionEntity; 8 import org.activiti.engine.impl.persistence.entity.ExecutionEntityManager; 9 import org.activiti.engine.impl.persistence.entity.HistoricTaskInstanceEntity; 10 import org.activiti.engine.impl.persistence.entity.TaskEntity; 11 import org.activiti.engine.impl.persistence.entity.TaskEntityManager; 12 import org.activiti.engine.impl.util.ProcessDefinitionUtil; 13 14 /** 15 * 跳转到指定节点代码 16 * 17 * @author juyanming 18 * 19 */ 20 public class Jump2TargetFlowNodeCommand implements Command<Void> { 21 private String curTaskId; 22 23 private String targetFlowNodeId; 24 25 private String deleteReason; 26 27 public Jump2TargetFlowNodeCommand(String curTaskId, String targetFlowNodeId,String deleteReason) { 28 super(); 29 this.curTaskId = curTaskId; 30 this.targetFlowNodeId = targetFlowNodeId; 31 this.deleteReason = deleteReason; 32 } 33 34 @Override 35 public Void execute(CommandContext commandContext) { 36 System.out.println("跳转到目标流程节点:" + targetFlowNodeId); 37 System.out.println("驳回原因:" + deleteReason); 38 ExecutionEntityManager executionEntityManager = commandContext.getExecutionEntityManager(); 39 TaskEntityManager taskEntityManager = commandContext.getTaskEntityManager(); 40 // 获取当前任务的来源任务及来源节点信息 41 TaskEntity taskEntity = taskEntityManager.findById(curTaskId); 42 ExecutionEntity executionEntity = executionEntityManager.findById(taskEntity.getExecutionId()); 43 Process process = ProcessDefinitionUtil.getProcess(executionEntity.getProcessDefinitionId()); 44 // 删除当前节点 45 taskEntityManager.deleteTask(taskEntity, deleteReason, true, true); 46 HistoricTaskInstanceEntity historicTaskInstance = commandContext.getDbSqlSession().selectById(HistoricTaskInstanceEntity.class,curTaskId); 47 48 if (historicTaskInstance != null) { 49 historicTaskInstance.setDeleteReason(deleteReason); 50 commandContext.getDbSqlSession().update(historicTaskInstance); 51 } 52 53 // 获取要跳转的目标节点 54 FlowElement targetFlowElement = process.getFlowElement(targetFlowNodeId); 55 executionEntity.setCurrentFlowElement(targetFlowElement); 56 ActivitiEngineAgenda agenda = commandContext.getAgenda(); 57 agenda.planContinueProcessInCompensation(executionEntity); 58 59 return null; 60 } 61 62 public String getCurTaskId() { 63 return curTaskId; 64 } 65 66 public void setCurTaskId(String curTaskId) { 67 this.curTaskId = curTaskId; 68 } 69 70 public String getTargetFlowNodeId() { 71 return targetFlowNodeId; 72 } 73 74 public void setTargetFlowNodeId(String targetFlowNodeId) { 75 this.targetFlowNodeId = targetFlowNodeId; 76 } 77 78 public String getDeleteReason() { 79 return deleteReason; 80 } 81 82 public void setDeleteReason(String deleteReason) { 83 this.deleteReason = deleteReason; 84 } 85 86 }