Activiti5

Starter

依赖包

<dependency>
    <groupId>org.activiti</groupId>
    <artifactId>activiti-engine</artifactId>
    <version>${activiti-version}</version>
</dependency>
<dependency>
    <groupId>org.activiti</groupId>
    <artifactId>activiti-spring</artifactId>
    <version>${activiti-version}</version>
</dependency>

根据需要,加入DB,例如:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.21</version>
</dependency>
<dependency>
    <groupId>oracle</groupId>
    <artifactId>oracle</artifactId>
    <version>11.2</version>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.3.168</version>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>9.4-1201-jdbc41</version>
</dependency>

流程设计工具

  1. 使用Activiti Modeler(网页流程设计器)
    • 一般适用于业务设计人员
    • Signavio 授权的基于Web的开源BPMN设计器
    • BPMN 2.0规范
    • 获取后置于tomcat中启用,获取方式:
      • 方式一:svn checkout http://signavio-core-components.googlecode.com/svn/trunk
      • 方式二:activiti包下的activiti-explorer.war(内置了Activiti Modeler组件)
    • 还可将此组件加入到自己项目中,为项目提供在线流程设计功能:
      <dependency> 
        <groupId>org.activiti</groupId>
        <artifactId>activiti-modeler</artifactId>
        <version>${activiti-version}</version>
      </dependency>
      
  2. 使用Activiti Designer(Eclipse插件)
    • 一般适用于开发人员

ProcessEngine(流程引擎)

ProcessEngineConfiguration:

  • org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration: 单独运行的流程引擎。
    • Activiti会自己处理事务
    • 默认
    • 数据库只在引擎启动时检测 (如果没有Activiti的表或者表结构不正确就会抛出异常)
  • org.activiti.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration: 单元测试时的辅助类。
    • Activiti会自己控制事务。
    • 默认使用H2内存数据库。
    • 数据库表会在引擎启动时创建,关闭时删除。
    • 使用它时,不需要其他配置(除非使用job执行器或邮件功能)
  • org.activiti.spring.SpringProcessEngineConfiguration: 在Spring环境下使用流程引擎
  • org.activiti.engine.impl.cfg.JtaProcessEngineConfiguration: 单独运行流程引擎,并使用JTA事务。

创建ProcessEngine:

方式一:

/*
 * 不使用配置文件,创建ProcessEngine对象
 */
@Test
public void envTest1(){
    //1.创建流程引擎配置对象ProcessEngineConfiguration
    ProcessEngineConfiguration configuration=ProcessEngineConfiguration.createStandaloneProcessEngineConfiguration();
    //2.配置
    //2.1)完善数据库相关配置--这里使用MySQL(默认为H2)
    configuration.setJdbcDriver("com.mysql.jdbc.Driver");
    configuration.setJdbcUrl("jdbc:mysql://localhost:3306/activitiTest?createDatabaseIfNotExist=true");
    configuration.setJdbcUsername("xxxx");
    configuration.setJdbcPassword("xxxxxx");
    //2.2)配置数据库建表策略,默认为false
    configuration.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
    //3.创建ProcessEngine
    ProcessEngine processEngine=configuration.buildProcessEngine();
    System.out.println(processEngine.getName());
}

方式二:

/*
 * 使用配置文件,加载创建ProcessEngine对象
 * 默认加载classpath下的activiti.cfg.xml文件
 */
@Test
public void envTest2(){
    //最简单获取ProcessEngine对象的方式
    ProcessEngine processEngine=ProcessEngines.getDefaultProcessEngine();
    System.out.println(processEngine.getName());
}

activiti.cfg.xml:

<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
    <property name="jdbcDriver" value="com.mysql.jdbc.Driver"/>
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/activitiTest?createDatabaseIfNotExist=true"/>
    <property name="jdbcUsername" value="xxxx"/>
    <property name="jdbcPassword" value="xxxxxx"/>
    <property name="databaseSchemaUpdate" value="true"/>
</bean>

ProcessDefinition(流程定义)

使用设计工具生成流程定义文件:

  • bpmn流程规则文件(xml格式)
  • png流程图片(自动生成)
  • 例如:HelloWorld.bpmn,HelloWorld.png

通过RepositoryServiceProcessDefinition进行操作

ProcessEngine processEngine=ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService=this.processEngine.getRepositoryService();

发布流程定义

  • 方式一:逐个添加资源文件
  • 方式二:添加zip包 (Activiti框架会自动解压解析,依次添加到act_ge_bytearray表中)
@Test
public void deployProcess()
{
    DeploymentBuilder builder=repositoryService.createDeployment();

    //方式一:
    /*InputStream bpmnInput=this.getClass().getClassLoader().getResourceAsStream("bpmn/HelloWorld.bpmn");
    InputStream pngInput=this.getClass().getClassLoader().getResourceAsStream("bpmn/HelloWorld.png");
    builder.name("Hello World!")
            .addInputStream("HelloWorld.bpmn", bpmnInput)
            .addInputStream("HelloWorld.png", pngInput)
            .deploy();*/
    //方式二:
    InputStream in=this.getClass().getClassLoader().getResourceAsStream("bpmn/HelloWorld.zip");
    ZipInputStream zipInputStream =new ZipInputStream(in);
    builder.name("Hello World!")
            .addZipInputStream(zipInputStream)
            .deploy();
}

发布成功后,会在3张表中添加数据:

  • act_re_deployment: 1条记录:
    • name:Process的显示别名
    • deploy_time:发布时间
  • act_ge_bytearray: 一般为2条记录(通过deployment_id关联act_re_deployment表):
    • 1条bpmn文件内容
    • 1条png文件内容
  • act_re_procdef: 1条记录(Process定义的相关信息,通过deployment_id关联act_re_deployment表)
    • id:{key}:{version}:{random}
    • version:发布时计算(同key的maxVersion+1,默认取1)
    • name:bpmn文件中定义的process节点的name属性
    • key:bpmn文件中定义的process节点的id属性
    • deployment_id:关联act_re_deployment表

