美文网首页架构收藏
业务场景实战(三)工作流引擎

业务场景实战(三)工作流引擎

作者: 后来丶_a24d | 来源:发表于2022-02-09 17:35 被阅读0次

思维导图

思维导图.png

系列总目录


背景

  • 简单解释:请假,离职多个节点审批,多个流程,不同场景(请假,离职)流程不一样,这时候工作流引擎能够很好的承接


    请假流程.png
  • 应用:

  1. 内部OA系统
  2. 一些内容设计比如PPT,海报模板设计时提供下载功能,下载时需要审批,不同租户审批的流程可设置成不一样

技术选型

对比项 Activiti Flowable JBMP Camunda
语言 Java Java Java Java
活跃度 很高github 8.3K 高4.9K 一般 比较高2.3K
优势 易上手,功能全面,文档丰富,支持云原生 基于activiti6,拓展一些新特性,有商业版 近年来新的文档少一些,应用和二次开发可能会比较吃力 基于activiti5,有商业版
  • 从成熟度,活跃度,github标星,易上手,以及对云原生的支持,结合公司自身业务发展选择activiti

入门

Spring-activiti项目

使用
  1. Spring-activiti使用的springboot版本比较低,而且pom文件也没有使用parent标签,所以很多依赖包版需要自己改
  2. 由于我本地是使用mysql8版本所以我把pom的mysql-connector-java的version改为8.0.15
  3. application.yml中spring相关的配置改成
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/activiti5?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&nullCatalogMeansCurrent=true
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: ****
  activiti:
    check-process-definitions: false
    #Activiti记录历史任务数据级别,full是最全的,方便日后查询使用
    history-level: full
    #创建数据库历史数据表
    db-history-used: true
    database-schema-update: true
    process-definition-location-prefix: classpath:/process/
  thymeleaf :
    mode: LEGACYHTML5
  1. 执行activiti.sql

  2. 启动

  3. http://localhost:8888/login 账号xiaomi 密码 1234

  4. 将resource下两个bpmn例子上传


    上传1.png
    上传2.png
  5. 然后就可以开始愉快的熟悉了

BPMN设计说明,流程图在线工具

spring:
      datasource: # 数据源的相关配置 注意加上nullCatalogMeansCurrent=true配置才能自动生成表
        type: com.zaxxer.hikari.HikariDataSource          # 数据源类型:HikariCP
        driver-class-name: com.mysql.jdbc.Driver          # mysql驱动
        url: jdbc:mysql://localhost:3306/activiti6?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&nullCatalogMeansCurrent=true
        username: root
        password: 3876556+0
        hikari:
          connection-timeout: 30000       # 等待连接池分配连接的最大时长(毫秒),超过这个时长还没可用的连接则发生SQLException, 默认:30秒
          minimum-idle: 5                 # 最小连接数
          maximum-pool-size: 20           # 最大连接数
          auto-commit: true               # 自动提交
          idle-timeout: 600000            # 连接超时的最大时长(毫秒),超时则被释放(retired),默认:10分钟
          pool-name: DateSourceHikariCP     # 连接池名字
          max-lifetime: 1800000           # 连接的生命时长(毫秒),超时而且没被使用则被释放(retired),默认:30分钟 1800000ms

      thymeleaf:
        cache: false

      activiti:
        check-process-definitions: false
        #Activiti记录历史任务数据级别,full是最全的,方便日后查询使用
        history-level: full
        #创建数据库历史数据表
        db-history-used: true
        database-schema-update: true
  • 启动
  • 浏览器输入http://localhost:8002 这边使用google打不开,使用safari能正常打开,不知道为啥
  • 可以愉快地编辑了,编辑完之后浏览器请求路径上有modelId参数
流程设计.png
  • 我们可以在这里面加段代码,输入xml文件,这边我们编辑的流程图就可以在别的地方使用了


    xml.png

