2020-7-14

Workflow

Activiti7

Activiti7 官方开发指南 概览 Activiti7的基本原理和使用

实例

0. ProcessEngine 核心对象初始化

Java 代码集成 可以通过 org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl::buildProcessEngine() 在buildProcessEngine 之前进行初始化的操作(最小设置一个数据源即可)

public  ProcessEngine getProcessEngine() {
    Setting setting = new Setting();
    setting.put("jdbcUrl",
            "jdbc:mysql://192.168.30.171:3306/workflow_test?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false");
    setting.put("driverClassName", "com.mysql.cj.jdbc.Driver");
    setting.put("username", "user_dev");
    setting.put("password", "dev-sny.com");
    HikariDSFactory fact = new HikariDSFactory(setting);
    DataSource source = fact.getDataSource();
    ////////////////////////
    ProcessEngineConfigurationImpl configuration = new StandaloneProcessEngineConfiguration();
    configuration.setDataSource(source);
    configuration.setDatabaseSchemaUpdate("true");//activiti数据库表处理策略 
    ProcessEngine processEngine = configuration.buildProcessEngine();
    return processEngine;
}

经典 Spring XML 配置

 <!-- Activiti单独运行的ProcessEngine配置 -->
    <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
        <!-- 数据源 -->
        <property name="dataSource" ref="dataSource"/>
        <!-- 
            activiti数据库表处理策略 
                false(默认值): 检查数据库的版本和依赖库的版本, 如果不匹配就抛出异常
                true: 构建流程引擎时, 执行检查, 如果需要就执行更新; 如果表不存在, 就创建; 
                create-drop: 构建流程引擎时创建数据库报表, 关闭流程引擎时就删除这些表; 
                drop-create: 先删除表再创建表; 
                create: 构建流程引擎时创建数据库表, 关闭流程引擎时不删除这些表
        -->
        <property name="databaseSchemaUpdate" value="true"/>
    </bean>
 //创建ProcessEngineConfiguration对象
ProcessEngineConfiguration configuration = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti-cfg.xml");
//创建ProcessEngine对象
ProcessEngine processEngine = configuration.buildProcessEngine();
System.out.println("processEngine = " + processEngine);
// 此时会在数据库创建 ACT_* 表

表说明

Activiti 的表都是以ACT_开头; 第二部分是表示表的用途的两个字母标识

act_re

表示 repository; 这个前缀的表包含了流程定义和流程静态资源 (图片, 规则, 等等) act_re_deployment act_re_model act_re_procdef

act_ru

表示 runtime; 这些运行时的表, 包含流程实例, 任务, 变量, 异步任务, 等运行中的数据; Activiti 只在流程实例执行过程中保存这些数据, 在流程结束时就会删除这些记录; 这样运行时表可以一直很小速度很快;

act_ru_deadletter_job act_ru_event_subscr act_ru_execution act_ru_identitylink //identity (组织机构), IdentityService接口所操作的表; 用户记录, 流程中使用到的用户和组; 这些表包含标识的信息, 如用户, 用户组, 等等;

act_ru_integration act_ru_job act_ru_suspended_job act_ru_task act_ru_timer_job act_ru_variable

act_hi

表示 history; 这些表包含历史数据, 比如历史流程实例, 变量, 任务等等; act_hi_actinst act_hi_attachment act_hi_comment act_hi_detail act_hi_identitylink act_hi_procinst act_hi_taskinst act_hi_varinst

act-ge

表示 general; 通用数据 act_ge_bytearray act_ge_property

act_evt_log 事件日志 act_procdef_info 进程定义信息

1. 流程部署

@Test
public void testDeployProcessDefinition() {
    ProcessEngine processEngine = getProcessEngine();
    RepositoryService repositoryService = processEngine.getRepositoryService();
    Deployment deployment = repositoryService.createDeployment()
            .addClasspathResource("TestBook.bpmn")
            .addClasspathResource("TestBook.png")
            .name("流程测试")
            .deploy();
}

受影响的表: 3个 act_re_deployment: 添加一条流程部署记录 act_re_procdef:: 添加一条流程定义记录, 一个流程定义记录和一个流程图一一对应, 流程定义记录的key就是流程图的id act_ge_bytearray: blob形式保存部署的资源