PS:一定要有bpmn流程规则文件,png流程图片可选,也可加入其它文件,比如video,txt等

查看流程定义信息

  1. Process概括信息:ProcessDefinition

    • 流程定义对象:流程规则的整体信息
    • 记录在act_re_procdef表中
  2. Process内部子节点(即活动)等详细信息:ActivityImpl

    • 活动对象:流程规则的活动信息
    • 记录在bmpn文件中(运行时加载解析bmpn文件到内存中)
  3. 注意:

    • 通过ProcessDefinitionQuery对象获取的是最简单的ProcessDefinition对象(从act_re_procdef表中查询)
    • 要获取Process内部的活动定义信息,需通过RepositoryServicegetProcessDefinition(id)方法(从内存中获取存放的活动Activity定义信息)
@Test
public void queryProcessDefinition()
{
    String key="helloWorld";
    List<ProcessDefinition> pds=repositoryService.createProcessDefinitionQuery().processDefinitionKey(key).list();
    for(ProcessDefinition pd:pds)
    {
        System.out.println("id:"+pd.getId()+",name:"+pd.getName()+",key:"+pd.getKey()+",version:"+pd.getVersion());
        //System.out.println(((ProcessDefinitionImpl)pd).getActivities());    //这样无法获得Activity信息 (这个ProcessDefinition是从DB中查询得到的)
        ProcessDefinitionImpl pdImpl=(ProcessDefinitionImpl)repositoryService.getProcessDefinition(pd.getId()); 
        //这个ProcessDefinition是对应的bpmn文件解析构建的,即内存中(实际是ProcessDefinitionEntity对象)
        System.out.println(pdImpl.getActivities());        //activity中包含Transitions等信息 (Actitity即节点)
    }
}

查看流程附件

查看流程附件(例如:流程图片)

  • 存放在表act_ge_bytearray的Bytes字段中(type为BLOB)
  • 通过 deployment_id & 文件名name 查找
@Test
public void queryProcessImage() throws IOException
{
    //eg1.获取附件,例如png图片,也可获取其他resource文件:
    String deploymentId="201";
    List<String> resources=repositoryService.getDeploymentResourceNames(deploymentId);    //通过发布ID,获取此发布的所有资源文件名
    String imgName=null;
    for(String res:resources)    //文件名过滤
    {
        System.out.println(res);
        if(res.endsWith(".png")){
            imgName=res;
            break;
        }
    }
    if(imgName!=null && imgName.length()!=0){
        //查找指定资源,以流的形式读出
        InputStream in=repositoryService.getResourceAsStream(deploymentId, imgName);
        FileUtils.copyInputStreamToFile(in, new File("D:/hello.png"));
    }

    //eg2.直接获取流程图:
    String processDefinitionId="helloWorld:2:304";
    InputStream in=repositoryService.getProcessDiagram(processDefinitionId);
    FileUtils.copyInputStreamToFile(in, new File("D:/hello2.png"));
}

删除流程定义

删除发布的流程 (act_re_deployment):

  • 方式一:普通删除 repositoryService.deleteDeployment(deploymentId);

    • 等价于:repositoryService.deleteDeployment(deploymentId, false);
    • 只删除发布流程相关的信息
    • 若当前ProcessDefinition有正在执行的ProcessInstance,则删除失败(保护),因为act_ru_*中有对ProcessDefinition id的引用
    • 删除下表记录:
      • act_re_deployment
      • act_ge_bytearray
      • act_re_procdef
    • 不会删除history(act_hi_*)
  • 方式二:级联删除 repositoryService.deleteDeployment(deploymentId, true);

    • 会删除ProcessDefinitionProcessInstance,包括history (暴力)
    • 删除下表记录:
      • act_re_deployment
      • act_ge_bytearray
      • act_re_procdef
      • act_ru_*
      • act_hi_*
@Test
public void deleteDeployment()
{
    String key="myProcess";
    List<Deployment> deployments=repositoryService.createDeploymentQuery().processDefinitionKey(key).list();
    for(Deployment deployment:deployments)
    {
        //方式一:
        repositoryService.deleteDeployment(deployment.getId());    
        //方式二:
        //repositoryService.deleteDeployment(deployment.getId(), true);
    }
}

ProcessInstance(流程实例)

