架构收藏

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

2022-02-09  本文已影响0人  后来丶_a24d

思维导图

思维导图.png

系列总目录


背景

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

技术选型

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

入门

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
流程设计.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标签可以设置会签规则,会签表示多人审批规则定义

原理


数据库

通用数据表

  1. act_ge_bytearray: 二进制数据表,存储通用的流程定义和流程资源
  2. act_ge_property: 属性数据表。存储整个流程引擎级别的数据

流程存储表

运行数据表

  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 运行时变量信息表

历史数据表

  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

  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())
转办
task.setAssignee(assigneeId)

表单

会签

超时任务

代码:
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>

业务场景分层

各个数据查询

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));
        }
    }

历史数据查询


参考文章

上一篇下一篇

猜你喜欢

热点阅读