2. 启动流程实例

每启动一个流程代表一个流程实例;

@Test
public void testStartProcess() {
    ProcessEngine processEngine = getProcessEngine();
    RuntimeService runtimeService = processEngine.getRuntimeService();
    Map<String, Object> variables=new HashMap<String,Object>(); 
    Map<String, Object> form=MapUtil.of("creater", "张三"); 
    variables.put("form", form);
    ProcessInstance processInstance = runtimeService
            .startProcessInstanceByKey("TestBook", "businessKey", variables);
    //可以设置流程实例的名字
    runtimeService.setProcessInstanceName(processInstance.getId(), "这是一个流程实例的名字, 准备给审批人看的! ");
}
//实例 查询
@Test
public void testQueryProcess() {
    ProcessEngine processEngine = getProcessEngine();
    RuntimeService runtimeService = processEngine.getRuntimeService();
    List<ProcessInstance> pis = runtimeService.createProcessInstanceQuery()
            .processDefinitionKey("TestBook").list();
    for (ProcessInstance processInstance: pis) {
        System.out.println("流程实例名称: "+processInstance.getName());
        System.out.println("流程实例变量: "+ processInstance.getProcessVariables());
    }
}

受影响的表: 9个 act_ru_execution: 添加了一个流程实例记录和该流程实例记录下的一个执行实例记录, 注意流程实例和执行实例的区别; act_ru_variable: 添加该流程实例的变量, 如果流程实例启动时附带了流程变量的话; act_ru_task: 添加了该流程实例的一个待办任务——>填写请假单 act_ru_identitylink: 添加了一条身份记录, 该流程实例的当前办理人变成了——>zhangsan act_hi_actinst: 历史活跃的实例节点添加了两条《开始》和《填写请假单》, 有相同的流程实例ID act_hi_procinst: 历史流程实例添加了一条记录 act_hi_taskinst: 历史任务里面添加了一个填写请假单记录 act_hi_varinst: 历史流程变量添加了两条记录aa和bb act_ge_property: next.dbid字段值 变成 7501

3. 用户查询任务

TaskService taskService = processEngine.getTaskService();
List<Task> taskList = taskService.createTaskQuery()
        .processDefinitionKey("holiday")
        .taskAssignee("zhangsan")
        .list();
 for (Task task: taskList) {
    System.out.println("任务ID: "+task.getId());//5007
    System.out.println("任务所属执行实例ID: "+task.getExecutionId());//5007
    System.out.println("任务所属流程实例ID: "+task.getProcessInstanceId());//5001
    System.out.println("任务所属流程定义ID: "+task.getProcessDefinitionId());//holiday:1:2504
    System.out.println("任务的办理人assignee: "+task.getAssignee()+", 任务的owner:"+task.getOwner());//zhangsan, null
    System.out.println("任务的名字: "+task.getName());//填写请假单
    System.out.println("任务的key: "+task.getTaskDefinitionKey());//_3, 是流程图中的任务节点的ID
    System.out.println("任务的领取时间: "+task.getClaimTime());//null
}
 

数据来源表: act_ru_task:

4. 用户处理任务

public void testCompleteTask() {
    String taskId = "2509";
    ProcessEngine processEngine = getProcessEngine();
    TaskService taskService = processEngine.getTaskService();
    Map<String, Object> variables=new HashMap<String,Object>();
    Form form = new Form("5", "张三", "0");
    variables.put("form", form);
    variables.put("testval", "testvalxxxff22");
    taskService.complete(taskId, variables);
}

受影响的表: 8个 act_ru_task: 《填写请假单任务》被删除, 新添加了一个《部门经理》任务, 即流程开始自动往下走了 act_ru_variable: 又增加了两条记录cc和dd act_ru_identitylink: 运行时身份表中, 增加了lisi这条记录 act_hi_varinst: 历史变量表中又增加了两条记录《cc和dd》 act_hi_taskinst: 历史任务表中新添加了一个《部门经理》任务 act_hi_identitylink: 历史身份表中增加了《lisi》这条记录 act_hi_actinst: 历史活跃的实例节点表中增加了一个《部门经理》记录 act_ge_property: next.dbid的值变成了10001