ProcessInstance特点:

  1. 一个Process只有一个ProcessInstance
    • ID不变
    • 可自定义ID规则,eg:{对象名称}-{对象ID}
  2. 流程实例ProcessInstance是一个特殊的Execution对象(ProcessInstance extends Execution
    • 代表一次执行,永远指向当前所在的活动节点(类似遍历链式结构的指针,指向当前节点)
    • 对于单线流程,描述每个节点的Execution对象就是ProcessInstance;
    • 对于分支流程,会在分支的最顶部创建一个Execution对象作为ProcessInstance来描述这个分支( 每个分支下产生的Execution对象都会挂在此ProcessInstance下)

启动流程

启动流程(生成ProcessInstance):runtimeService.startProcessInstanceByXxx

  • 方式一:通过processDefinition的Id启动流程
  • 方式二:通过ProcessDefinition的key启动流程(系统会使用此key下版本最高的ProcessDefinition)
@Test
public void startProcess(){

    //RuntimeService 负责流程的启动,流转等操作
    RuntimeService runtimeService=this.processEngine.getRuntimeService();

    //方式一:
    /*String id="helloWorld:1:204";
    ProcessInstance processInstance=runtimeService.startProcessInstanceById(id);*/

    //方式二:
    String key="helloWorld";
    ProcessInstance processInstance=runtimeService.startProcessInstanceByKey(key);

    System.out.println("id:"+processInstance.getId()+",activityId:"+processInstance.getActivityId());
}

启动成功后:

  • act_ru_execution表: 记录当前活动(只要流程到达某一个节点,即具体活动,都会在这张表中添加数据)
  • act_ru_task表: 记录人工参与的任务信息 (主要记录:任务办理者assignee,任务分配时间create_Time)

查看流程状态

查看流程状态,使用ProcessInstanceQuery (通过runtimeService.createProcessInstanceQuery()获取)

@Test
public void queryProcessState()
{
    String pid="1001";
    ProcessInstance processInstance=this.runtimeService.createProcessInstanceQuery()
        .processInstanceId(pid)
        .singleResult();
    if(processInstance!=null)
        System.out.println("id:"+processInstance.getId()+",activityId:"+processInstance.getActivityId());
}

Task(任务)

查看任务

查看任务,使用TaskQuery(通过taskService.createTaskQuery()获取),主要查询表act_ru_task

/**
 * 查看任务——公有任务candidate (candicateUser,candicateGroup -- act_ru_identitylink)
 * taskService.createTaskQuery().taskCandidateXxx
 * */
@Test
public void queryCandidateTask()
{
    String candidateGroup="ROLE_User";
    List<Task> tasks=this.taskService.createTaskQuery().taskCandidateGroup(candidateGroup).list();
    System.out.println("-----------["+candidateGroup+"]可认领任务列表");

    for(Task task:tasks)
        System.out.println("id:"+task.getId()+",name:"+task.getName()+",assignee:"+task.getAssignee()+",createTime:"+task.getCreateTime());

    String candidateUser="Lucy";
    tasks=this.taskService.createTaskQuery().taskCandidateUser(candidateUser).list();
    System.out.println("-----------["+candidateUser+"]可认领任务列表");

    for(Task task:tasks)
        System.out.println("id:"+task.getId()+",name:"+task.getName()+",assignee:"+task.getAssignee()+",createTime:"+task.getCreateTime());
}

/**
 * 查看任务——私有任务assignee
 * taskService.createTaskQuery().taskAssignee
 * */
@Test
public void queryAssigneeTask()
{
    String assignee="CEO Tom";
    List<Task> tasks=this.taskService.createTaskQuery()
                                    .taskAssignee(assignee)
                                    .orderByTaskCreateTime().desc()
                                    .list();
    System.out.println("-----------["+assignee+"]私有任务列表");
    for(Task task:tasks)
        System.out.println("id:"+task.getId()+",name:"+task.getName()+",assignee:"+task.getAssignee()+",createTime:"+task.getCreateTime());
}

办理任务

公有任务需被认领(使用taskService.claim)后才可执行

@Test
public void claimTask(){
    String taskId="1202";
    String userId="Lucy";
    this.taskService.claim(taskId, userId);
}

办理任务(使用taskService.complete(taskId)

@Test
public void doTask()
{
    String taskId="1302";
    this.taskService.complete(taskId);
}

完成task后会:

  • 会在act_hi_*表中添加记录:act_hi_actinst,act_hi_taskinst,act_hi_proinst
  • 删除act_ru_*表中的相关记录::act_ru_execution,act_ru_task

监听器

  • TaskListener:任务监听器,监听事件:
    1. assignment -- 给指定人分配task时触发
      • 在bpmn文件中指定;
      • 认领任务后;
      • create时setAssignee后;
      • 注意:直接task.setAssignee(...)并不会触发
    2. create -- 启动task时触发
    3. complete -- 完成task时触发
    4. delete -- 结束后删除task时触发
  • ExecutionListener:执行节点监听器,一般监听事件:
    1. start
    2. end
  • 使用:
    • 使用expression设置
    • 使用delegateExpression设置
    • 使用class
    • 。。。

使用举例1(TaskListener,delegateExpression): 1) xxx.bpmn.xml:

<userTask id="usertask1" name="User Task1">
  <extensionElements>
    <activiti:taskListener event="create" delegateExpression="${springTaskListenerBean}"></activiti:taskListener>
  </extensionElements>
</userTask>

2) SpringTaskListenerBean.java:

@Component
public class SpringTaskListenerBean implements TaskListener {
    @Override
    public void notify(DelegateTask delegateTask) {
        delegateTask.setVariable("status", delegateTask.getEventName());
        System.out.println(delegateTask.getEventName()+" : "+delegateTask.getName()
                +" ( status: "+delegateTask.getVariable("status")+")");
    }
}

使用举例2(TaskListener,expression): 1) xxx.bpmn.xml

 <userTask id="reqApprove" name="TPM Manager 审批" activiti:assignee="${approver}">
      <extensionElements>
        <activiti:taskListener event="assignment" expression="${processService.doProcApproverAssignment(task)}"></activiti:taskListener>
      </extensionElements>
    </userTask>

2) ProcessService.java

@Service("processService")
public class ProcessService{

    public void doProcApproverAssignment(DelegateTask delegateTask){
        System.out.println("Do Proc Approver Assignement...");
        String reqId=(String)delegateTask.getVariable("reqId");
        System.out.println(delegateTask.getAssignee());
        ...
    }
}

使用举例3(executionListener,delegateExpression):

 <startEvent id="basicSingleStart" name="Start" activiti:initiator="requesterUser">
  <extensionElements>
    <activiti:executionListener event="end" delegateExpression="${procStartListener}"></activiti:executionListener>
  </extensionElements>
</startEvent>
public class ProcStartListener implements TaskListener
{
    @Override
    public void notify(DelegateTask delegateTask)
    {
        System.out.println("Process Start:"+delegateTask.getName());
        String reqId=(String)delegateTask.getVariable(ProcessVariable.reqId);
        if(reqId!=null)
        ...
    }
}

使用举例3(executionListener,expression): 1) xxx.bpmn.xml

<startEvent id="basicSingleStart" name="Start">
  <extensionElements>
    <activiti:executionListener event="end" expression="${processService.setProcBusinessKey(execution)}"></activiti:executionListener>
  </extensionElements>
</startEvent>

2) ProcessService.java

@Service("processService")
public class ProcessService{
    public void setProcBusinessKey(DelegateExecution execution){
        System.out.println("Set Proc BusinessKey...");
        String key=execution.getProcessBusinessKey();
        if(key==null){
            key=(String)execution.getVariable(ProcessVariable.reqId);
            System.out.println("Set Basic Process BusinessKey:"+key);
            if(execution instanceof ExecutionEntity)
                ((ExecutionEntity)execution).setBusinessKey(key);
            else if(execution instanceof ExecutionImpl)
                ((ExecutionImpl)execution).updateProcessBusinessKey(key);
        }
    }
}

