首页 > 代码库 > ACTIVITI流程使用说明

ACTIVITI流程使用说明

ACTIVITI流程使用说明

1) 流程图的绘制

流程图可以使用eclipse插件完成,eclipse plug 地址:http://activiti.org/designer/update/
通过插件的绘制流程功能,绘制好的流程图如下图:

wKiom1RgUX6wh4riAAF4VimWMeE349.jpg

最终可以保存为*.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接口类,如下图:

wKioL1RgUimwfaRsAAD96zaZvIo074.jpg

在独立开发环境中可以使用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="http://www.mamicode.com/mysql" />
    <property name="dataSource" ref="dataSource" />
    <property name="transactionManager" ref="transactionManager" />
    <property name="databaseSchemaUpdate" value="http://www.mamicode.com/true" />
    <property name="enableSafeBpmnXml" value="http://www.mamicode.com/true"/>
    <property name="jobExecutorActivate" value="http://www.mamicode.com/true" />
    <property name="identityService" ref="AriesIdentityService" />
    <property name="deploymentResources"
		value="http://www.mamicode.com/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:

wKioL1RgU2GTWWvTAAA4lvxDlk8597.jpg

  • 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包部署后学习也是不错的方法!


ACTIVITI流程使用说明