Activiti7 关键对象

部分参考文档

ProcessEngine

ProcessEngine 对象这是Activiti工作的核心. 负责生成流程运行时的各种实例及数据, 监控和管理流程的运行;
processEngine 是Activiti的门面接口, 提供了访问各种service的方法, 是使用Activiti必须用的对象;

已定义的流程查询

@Test
public void testQueryProcessDefinition() {
    ProcessEngine processEngine = getProcessEngine();
    List<ProcessDefinition> list = processEngine.getRepositoryService()// 与流程定义和部署对象相关的Service
            .createProcessDefinitionQuery()// 创建一个流程定义查询
            /* 指定查询条件,where条件 */
            // .deploymentId(deploymentId)//使用部署对象ID查询
            // .processDefinitionId(processDefinitionId)//使用流程定义ID查询
            // .processDefinitionKey(processDefinitionKey)//使用流程定义的KEY查询
            // .processDefinitionNameLike(processDefinitionNameLike)//使用流程定义的名称模糊查询
            /* 排序 */
            .orderByProcessDefinitionVersion().asc()// 按照版本的升序排列
            // .orderByProcessDefinitionName().desc()//按照流程定义的名称降序排列
            .list();// 返回一个集合列表, 封装流程定义
    // .singleResult();//返回唯一结果集
    // .count();//返回结果集数量
    // .listPage(firstResult, maxResults)//分页查询
    if (list != null && list.size() > 0) {
        for (ProcessDefinition processDefinition: list) {
            System.out.println("流程定义ID:" + processDefinition.getId());// 流程定义的key+版本+随机生成数
            System.out.println("流程定义名称:" + processDefinition.getName());// 对应.bpmn文件中的name属性值
            System.out.println("流程定义的key:" + processDefinition.getKey());// 对应.bpmn文件中的id属性值
            System.out.println("流程定义的版本:" + processDefinition.getVersion());// 当流程定义的key值相同的情况下, 版本升级, 默认从1开始
            System.out.println("资源名称bpmn文件:" + processDefinition.getResourceName());
            System.out.println("资源名称png文件:" + processDefinition.getDiagramResourceName());
            System.out.println("部署对象ID:" + processDefinition.getDeploymentId());
        }
    }
}

RepositoryService

流程定义和部署相关的存储服务;

TaskRuntime

If you are building business(业务) applicationscreating Tasks for users and groups in your organisation is something that you might find handy

in short 提供运行时用户任务的相关API操作

创建任务

taskRuntime.create(
    TaskPayloadBuilder.create()
        .withName("First Team Task") 
        .withDescription("This is something really important")
        .withAssignee(assignee)//指定用户
        .withPriority(10)
    .build());

查询任务

指定用户等 查询任务列表

GetTasksPayloadBuilder gtpBuilder= new GetTasksPayloadBuilder();
GetTasksPayload gtp = gtpBuilder
    .withAssignee("admin")//用户
    .withProcessInstanceId("")// 流程实例id 
    .withGroup("")//用户组
    .build();
taskRuntime.tasks(Pageable.of(0,10));

完成任务

//流程变量
Map<String, Object> variables=new HashMap<String,Object>();
String businessKey = task.getBusinessKey();
Object found = serciveCombination.getOne("id", businessKey, bindEntity);
variables.put("form", found);
if(task.getAssignee() == null){//组任务 - 未指定处理人的 先拾取
    final ClaimTaskPayload taskPayload = TaskPayloadBuilder.claim()
        .withTaskId(task.getId())
        .build();
    taskRuntime.claim(taskPayload);
}
final CompleteTaskPayload taskPayload = TaskPayloadBuilder.complete()
    .withTaskId(task.getId())
    .withVariables(variables)
    .build();
taskRuntime.complete(taskPayload);
return task;

ProcessRuntime API (6以后)

与 TaskRuntime API相似, 与 ProcessRuntime API进行交互当前登录的用户必须具有” ACTIVITI_USER”角色;