PS

  • execution:DelegateExecution提供外出执行的额外信息。
  • task:DelegateTask提供当前任务的额外信息。注意,只对任务监听器的表达式有效。

系统任务

ServcieTask 系统任务,自动执行

1) xxx.bpmn.xml:

<serviceTask id="servicetask" name="service Task" activiti:delegateExpression="myJavaDelegate"></serviceTask>

2) MyJavaDelegate.java

public class MyJavaDelegate implements JavaDelegate
{
    @Override
    public void execute(DelegateExecution execution) throws Exception
    {
        System.out.println("Execution Service Task...");
        execution.setVariable("serviceVar", "Hello");
    }
}

Gateway 网关

排他网关

排他网关(ExclusiveGateway):

  • 在flow上(线上)设置条件condition,跟jbpm不同
  • 根据condition结果,执行其中一条分支

ExclusiveGateway

<sequenceFlow id="flow2" name="input == 1" sourceRef="exclusivegateway" targetRef="usertask1">
        <conditionExpression xsi:type="tFormalExpression"><![CDATA[${input == 1}]]></conditionExpression>
</sequenceFlow>
public void testExclusiveGateway(){
    Map<String,Object> variables=new HashMap<String,Object>();
    variables.put("input", 2);
    runtimeService.startProcessInstanceByKey("exclusiveGatewayTest",variables);

    Task task=taskService.createTaskQuery().singleResult();
    assertTrue("User Task2".equals(task.getName()));
}

并行网关

并行网关(ParallelGateway):

  • 无需设置condition
  • 等分支都执行完毕再继续向下执行

例如: ParallelGatewayUserTask1UserTask2都完成后UserTask3才可被执行

包含网关

包含网关(InclusiveGateway)

  • 排他网关ExclusiveGateway和并行网关PallelGateway的结合
  • 符合Condition的分支会并行执行

例如: InclusiveGatewayinput=2,则UserTask1不会被执行,且只有UserTask2UserTask3都被执行完成后,流程才会再往下

与Spring整合

<aop:aspectj-autoproxy proxy-target-class="true"/>
<context:annotation-config/>
<context:component-scan base-package="xxxx" ></context:component-scan>
<context:property-placeholder location="classpath:jdbc-mine.properties"/>

<!-- DataSource -->
<bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>

    <property name="initialSize" value="1"/>
    <property name="minIdle" value="2"/>
    <property name="maxActive" value="200"/>   
    <property name="maxIdle" value="30"/>       
    <property name="maxWait" value="10000"/>  
    <property name="removeAbandoned" value="true"/>  
    <!-- <property name="logAbandoned" value="true"/>   -->
    <property name="validationQuery" value="select 1 from dual"/> 
    <property name="testOnBorrow" value="true"/>
</bean>

<!-- 声明式事务 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>
<tx:advice id="txAdvice" transaction-manager="txManager" >
    <tx:attributes>
        <tx:method name="list*" propagation="REQUIRED" read-only="true"/>
        <tx:method name="get*" propagation="REQUIRED" read-only="true"/>
        <tx:method name="detail*" propagation="REQUIRED" read-only="true" />
        <tx:method name="test*" propagation="REQUIRED" read-only="true"  />
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>
 <aop:config>
    <aop:pointcut expression="execution(* com.cj..service.*.*(..))" id="myShuttleService"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="myShuttleService" order="2"/>
</aop:config> 

<!-- Activiti: processEngineConfiguration -->
<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
    <property name="dataSource" ref="dataSource" />
    <property name="transactionManager" ref="txManager" />
    <property name="databaseSchemaUpdate" value="true" />
    <property name="jobExecutorActivate" value="true" />

    <property name="history" value="full" />
    <property name="activityFontName" value="宋体" />
    <property name="labelFontName" value="宋体" />
</bean>

<!-- Activiti: processEngine -->
<bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean" destroy-method="destroy">
    <property name="processEngineConfiguration" ref="processEngineConfiguration" />
</bean>

<!-- Activiti: Service -->
<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="historyService" factory-bean="processEngine" factory-method="getHistoryService" />
<bean id="managementService" factory-bean="processEngine" factory-method="getManagementService" />
<bean id="identityService" factory-bean="processEngine" factory-method="getIdentityService" />

<!-- 根据需要注入:-->
<!-- Activiti: javaDelegate -->
<bean id="myJavaDelegate" class="com.cj.test.javaDelegate.MyJavaDelegate"></bean>
<!-- Activiti: taskListener -->
<bean id="myTaskListener" class="com.cj.test.taskListener.MyTaskListener"></bean>

测试

PluggableActivitiTestCaseResourceActivitiTestCaseSpringActivitiTestCase

  • extends AbstractActivitiTestCase
  • 定义了processEngine和所有的API的Servcie
  • 使用test开头的方法作为单元测试方法: testXxx(...)
  • 可使用@Deployment注解deploy测试流程