xml设计以及解释

  • 拿Spring-activiti的采购流程图解释


    采购流程图.png
  • 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" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.activiti.org/test">
  <process id="purchase" name="purchaseprocess" isExecutable="true">
    <startEvent id="startevent1" name="Start" activiti:initiator="${starter}" />
    <userTask id="purchaseAuditi" name="采购经理审批" activiti:formKey="test12138" activiti:assignee="${purchaseManager}" activiti:candidateGroups="采购经理">
      <extensionElements>
        <activiti:formProperty id="startDate"  label="开始日期" type="string">
          <activiti:properties>
            <activiti:property id="author" value="code" />
          </activiti:properties>
        </activiti:formProperty>
        <activiti:formProperty id="endDate" label="结束日期" type="string">
          <activiti:properties>
            <activiti:property id="author" value="code2" />
          </activiti:properties>
        </activiti:formProperty>
      </extensionElements>
    </userTask>
    <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="purchaseAuditi" />
    <exclusiveGateway id="exclusivegateway1" name="Exclusive Gateway" />
    <sequenceFlow id="flow2" sourceRef="purchaseAuditi" targetRef="exclusivegateway1" />
    <userTask id="updateapply" name="调整采购申请" activiti:assignee="${changePurchase}" />
    <sequenceFlow id="flow4" name="不通过" sourceRef="exclusivegateway1" targetRef="updateapply">
      <conditionExpression xsi:type="tFormalExpression">${purchaseauditi=='false'}</conditionExpression>
    </sequenceFlow>
    <exclusiveGateway id="exclusivegateway2" name="是否重新申请" />
    <sequenceFlow id="flow5" sourceRef="updateapply" targetRef="exclusivegateway2" />
    <endEvent id="endevent1" name="End" />
    <sequenceFlow id="flow6" name="不重新申请" sourceRef="exclusivegateway2" targetRef="endevent1">
      <conditionExpression xsi:type="tFormalExpression">${updateapply=='false'}</conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="flow7" name="重新申请" sourceRef="exclusivegateway2" targetRef="purchaseAuditi">
      <conditionExpression xsi:type="tFormalExpression">${updateapply=='true'}</conditionExpression>
    </sequenceFlow>
    <subProcess id="pay" name="付费子流程">
      <startEvent id="startevent2" name="Start" />
      <userTask xmlns:camunda="http://camunda.org/schema/1.0/bpmn" id="financeaudit" name="财务审批" activiti:candidateGroups="财务管理员" camunda:assignee="${finance}" />
      <sequenceFlow id="flow9" sourceRef="startevent2" targetRef="financeaudit" />
      <exclusiveGateway id="exclusivegateway3" name="Exclusive Gateway" />
      <sequenceFlow id="flow10" sourceRef="financeaudit" targetRef="exclusivegateway3" />
      <exclusiveGateway id="exclusivegateway4" name="Exclusive Gateway" />
      <sequenceFlow id="flow11" name="通过" sourceRef="exclusivegateway3" targetRef="exclusivegateway4">
        <conditionExpression xsi:type="tFormalExpression">${finance=='true'}</conditionExpression>
      </sequenceFlow>
      <userTask xmlns:camunda="http://camunda.org/schema/1.0/bpmn" id="manageraudit" name="总经理审批" activiti:candidateGroups="总经理" camunda:assignee="${ceo}" />
      <sequenceFlow id="flow12" name="金额大于1万" sourceRef="exclusivegateway4" targetRef="manageraudit">
        <conditionExpression xsi:type="tFormalExpression">${money&gt;10000}</conditionExpression>
      </sequenceFlow>
      <userTask xmlns:camunda="http://camunda.org/schema/1.0/bpmn" id="paymoney" name="出纳付款" activiti:candidateGroups="出纳员" camunda:assignee="${cashier}" />
      <sequenceFlow id="flow13" name="金额小于1万" sourceRef="exclusivegateway4" targetRef="paymoney">
        <conditionExpression xsi:type="tFormalExpression">${money&lt;10000}</conditionExpression>
      </sequenceFlow>
      <endEvent id="endevent2" name="End" />
      <sequenceFlow id="flow14" sourceRef="paymoney" targetRef="endevent2" />
      <exclusiveGateway id="exclusivegateway5" name="Exclusive Gateway" />
      <sequenceFlow id="flow15" sourceRef="manageraudit" targetRef="exclusivegateway5" />
      <endEvent id="errorendevent1" name="总经理不同意">
        <errorEventDefinition />
      </endEvent>
      <sequenceFlow id="flow17" name="通过" sourceRef="exclusivegateway5" targetRef="paymoney">
        <conditionExpression xsi:type="tFormalExpression">${manager=='true'}</conditionExpression>
      </sequenceFlow>
      <endEvent id="errorendevent2" name="财务不同意">
        <errorEventDefinition />
      </endEvent>
      <sequenceFlow id="flow18" sourceRef="exclusivegateway3" targetRef="errorendevent2">
        <conditionExpression xsi:type="tFormalExpression">${finance=='false'}</conditionExpression>
      </sequenceFlow>
      <sequenceFlow id="flow23" sourceRef="exclusivegateway5" targetRef="errorendevent1">
        <conditionExpression xsi:type="tFormalExpression">${manager=='false'}</conditionExpression>
      </sequenceFlow>
    </subProcess>
    <boundaryEvent id="boundaryerror1" name="Error" attachedToRef="pay">
      <errorEventDefinition />
    </boundaryEvent>
    <sequenceFlow id="flow19" name="捕获子流程异常" sourceRef="boundaryerror1" targetRef="updateapply" />
    <sequenceFlow id="flow20" name="进入付费子流程" sourceRef="exclusivegateway1" targetRef="pay">
      <conditionExpression xsi:type="tFormalExpression">${purchaseauditi=='true'}</conditionExpression>
    </sequenceFlow>
    <userTask xmlns:camunda="http://camunda.org/schema/1.0/bpmn" id="receiveitem" name="收货确认" activiti:assignee="${starter}" camunda:assignee="${receive}" />
    <sequenceFlow id="flow21" sourceRef="pay" targetRef="receiveitem" />
    <endEvent id="endevent3" name="End" />
    <sequenceFlow id="flow22" sourceRef="receiveitem" targetRef="endevent3" />
  </process>
  <!-- 这部分只是帮助前端展示 -->
  <bpmndi:BPMNDiagram id="BPMNDiagram_purchase">
  </bpmndi:BPMNDiagram>