主要是操作流程的一般是使用 ProcessPayloadBuilder 根据流程定义key, 添加流程变量数据组建 ProcessInstance然后使用 ProcessRuntime.start(ProcessInstance) 启动流程

 
//ProcessPayloadBuilder 负载流程 builder
//使用 processRuntime 对象启动它; 返回流程实例
ProcessInstance processInstance = processRuntime.start(ProcessPayloadBuilder
        .start()
        //key 对应上面查到ProcessDefinition 的key; (其实是 act_re_procdef 表的key字段?); 最终是 bpmn20.xml 定义的id
        .withProcessDefinitionKey("categorizeHumanProcess")
        .withName("Processing Content: " + content)
        .withVariable("content"content)
        .build());
//
public interface ProcessRuntime {
  ProcessRuntimeConfiguration configuration();
  ProcessDefinition processDefinition(String processDefinitionId);
  Page processDefinitions(Pageable pageable);
  Page processDefinitions(Pageable pageable, 
              GetProcessDefinitionsPayload payload);
  ProcessInstance start(StartProcessPayload payload);
  Page processInstances(Pageable pageable);
  Page processInstances(Pageable pageable, 
              GetProcessInstancesPayload payload);
  ProcessInstance processInstance(String processInstanceId);
  ProcessInstance suspend(SuspendProcessPayload payload);
  ProcessInstance resume(ResumeProcessPayload payload);
  ProcessInstance delete(DeleteProcessPayload payload);
  void signal(SignalPayload payload);
  ...
}

TaskServer

操作任务的方法, 例如(任务的完成, 挂起, 激活, 添加处理人, 认领, 删除等操作)

HistoryService

历史记录相关服务接口;

Connector

对应BPMN 的连接对象(Connecting Objects)(顺序流, 消息流 箭头线)

使用 Connector 修改内容 //TODO?

@Bean
 public Connector discardTextConnector() {
    return integrationContext -> {
        String contentToDiscard = (String) integrationContext.getInBoundVariables().get("content");
        contentToDiscard += " :( ";
        integrationContext.addOutBoundVariable("content", 
                contentToDiscard);
        logger.info("Final Content: " + contentToDiscard);
        return integrationContext;
    };
}

Event listener

对应BPMN 事件 (各种圆圈)

在Activiti 中开发人员可以通过配置监听器的方式监听各种动作, 例如流程 的启动, 结束, 创建, 任务的完成

The only requirement an event-listener hasis to implement org.activiti.engine.delegate.event.ActivitiEventListener. Below is an example implementation of a listenerwhich outputs all events received to the standard-outwith exception of events related to job-execution:

public class MyEventListener implements ActivitiEventListener {
 
  @Override
  public void onEvent(ActivitiEvent event) {
    switch (event.getType()) {
 
      case JOB_EXECUTION_SUCCESS:
        System.out.println("A job well done!");
        break;
 
      case JOB_EXECUTION_FAILURE:
        System.out.println("A job has failed...");
        break;
 
      default:
        System.out.println("Event received: " + event.getType());
    }
  }
 
  @Override
  public boolean isFailOnException() {
    // The logic in the onEvent method of this listener is not criticalexceptions
    // can be ignored if logging fails...
    return false;
  }
}

Activiti6.0 User Guide Activiti7的核心详解

流程实例的业务key

启动流程实例时, 指定的businessKey, 就会在act_run_execution表中存储businessKey;

BusinessKey: 业务标识, 通常为业务表的主键, 业务标识和流程实例一一对应; 业务标识来源于业务系统; 存储业务标识就是根据业务标识来关联查询业务系统的数据;

比如: 请假流程启动一个流程实例, 就可以将请假单的id作为业务标识存储到Activiti中, 将来查询Activiti的流程实例信息就可以获取请假单的id从而关联查询业务系统数据库得到请假单信息;

RuntimeService runtimeService = processEngine.getRuntimeService();
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processDefinitionKey, businessKey);

流程变量

流程变量在Activiti中是一个非常重要的角色, 流程运转有时需要靠流程变量, 业务系统和Activiti结合时少不了流程变量, 流程变量就是Activiti在管理工作流时根据管理需要而设置的变量;

比如在请假流程流转时如果请假天数>3天则有总经理审批, 否则由人事直接审批, 请假天数就可以设置流程变量, 在流程流转时使用;