使用:

  • 方式一:extends PluggableActivitiTestCase
    • 使用默认配置文件:activiti.cfg.xml
  • 方式二:extends ResourceActivitiTestCase
    • 可指定配置文件,在构造函数中配置调用:super("xxx");
      public class GatewayTest extends ResourceActivitiTestCase{
        public GatewayTest(){
            super("activiti-test.cfg.xml");
        }
        @Deployment(resources = "bpmn/gateway/ExclusiveGateway.bpmn")
        public void testExclusiveGateway(){
            Map<String,Object> variables=new HashMap<String,Object>();
            variables.put("input", 2);
            runtimeService.startProcessInstanceByKey("exclusiveGatewayTest",variables);
            Task task=taskService.createTaskQuery().singleResult();
            assertTrue("User Task2".equals(task.getName()));
        }
      }
      
  • 方式三:extends SpringActivitiTestCase

    • 整合Spring测试(ProcessEngine,Service由Spring管理)
    • beans-test.xml:

      <bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
        <property name="driverClass" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
      </bean>
      
      <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
      </bean>
      <tx:advice id="txAdvice" transaction-manager="txManager" >
        <tx:attributes>
            <tx:method name="list*" propagation="REQUIRED" read-only="true"/>
            <tx:method name="get*" propagation="REQUIRED" read-only="true"/>
            <tx:method name="detail*" propagation="REQUIRED" read-only="true" />
            <tx:method name="test*" propagation="REQUIRED" read-only="true"  />
            <tx:method name="*"/>
        </tx:attributes>
      </tx:advice>
      
      <bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
        <property name="dataSource" ref="dataSource" />
        <property name="transactionManager" ref="txManager" />
        <property name="databaseSchemaUpdate" value="true" />
        <property name="jobExecutorActivate" value="true" />
        <!-- <property name="history" value="full" /> -->
        <property name="activityFontName" value="微软雅黑" />
        <property name="labelFontName" value="微软雅黑" />
      </bean>
      <bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean" destroy-method="destroy">
        <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="historyService" factory-bean="processEngine" factory-method="getHistoryService" />
      <bean id="managementService" factory-bean="processEngine" factory-method="getManagementService" />
      <bean id="identityService" factory-bean="processEngine" factory-method="getIdentityService" />
      
    • Test Class:

      @ContextConfiguration("classpath:beans-test.xml")
      public class TaskSpringTest extends SpringActivitiTestCase
      {
        @Deployment(resources="bpmn/task/ServiceTask-spring.bpmn")
        public void testServiceTask(){
            ProcessInstance procInst=runtimeService.startProcessInstanceByKey("serviceTaskSpringTest");
      
            //User Task1
            Task task=taskService.createTaskQuery().singleResult();
            assertTrue("User Task1".equals(task.getName()));
            taskService.complete(task.getId());
      
            //Service Task -- auto execute!
      
            //User Task2
            task=taskService.createTaskQuery().singleResult();
            assertTrue("User Task2".equals(task.getName()));
      
            String var=(String)taskService.getVariable(task.getId(), "serviceVar");
            assertTrue("Hello".equals(var));
      
            taskService.complete(task.getId());
            assertProcessEnded(procInst.getId());
        }
      }
      

功能组件介绍

Service

引擎API:

  • RepositoryService:流程资源服务的接口,主要用于对流程定义的部署、查询和删除操作

    • repositoryService.createProcessDefinitionQuery()
    • repositoryService.getBpmnModel(procDefId)
    • repositoryService.createDeployment()
    • repositoryService.getDeploymentResourceNames(deploymentId)
    • repositoryService.getResourceAsStream(...)
    • repositoryService.getProcessDiagram(processDefinitionId)
    • ...
  • RuntimeService:运行时服务接口,主要对流程实例,变量,活动等的操作

    • runtimeService.startProcessInstanceByXxx(...)
    • runtimeService.createExecutionQuery()
    • runtimeService.getActiveActivityIds(procInstId);
    • ...
  • TaskService:任务服务接口

    • taskService.createTaskQuery()
    • taskService.claim(taskId, userId);
    • taskService.complete(taskId);
    • taskService.createAttachment(...)
    • taskService.addComment(...)
    • ...
  • HistoryService:流程历史的服务接口

    • historyService.createHistoricVariableInstanceQuery()
    • historyService.createHistoricProcessInstanceQuery()
    • historyService.createHistoricTaskInstanceQuery()
    • ...
  • IdentityService:用户、组管理服务接口,用于管理Group、User、Membership

    • identityService.setAuthenticatedUserId(userId);
    • identityService.createUserQuery()
    • identityService.createGroupQuery()
    • newUser,saveUser,deleteUser,...
    • newGroup,saveGroup,deleteGroup,...
    • newMembership,saveMembership,deleteMemberShip,...
    • ...
  • FormService:表单服务接口,用于访问表单数据,渲染表单等操作

    • formService.getXxxFormData(...)
    • formService.getRenderedXxxFormData(...)
    • formService.submitXxxFormData(...)
    • ...
  • ManagementService:提供流程管理和控制操作的接口服务

      String jobId=managementService.createJobQuery()
          .processInstanceId("2401")
          .singleResult()
          .getId();
      managementService.executeJob(jobId);
    

Tables

Activiti 数据库中表的命名都是以 ACT_开头

  • ACT_RE_*: Repository。存储静态信息,如:流程定义、流程的资源(图片、规则)。
  • ACT_RU_*: Runtime。运行时的表
    • Activiti 只存储流程实例执行期间的运行时数据
    • 如:流程变量、用户任务、变量、作业等
    • 当流程实例结束时,将删除这些记录
  • ACT_ID_*:Identity。记录标识信息,如:用户、用户组等
  • ACT_HI_*:History。记录历史的相关数据,如:结束的流程实例、变量、任务等
  • ACT_GE_*:General。记录通用数据

Var

  1. 运行时变量,存放在act_ru_variable表中
    • 全局变量
    • 本地变量
  2. 历史变量,涉及表act_hi_detail,act_hi_varinst
    • 有4个历史级别:
      • none: 不保存任何历史记录,可以提高系统性能;
      • activity:保存所有的流程实例、任务、活动信息;
      • audit:也是Activiti的默认级别,保存所有的流程实例、任务、活动、表单属性;
      • full: 最完整的历史记录,除了包含audit级别的信息之外还能保存详细,例如:流程变量
    • 可配置:
      <bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
        <property name="history" value="full">
      </property></bean>
      
    • 查询变量:
      List<historicvariableinstance> list = historyService.createHistoricVariableInstanceQuery()
                .processInstanceId(processInstanceId)
                .list();
      for (HistoricVariableInstance variable : list) {
      System.out.println("variable: " + variable.getVariableName() + " = " + variable.getValue());
      }
      
    • 只查询表单字段:
      List<historicdetail> formProperties = historyService.createHistoricDetailQuery()
                .processInstanceId(processInstance.getId())
                .formProperties()
                .list();
      for (HistoricDetail historicDetail : formProperties) {
        HistoricFormProperty field = (HistoricFormProperty) historicDetail;
        System.out.println("field id: " + field.getPropertyId() + ", value: " + field.getPropertyValue());
      }
      

