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

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

作者: 后来丶_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