如果将POJO存储到流程变量中, 必须实现序列化接口Serializable, 为了防止由于新增字段无法反序列化;

● 流程变量的作用域范围可以是一个流程实例(ProcessInstance), 一个任务(Task)或一个执行实例(Execution); ● 流程变量的作用域范围的默认值是流程实例, 作用域范围最大, 可以称为global变量; ● 流程变量的作用域范围如果仅仅针对一个任务或一个执行实例, 那么作用域范围没有流程实例大, 可以称为local变量; ● global变量中变量名不允许重复, 设置相同名称的变量, 后设置的值会覆盖前设置的变量值; ● local变量由于在不同的任务或不同的执行实例中, 作用域互不影响, 变量名可以相同没有影响; local变量名也可以和global变量名相同, 没有影响;

可以在assignee处设置UEL表达式, 表达值的值就是任务的负责人:

见下

可以在连线上设置UEL表达式, 决定流程的走向, day小于3则执行该顺序流:

 <sequenceFlow id="flow6" sourceRef="exclusivegateway1" targetRef="usertask2">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${day<3}]]></conditionExpression>
</sequenceFlow>

变量表达式定义

● Activiti使用UEL表达式, UEL是JavaEE规范的一部分, URL(Unified Expression Language)即统一表达式语言, Activiti支持两种UEL表达式: UEL-value和UEL-method;

UEL-value 定义

<userTask id="usertask1" name="User Task" activiti:assignee="${assignee}"></userTask> assignee 这个变量是Activiti的一个流程变量;

<userTask id="usertask1" name="User Task" activiti:assignee="${user.assignee}"></userTask> user 这个变量也是Activiti的一个流程变量, user.assignee表示通过调用user的getter方法获取值;

UEL-method 定义 userServer 是Spring容器中的一个Bean, 下 UEL-method 的含义是调用该Bean的一个getUserId()方法; 非Spring 需要注入到activiti的processEngineConfiguration的bean中?

<userTask id="usertask1" name="User Task" activiti:assignee="${userServer.getUserId}"></userTask>

监听器

activiti 中每个流程信息是通过 ProcessInstance 形容, 它有这么几个状态: created, started, completed, cancelled, resumed, updated, suspended, 与之对应的相干事件形容类是: ProcessCreatedEvent, ProcessStartedEvent, ProcessCompletedEvent, ProcessCancelledEvent, ProcessResumedEvent, ProcessUpdatedEvent, ProcessSuspendedEvent等;

每个流程节点在 activiti 中 通过 Task 来形容, 它有这么几个状态: created, assigned, completed, updated, cancelled, suspended等, 与之对应的相干事件形容类是: TaskCreatedEvent, TaskAssignedEvent, TaskCompletedEvent, TaskUpdatedEvent, TaskCancelledEvent, TaskSuspendedEvent等;

全局事件监听

在流程实例中注入相对应的属性bean

<bean id="processEngine" name="processEngineConfiguration"
    class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
    <property name="dataSource" ref="dataSource" />
    <property name="databaseSchemaUpdate" value="true" />
    <property name="eventListeners">
        <list>
            <bean class="com.event.MyEventListener" />
        </list>
    </property>
</bean>

基于Spring 整合时

@Component
public class ActivitiConfig implements ProcessEngineConfigurationConfigurer {
 
   @Autowired
   private ComActivitiEventListener comActivitiEventListener;
   @Override
   public void configure(SpringProcessEngineConfiguration processEngineConfiguration) {
       List<ActivitiEventListener>  activitiEventListener=new ArrayList<ActivitiEventListener>();
       activitiEventListener.add(comActivitiEventListener );//配置全局监听器
       processEngineConfiguration.setEventListeners(activitiEventListener);
   }
}

任务监听器

任务监听器是发生对应的任务相关事件时执行自定义的Java逻辑或表达式;

任务事件包括: create, assignment, complete, all:

assignment: 任务分配给指定的人员时触发; 当流程到达userTask, assignment事件会在create事件之前发生; create: 任务创建并设置所有属性后触发; complete: 当任务完成, 并尚未从运行数据中删除时触发; delete: 只在任务删除之前发生; 注意在通过completeTask正常完成时, 也会执行;