User

统一身份管理:

  • Activiti用户管理系统
    • 使用identityService 管理User,Group,Membership
    • 关联的表:act_id_user,act_id_info,act_id_group,act_id_membership
    • 在流程定义中可直接使用定义的User,Group,例如
      • activiti:candidateGroups="admin,manager"
      • activiti:candidateUsers="Tom,Jerry"
  • 业务系统的用户管理系统,比如
    • 使用LDAP
    • 使用部门,角色,权限等更为复杂的组织系统
  • 结合方式:
    • 业务系统操作时同步管理Activiti用户数据(调用identityService)
    • 自定义SessionFactory,非侵入式替换接口实现
    • 用视图覆盖同名的ACT_ID_*系列表(需在引擎配置中设置属性dbIdentityUsed为false)

Form

Activiti管理表单

  • 配置在流程定义中(可加在StartEvent,Task节点上)
  • 表单支持变量自动替换(UEL语法)
  • 表单内容以key-value形式保存在表act_hi_detail
  • 使用方式一:在流程定义文件中用activiti:formProperty属性定义

    • 表单没有布局,所有的表单元素会顺序逐行输出在页面
        <startevent id="startevent1" name="Start">
          <extensionelements>
            <activiti:formproperty id="name" name="Name" type="string"></activiti:formproperty>
          </extensionelements>
        </startevent>
        <usertask id="usertask1" name="First Step">
          <extensionelements>
            <activiti:formproperty id="setInFirstStep" name="SetInFirstStep" type="date"></activiti:formproperty>
                <activiti:formproperty id="remark" name="Remark" type="string"></activiti:formproperty>
          </extensionelements>
        </usertask>
      
    • 通过formService.getXxxForm获取表单:
      • formService.getStartFormData(processDefinitionId),返回StartFormData
      • formService.getTaskFormData(taskId),返回TaskFormData
    • 提交表单:formService.submitXxxFormData(...)
  • 使用方式二:在流程定义文件中用activiti:formkey属性引用

    • 流程运行时从部署的表单文件中读取,原样输出显示
    • 注意:表单内容写好保存到.form文件,需同流程定义文件一同部署
      <process id="FormKey" name="FormKey">
        <startevent id="startevent1" name="Start" activiti:formkey="form/start.form"></startevent>
        …
      </process>
      
    • 通过formService.getRenderedXxxForm获取表单:
      • formService.getRenderedStartForm(processDefinitionId)
      • formService.getRenderedTaskForm(taskId)
      • 注意: return Object (返回内容是经FormEngine处理过的)
      • 默认使用的表单引擎为JuelFormEngine,可自定义进行扩展
    • 提交表单:formService.submitXxxFormData(...)

非Activiti管理表单

  • 表单呈现自定义
  • 表单内容置于自定义的Table中,通过businessKey传入与流程关联

常见应用

Attachment & Comment

都是存放在历史记录中的,即act_hi_attachment,act_hi_comment

Attachment:

  1. 涉及到的table:act_hi_attachment,act_ge_bytearray, act_hi_comment
  2. 添加attachment,两种方式:
    • 方式一:使用 inputStream
      • inputStream 的实际内容存放在 act_ge_bytearray中,通过contentId关联
      • taskService.createAttachment(attachmentType, taskId, processInstanceId, attachmentName, attachmentDescription, content)
    • 方式二:使用 url
      • taskService.createAttachment(attachmentType, taskId, processInstanceId, attachmentName, attachmentDescription, url)
  3. 会在act_hi_comment表中插入数据 (type:event,action:AddAttachment,message:attachmentName,full_msg:null)
  4. 获取Attachment(search act_hi_attachment):
    • 方式一:List<Attachment> taskService.getTaskAttachments(taskId);
    • 方式二:List<Attachment> taskService.getProcessInstanceAttachments(procInstId);

Comment:

  1. 涉及到的table:act_hi_comment
  2. 添加comment:
    • taskService.addComment(taskId, processInstanceId, message)
    • taskService.addComment(taskId, processInstanceId, type, message)
  3. 会在act_hi_comment表中插入数据 (type:comment,action:AddComment,full_msg:msg)
  4. 获取Comment(search act_hi_comment):
    • 方式一:List<Comment> taskService.getTaskComments(taskId);
    • 方式二:List<Comment> taskService.getProcessInstanceComments(procInstId); (会查出因为createAttachment而添加的comment记录)

多实例

<userTask id="miTasks" name="My Task" activiti:assignee="${user}">
    <multiInstanceLoopCharacteristics isSequential="false"
    activiti:collection="${myService.resolveUsersForTask()}" activiti:elementVariable="user" >
    <completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.6 }</completionCondition>
    </multiInstanceLoopCharacteristics>
</userTask>
  • isSequential="true" 顺序执行(多级串行)
  • isSequential="false" 并行执行(并行会签)
  • 每个上级流程为每个实例创建分支时都要提供如下变量:
    • nrOfInstances:实例总数
    • nrOfActiveInstances:当前活动的实例数量 (对于顺序执行的多实例,值一直为1)
    • nrOfCompletedInstances:已经完成实例的数目
    • 可以通过execution.getVariable(x)方法获得这些变量
  • 每个创建的分支都会有分支级别的本地变量(临时)
    • 其他实例不可见, 不会保存到流程实例级别
    • 比如:loopCounter(表示特定实例的在循环的索引值)

CallActivity & SubProcess

CallActivity 调用外部定义的流程

  • New ProcessInstance
  • Business Key is null
  • for transfer: set inputParamater & outParamater
  • search by some unique var

SubProcess 子流程 (内嵌在当前流程定义中)

  • has Main Process information

CallActiviti & SubProcess (包含SubProcess和CallActivity) BasicSingle (被Call的外部流程定义)