</definitions>
  1. process标签定义了整体流程的id name
<process id="purchase" name="purchaseprocess" isExecutable="true">
  1. startEvent标签定义了开始事件
<startEvent id="startevent1" name="Start" activiti:initiator="${starter}" />
  1. userTask用户事件, 表示用户事件节点,比如图示的采购经理审批,用户事件节点可以有表单信息承接,表单信息处理一般都是放在应用侧,流程引擎底层服务只是存储应用侧定义的表单信息
  2. sequenceFlow标签就是图示的箭头,节点流程箭头
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="purchaseAuditi" />
  1. exclusiveGateway排他网关, 根据不同条件进入不同流程


    排他网关.png
  2. endEvent结束事件
  3. subProcess标签标示子流程


    image.png
  • 还有一些常用标签
  1. boundaryEvent边界事件可以定义timerEventDefinition超时时间


    超时提醒.png
  2. userTask中multiInstanceLoopCharacteristics标签可以设置会签规则,会签表示多人审批规则定义

原理

  • 将业务流程的每个节点读取到数据库中,这样每个节点(包括开始节点和结束节点)就是数据库中的一条记录,当发生业务流程的时候,不断的从业务流程图中读取下一个节点,其实就相当于操作节点对应的数据库记录,这样就实现流程管理和状态字段无关

数据库

  • activiti7流程引擎主要是跟25张表打交道, Activiti 数据库表结构中介绍了activiti6中各个表结构中字段,对于activiti7有参考意义

通用数据表

  • act_ge ge表示 general 全局通用数据及设置,各种情况都使用的数据
  1. act_ge_bytearray: 二进制数据表,存储通用的流程定义和流程资源
  2. act_ge_property: 属性数据表。存储整个流程引擎级别的数据

流程存储表

  • act_re re表示 repository 存储,包含的是静态信息,如,流程定义,流程的资源
  • act_re_deployment : 部署流程定义时需要被持久化保存下来的信息
  • act_re_model: 流程设计器设计流程后,保存数据到该表
  • act_re_procdef: 业务流程定义数据表。此表和 ACT_RE_DEPLOYMENT 是多对一的关系,即,一个部署的bar包里可能包含多个流程定义文件,每个流程定义文件都会有一条记录在 ACT_REPROCDEF 表内,每个流程定义的数据,都会对于 ACT_GE_BYTEARRAY 表内的一个资源文件和 PNG 图片文件

运行数据表

  • act_ru:'ru’表示 runtime,此前缀的表是记录运行时的数据,包含流程实例,任务,变量,异步任务等运行中的数据。Activiti只在流程实例执行过程中保存这些数据,在流程结束时就会删除这些记录
  1. act_ru_deadletter_job 作业死亡信息表,作业失败超过重试次数
  2. act_ru_event_subscr 运行时事件表
  3. act_ru_execution 运行时流程执行实例表
  4. act_ru_identitylink 运行时用户信息表
  5. act_ru_integration 运行时积分表
  6. act_ru_job 运行时作业信息表
  7. act_ru_suspended_job 运行时作业暂停表
  8. act_ru_task 运行时任务信息表
  9. act_ru_timer_job 运行时定时器作业表
  10. act_ru_variable 运行时变量信息表

