一:Activiti的介绍
Activiti定义:
Activiti5是由Alfresco软件在2010年5月17日发布的业务流程管理(BPM)框架,它是覆盖了业务流程管理、工作流、服务协作等领域的一个开源的、灵活的、易扩展的可执行流程语言框架。Activiti基于Apache许可的开源BPM平台,创始人Tom Baeyens是JBoss jBPM的项目架构师,它特色是提供了eclipse插件,开发人员可以通过插件直接绘画出业务流程图。
二:牛刀小试---------搭建第一个Activiti项目流程
环境:IDEA(或者Eclipse,本人这里是用IDEA进行的)
步骤:
(1)第一步:IDEA安装Activiti插件
首先,创建一个普通的Java工程即可,然后按照下面流程进行:
1:点击菜单中的File(最左上角),选择settings
2:3
3:然后点击右边的安装,等待一下就可以啦。
(2)创建Activiti流图
(3)创建一个流程图(这里就用一个学生请假实例来进行)
1:首先选择一个startEvent,也就是流程的开始,并且点击一个额外的界面地方,然后输入该处理流程的名称和ID,这里就输入为shenqing
2:在选择一个UserTask按钮,表示一个处理任务,同理命名为“请假申请”
3:在选择一个UserTask按钮,表示一个处理任务,同理命名为“班主任”
4:在选择一个UserTask按钮,表示一个处理任务,同理命名为“教务处”
5:选择一个EndEvent按钮,表示流程的结束;
6:将各个按钮进行连线。(将鼠标放到每个按钮的“正中心”,然后拖着到想要链接的另外一个按钮即可,出现线条)
7:最终的效果。描述:就是学生提交请假申请——》班主任审核——》教务处审核
(4)将第三步中创建的shenqing.bpmn文件生成一个png格式的内容。对于这个的话,在IDEA与Eclipse有一点不一样,因为,在Eclipse中,当保存了之后,就会生成一个对应的png的图片,而在IDEA中需要手动进行生成。
1:首先将shenqing.bpmn的后缀改为xml
2:当点击xml文件,我们会看到里面之前的文字都是乱码了,那么如何进行解决?
小点1:找到自己IDEA的安装目录下的bin文件
小点2:找到如图所示的内容
小点3:分别打开这两个文件,然后添加一行内容(追加到最后即可):
小点4:保存内容,然后重启IDEA,就会发现不会乱码了
3:右击xml文件,然后选择:
4:然后继续:
保存到对应的工程下面即可。就会看到有个shenqing.png的内容出现
5:将之前修改为xml的文件的后缀改回原来的bpmn.
(5)创建一个acitiviti.cfg.xml文件,放在src目录下即可。这个主要是用于存放后面acitivi部署流程中,创建的相关联的一些表。
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration"> <property name="jdbcDriver" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/activititest?useUnicode=true&characterEncoding=utf8"></property> <property name="jdbcUsername" value="xxxxxx"></property> <property name="jdbcPassword" value="xxxxxxxx"></property> <!-- 创建表的策略 --> <property name="databaseSchemaUpdate" value="true"></property> </bean> </beans>
(6)导入Activiti的包--------当然也可以直接通过Maven进行依赖包的管理
(7)创建一个数据库生成的测试。(注意:要保证本地有对应名字的数据库)
package com.hnu.scw.activiti; import org.activiti.engine.ProcessEngine; import org.activiti.engine.ProcessEngineConfiguration; import org.junit.Test; /** * @author scw * @create 2018-01-15 11:06 * @desc 从数据源和流程图中,生成一个数据库表(这个就是Activiti流程控制的关键的数据表) **/ public class ActivitiTable { /** * 创建Activiti流的相关的数据库表 */ @Test public void creatTable(){ ProcessEngine processEngine = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml") .buildProcessEngine(); } }
如果,运行测试方法成功之后,再进入数据库,我们会看到产生了如下多张数据表(23张):
后面我会再详细讲解这每个表的作用,也是activiti的关键所在。
(8)进行流程部署的重点开发(按照下面的流程进行)------------重点的重点
package com.hnu.scw.activiti; import org.activiti.engine.ProcessEngine; import org.activiti.engine.ProcessEngines; import org.activiti.engine.task.Task; import org.junit.Test; import java.util.List; /** * @author scw * @create 2018-01-15 11:04 * @desc 用于进行演示Activiti的首例程序,即描述如何在代码中实现学生进行请假申请,班主任审核,教务处审核 **/ public class ActivitiTest { /** * 1、部署流程 * 2、启动流程实例 * 3、请假人发出请假申请 * 4、班主任查看任务 * 5、班主任审批 * 6、最终的教务处Boss审批 */ /** * 1:部署一个Activiti流程 * 运行成功后,查看之前的数据库表,就会发现多了很多内容 */ @Test public void creatActivitiTask(){ //加载的那两个内容就是我们之前已经弄好的基础内容哦。 //得到了流程引擎 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); processEngine.getRepositoryService() .createDeployment() .addClasspathResource("shenqing.bpmn") .addClasspathResource("shenqing.png") .deploy(); } /** * 2:启动流程实例 */ @Test public void testStartProcessInstance(){ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); processEngine.getRuntimeService() .startProcessInstanceById("shenqing:1:4"); //这个是查看数据库中act_re_procdef表 } /** * 完成请假申请 */ @Test public void testQingjia(){ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); processEngine.getTaskService() .complete("104"); //查看act_ru_task表 } /** * 小明学习的班主任小毛查询当前正在执行任务 */ @Test public void testQueryTask(){ //下面代码中的小毛,就是我们之前设计那个流程图中添加的班主任内容 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); List<Task> tasks = processEngine.getTaskService() .createTaskQuery() .taskAssignee("小毛") .list(); for (Task task : tasks) { System.out.println(task.getName()); } } /** * 班主任小毛完成任务 */ @Test public void testFinishTask_manager(){ ProcessEngine engine = ProcessEngines.getDefaultProcessEngine(); engine.getTaskService() .complete("202"); //查看act_ru_task数据表 } /** * 教务处的大毛完成的任务 */ @Test public void testFinishTask_Boss(){ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); processEngine.getTaskService() .complete("302"); //查看act_ru_task数据表 } }注意:通过上面的代码,一个个@Test,过来,主要是查看act_get_bytearray,act_ru_task和act_re_procdef三张表,当每进行不同的代码的时候,记得关注一下数据库的变化哦。。。你就会发现其中的奥秘的,
(9)小试牛刀的审批流程,基本完成。。。。。。。项目结构如下所示:
三:Activiti流程部署的方法
描述:对于流程处理, 第一步就是要进行流程的部署操作,在”小试牛刀“上面的代码中,主要是采取读取bpmn和png的资源文件的方法,那么除了这种方法之外,还有其他的方法吗?
备注:首先,说一下,在接下来的操作的时候,主要看下面的三张表的改变:
涉及到的表
* act_ge_bytearray:
* 1、英文解释
* act:activiti
* ge:general
* bytearray:二进制
* 2、字段
* name_:文件的路径加上名称
* bytes_:存放内容
* deployment_id_:部署ID
* 3、说明:
* 如果要查询文件(bpmn和png),需要知道deploymentId
* act_re_deployment
* 1、解析
* re:repository
* deployment:部署 用户描述一次部署
* 2、字段
* ID_:部署ID 主键
* act_re_procdef
* 1、解释
* procdef: process definition 流程定义
* 2、字段
* id_:pdid:pdkey:pdversion:随机数
* name:名称
* key:名称
* version:版本号
* 如果名称不变,每次部署,版本号加1
* 如果名称改变,则版本号从1开始计算
* deployment_id_:部署ID
方法一:
/** * 通过bpmn和png资源进行部署 */ @Test public void testDeployFromClasspath(){ //得到流程引擎 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); processEngine.getRepositoryService() .createDeployment() .addClasspathResource("shenqing.bpmn") .addClasspathResource("shenqing.png") .deploy(); }方法二:
/** * 通过 inputstream完成部署 */ @Test public void testDeployFromInputStream(){ InputStream bpmnStream = this.getClass().getClassLoader().getResourceAsStream("shenqing.bpmn"); //得到流程引擎 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); processEngine.getRepositoryService() .createDeployment() .addInputStream("shenqing.bpmn", bpmnStream) .deploy(); }
方法三:
/** * 通过zipinputstream完成部署 * 注意:这个的话,需要将bpmn和png文件进行压缩成zip文件,然后放在项目src目录下即可(当然其他目录也可以) */ @Test public void testDeployFromZipinputStream(){ InputStream in = this.getClass().getClassLoader().getResourceAsStream("shenqing.zip"); ZipInputStream zipInputStream = new ZipInputStream(in); ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); processEngine.getRepositoryService() .createDeployment() .addZipInputStream(zipInputStream) .deploy(); }
删除已经部署的Activiti的代码:
/** * 删除已经部署的Activiti流程 */ @Test public void testDelete(){ //得到流程引擎 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); //第一个参数是部署的流程的ID,第二个true表示是进行级联删除 processEngine.getRepositoryService() .deleteDeployment("601",true); }
四:关于流程部署相关的其他API的示例
(1)
/** * 根据名称查询流程部署 */ @Test public void testQueryDeploymentByName(){ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); List<Deployment> deployments = processEngine.getRepositoryService() .createDeploymentQuery() .orderByDeploymenTime()//按照部署时间排序 .desc()//按照降序排序 .deploymentName("请假流程") .list(); for (Deployment deployment : deployments) { System.out.println(deployment.getId()); } }数据库情况:
执行后输出:
801
(2)
/** * 查询所有的部署流程 */ @Test public void queryAllDeplyoment(){ //得到流程引擎 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); List<Deployment> lists = processEngine.getRepositoryService() .createDeploymentQuery() .orderByDeploymenTime()//按照部署时间排序 .desc()//按照降序排序 .list(); for (Deployment deployment:lists) { System.out.println(deployment.getId() +" 部署名称" + deployment.getName()); } }
数据库情况:
程序执行后输出:
801 部署名称请假流程
(3)
/** * 查询所有的流程定义 */ @Test public void testQueryAllPD(){ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); List<ProcessDefinition> pdList = processEngine.getRepositoryService() .createProcessDefinitionQuery() .orderByProcessDefinitionVersion() .desc() .list(); for (ProcessDefinition pd : pdList) { System.out.println(pd.getName()); } }
数据库情况:
程序执行后输出:
shenqing
(4)
/** * 查看流程图 * 根据deploymentId和name(在act_ge_bytearray数据表中) */ @Test public void testShowImage() throws Exception{ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); InputStream inputStream = processEngine.getRepositoryService() /** * deploymentID * 文件的名称和路径 */ .getResourceAsStream("801","shenqing.png"); OutputStream outputStream3 = new FileOutputStream("e:/processimg.png"); int b = -1 ; while ((b=inputStream.read())!=-1){ outputStream3.write(b); } inputStream.close(); outputStream3.close(); }数据库情况:
(5)
/** * 根据pdid查看图片(在act_re_procdef数据表中) * @throws Exception */ @Test public void testShowImage2() throws Exception{ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); InputStream inputStream = processEngine.getRepositoryService() .getProcessDiagram("shenqing:1:804"); OutputStream outputStream = new FileOutputStream("e:/processimg.png"); int b = -1 ; while ((b=inputStream.read())!=-1){ outputStream.write(b); } inputStream.close(); outputStream.close(); }数据库情况:
(6)
/** * 查看bpmn文件(在act_re_procdef数据表中) */ @Test public void testShowBpmn() throws Exception{ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); InputStream inputStream = processEngine.getRepositoryService() .getProcessModel("shenqing:1:804"); OutputStream outputStream = new FileOutputStream("e:/processimg.bpmn"); int b = -1 ; while ((b=inputStream.read())!=-1){ outputStream.write(b); } inputStream.close(); outputStream.close(); }数据库情况:
五:关于流程实例的相关API
涉及到的表:
* act_hi_actinst
* 1、说明
* act:activiti
* hi:history
* actinst:activity instance
* 流程图上出现的每一个元素都称为activity
* 流程图上正在执行的元素或者已经执行完成的元素称为activity instance
* 2、字段
* proc_def_id:pdid
* proc_inst_id:流程实例ID
* execution_id_:执行ID
* act_id_:activity
* act_name
* act_type
* act_hi_procinst
* 1、说明
* procinst:process instance 历史的流程实例
* 正在执行的流程实例也在这张表中
* 如果end_time_为null,说明正在执行,如果有值,说明该流程实例已经结束了
* act_hi_taskinst
* 1、说明
* taskinst:task instance 历史任务
* 正在执行的任务也在这张表中
* 如果end_time_为null,说明该任务正在执行
* 如果end_time不为null,说明该任务已经执行完毕了
* act_ru_execution
* 1、说明
* ru:runtime
* 代表正在执行的流程实例表
* 如果当期正在执行的流程实例结束以后,该行在这张表中就被删除掉了,所以该表也是一个临时表
* 2、字段
* proc_inst_id_:piid 流程实例ID,如果不存在并发的情况下,piid和executionID是一样的
* act_id:当前正在执行的流程实例(如果不考虑并发的情况)的正在执行的activity有一个,所以act_id就是当前正在执行的流程实例的正在执行的
* 节点
* act_ru_task
* 1、说明
* 代表正在执行的任务表
* 该表是一个临时表,如果当前任务被完成以后,任务在这张表中就被删除掉了
* 2、字段
* id_: 主键 任务ID
* execution_id_:执行ID
* 根据该ID查询出来的任务肯定是一个
* proc_inst_id:piid
* 根据该id查询出来的任务
* 如果没有并发,则是一个
* 如果有并发,则是多个
* name_:任务的名称
* assignee_:任务的执行人
(1)启动流程实例
方法一:
/** * 启动流程实例,通过PID */ @Test public void testStartProcessInstanceByPID(){ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); ProcessInstance processInstance = processEngine.getRuntimeService() .startProcessInstanceById("shenqing:1:804"); //这个就是从部署的时候生成的一个内容,如下数据库所示 System.out.println(processInstance.getId()); }
方法二:
/** * 根据pdkey启动流程实例,默认启动最高版本的 */ @Test public void testStartPIByPDKEY(){ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); processEngine.getRuntimeService() .startProcessInstanceByKey("shenqing"); //这个字段对应上面那个数据库中的Key字段 }
对应的数据库内容:
执行后:(就多出了一条流程)
(2)完成任务
/** * 完成任务 */ @Test public void testFinishTask(){ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); processEngine.getTaskService() .complete("1102"); }
对应的表:
执行后的情况:(注意表不同了,原来那个表的字段ID的条目信息,就没了)
(3)
/** * 查询任务 * 根据任务的执行人查询正在执行任务(通过act_ru_task数据表) */ @Test public void testQueryTaskByAssignee(){ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); /** * 当前班主任小毛人这个人当前正在执行的所有的任务 */ List<Task> tasks = processEngine.getTaskService() .createTaskQuery() .orderByTaskCreateTime() .desc() .taskAssignee("小毛") .list(); for (Task task : tasks) { System.out.println(task.getName()); System.out.println(task.getAssignee()); } }
数据库情况:
程序执行后,输出:
班主任
小毛
/** * 查询所有的正在执行的任务 */ @Test public void testQueryTask(){ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); List<Task> tasks = processEngine.getTaskService() .createTaskQuery() .list(); for (Task task : tasks) { System.out.println(task.getName()); } }
数据库情况:
程序执行后,输出内容:
请假申请
班主任
请假申请
/** * 根据piid查询任务 */ @Test public void testQueryTaskByPIID(){ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); List<Task> tasks = processEngine.getTaskService() .createTaskQuery().executionId("1001") .list(); for (Task task : tasks) {//因为没有并发,所以就有一个 System.out.println(task.getName()); } }
数据库情况:
程序执行后输出:
班主任
(6)/** * 根据piid得到当前正在执行的流程实例的正在活动的节点 */ @Test public void testActivity(){ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); /** * 根据piid得到流程实例 */ ProcessInstance pi = processEngine.getRuntimeService() .createProcessInstanceQuery() .processInstanceId("1001") .singleResult(); String activityId = pi.getActivityId();//当前流程实例正在执行的activityId System.out.println(activityId); }
数据库情况:
程序执行后输出:
班主任
(7)查询历史执行的任务(这个包括当前在执行的和已经执行过的任务,可以通过Delete_Reason这个字段进行区别)
/** * 查看已经完成的任务和当前在执行的任务 */ @Test public void findHistoryTask(){ ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine(); //如果只想获取到已经执行完成的,那么就要加入completed这个过滤条件 List<HistoricTaskInstance> historicTaskInstances1 = defaultProcessEngine.getHistoryService() .createHistoricTaskInstanceQuery() .taskDeleteReason("completed") .list(); //如果只想获取到已经执行完成的,那么就要加入completed这个过滤条件 List<HistoricTaskInstance> historicTaskInstances2 = defaultProcessEngine.getHistoryService() .createHistoricTaskInstanceQuery() .list(); System.out.println("执行完成的任务:" + historicTaskInstances1.size()); System.out.println("所有的总任务数(执行完和当前未执行完):" +historicTaskInstances2.size()); }
执行结果:
执行完成的任务:1
所有的总任务数(执行完和当前未执行完):3
六:关于ProcessDefinitionEntity(流程定义实体)的相关内容
上面的这个图就是对于一个流程的相关内容,这部分要特别注意,是非常重要的一个内容。
package com.hnu.scw.activiti.activityimpl; import java.util.List; import org.activiti.engine.ProcessEngine; import org.activiti.engine.ProcessEngines; import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity; import org.activiti.engine.impl.pvm.PvmTransition; import org.activiti.engine.impl.pvm.process.ActivityImpl; import org.activiti.engine.runtime.ProcessInstance; import org.junit.Test; public class ProcessDefinitionEntityTest { /** * 根据pdid得到processDefinitionEntity */ @Test public void testProcessDefinitionEntity(){ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); /** * 根据pdid得到ProcessDefinitionEntry */ ProcessDefinitionEntity processDefinitionEntity = (ProcessDefinitionEntity)processEngine.getRepositoryService() .getProcessDefinition("qingjia1:1:804"); } /** * 根据pdid得到processDefinitionEntity中的activityimpl */ @Test public void testGetActivityImpl(){ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); /** * 根据pdid得到ProcessDefinitionEntry */ ProcessDefinitionEntity processDefinitionEntity = (ProcessDefinitionEntity)processEngine.getRepositoryService() .getProcessDefinition("qingjia1:1:804"); /** * ActivityImpl是一个对象 * 一个activityImpl代表processDefinitionEntity中的一个节点 */ List<ActivityImpl> activityImpls = processDefinitionEntity.getActivities(); for (ActivityImpl activityImpl : activityImpls) { System.out.println(activityImpl.getId()); System.out.print("hegiht:"+activityImpl.getHeight()); System.out.print("width:"+activityImpl.getWidth()); System.out.print(" x:"+activityImpl.getX()); System.out.println(" y:"+activityImpl.getY()); } } /** * 得到ProcessDefinitionEntity中的所有的ActivityImpl的所有的PvmTransition */ @Test public void testSequenceFlow(){ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); /** * 根据pdid得到ProcessDefinitionEntry */ ProcessDefinitionEntity processDefinitionEntity = (ProcessDefinitionEntity)processEngine.getRepositoryService() .getProcessDefinition("qingjia1:1:804"); /** * ActivityImpl是一个对象 * 一个activityImpl代表processDefinitionEntity中的一个节点 */ List<ActivityImpl> activityImpls = processDefinitionEntity.getActivities(); for (ActivityImpl activityImpl : activityImpls) { /** * 得到一个activityimpl的所有的outgoing */ List<PvmTransition> pvmTransitions = activityImpl.getOutgoingTransitions(); for (PvmTransition pvmTransition : pvmTransitions) { System.out.println("sequenceFlowId:"+pvmTransition.getId()); } } } /** * 得到当前正在执行的流程实例的activityimpl-->PvmTransition */ @Test public void testQueryActivityImpl_Ing(){ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); ProcessDefinitionEntity processDefinitionEntity = (ProcessDefinitionEntity)processEngine.getRepositoryService() .getProcessDefinition("qingjia:1:504"); //根据piid获取到activityId ProcessInstance pi = processEngine.getRuntimeService() .createProcessInstanceQuery() .processInstanceId("1001") .singleResult(); //根据流程实例得到当前正在执行的流程实例的正在执行的节点 ActivityImpl activityImpl = processDefinitionEntity.findActivity(pi.getActivityId()); System.out.print("流程实例ID:"+pi.getId()); System.out.print(" 当前正在执行的节点:"+activityImpl.getId()); System.out.print(" hegiht:"+activityImpl.getHeight()); System.out.print(" width:"+activityImpl.getWidth()); System.out.print(" x:"+activityImpl.getX()); System.out.println(" y:"+activityImpl.getY()); } }
七:通过当前系统登陆用户,获取到的相关Activiti内容
备注:上面这个流程图,一定要根据上面的知识点来进行推到学习,一定要弄清楚每个内容对应的属性和对象指的是什么,因为这个在实际的开发项目中,是非常有用,并且是经常进行使用的内容。
上面图所对应的代码如下:
package com.hnu.scw.activiti.utils; import java.util.ArrayList; import java.util.List; import org.activiti.engine.ProcessEngine; import org.activiti.engine.ProcessEngines; import org.activiti.engine.repository.ProcessDefinition; import org.activiti.engine.runtime.ProcessInstance; import org.activiti.engine.task.Task; public class ActivitiUtils { /** * 当前用户-->当前用户正在执行的任务--->当前正在执行的任务的piid-->该任务所在的流程实例 * @param assignee * @return */ public static List<ProcessInstance> getPIByUser(String assignee){ List<ProcessInstance> pis = new ArrayList<ProcessInstance>(); ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); /** * 该用户正在执行的任务 */ List<Task> tasks = processEngine.getTaskService() .createTaskQuery() .taskAssignee(assignee) .list(); for (Task task : tasks) { /** * 根据task-->piid-->pi */ String piid = task.getProcessInstanceId(); ProcessInstance pi = processEngine.getRuntimeService() .createProcessInstanceQuery() .processInstanceId(piid) .singleResult(); pis.add(pi); } return pis; } /** * 根据当前的登录人能够推导出所在的流程定义 */ public static void getProcessInstance(String assignee){ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); List<Task> tasks = processEngine.getTaskService() .createTaskQuery() .taskAssignee(assignee) .list(); for (Task task : tasks) { String pdid = task.getProcessDefinitionId(); ProcessDefinition processDefinition = processEngine.getRepositoryService() .createProcessDefinitionQuery() .processDefinitionId(pdid) .singleResult(); } } }
八:详细解析Task任务(非常重要)
任务的概念:需要有人进行审批或者申请的为任务
任务的执行人的情况类型:
情况一:当没有进入该节点之前,就可以确定任务的执行人
实例:比如进行“请假申请”的流程时候,最开始执行的就是提交”请假申请“,那么就需要知道,谁提交的“请假”,很明显,在一个系统中,谁登陆到系统里面,谁就有提交“请假任务”的提交人,那么执行人就可以确定就是登录人。
情况二:有可能一个任务节点的执行人是固定的。
实例:比如,在“公司财务报账”的流程中,最后一个审批的人,一定是财务部的最大的BOSS,所以,这样该流程的最后一个节点执行人,是不是就已经确定是为“财务部最大BOSS”了。
情况三:一个节点任务,之前是不存在执行人(未知),只有当符合身份的人,登陆系统,进入该系统,才能确定执行人。
实例:比如,如果当前的流程实例正在执行“自荐信审批”,这个时候,自荐信审批没有任务执行人,因为审批人是可以很多个,无法确定到底是谁,只有当咨询员登录系统以后才能给该任务赋值执行人,即存在只要是咨询员登陆,那么就可以看到所有的“自荐信”。
情况四:一个任务节点有n多人能够执行该任务,但是只要有一个人执行完毕就完成该任务了:组任务
实例:比如,“进入地铁站通道”的流程,我们一般地铁都是有N个安全检查的入口,有很多个人在进行检查,那么我们要想通过检查,那么任意一个检察员只要通过即可。
详细分析:
针对情况一:
步骤:
(1)首先构建流程图:(注意区别上面的第一次画的内容)
(2)将bpmn的内容,生成一个png的图片(这个操作方法,上面都已经很详细了,不多说)
(3)代码实现步骤:
1:
/** * 部署流程 */ @Test public void startDeployTest(){ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); processEngine.getRepositoryService() .createDeployment() .name("请假流程:情况一") .addClasspathResource("com/hnu/scw/task/shenqing.bpmn") .deploy(); }数据库情况:
2:
/** * 启动流程实例 * 可以设置一个流程变量 */ @Test public void testStartPI(){ /** * 流程变量 * 给<userTask id="请假申请" name="请假申请" activiti:assignee="#{student}"></userTask> * 的student赋值 */ Map<String, Object> variables = new HashMap<String, Object>(); variables.put("student", "小明"); ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); processEngine.getRuntimeService() .startProcessInstanceById("shenqing1:1:1304",variables); }数据库情况:
分析:如果,我们安装下面的代码执行,那么就出出现如下的错误
/** * 启动流程实例 * 可以设置一个流程变量 */ @Test public void testStartPI(){ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); processEngine.getRuntimeService() .startProcessInstanceById("shenqing1:1:1304"); }原因:是否还记得,我们在画流程图的时候,对该请假申请的节点,分配了一个#{student},这个变量,这个其实含义就是说,当我们进行该节点的处理的时候,就需要分配一个执行人,如果没有分配,就会发生上面的错误。然后再回头想一下,是不是就是我们的第一种情况呢?因为,在进行请假的流程的执行开始的时候,其实申请人是已经可以确定了,就是登陆的用户。
3:后面的代码如下:
/** * 在完成请假申请的任务的时候,给班主任审批的节点赋值任务的执行人 */ @Test public void testFinishTask_Teacher(){ Map<String, Object> variables = new HashMap<String, Object>(); variables.put("teacher", "我是小明的班主任"); ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); processEngine.getTaskService() .complete("1405", variables); //完成任务的同时设置流程变量 } /** * 在完成班主任审批的情况下,给教务处节点赋值 */ @Test public void testFinishTask_Manager(){ Map<String, Object> variables = new HashMap<String, Object>(); variables.put("manager", "我是小明的教务处处长"); ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); processEngine.getTaskService() .complete("1603", variables); //完成任务的同时设置流程变量 } /** * 结束流程实例 */ @Test public void testFinishTask(){ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); processEngine.getTaskService() .complete("1703"); }总结:针对情况一,那么我们必须要进入该节点执行前,就要分配一个执行人。
情况二:这个情况的话,这里不多介绍,因为之前的知识点中,都是在画流程图的时候就已经分配这个执行人了。可以回头去看看。
情况三:
步骤:(1)画流程图,这里不多介绍,就说一下需要修改的地方。
(2)编写的TaskListener监听类
package com.hnu.scw.tasklistener; import org.activiti.engine.delegate.DelegateTask; import org.activiti.engine.delegate.TaskListener; /** * @author Administrator * @create 2018-01-16 11:10 * @desc tack任务的监听,主要是为了动态分配执行人 **/ public class MyTaskListener implements TaskListener { @Override public void notify(DelegateTask delegateTask) { /** * 任务的执行人可以动态的赋值 * 1、流程变量 * 可以通过提取流程变量的方式给任务赋值执行人 * 2、可以操作数据库 * 方法一:(必须在web环境) WebApplicationContext ac = WebApplicationContextUtils * .getWebApplicationContext(ServletActionContext.getServletContext()); xxxxService xxxxService = (xxxxService) ac.getBean("xxxxService"); 方法二:通过JDBC来进行数据库操作 */ //动态分配(这里是从上一节点中的tack变量的map中获取,只有流程没有结束,所有的变量都是可以获取) /*String value = (String)delegateTask.getVariable("aaa"); delegateTask.setAssignee(value);*/ //静态分配(用于确定该执行人就只有一种情况,是一种固定的) delegateTask.setAssignee("我是班主任"); } }
通过这样的方式的话,当有“请假申请”进行提交之后,“班主任”的这个节点,就会自动进行分配执行人。
情况四:
流程图如下:
具体的测试代码:(注意看写的注释内容,就明白了对应的数据库的什么表)
package com.hnu.scw.test; import org.activiti.engine.ProcessEngine; import org.activiti.engine.ProcessEngines; import org.activiti.engine.task.IdentityLink; import org.activiti.engine.task.Task; import org.junit.Test; import java.util.List; /** * @author scw * @create 2018-01-23 15:45 * @desc 关于对于组任务的测试内容 **/ public class GroupTaskTest { /** * 主要是对于某些任务流程中,有N个人,但是只需要其中的某一个通过, * 则该任务就通过了,所以针对这样的业务需求,就有如下的内容 */ @Test public void deployTashTest(){ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); processEngine.getRepositoryService() .createDeployment() .addClasspathResource("com/hnu/scw/test/task3.bpmn") .addClasspathResource("com/hnu/scw/test/task3.png") .name("组任务的测试") .deploy(); } /** * 当启动完流程实例以后,进入了"电脑维修"节点,该节点是一个组任务 * 这个时候,组任务的候选人就会被插入到两张表中 * act_ru_identitylink 存放的是当前正在执行的组任务的候选人 * act_hi_identitylink */ @Test public void processTaskStartTest(){ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); processEngine.getRuntimeService() .startProcessInstanceByKey("task3"); } /** * 对于act_hi_identitylink表,根据任务ID,即TASK_ID字段查询候选人 */ @Test public void testQueryCandidateByTaskId(){ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); List<IdentityLink> identityLinks = processEngine.getTaskService() .getIdentityLinksForTask("2104"); for (IdentityLink identityLink : identityLinks) { System.out.println(identityLink.getUserId()); } } /** * 对于act_hi_identitylink表,根据候选人,即USER_ID_查看组任务 */ @Test public void testQueryTaskByCandidate(){ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); List<Task> tasks = processEngine.getTaskService() .createTaskQuery() .taskCandidateUser("工程师1") .list(); for (Task task : tasks) { System.out.println(task.getName()); } } /** * 候选人中的其中一个人认领任务 */ @Test public void testClaimTask(){ ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); processEngine.getTaskService() /** * 第一个参数为taskId * 第二个参数为认领人 */ .claim("2104", "工程师2"); } }
九:实际项目中的关于Activiti的工具类方法封装
package com.hnu.scw.activiti.utils; import org.activiti.engine.ProcessEngine; import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity; import org.activiti.engine.impl.pvm.PvmActivity; import org.activiti.engine.impl.pvm.PvmTransition; import org.activiti.engine.impl.pvm.process.ActivityImpl; import org.activiti.engine.repository.Deployment; import org.activiti.engine.repository.ProcessDefinition; import org.activiti.engine.runtime.ProcessInstance; import org.activiti.engine.task.Task; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.zip.ZipInputStream; /** * @author scw * @create 2018-01-24 9:51 * @desc 针对流程管理的工具类 **/ @Component("activitiUtils") public class ActivitiUtils { @Resource(name = "processEngine") private ProcessEngine processEngine; /** * 部署流程 * @param file 流程的zip文件 * @param processName 流程的名字 * @throws IOException */ public void deployeProcess(File file , String processName)throws IOException{ InputStream inputStream = new FileInputStream(file); ZipInputStream zipInputStream = new ZipInputStream(inputStream); this.processEngine.getRepositoryService() .createDeployment() .name(processName) .addZipInputStream(zipInputStream) .deploy(); } /** * 通过字节流来进行部署流程 * @param io * @param processName */ public void deplyoProcessByInputSterm(InputStream io , String processName){ ZipInputStream zipInputStream = new ZipInputStream(io); this.processEngine.getRepositoryService() .createDeployment() .name(processName) .addZipInputStream(zipInputStream) .deploy(); } /** * 查询所有的部署流程 * @return */ public List<Deployment> getAllDeplyoment(){ return this.processEngine.getRepositoryService() .createDeploymentQuery() .orderByDeploymenTime() .desc() .list(); } /** * 查询所有的部署定义信息 * @return */ public List<ProcessDefinition> getAllProcessInstance(){ return this.processEngine.getRepositoryService() .createProcessDefinitionQuery() .orderByProcessDefinitionVersion() .desc() .list(); } /** * 根据部署ID,来删除部署 * @param deplyomenId */ public void deleteDeplyomentByPID(String deplyomenId){ this.processEngine.getRepositoryService() .deleteDeployment(deplyomenId , true); } /** * 查询某个部署流程的流程图 * @param pid * @return */ public InputStream lookProcessPicture(String pid){ return this.processEngine.getRepositoryService() .getProcessDiagram(pid); } /** * 开启请假的流程实例 * @param billId * @param userId */ public void startProceesInstance(Long billId , String userId){ Map<String , Object> variables = new HashMap<>(); variables.put("userID" , userId); this.processEngine.getRuntimeService() .startProcessInstanceByKey("shenqingtest" , ""+billId , variables); //第一个参数,就是流程中自己定义的名字,这个一定要匹配,否则是找不到的。 } /** * 查询当前登陆人的所有任务 * @param userId * @return */ public List<Task> queryCurretUserTaskByAssignerr(String userId){ return this.processEngine.getTaskService() .createTaskQuery() .taskAssignee(userId) .orderByTaskCreateTime() .desc() .list(); } /** * 根据TaskId,获取到当前的执行节点实例对象 * @param taskId * @return */ public ActivityImpl getActivityImplByTaskId(String taskId){ //首先得到任务 Task task = this.getTaskByTaskId(taskId); //其次,得到流程实例 ProcessInstance processInstance = this.getProcessInstanceByTask(task); //再次,根据流程实例来获取到流程定义 ProcessDefinitionEntity processDefinitionEntity = this.getProcessDefinitionEntityByTask(task); //再根据,流程定义,通过流程实例中来获取到activiti的ID,从而得到acitviImp ActivityImpl activity = processDefinitionEntity.findActivity(processInstance.getActivityId()); return activity; } /** * 根据taskId,判断对应的流程实例是否结束 * 如果结束了,那么得到的流程实例就是返回一个null * 否则就是返回对应的流程实例对象 * 当然也可以选择返回boolean类型的 * @param taskId 任务ID * @return */ public ProcessInstance isFinishProcessInstancs(String taskId){ //1,先根据taskid,得到任务 Task task = getTaskByTaskId(taskId); //2:完成当前任务 finishCurrentTaskByTaskId(taskId); //3:得到当前任务对应得的流程实例对象 ProcessInstance processInstance = getProcessInstanceByTask(task); return processInstance; } /** * 获取当前执行节点的所有出口 * @param activity * @return */ public List<PvmTransition> getCurrentActivitiImplPvm(ActivityImpl activity){ List<PvmTransition> outgoingTransitions = activity.getOutgoingTransitions(); return outgoingTransitions; } /** * 根据taskId获取到task * @param taskId * @return */ public Task getTaskByTaskId(String taskId) { //得到当前的任务 Task task = this.processEngine.getTaskService() .createTaskQuery() .taskId(taskId) .singleResult(); return task; } /** * 根据Task中的流程实例的ID,来获取对应的流程实例 * @param task 流程中的任务 * @return */ public ProcessInstance getProcessInstanceByTask(Task task) { //得到当前任务的流程 ProcessInstance processInstance = this.processEngine.getRuntimeService() .createProcessInstanceQuery() .processInstanceId(task.getProcessInstanceId()) .singleResult(); return processInstance; } /** * 根据Task来获取对应的流程定义信息 * @param task * @return */ public ProcessDefinitionEntity getProcessDefinitionEntityByTask(Task task){ ProcessDefinitionEntity processDefinitionEntity = (ProcessDefinitionEntity) this.processEngine.getRepositoryService() .getProcessDefinition(task.getProcessDefinitionId()); return processDefinitionEntity; } /** * 根据taskId获取到businesskey,这个值是管理activiti表和自己流程业务表的关键之处 * @param taskId 任务的ID * @return */ public String getBusinessKeyByTaskId(String taskId){ Task task = this.getTaskByTaskId(taskId); ProcessInstance processInstance = this.getProcessInstanceByTask(task); //返回值 return processInstance.getBusinessKey(); } /** * 根据taskId,完成任务 * @param taskId */ public void finishCurrentTaskByTaskId(String taskId){ this.processEngine.getTaskService().complete(taskId); } /** * 完成任务的同时,进行下一个节点的审批人员的信息的传递 * @param taskId * @param object */ public void finishCurrentTaskByTaskId(String taskId , Object object){ Map<String , Object> map = new HashMap<>(); map.put("assigeUser" , object); this.processEngine.getTaskService().complete(taskId , map); } }
OK,这上面就是关于对于控制流的一些介绍了。对于这个,我们在很多的流程管理项目中都是有很好的借鉴意义的。当然,对于Actitivi这个框架,提供的只是部分的API接口,而且当我们刚开始接触的时候,对于里面内部存在的表的关系还会很模糊,但是,只要慢慢的熟悉了,这个就能够很好的理解了。另外的话,还说一个知识点。
附加知识点:
问题:Activiti里面本身自带有很多的数据表,它里面都是存在着关联关系,那么如何将其本身的表与我们的实际业务中的表进行关联呢?
解惑:其实,这个对于Activiti早已经想到这个问题,就是通过act_ru_exectution这个表中的business_key这个字段来进行关联。
实例分析:比如,针对上面的请假流程,那么,我们肯定在自己的业务中,就需要一张请假的信息表,比如,里面就包含,请假原因,请假人,请假时间等等基本请假信息。然后,我们其他的业务,也会根据这张表的内容,进行不断的扩充,比如,还需要记录对每条请假信息,每个审批节点中每个人的具体描述信息,那么这样就出现了一张“请假审批详细表”,很明显,这两张表就是通过“请假表中的主键ID”来进行关联的,那么就作为“请假详情表”中的外键。。。那么,同理,我们也是一样的,我们就通过对于act_ru_exectution这个数据表的business_key字段来关联着我们的业务主键即可。所以,这样就把我们自身的业务和Activiti进行了关联。如下图:
附加知识点2:Activiti工作流的自带数据表的含义
(1)资源库流程规则表
1)act_re_deployment 部署信息表
2)act_re_model 流程设计模型部署表
3)act_re_procdef 流程定义数据表
(2):运行时数据库表
1)act_ru_execution 运行时流程执行实例表
2)act_ru_identitylink 运行时流程人员表,主要存储任务节点与参与者的相关信息
3)act_ru_task 运行时任务节点表
4)act_ru_variable 运行时流程变量数据表
(3):历史数据库表
1)act_hi_actinst 历史节点表
2)act_hi_attachment 历史附件表
3)act_hi_comment 历史意见表
4)act_hi_identitylink 历史流程人员表
5)act_hi_detail 历史详情表,提供历史变量的查询
6)act_hi_procinst 历史流程实例表
7)act_hi_taskinst 历史任务实例表
8)act_hi_varinst 历史变量表
(4):组织机构表
1)act_id_group 用户组信息表
2)act_id_info 用户扩展信息表
3)act_id_membership 用户与用户组对应信息表
4)act_id_user 用户信息表
这四张表很常见,基本的组织机构管理,关于用户认证方面建议还是自己开发一套,组件自带的功能太简单,使用中有很多需求难以满足
(5):通用数据表
1)act_ge_bytearray 二进制数据表
2)act_ge_property 属性数据表存储整个流程引擎级别的数据,初始化表结构时,会默认插入三条记录,
附加知识点3:完整的一个Activiti工作流的项目代码。
环境:IDEA+ SpringMvc+Spring+Hibernate +Mysql
获取方式:留言即可。我会随时浏览信息的。。。。