Transaction 事务

  • Case1: serviceTask[处理成功申请] 同步,即设置activiti:async="false" (默认)

    <serviceTask id="service1" name="处理成功申请" activiti:class="my.custom.Delegate" activiti:async="false"
    

    Async False

  • Case2: serviceTask[处理成功申请] 异步,即设置activiti:async="true",打开jobExecutorActivate(processEngineConfiguration中)

    <serviceTask id="service1" name="处理成功申请" activiti:class="my.custom.Delegate" activiti:async="true"
    

    Async True (后续执行失败后回滚到此异步task)

异步执行说明:

  • 异步只是指:执行方式异步
  • 即启动新的线程(相对于主线程),但在流程控制上,若流程未完结,不会提前执行下一个节点
  • 异步执行是一个新的执行单元(Transaction原子)
  • 所以设置异步执行,流程不会提前结束,会继续执行下一个节点直到流程结束

异步节点:TimerCatchEvent

  1. Timer无异步设置,本身就是异步执行的,执行失败(Timer或后面同步节点异常)会停留在TimerCatchEvent节点
  2. 在流程中设置Timer,会等到Timer执行完成后流程才能结束
  3. Timer Event: start (流程执行到此Timer节点时触发),end(此异步Timer完成到达设置的执行时间时触发)
    • trigger startEvent
    • create async thread (Sleep)
    • at setup time(Weakup)
    • trigger endEvent
    • next Activity

PS: 一般情况下,执行一个task为一个原子,执行taskService.complete(taskId) 时,若其中发生异常,将回滚 (这个task中发生的所有操作都会回滚,不用担心业务数据和流程数据不同步的问题)

流程跟踪

  • 方式一:Activiti流程引擎根据流程定义文件(.xml)生成图片
    • 方便,不灵活,易错位
    • 可直接生成Highlight Activity的图片
    • 弊端:坐标错位不一致,中文乱码,Flow上文字不显示
  • 方式二:直接获取部署时上传的对应流程图
    • 灵活,复杂
    • 读取图片后通过前端Javascript和Css配合跟踪(Highlight Activity)
    • 可利用静态服务提升读取Activiti流程图的性能
    • 弊端:同方法一
  • 方式三:使用Diagram Viewer流程图跟踪组件
    • 灵活,方便
    • 根据读取的数据,在前端绘制流程图

使用流程定义文件(.xml)

  • 生成流程图
    BpmnModel bpmnModel=repositoryService.getBpmnModel(procInst.getProcessDefinitionId());
    InputStream in=ProcessDiagramGenerator.generatePngDiagram(bpmnModel);
    
  • 生成活动节点标记的流程图(activeActivity)
    List<String> activityIds=runtimeService.getActiveActivityIds(procInst.getId());
    InputStream in=ProcessDiagramGenerator.generateDiagram(bpmnModel, "png", activityIds);
    
  • 生成带活动踪迹的流程图(activeActivity+historyFlow)
    List<String> highLightedFlows = getHighLightedFlows(processDefinition , processInstanceId);     //具体实现参考ProcessInstanceHighlightsResource.java
    InputStream in=ProcessDiagramGenerator.generateDiagram(bpmnModel, "png", activityIds,highLightedFlows);
    

使用部署上传的流程图

这种方式在优势灵活性很大,可以显示你要展示的所有信息; 因为完全就是一堆信息点汇合起来的跟踪功能,需要后端提供数据给前端分析; 例如:有哪些Activity及其坐标、高度、宽度、是否当前节点等信息

Diagram

后端:

  • 获取流程图
    • 方法一:
      InputStream in=repositoryService.getProcessDiagram(processDefinitionId);
      
    • 方法二:
      String resourceName = procDef.getDiagramResourceName();
      InputStream in = repositoryService.getResourceAsStream(processDefinitionId, resourceName);
      
  • 获取流程定义的所有节点信息(All Activities Info)
    • Process内部节点(即活动)等详细信息:ActivityImpl
    • 记录在bmpn文件中(运行时加载解析bmpn文件到内存中)
    • 通过RepositoryService的getProcessDefinition(id)方法从内存中获取Activity定义信息
      ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService)
                              .getDeployedProcessDefinition(procInst.getProcessDefinitionId());
      List<ActivityImpl> activitiList = processDefinition.getActivities();//获得当前流程定义的所有节点
      
  • 获取当前活动节点信息(Active Activity Info)

    • 注意:示例中是返回所有节点信息(包括标注是否为当前活动节点)

      ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService )
                          .getDeployedProcessDefinition(procInst.getProcessDefinitionId());
      
      List<String> activityIds=runtimeService.getActiveActivityIds(procInst.getId());
      List<Map<String, Object>> activityInfos= new ArrayList<Map<String, Object>>();
      
      for(String id:activityIds){
         ActivityImpl activity=processDefinition.findActivity(id);
         Map<String,Object> activityImageInfo=new HaspMap<String,Object>();
         activityImageInfo.put( "x", activity.getX());
         activityImageInfo.put( "y", activity.getY());
         activityImageInfo.put( "width", activity.getWidth());
         activityImageInfo.put( "height", activity.getHeight());
         activityInfos.add(activityImageInfo);
      }
      

前端:

  • 用js和css配合动态标记当前节点(可参考kft-activiti-demo项目的workflow.js代码)

使用Diagram Viewer组件

  • 这是官方在5.12版本中提供的一个 Javascript跟踪工具,使用方便,
  • 简单配置就可以使用,兼容各种浏览器,并且可以自己扩展
  • 以Raphaël为基础库,用REST方式获取JSON数据,生成流程图并把流程的处理过程用不同的颜色加以标注

Diagram

