ACTIVITI流程使用说明
1) 流程图的绘制
流程图可以使用eclipse插件完成,eclipse plug 地址:http://activiti.org/designer/update/
通过插件的绘制流程功能,绘制好的流程图如下图:
最终可以保存为*.bpmn20.xml,这个xml文件就是我们的流程定义文件。
生成的xml文件如下:
文件名:ProjectReportProcess.bpmn20.xml
<?xml version="1.0" encoding="UTF-8"?> <definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test"> <process id="projectReportProcess" name="My process" isExecutable="true"> <extensionElements> <activiti:executionListener event="start" class="com.fangxin365.bpm.project.listener.ProjectReportStartExecutionListener"></activiti:executionListener> </extensionElements> <startEvent id="startevent1" name="Start"></startEvent> <userTask id="usertask1" name="项目领导审批报告" activiti:assignee="houyousong" activiti:formKey="formKey1"> <documentation>这里是项目领导首先进行审批</documentation> <extensionElements> <activiti:taskListener event="complete" delegateExpression="${completeListener}"> <activiti:field name="step"> <activiti:expression>PROMNG_AUDIT</activiti:expression> </activiti:field> </activiti:taskListener> </extensionElements> </userTask> <userTask id="usertask2" name="领导一会签审批" activiti:assignee="liuxiaoguang" activiti:formKey="formKey1"> <documentation>这里是会签领导(刘晓光)进行审批</documentation> <extensionElements> <activiti:taskListener event="complete" delegateExpression="${completeListener}"> <activiti:field name="step"> <activiti:expression>CS_MNG1_AUDIT</activiti:expression> </activiti:field> </activiti:taskListener> </extensionElements> </userTask> <parallelGateway id="exclusivegateway1" name="Exclusive Gateway"></parallelGateway> <userTask id="usertask3" name="领导二会签审批" activiti:assignee="zhangxiangfu" activiti:formKey="formKey1"> <documentation>这里是会签领导(张香附)进行审批</documentation> <extensionElements> <activiti:taskListener event="complete" delegateExpression="${completeListener}"> <activiti:field name="step"> <activiti:expression>CS_MNG2_AUDIT</activiti:expression> </activiti:field> </activiti:taskListener> </extensionElements> </userTask> <userTask id="usertask4" name="领导三会签审批" activiti:assignee="huangke" activiti:formKey="formKey1"> <documentation>这里是会签领导(黄克)进行审批</documentation> <extensionElements> <activiti:taskListener event="complete" delegateExpression="${completeListener}"> <activiti:field name="step"> <activiti:expression>CS_MNG3_AUDIT</activiti:expression> </activiti:field> </activiti:taskListener> </extensionElements> </userTask> <sequenceFlow id="flow4" sourceRef="exclusivegateway1" targetRef="usertask3"></sequenceFlow> <sequenceFlow id="flow5" sourceRef="exclusivegateway1" targetRef="usertask4"></sequenceFlow> <parallelGateway id="exclusivegateway2" name="Exclusive Gateway"></parallelGateway> <endEvent id="endevent1" name="End"></endEvent> <sequenceFlow id="flow10" sourceRef="exclusivegateway1" targetRef="usertask2"></sequenceFlow> <serviceTask id="servicetask1" name="审批通过处理" activiti:delegateExpression="${passedServiceTask}"></serviceTask> <sequenceFlow id="flow13" sourceRef="exclusivegateway2" targetRef="servicetask1"></sequenceFlow> <sequenceFlow id="flow14" sourceRef="servicetask1" targetRef="endevent1"></sequenceFlow> <serviceTask id="servicetask2" name="审批驳回处理" activiti:delegateExpression="${rejectServiceTask}"></serviceTask> <exclusiveGateway id="gateway_1" name="Exclusive Gateway"></exclusiveGateway> <sequenceFlow id="flow17" sourceRef="usertask1" targetRef="gateway_1"></sequenceFlow> <sequenceFlow id="flow19" name="通过" sourceRef="gateway_1" targetRef="exclusivegateway1"> <documentation>is not end</documentation> <conditionExpression xsi:type="tFormalExpression"><![CDATA[${PROMNG_AUDIT_PASSED}]]></conditionExpression> </sequenceFlow> <sequenceFlow id="flow22" name="驳回" sourceRef="gateway_1" targetRef="servicetask2"> <conditionExpression xsi:type="tFormalExpression"><![CDATA[${!PROMNG_AUDIT_PASSED}]]></conditionExpression> </sequenceFlow> <sequenceFlow id="flow23" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow> <exclusiveGateway id="gateway_2" name="Exclusive Gateway"></exclusiveGateway> <exclusiveGateway id="gateway_3" name="Exclusive Gateway"></exclusiveGateway> <exclusiveGateway id="gateway_4" name="Exclusive Gateway"></exclusiveGateway> <sequenceFlow id="flow24" sourceRef="usertask2" targetRef="gateway_4"></sequenceFlow> <sequenceFlow id="flow25" sourceRef="usertask3" targetRef="gateway_3"></sequenceFlow> <sequenceFlow id="flow26" sourceRef="usertask4" targetRef="gateway_2"></sequenceFlow> <sequenceFlow id="flow27" name="通过" sourceRef="gateway_4" targetRef="exclusivegateway2"> <conditionExpression xsi:type="tFormalExpression"><![CDATA[${CS_MNG1_AUDIT_PASSED}]]></conditionExpression> </sequenceFlow> <sequenceFlow id="flow28" name="通过" sourceRef="gateway_3" targetRef="exclusivegateway2"> <conditionExpression xsi:type="tFormalExpression"><![CDATA[${CS_MNG2_AUDIT_PASSED}]]></conditionExpression> </sequenceFlow> <sequenceFlow id="flow29" name="通过" sourceRef="gateway_2" targetRef="exclusivegateway2"> <conditionExpression xsi:type="tFormalExpression"><![CDATA[${CS_MNG3_AUDIT_PASSED}]]></conditionExpression> </sequenceFlow> <sequenceFlow id="flow30" name="驳回" sourceRef="gateway_2" targetRef="servicetask2"> <conditionExpression xsi:type="tFormalExpression"><![CDATA[${!CS_MNG3_AUDIT_PASSED}]]></conditionExpression> </sequenceFlow> <sequenceFlow id="flow31" name="驳回" sourceRef="gateway_3" targetRef="servicetask2"> <conditionExpression xsi:type="tFormalExpression"><![CDATA[${!CS_MNG2_AUDIT_PASSED}]]></conditionExpression> </sequenceFlow> <sequenceFlow id="flow32" name="驳回" sourceRef="gateway_4" targetRef="servicetask2"> <conditionExpression xsi:type="tFormalExpression"><![CDATA[${!CS_MNG1_AUDIT_PASSED}]]></conditionExpression> </sequenceFlow> <endEvent id="endevent2" name="End"></endEvent> <sequenceFlow id="flow33" sourceRef="servicetask2" targetRef="endevent2"></sequenceFlow> </process> <bpmndi:BPMNDiagram id="BPMNDiagram_projectReportProcess"> <bpmndi:BPMNPlane bpmnElement="projectReportProcess" id="BPMNPlane_projectReportProcess"> <bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1"> <omgdc:Bounds height="35.0" width="35.0" x="28.0" y="209.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1"> <omgdc:Bounds height="55.0" width="105.0" x="165.0" y="199.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="usertask2" id="BPMNShape_usertask2"> <omgdc:Bounds height="55.0" width="105.0" x="580.0" y="55.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="exclusivegateway1" id="BPMNShape_exclusivegateway1"> <omgdc:Bounds height="40.0" width="40.0" x="420.0" y="207.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="usertask3" id="BPMNShape_usertask3"> <omgdc:Bounds height="55.0" width="105.0" x="580.0" y="199.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="usertask4" id="BPMNShape_usertask4"> <omgdc:Bounds height="55.0" width="105.0" x="580.0" y="339.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="exclusivegateway2" id="BPMNShape_exclusivegateway2"> <omgdc:Bounds height="40.0" width="40.0" x="960.0" y="206.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1"> <omgdc:Bounds height="35.0" width="35.0" x="1266.0" y="208.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="servicetask1" id="BPMNShape_servicetask1"> <omgdc:Bounds height="55.0" width="105.0" x="1086.0" y="198.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="servicetask2" id="BPMNShape_servicetask2"> <omgdc:Bounds height="55.0" width="105.0" x="580.0" y="460.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="gateway_1" id="BPMNShape_gateway_1"> <omgdc:Bounds height="40.0" width="40.0" x="315.0" y="206.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="gateway_2" id="BPMNShape_gateway_2"> <omgdc:Bounds height="40.0" width="40.0" x="750.0" y="346.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="gateway_3" id="BPMNShape_gateway_3"> <omgdc:Bounds height="40.0" width="40.0" x="810.0" y="207.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="gateway_4" id="BPMNShape_gateway_4"> <omgdc:Bounds height="40.0" width="40.0" x="860.0" y="62.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="endevent2" id="BPMNShape_endevent2"> <omgdc:Bounds height="35.0" width="35.0" x="615.0" y="580.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4"> <omgdi:waypoint x="460.0" y="227.0"></omgdi:waypoint> <omgdi:waypoint x="580.0" y="226.0"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow5" id="BPMNEdge_flow5"> <omgdi:waypoint x="440.0" y="247.0"></omgdi:waypoint> <omgdi:waypoint x="440.0" y="366.0"></omgdi:waypoint> <omgdi:waypoint x="580.0" y="366.0"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow10" id="BPMNEdge_flow10"> <omgdi:waypoint x="440.0" y="207.0"></omgdi:waypoint> <omgdi:waypoint x="440.0" y="82.0"></omgdi:waypoint> <omgdi:waypoint x="580.0" y="82.0"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow13" id="BPMNEdge_flow13"> <omgdi:waypoint x="1000.0" y="226.0"></omgdi:waypoint> <omgdi:waypoint x="1086.0" y="225.0"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow14" id="BPMNEdge_flow14"> <omgdi:waypoint x="1191.0" y="225.0"></omgdi:waypoint> <omgdi:waypoint x="1266.0" y="225.0"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow17" id="BPMNEdge_flow17"> <omgdi:waypoint x="270.0" y="226.0"></omgdi:waypoint> <omgdi:waypoint x="315.0" y="226.0"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow19" id="BPMNEdge_flow19"> <omgdi:waypoint x="355.0" y="226.0"></omgdi:waypoint> <omgdi:waypoint x="420.0" y="227.0"></omgdi:waypoint> <bpmndi:BPMNLabel> <omgdc:Bounds height="14.0" width="24.0" x="-32.0" y="0.0"></omgdc:Bounds> </bpmndi:BPMNLabel> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow22" id="BPMNEdge_flow22"> <omgdi:waypoint x="335.0" y="246.0"></omgdi:waypoint> <omgdi:waypoint x="335.0" y="487.0"></omgdi:waypoint> <omgdi:waypoint x="580.0" y="487.0"></omgdi:waypoint> <bpmndi:BPMNLabel> <omgdc:Bounds height="14.0" width="100.0" x="10.0" y="0.0"></omgdc:Bounds> </bpmndi:BPMNLabel> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow23" id="BPMNEdge_flow23"> <omgdi:waypoint x="63.0" y="226.0"></omgdi:waypoint> <omgdi:waypoint x="165.0" y="226.0"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow24" id="BPMNEdge_flow24"> <omgdi:waypoint x="685.0" y="82.0"></omgdi:waypoint> <omgdi:waypoint x="860.0" y="82.0"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow25" id="BPMNEdge_flow25"> <omgdi:waypoint x="685.0" y="226.0"></omgdi:waypoint> <omgdi:waypoint x="810.0" y="227.0"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow26" id="BPMNEdge_flow26"> <omgdi:waypoint x="685.0" y="366.0"></omgdi:waypoint> <omgdi:waypoint x="750.0" y="366.0"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow27" id="BPMNEdge_flow27"> <omgdi:waypoint x="900.0" y="82.0"></omgdi:waypoint> <omgdi:waypoint x="980.0" y="82.0"></omgdi:waypoint> <omgdi:waypoint x="980.0" y="206.0"></omgdi:waypoint> <bpmndi:BPMNLabel> <omgdc:Bounds height="14.0" width="24.0" x="10.0" y="0.0"></omgdc:Bounds> </bpmndi:BPMNLabel> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow28" id="BPMNEdge_flow28"> <omgdi:waypoint x="850.0" y="227.0"></omgdi:waypoint> <omgdi:waypoint x="960.0" y="226.0"></omgdi:waypoint> <bpmndi:BPMNLabel> <omgdc:Bounds height="14.0" width="24.0" x="10.0" y="0.0"></omgdc:Bounds> </bpmndi:BPMNLabel> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow29" id="BPMNEdge_flow29"> <omgdi:waypoint x="790.0" y="366.0"></omgdi:waypoint> <omgdi:waypoint x="980.0" y="366.0"></omgdi:waypoint> <omgdi:waypoint x="980.0" y="246.0"></omgdi:waypoint> <bpmndi:BPMNLabel> <omgdc:Bounds height="14.0" width="24.0" x="10.0" y="0.0"></omgdc:Bounds> </bpmndi:BPMNLabel> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow30" id="BPMNEdge_flow30"> <omgdi:waypoint x="770.0" y="386.0"></omgdi:waypoint> <omgdi:waypoint x="769.0" y="487.0"></omgdi:waypoint> <omgdi:waypoint x="685.0" y="487.0"></omgdi:waypoint> <bpmndi:BPMNLabel> <omgdc:Bounds height="14.0" width="36.0" x="10.0" y="0.0"></omgdc:Bounds> </bpmndi:BPMNLabel> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow31" id="BPMNEdge_flow31"> <omgdi:waypoint x="830.0" y="247.0"></omgdi:waypoint> <omgdi:waypoint x="830.0" y="486.0"></omgdi:waypoint> <omgdi:waypoint x="685.0" y="487.0"></omgdi:waypoint> <bpmndi:BPMNLabel> <omgdc:Bounds height="14.0" width="36.0" x="10.0" y="0.0"></omgdc:Bounds> </bpmndi:BPMNLabel> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow32" id="BPMNEdge_flow32"> <omgdi:waypoint x="880.0" y="102.0"></omgdi:waypoint> <omgdi:waypoint x="880.0" y="487.0"></omgdi:waypoint> <omgdi:waypoint x="685.0" y="487.0"></omgdi:waypoint> <bpmndi:BPMNLabel> <omgdc:Bounds height="14.0" width="36.0" x="10.0" y="0.0"></omgdc:Bounds> </bpmndi:BPMNLabel> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow33" id="BPMNEdge_flow33"> <omgdi:waypoint x="632.0" y="515.0"></omgdi:waypoint> <omgdi:waypoint x="632.0" y="580.0"></omgdi:waypoint> </bpmndi:BPMNEdge> </bpmndi:BPMNPlane> </bpmndi:BPMNDiagram> </definitions>
- 关于xml的内容我会在下面详细解释。
- 2) 如何将生成好的流程文件部署到我们的项目中。
首先我需要介绍一下Activit中的一些主要API接口类,如下图:
在独立开发环境中可以使用activiti.cfg.xml进行配置,如果项目中使用了spring也可以直接在spring的bean定义中进行配置,在我们的项目中使用spring,配置如下:
<bean id="AriesIdentityService" class="com.share.bpm.service.impl.AriesIdentityServiceImpl"/> <bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration"> <property name="databaseType" value="mysql" /> <property name="dataSource" ref="dataSource" /> <property name="transactionManager" ref="transactionManager" /> <property name="databaseSchemaUpdate" value="true" /> <property name="enableSafeBpmnXml" value="true"/> <property name="jobExecutorActivate" value="true" /> <property name="identityService" ref="AriesIdentityService" /> <property name="deploymentResources" value="classpath*:ProjectReportProcess.bpmn20.xml" /> </bean> <bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean"> <property name="processEngineConfiguration" ref="processEngineConfiguration" /> </bean> <bean id="repositoryService" factory-bean="processEngine" factory-method="getRepositoryService" /> <bean id="runtimeService" factory-bean="processEngine" factory-method="getRuntimeService" /> <bean id="taskService" factory-bean="processEngine" factory-method="getTaskService" /> <bean id="identityService" factory-bean="processEngine" factory-method="getIdentityService" /> <bean id="formService" factory-bean="processEngine" factory-method="getFormService" /> <bean id="historyService" factory-bean="processEngine" factory-method="getHistoryService" /> <bean id="managementService" factory-bean="processEngine" factory-method="getManagementService" />
上面配置中已经配置好了processEngine以及API里面的基本类,唯一需要说明的是AriesIdentityService这个bean是将Activiti的用户组与我们目前系统当中的用户和职位进行关联。网上主要有三种同步的方案,在这里我们采用的是方案二:覆盖IdentifyService接口的实现。具体解释请参考网址:
可以看到橘***的部分就是我刚刚生成的流程定义文件,我是将它作为processEngineConfiguration的属性deploymentResources进行部署的,这样在spring加载时就可以自动进行部署,当然还有别的方法,比如在服务器运行时可以通过
API进行编程式部署:
String barFileName = "path/to/process-one.bar"; ZipInputStream inputStream = new ZipInputStream(new FileInputStream(barFileName)); repositoryService.createDeployment() .name("process-one.bar") .addZipInputStream(inputStream) .deploy();
或者参考ProcessDefinitionAction.deploy()
使用Ant进行部署
<taskdef name="deploy-bar" classname="org.activiti.engine.impl.ant.DeployBarTask"> <classpath> <fileset dir="..."> <include name="activiti-cfg.jar" /> <include name="your-db-driver.jar" /> </fileset> <fileset dir="${activiti.home}/lib"> <include name="activiti-engine-${activiti.version}.jar" /> <include name="ibatis-sqlmap-*.jar" /> </fileset> </classpath> </taskdef> <deploy-bar file=".../yourprocess.bar" />
使用Activiti Probe部署
略
需要注意的是每次部署后,如果定义文件名称相同,那么在Activiti内部会将流程定义的版本号增加,已经在运行的流程依然会使用老的版本号,新创建的流程则会使用最新版本的流程定义。在打包部署时还可以将流程图一并部署,可以分不同的流程图,但名称要与流传给您定义文件相同,如果没有上传流程图,系统会自动生成一个所有流程的流程到一张图中。
3) 流程的流转以及相关配置
启动流程
对于我们上面部署的项目审批流程来说,我选在了在项目列表后面添加启动流程按钮,当点击按钮后,执行了如下API操作:(ProjectStageAction.java)
Map<String,Object> variables = new HashMap<String,Object>(); variables.put("id", nowProject.getId()); variables.put("stageId", nowStage.getId()); //启动流程实例 ProcessInstance pi = runtimeService.startProcessInstanceByKey("projectReportProcess",variables); nowProject.setProcessId(pi.getId()); nowProject.setAuditStatus(ProjectAuditStatus.SUBMIT_TO_PROMNG); dao.update(nowProject);
上面这段代码首先创建了流程实例需要用到的两个变量id,和stageId并在流程启动时设置到ProcessInstance流程实例中,这两个变量在流程的任何步骤中都可以获取和改变。使用runtimeService.startProcessInstanceByKey方法来启动流程实例,key就是流程定义文件中的process属性id。
此外我们还将流程实例id(pi.getId())更新给了项目(nowProject)并设置项目状态为SUBMIT_TO_PROMNG(已提交待审批)
关于流程流转状态定义在Enum中(请参考枚举类:ProjectAuditStatus)
具体定义如下:
//0 在点击"提交审批"按钮前,项目的审批状态 NO_SUBMIT("未提交"), //1 提交后,项目的状态 SUBMIT_TO_PROMNG("已提交待审批"), //2 项目领导审批通过 PROMNG_PASSED("项目领导审批通过"), //3 项目领导审批未通过 PROMNG_REJECT("项目领导审批驳回"), //4 部分会签领导审批未通过 CS_MNG_REJECT("会签领导审批驳回"), //5 部分会签领导审批通过 CS_MNG_PART_PASSED("会签领导审批中"), //6 全部会签领导审批通过 PASSED("项目审批通过");
此状态只针对目前项目审批流程,后续开发中还可能会定义更多的流程状态。
项目领导审批
流程启动后就会流转到第一个UserTask — 项目领导审批,让我们看看上面的流程定义xml文件:
<userTask id="usertask1" name="项目领导审批报告" activiti:assignee="houyousong" activiti:formKey="formKey1"> <documentation>这里是项目领导首先进行审批</documentation> <extensionElements> <activiti:taskListener event="complete" delegateExpression="${completeListener}"> <activiti:field name="step"> <activiti:expression>PROMNG_AUDIT</activiti:expression> </activiti:field> </activiti:taskListener> </extensionElements> </userTask>
可以看到activiti:assignee属性指定了谁拥有这个任务,这个字符串必须与passport中的用户名匹配,现在是houyousong(侯友松),那么任务就会停留,直到侯友松完成了这个任务,流程才会继续执行,下面我们看看侯友松的“我的任务”列表:
这个列表列出了当前用户所拥有的所有任务,对应的Action是com.share.actions.bpm. MyTaskListAction,点击“开始审批”,跳转到com.fangxin365.actions.bpm.project.ProjectReportProcessAction的goTaskForm()方法,传入的参数是taskId,处理如下:
@Action(value = "goTaskForm", results = { @Result(name = "success", location = "/WEB-INF/json/jsonText.jsp"), @Result(name="formKey1", type = "redirectAction", params = { "namespace","/admin/food","actionName", "fangxin_project_show", "pageSize", "${pageSize}", "pageNum", "${pageNum}", "id","${projectId}","stageId","${stageId}","isAudit","true", "taskId","${taskId}" }) }) public String goTaskForm(){ Map<String,Object> data = new HashMap<String, Object>(); data.put("error", null); if(null != taskId){ TaskFormData tfd = formService.getTaskFormData(taskId); this.projectId = String.valueOf(taskService.getVariable(taskId, "id")); this.stageId = String.valueOf(taskService.getVariable(taskId,"stageId")); log.info("projectId:"+projectId+",stageId:"+stageId); if(null != tfd){ String formKey = tfd.getFormKey(); data.put("taskFormKey", formKey); if(formKey != null){ return formKey; } } else{ data.put("error", "formKeyNull"); } } else{ data.put("error", "taskIdNull"); } return SUCCESS; }
方法中主要逻辑是通过TaskId找到流程定义xml中的formKey,也就是“formKey1”,然后跳转到对应的Action,这里是fangxin_project_show并把参数一起传递过去。
fangxin_project_show对应的Action是FangxinProjectAction的show()方法,该Action根据是否有isAudit参数决定重定向到项目编辑页面还是到项目审计页面,我们传递了isAudit,所以会调转到审批页面。
在页面中点击审批弹出对话框
输入审批意见后,可以审批通过或者驳回, 点击这两个按钮后会提交到
com.fangxin365.actions.bpm.project.doAudit()方法:
/** * 执行审批 * @return */ @Action(value = "do_audit", results = { @Result(name = "success", location = "/WEB-INF/json/jsonText.jsp")}) public String doAudit(){ Map<String,Object> data = new HashMap<String, Object>(); data.put("error", null); String pass_key = taskId.concat("_passed"); String desc_key = taskId.concat("_DESC"); taskService.setVariable(taskId, pass_key, isAudit); taskService.setVariable(taskId, desc_key, auditDesc); taskService.complete(taskId); ActionContext.getContext().put("data", data); return SUCCESS; }
审批是否通过传递给参数isAudit (Boolean),审批意见传递给参数auditDesc, doAudit()中完成本任务使用:
taskService.complete(taskId);
这样,侯友松的任务就完成了,同时taskId作为key审批结果作为value存储到了流程变量中(”${taskId}_passed”),这个变量是为了在TaskListener中能够根据taskId捕获到审批的结果。TaskListener顾名思义可以监听task的事件,目前我定义的事件是complete即当task完成时触发,taskListener的配置也在流程定义中:
<activiti:taskListener event="complete" delegateExpression="${completeListener}">
在userTask中的delegateExpression指明了task的引用:”${completeListener}”这个是一个EL,应用的是Spring中对应的bean,在这里是com.fangxin365.bpm.project.listener. ProjectAuditTaskCompleteListener
该类实现了TaskListener接口,需要实现notify方法,具体实现如下:
public void notify(DelegateTask task) { //更新项目状态 Object stageido = task.getVariable("stageId"); if(null != stageido){ Long stageId = Long.parseLong(stageido.toString()); ProjectStage ps = (ProjectStage)authDao.load(ProjectStage.class, stageId); FangxinProject fp = ps.getProject(); String taskId = task.getId(); String key = taskId.concat("_passed"); Object value = task.getVariable(key); String stepStr = step.getExpressionText(); if(value != null){ boolean isPassed = Boolean.valueOf(value.toString()); if(stepStr.equals("PROMNG_AUDIT")){ if(isPassed){ ps.setAuditStatus(ProjectAuditStatus.PROMNG_PASSED); fp.setAuditStatus(ProjectAuditStatus.PROMNG_PASSED); } else{ ps.setAuditStatus(ProjectAuditStatus.PROMNG_REJECT); fp.setAuditStatus(ProjectAuditStatus.PROMNG_REJECT); } } else if(stepStr.equals("CS_MNG1_AUDIT") || stepStr.equals("CS_MNG2_AUDIT") || stepStr.equals("CS_MNG3_AUDIT")){ if(isPassed){ ps.setAuditStatus(ProjectAuditStatus.CS_MNG_PART_PASSED); fp.setAuditStatus(ProjectAuditStatus.CS_MNG_PART_PASSED); } else{ ps.setAuditStatus(ProjectAuditStatus.CS_MNG_REJECT); fp.setAuditStatus(ProjectAuditStatus.CS_MNG_REJECT); } } task.setVariable(stepStr+"_PASSED", isPassed); //TODO:异步保存流程审计日志,可以使用Event String desc = task.getVariable(taskId.concat("_DESC")).toString(); BpmAuditLog bal = new BpmAuditLog(); bal.setAuditDesc(desc); bal.setAuditTime(new Date()); bal.setAuditType(step.getExpressionText()); bal.setIsPassed(isPassed); bal.setTaskId(taskId); bal.setProcessIncId(task.getProcessInstanceId()); bal.setOwner(SystemUtils.getUser(true)); bal.setCommentStr1("ProjectAudit"); bal.setCommentStr2(String.valueOf(task.getVariable("id"))); authDao.update(fp); authDao.save(bal); }
这个listener的核心任务是1,更新project对象和projectStage对象的auditStatus状态,2设置step变量到流程中,3记录审计日志并持久化。
String stepStr = step.getExpressionText();
这个stepStr是从流程定义文件中而来:
<activiti:field name="step"> <activiti:expression>PROMNG_AUDIT</activiti:expression> </activiti:field>
也就是“PROMNG_AUDIT”,这里可以写死是因为流程step在流程定义中是确定的。
设置流程流转判断变量:
task.setVariable(stepStr+"_PASSED", isPassed);
- 这里设置的流程变量是为了在接下来的分支进行判断,根据现在设置的这个流程变量决定结束流程还是继续。(在这里变量的key是“PROMNG_AUDIT_PASSED”)
最后在方法中保存审计日志,目前写在了方法里,应该改为异步保存,以不影响流程的正常执行(Event机制)。 - 4) 流程的流转判断
流程的流转由流程定义文件中的sequenceFlow决定,具体到这里通过审批的流转:
<sequenceFlow id="flow19" name="通过" sourceRef="gateway_1" targetRef="exclusivegateway1"> <conditionExpression xsi:type="tFormalExpression"><![CDATA[${PROMNG_AUDIT_PASSED}]]></conditionExpression> </sequenceFlow>
没有通过审批的流转:
<sequenceFlow id="flow22" name="驳回" sourceRef="gateway_1" targetRef="servicetask2"> <conditionExpression xsi:type="tFormalExpression"><![CDATA[${!PROMNG_AUDIT_PASSED}]]></conditionExpression> </sequenceFlow>
- 我们看到,都是使用上面我们设置的流程变量“PROMNG_AUDIT_PASSED”,如果为真,继续流转到下一步,如果为假,流程结束。
- 5) 流程的分支
假设审批通过了,我们可以看到流程将流转到targetRef=”exclusivegateway1″,这是一个并行分支,分支分别流向了三个userTask:usertask2、usertask3和usertask4
分别对应着会签领导一审批、会签领导二审批和会签领导三审批,流程会创建三个流程任务分别指派个这三个人,当然这只是一种安人来分配任务的方式,还可以按照职位进行分配。
说明一下流程图中常用的两种Gateway:
- ParallelGateway会并行的执行后面的每一个任务,或者当前面的每一个任务都执行后才继续执行后面的任务,我在流程中使用它来进行会签分支。
ExclusiveGateway会有条件的执行后面的任务,我在流程中使用它判断是否审批通过从而决定结束审批还是继续。 - 6) 接下来的会签领导审批
具体到会签领导的审批,其实和项目领导审批的步骤是一样的,甚至Action和listener都是可以复用的,因为他们的不同都写在了流程定义文件中,对于每个任务的具体处理逻辑实际上是相似的。需要注意的是会签后的结果。如果三个(或者多个)领导会签都审批通过了,那么将流转至最终的审批通过的任务
<serviceTask id="servicetask1" name="审批通过处理" activiti:delegateExpression="${passedServiceTask}"></serviceTask>
如果至少有一个会签领导审批未通过,则流程结束,并流转到审批驳回处理的serviceTask
<serviceTask id="servicetask2" name="审批驳回处理" activiti:delegateExpression="${rejectServiceTask}"></serviceTask>
- 7) 流程结束
经过serviceTask(不需要人工处理)后,流程将结束。流程实例被销毁,流程申明周期结束。
可以将需要处理的逻辑卸载serviceTask中,如发邮件(目前没有内容)。 - 8) 接下来的工作
目前流程相关代码在分支 Branch_branch_bpm_02_19 中。
根据候总上次会议的要求,需要在组内会签审批后到会签审批小组继续审批一次,这个流程还没有实现,但是原理与目前的会签审批类似,甚至不需要新增task和listener只需要增加流程流转状态即可,在目前会签流程后在增加一组会签流程即可。 - 9) Activiti学习资源
以上为项目中用到的一些Activiti流程分析,
更多的内容请参考用户手册:http://activiti.org/userguide/index.html
以及在线API
http://activiti.org/javadocs/index.html
另外十分钟教程也不错
http://activiti.org/userguide/index.html#10minutetutorial
还有,下载Activity的war包部署后学习也是不错的方法!