历史数据表

  • act_hi:'hi’表示 history,此前缀的表包含历史数据,如历史(结束)流程实例,变量,任务等等
  1. act_hi_actinst 历史节点表
  2. act_hi_attachment 历史附件表
  3. act_hi_comment 历史意见表
  4. act_hi_detail 历史详情表,提供历史变量的查询
  5. act_hi_identitylink 历史流程用户信息表
  6. act_hi_procinst 历史流程实例表
  7. act_hi_taskinst 历史任务实例表
  8. act_hi_varinst 历史变量表

其他表

  1. act_evt_log 流程引擎的通用事件日志记录表
  2. act_procdef_info 流程定义的动态变更信息

拓展UserTask

  • 使用activiti流程引擎时,有时候不能满足我们的需求,比如没有处理人时自动跳过任务,任务处理人是任务发起人时自动跳过任务等这种自定义需求时,需要拓展BPMN
  • 拓展方式
  1. 在bpmn流程图的usertask属性中增加自定义标签,比如无处理人自动跳过skipNoProcess
 <userTask id="UserTask_06y6qko" name="A" activiti:skipNoProcess="true"
  1. ProcessEngineConfigurationConfigurer中增加 BpmnXMLConverter.addConverter(继承UserTaskXMLConverter,拓展bpmn文件需要拓展转换能力)
  2. 增加后置处理器能力继承AbstractActivityBpmnParseHandler,ProcessEngineConfigurationConfigurer增加handler

功能

部署流程

  • 例子
String fileName = "***".bpmn";
File file = new File("***".bpmn");
InputStream inputStream = new FileInputStream(file);
MultipartFile multipartFile = new MockMultipartFile(file.getName(), inputStream);
//实现流程部署
Deployment deployment = processEngine.getRepositoryService().createDeployment().addBytes(fileName, multipartFile.getBytes()).name("测试子流程加assignee测试" + RandomUtil.createRandomCharData(10)).key("test_" + RandomUtil.createRandomCharData(10)).deploy();

启动流程

 // 开始流程 -------------------
Map<String, Object> variables = new HashMap<>();
variables.put("purchaseManager", "seeger");
ProcessInstance processInstance = processEngine.getRuntimeService().startProcessInstanceByKey(deployment.getKey(), variables);

完成/转办/回退/审批/取消/改派/任务

完成
processEngine.getTaskService().complete(task.getId())
转办
  • 有些时候需要将节点的assign换成其他人, 重新set aasign即可
task.setAssignee(assigneeId)

表单

  • 参考开始流程的variables设置

会签

  • 涉及到多个审批人时会用到

超时任务

  • 可监听ExecutionListener, 在bpmn设计流程时增加TimeoutListener
代码:
public class TimeoutListener implements ExecutionListener {
    @Override
    public void notify(final DelegateExecution execution) {
    }
}
bpmn流程
    <task id="****" name="超时提醒">
      <extensionElements>
        <activiti:executionListener class="***.TimeoutListener" event="start">
          <activiti:field name="timeout">
            <activiti:string>10</activiti:string>
          </activiti:field>
        </activiti:executionListener>
      </extensionElements>
      <incoming>****</incoming>
    </task>

业务场景分层

  • 工作流引擎底层只封装activiti能力,具体拓展还需要在工作流引擎架一层应用层

各个数据查询

  • 可通过ManagementService查询各个表数据
private ManagementService managementService;
 @Test
    public void testInsertQueryTimeOutingTask() throws InterruptedException {
        List<Job> jobs = managementService.executeCommand(new QueryTimeJobQueryCmd());
        Thread.sleep(60000);
        System.out.println("jobs");
    }

    public static class QueryTimeJobQueryCmd implements Command<List<Job>> {

        @SneakyThrows
        @Override
        public List<Job> execute(CommandContext commandContext) {
            TimerJobQueryImpl timerJobQuery = new TimerJobQueryImpl();
            DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
            Date date = dateFormat.parse("20220125");
            Date date1 = dateFormat.parse("20220126");
            return commandContext.getTimerJobEntityManager().findJobsByQueryCriteria(timerJobQuery, new org.activiti.engine.impl.Page(0, 10));
        }
    }

历史数据查询

  • 查询act_hi_*表数据

参考文章

相关文章

网友评论

    本文标题:业务场景实战(三)工作流引擎

    本文链接:https://www.haomeiwen.com/subject/okjicrtx.html