自定义一个任务监听器类, 然后此类必须实现org.activiti.engine.delegate.TaskListener接口

<userTask id="usertask1" name="User Task">
    <extensionElements>
    <activiti:taskListener event="complete" class="net.sny.cloud.ApplicationMain"></activiti:taskListener>
    </extensionElements>
</userTask>

组任务

在流程定义中在任务结点的assignee 固定设置任务负责人, 在流程定义时将参与者固定设置在.bpmn文件中, 如果临时任务负责人变更则需要修改流程定义, 系统可扩展性差;

针对这种情况可以给任务设置多个候选人, 可以从候选人中选择参与者来完成任务;

在流程图中任务结点的配置中设置 Candidate users, 多个候选人之间用逗号隔开

查询组任务

ProcessEngine processEngine = getProcessEngine();
TaskService taskService = processEngine.getTaskService();
List<String> groups = new ArrayList<String>();
//groups.add("超级管理员");
groups.add("流程管理员");
groups.add("其他");
List<Task> taskList = taskService.createTaskQuery()
        .processDefinitionKey(PROCESS_KEY)//
        .taskCandidateUser(candidateUser)//根据候选人查询
        //.taskCandidateGroupIn(groups)//还可以根据用户组
        .list();
printTaskList(taskList);
 

组任务拾取

候选人员拾取组任务后该任务变为自己的个人任务;

//第一个参数: 拾取的任务id
//第二个参数: 任务候选人id
`taskService.claim(taskId, userId);`

如果个人不想办理该组任务, 可以归还组任务, 归还后该用户不再是该任务的负责人

组任务归还

// 校验userId是否是taskId的负责人, 如果是负责人才可以归还组任务

Task task = taskService.createTaskQuery().taskId(taskId)
// 如果设置为null, 归还组任务,该任务没有负责人
taskService.setAssignee(taskId, null);

任务交接

任务交接,任务负责人将任务交给其它候选人办理该任务

// 当前待办任务
String taskId = "6004";
// 任务负责人
String userId = "zhangsan";
// 将此任务交给其它候选人办理该 任务
String candidateuser = "lisi";
// 校验userId是否是taskId的负责人, 如果是负责人才可以归还组任务
Task task = taskService.createTaskQuery().taskId(taskId)
    .taskAssignee(userId).singleResult();
if (task != null) {
//交接任务
    taskService.setAssignee(taskId, candidateuser);
} 

候选人和候选组

在流程图设置 Candidate users 属性表示非固定的, 多个用户之间候选处理;

ProcessEngine processEngine = getProcessEngine();
TaskService taskService = processEngine.getTaskService();
List<String> groups = new ArrayList<String>();
groups.add("张三");
groups.add("例如");
List<Task> taskList = taskService.createTaskQuery()
        .processDefinitionKey(PROCESS_KEY)//
        .taskCandidateUser(candidateUser)//根据候选人查询
        .list();

有种情况是用户不固定(或者说是动态的), 以角色候选, 另外一个属性 candidate groups 指定角色即可解决这个问题;

//////
ProcessEngine processEngine = getProcessEngine();
TaskService taskService = processEngine.getTaskService();
List<String> groups = new ArrayList<String>();
groups.add("部门经理");
groups.add("总经理");
List<Task> taskList = taskService.createTaskQuery()
        .processDefinitionKey(PROCESS_KEY)//
        .taskCandidateUser(candidateUser)//根据候选人查询
        .list();

Activiti 也有内置的用户与角色关联管理, 需要用到IdentityService的API 进行管理关系;

/** 添加用户与角色组关联*/
IdentityService identityService = processEngine.getIdentityService();//
// 创建角色
identityService.saveGroup(new GroupEntity("总经理"));
identityService.saveGroup(new GroupEntity("部门经理"));
// 创建用户
identityService.saveUser(new UserEntity("张三"));
identityService.saveUser(new UserEntity("李四"));
identityService.saveUser(new UserEntity("王五"));
// 建立用户和角色的关联关系
identityService.createMembership("张三", "部门经理");
identityService.createMembership("李四", "部门经理");
identityService.createMembership("王五", "总经理");

ref 组任务和网关

with spring boot

Activiti with spring boot