后端:

  1. 添加依赖包
     <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-diagram-rest</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    
  2. 配置一个REST拦截器

    • 可使用自定义也可使用activiti自带的DiagramRestApplication
    • web.xml
      <!-- Activiti: Restlet adapter - used to expose modeler functionality through REST -->
      <servlet>
        <servlet-name>MyActivitiRestletServlet</servlet-name>
        <servlet-class>org.restlet.ext.servlet.ServerServlet</servlet-class>
        <init-param>
            <!-- Application class name -->
            <param-name>org.restlet.application</param-name>
            <!-- <param-value>org.activiti.rest.diagram.application.DiagramRestApplication</param-value> -->
            <param-value>com.cj.xxx.MyActivitiRestApplication</param-value>
        </init-param>
      </servlet>
      <!-- Catch all service requests -->
      <servlet-mapping>
        <servlet-name>MyActivitiRestletServlet</servlet-name>
        <url-pattern>/service/*</url-pattern>
      </servlet-mapping>
      
    • MyActivitiRestApplication.java

      public class MyActivitiRestApplication extends ActivitiRestApplication{
        public MyActivitiRestApplication(){
            super();
        }
      
        /**
         * Creates a root Restlet that will receive all incoming calls.
         */
        @Override
        public synchronized Restlet createInboundRoot(){
            Router router = new Router(getContext());
            router.attachDefault(DefaultResource.class);
            //DiagramServicesInit.attachResources(router);
      
            router.attach("/process-instance/{processInstanceId}/highlights", ProcessInstanceHighlightsResource.class);
            router.attach("/process-instance/{processInstanceId}/diagram-layout", ProcessDefinitionDiagramLayoutResource.class);
            router.attach("/process-definition/{processDefinitionId}/diagram-layout", MyProcessDefinitionDiagramLayoutResource.class);
      
            JsonpFilter jsonpFilter = new JsonpFilter(getContext());
            jsonpFilter.setNext(router);
            return jsonpFilter;
        }
      }
      

前端使用:

复制Diagram Viewer组件到项目

  • 从官网包activiti-explorer.war中找到diagram-viewer拷贝到项目
  • 根据需要编写修改js文件,在页面中应用

测试:

  1. 获取完整效果,eg:

    • trigger: baseUrl+/resources/diagram-viewer/index.html?processDefinitionId=leave:1:4&processInstanceId=5
    • 响应的HTML上面绘制了对应的流程追踪图
  2. 获取流程定义所有元素信息,eg:

    • trigger: baseUrl + "/service/process-definition/{processDefinitionId}/diagram-layout?callback=?"
    • or trigger: baseUrl + "/service/process-definition/{processDefinitionKey}/diagram-layout?callback=?"
    • result:
      {"processDefinition":{"id":"leave:1:20","name":"请假流程","key":"leave","version":1,"deploymentId":"1","isGraphicNotationDefined":true},
      "activities":[
          {"activityId":"deptLeaderAudit","properties":{"name":"部门领导审批","type":"userTask"},"x":90,"y":80,"width":105,"height":55},
          {"activityId":"exclusivegateway5","properties":{"name":"Exclusive Gateway","type":"exclusiveGateway"},"x":250,"y":87,"width":40,"height":40},
          {"activityId":"modifyApply","properties":{"name":"调整申请","type":"userTask"},"x":218,"y":190,"width":105,"height":55},
          {"activityId":"hrAudit","properties":{"name":"人事审批","type":"userTask"},"x":358,"y":80,"width":105,"height":55},
          {"activityId":"exclusivegateway6","properties":{"name":"Exclusive Gateway","type":"exclusiveGateway"},"x":495,"y":87,"width":40,"height":40},
          {"activityId":"reportBack","properties":{"name":"销假","type":"userTask"},"x":590,"y":80,"width":105,"height":55},
          {"activityId":"exclusivegateway7","properties":{"name":"Exclusive Gateway","type":"exclusiveGateway"},"x":250,"y":280,"width":40,"height":40},
          {"activityId":"startevent1","properties":{"name":"Start","type":"startEvent"},"x":10,"y":90,"width":35,"height":35},
          {"activityId":"endevent1","properties":{"name":"End","type":"endEvent"},"x":625,"y":283,"width":35,"height":35}
          ],
      "sequenceFlows":[
          {"id":"flow3","name":"","flow":"(deptLeaderAudit)--flow3-->(exclusivegateway5)","xPointArray":[195,250],"yPointArray":[107,107]},
          {"id":"flow4","name":"不同意","flow":"(exclusivegateway5)--flow4-->(modifyApply)","xPointArray":[270,270],"yPointArray":[127,190]},
          {"id":"flow5","name":"同意","flow":"(exclusivegateway5)--flow5-->(hrAudit)","xPointArray":[290,358],"yPointArray":[107,107]},
          {"id":"flow11","name":"","flow":"(modifyApply)--flow11-->(exclusivegateway7)","xPointArray":[270,270],"yPointArray":[245,280]},
          {"id":"flow6","name":"","flow":"(hrAudit)--flow6-->(exclusivegateway6)","xPointArray":[463,495],"yPointArray":[107,107]},
          {"id":"flow7","name":"同意","flow":"(exclusivegateway6)--flow7-->(reportBack)","xPointArray":[535,590],"yPointArray":[107,107]},
          {"id":"flow9","name":"不同意","flow":"(exclusivegateway6)--flow9-->(modifyApply)","xPointArray":[515,514,323],"yPointArray":[127,217,217]},
          {"id":"flow8","name":"","flow":"(reportBack)--flow8-->(endevent1)","xPointArray":[642,642],"yPointArray":[135,283]},
          {"id":"flow10","name":"重新申请","flow":"(exclusivegateway7)--flow10-->(deptLeaderAudit)","xPointArray":[250,142,142],"yPointArray":[300,299,135]},
          {"id":"flow12","name":"结束流程","flow":"(exclusivegateway7)--flow12-->(endevent1)","xPointArray":[290,625],"yPointArray":[300,300]},
          {"id":"flow2","name":"","flow":"(startevent1)--flow2-->(deptLeaderAudit)","xPointArray":[45,90],"yPointArray":[107,107]}
          ]
      };
      
  3. 获取当前活动节点和线信息进行高亮处理,eg:

    • trigger: baseUrl + "/service/process-instance/{processInstanceId}/highlights?callback=?"
    • result:
      {"processInstanceId":"108","processDefinitionId":"leave:1:20","activities":["hrAudit"],"flows":["flow2","flow3","flow5"]});
      

Reference