Activit7 workflow application + actual combat (the most detailed and complete in the whole network, the length is huge, don't look at it)

foreword

At the request of the company, it is necessary to study the framework activiti7 in the project, so the following content is available.

This article is very long, if you want to be simpler, you can go to the topic to view it from the link below:

(后续只更新专题的内容,总篇幅太长不动了)

(1) Relevant application and tool versions

  • Activiti version: 7.0.0.SR1入门代码使用
  • Activiti version: 7.1.0.M6业务实战代码使用
  • jdk1.8
  • database (mysql)入门使用 mysql
  • Database (kingbase8)业务实战使用 kingbase8

(2) Workflow background introduction

  1. The first version of Activity was released in May 2010. At that time, it only supported the simplest process processing. Later versions have gradually improved the support for the BPMN 2.0 specification. Its core is developed using Java. Its predecessor is JBPM.
  2. activiti5, activiti6, and flowable were developed by the Tijs Rademakers team, and Activiti7 was developed by the Salaboy team. During the period from 2016.7 to 2017.5, there were major differences within the activiti team, and finally Activiti was handed over to the Salaboy team (which can be understood as a work handover before leaving), and the team developed a new workflow framework: flowable.
  3. In mid-June 2019, salboy has left Alfresco, and the development trend of activiti7/8 is unclear.
  4. Flowable takes the 6.4.1 version as a watershed and vigorously develops its commercial version products. The open source version is not maintained in time. Some functions are no longer released in the open source version, such as form generator (form engine), synchronization of historical data to other data sources, es, etc. dmn is currently a semi-finished product, not as stable and easy to use as camunda, and has weak support for dmn specifications. Some components of the commercial version are commercialized, so the open source version is no longer maintained. Mongdb is also currently in commercial products, and the open source version is almost unusable.

(3) What is workflow

  1. Workflow is to automate the management of business processes through computers.
  2. It mainly solves "the process of automatically transferring documents, information or tasks among multiple participants according to some predefined rules, so as to achieve an expected business goal, or promote the realization of this goal"

(4) Workflow Applicable Application Scenarios

  • Key business processes: order, quotation processing, contract review, customer call processing, supply chain management, etc.
  • Administrative management: business trip applications, overtime applications, leave applications, car applications, various office supplies applications, purchase applications, daily and weekly reports, etc. All administrative forms that were originally processed manually.
  • Personnel management: employee training arrangement, performance appraisal, job change processing, employee file information management, etc.
  • Finance-related categories: payment requests, receivables processing, daily reimbursement processing, business trip reimbursement, budget and plan applications, etc.
  • Customer service category: customer information management, customer complaints, request processing, after-sales service management, etc.
  • Special services: ISO series corresponding processes, quality management corresponding processes, product data information management, customs declaration processing for trading companies, cargo tracking processing for logistics companies, etc. Various tasks that are completed manually through forms can be automatically and standardizedly implemented by using workflow software .

(5) Advantages and disadvantages of workflow

  1. Pros (too many to list the obvious three)
  • The biggest advantage is that it is free and open source;
  • Process automation, office automation;
  • Decouple process and business;
  1. shortcoming
  • Nodes are understood differently. Foreigners understand nodes as "script tasks" and "user tasks", which cannot be subdivided and operated by a single person (or role). The Chinese-style node understanding mostly includes logical processing such as judgment, and can be subdivided, and may also involve multi-person operations;
  • Backtracking is difficult. In the workflow, there are only pointers, no reverse thinking, and backtracking often involves a large number of tables and table records. For example, it does not include the rejection function...
  • Extension needs to be implemented with many Events.
  • It is easy to start, but the secondary development is difficult and the threshold is high.

(6) What can workflow do for us?

The approximate required steps for two and three people involved in the following processes:
insert image description here
reimbursement process and leave process, procurement -> warehousing
insert image description here

  1. In the first picture, there are only five steps: initiate, query, transfer to the next task, complete the task, and end. Each additional person needs to increase the middle three links.
  2. In the second picture, every time a process scene is switched, the entire process needs to be re-implemented. And increase or decrease the approval node, need to customize the development again.

结论:若没有 Activiti ,我们会过度关心业务需求,且流程环节无法进行有机组合,不够灵活。

(7) Summary of workflow

  • 很多博客、教学视频都会说,用了工作流后,不管流程怎么变,你都不需要改代码,这其实是错误的,因为我们的流程充满了业务,改了流程环节,必定牵扯到业务,所以肯定会进行代码上的修改的。特别是你新增了步骤,修改了业务数据,这些都是必须要改代码的。
  • 如果你每个步骤都是进行同样的操作,只是数据的值不同,就像请假的两次审核,还真是可以做到不管怎么变流程,都不用改代码。
  • Activiti 最大的作用是让我们不必“过分关注业务需求”,自动化让我们更多的关注于流程节点的合理性设计。

The following is a schematic diagram of the states of each node in the workflow:
insert image description here

(8) Activiti Reference Documentation

Activiti is a workflow framework with the largest number of users, mature technology, and adaptable to multiple databases. Workflow is used wherever process management is required. The following are some relevant official addresses, click to enter to get:

  1. Activiti official website

  2. Activiti Official Getting Started Tutorial: Activiti User Guide

  3. Draw a flow chart online

  4. Activiti database table structure

(9) BPMN2.0

The biggest difference between BPMN2.0 and BPMN1.0 is that it defines and standardizes the execution semantics and format of the process engine, uses standard graph elements to describe the real business process, and ensures that the same process is executed consistently in different process engines result. In the 2.0 set of standards, three types of basic elements are mainly defined for process execution, namely Activities, Gateways, and Events.

1 start event

insert image description here

1.1 StartEvent (empty start event)

An empty start event technically means that no trigger condition is specified to start the process instance. This means that the engine cannot predict when a process instance will start. The empty start event is used when the process instance is started through the API. By calling the startProcessInstanceByXXX method, the sub-process has an empty start event

ProcessInstance processInstance = runtimeService.startProcessInstanceByXXX();

1.2 TimerStartEvent (timing start event)

A timed start event is used to create a process instance at a specified time. It can be used for both processes that are started only once and processes that should be started multiple times at specific time intervals. Sub-processes cannot use timed start events, which start counting time after the process is released. There is no need to call startProcessInstanceByXXX. When a new version of the process that includes a timed start event is deployed, the corresponding previous timer will be deleted.
insert image description here
Among them, timeDuration is how long to wait before specifying the timer; timeDate is to use the ISO 8601 format to specify a certain time, the time to trigger the event; timeCycle specifies the interval of repeated execution, which can be used to start the process instance periodically, or send for timeout Multiple reminders.

1.3 MessageStartEvent (message start event)

The message start event can be used to use a named message to start the process instance, which can help us use the message name to select the correct start event.

注意:

  • 消息开始事件的名称在给定流程定义中不能重复。流程定义不能包含多个名称相同的消息开始事件。 如果两个或以上消息开始事件应用了相同的事件,或两个或以上消息事件引用的消息名称相同,activiti会在发布流程定义时抛出异常。
  • 消息开始事件的名称在所有已发布的流程定义中不能重复。 如果一个或多个消息开始事件引用了相同名称的消息,而这个消息开始事件已经部署到不同的流程定义中, activiti就会在发布时抛出一个异常。
  • 流程版本:在发布新版本的流程定义时,之前订阅的消息订阅会被取消。 如果新版本中没有消息事件也会这样处理。
    When a process instance starts, the message start event can be triggered using the following methods in RuntimeService:
ProcessInstance startProcessInstanceByMessage(String messageName);
ProcessInstance startProcessInstanceByMessage(String messageName, Map<String, Object> processVariables);
ProcessInstance startProcessInstanceByMessage(String messageName, String businessKey, Map<String, Object< processVariables);

insert image description here

1.4 ErrorStartEvent (error start event)

The error start event can be used to trigger an event sub-process, the error start event cannot be used to start the process instance, and the error start event is an interrupt event.
insert image description here

1.5 SignalStartEvent (signal start event)

The signal start event can be used to start a process instance through a named signal (signal). Signals can be triggered within the process instance using the "intermediate signal throwing transaction", or through the API (runtimService.signalEventReceivedXXX method). In both cases, a signalStartEvent with the same name is started in all process instances.
insert image description here

2 end event

2.1 EndEvent (null end event)

A null-ended event means that when the event is reached, no result is specified to be thrown. In this way, the engine will directly end the currently executing branch without doing other things.
insert image description here

2.2 ErrorEndEvent (error end event)

When the process execution reaches the error end event, the current branch of the process will end and an error will be thrown. This error can be caught by the corresponding intermediate boundary error event. If no matching boundary error event is found, an exception is thrown.
insert image description here

2.3 TerminateEndEvent (terminate end event)

A terminate event is represented as an end event, which has a terminateEventDefinition sub-element. The terminateAll attribute is optional and is false by default. An optional attribute terminateAll can be added. If true, the (root) process instance will be terminated regardless of whether a terminate end event is placed in the process definition, and regardless of whether it is in a subprocess (even nested).
insert image description here

2.4 CancelEndEvent (cancel end event)

Cancel end events can only be used in conjunction with BPMN transactional subprocesses. When the cancel end event is reached, the cancel event is thrown, which must be caught by the cancel boundary event. A cancel boundary event cancels the transaction and triggers the compensation mechanism.
insert image description here

3 Boundary events

Regarding the boundary event, it is a capture event, which will be attached to a link, and the boundary event cannot trigger an event, which means that when the node is running, the event will listen to the corresponding trigger type. When the event is caught, the node will be interrupted, and the subsequent connection of the event will be executed.
insert image description here

3.1 TimerBoundaryEvent (timing boundary event)

A timed boundary event is a clock that pauses for a warning. When the process executes to the link bound to the boundary event, a timer will be started. When the timer fires (for example, after a certain time), the link will be interrupted and continue to execute along the outgoing line of the timing boundary event.
insert image description here

3.2 ErrorBoundaryEvent (error boundary event)

An intermediate catch error event on a node boundary, or simply a boundary error event, catches errors thrown within the scope of the node. Define a boundary error event, which is mostly used in embedded sub-processes or calling nodes. In the case of sub-processes, it will create a scope for all internal nodes. Errors are thrown by the error end event. This error will be passed to the upper scope until an error event definition is found that matches the boundary error event. When an error event is caught, the node bound to the boundary task will be destroyed, and all internal execution branches will also be destroyed (for example, synchronization nodes, embedded sub-processes, etc.). Process execution continues along the outgoing edge of the boundary event.
insert image description here

3.3 MessageBoundaryEvent (message boundary event)

Boundary message events, catch messages with the same message name according to the referenced message definition.
insert image description here

3.4 CancelBoundaryEvent (cancel boundary event)

Intermediate capture cancellation on the boundary of a transactional subprocess, or simply boundary cancellation event, fires when the transaction is cancelled. When a cancel boundary event fires, all execution in the current scope is first interrupted. Then start compensating all active compensating boundary events within the transaction. Compensation is performed synchronously. For example, leaving the transaction money, the boundary transaction will wait for the compensation to be executed. When the compensation is complete, the transaction sub-process will continue along the outgoing connection of the cancellation boundary transaction.
insert image description here

3.5 CompensationBoundaryEvent (compensation boundary event)

Mid-capture offsets at node boundaries, or simply offset boundary events, can be used to set a node's compensation handler. Compensation boundary events must use direct references to set unique compensation handlers. The strategy for compensating boundary events differs from other boundary events. Other boundary events (such as signal boundary events) are activated when they reach the associated node. When leaving the node, it will be suspended and the corresponding event subscription will be cancelled. Compensating for boundary events is different. Compensation boundary events fire when the associated node completes successfully. The event subscription will only be deleted when the compensation event is triggered or the corresponding process instance ends.
insert image description here

3.6 SignalBoundaryEvent (signal boundary event)

A signal boundary event that catches a signal with the same signal name referenced by the signal definition. Unlike other events (such as the boundary error event), the boundary signal event does not only capture the signal of the orientation it is bound to. Signal events have a global scope (broadcast semantics), which means that signals can be triggered anywhere, even in different process instances. Once a signal is captured, it does not stop the propagation of the signal. If you have two signal boundary events that capture the same signal event, both boundary events will be fired even if they are in different process instances.
insert image description here

4 Intermediate capture/trigger events

insert image description here

4.1 TimerCatchingEvent (timed intermediate capture event)

Timed intermediate events act as a listener. When execution reaches a capture event node, a timer is started. When the timer fires (for example, after a period of time), the process will continue to execute along the outgoing node of the timed intermediate event.
insert image description here

4.2 SignalCatchingEvent (signal intermediate capture event)

An intermediate catch signal event catches a signal of the same signal name by referencing the signal definition. Unlike other events (such as error events), signals are not consumed after they are caught. If you have two active signal boundary events that capture the same signal event, both boundary events will be fired even if they are in different process instances.
insert image description here

4.3 MessageCatchingEvent (message capture event)

Catch a message with a specific name.
insert image description here

4.4 SignalThrowingEvent (signal middle trigger event)

An intermediate trigger signal event throws a signal event for the defined signal. In activiti, signals are broadcast to all active handlers (eg, so signal events are caught). Signals can be published both synchronously and asynchronously.
insert image description here

4.5 CompensationThrowingEvent (compensation intermediate trigger event)

An intermediate trigger compensation event can be used to trigger compensation. Triggered compensation means that compensation can be triggered by a specific node or scope containing compensation events. Compensation is done through compensation processors assigned to nodes.
insert image description here

4.6 NoneThrowingEvent (null event triggered in the middle)

This event is typically used to indicate that some state has been reached in the process.
insert image description here

5 tasks

In the use of workflow Activiti, tasks are indispensable elements. Through various tasks, the execution of each link in the operating system is completed. Tasks are divided into user tasks, script tasks, Java service tasks, mail tasks, manual tasks, The business rule task and the call activity (sub-process) task are introduced one by one below.
insert image description here

5.1 User tasks

User tasks are used to set up work that must be done by people. When the process executes a user task, a new task will be created and added to the task list of the assignee or group.
insert image description here

5.2 Script tasks

The script task is an automatic node. When the process reaches the script task, the corresponding script will be executed.
insert image description here

5.3 Java service tasks

Java service tasks are used to call external java classes.
insert image description here

5.4 Mail task

activiti strengthens the business process and supports automatic email tasks, which can send emails to one or more participants, including support for cc, bcc, HTML content, etc. The activiti engine needs to send mail through an external mail server that supports the SMTP function. In order to actually send the mail, the engine needs to know how to access the mail server. Configure in the activiti.cfg.xml configuration file:

mailServerHost—邮件服务器的主机名

mailServerPort—邮件服务器上的SMTP传输端口。默认为25

mailServerDefaultFrom—如果用户没有指定发送邮件的邮件地址,默认设置的发送者的邮件地址。

mailServerUsername—邮件服务器认证用户名

mailServerPassword—邮件服务器认证密码

mailServerUseSSL—ssl交互。默认为false

mailServerUseTLS—是否需要支持TLS。默认为false。

insert image description here

5.5 Manual tasks

Manual tasks define tasks external to the BPM engine. It is used to indicate that the work needs to be done by someone, but the engine does not need to know, and there is no corresponding system and UI interface. For the engine, a manual task is a pass-through activity that automatically descends after the process reaches it.
insert image description here

5.6 Receive tasks

A receive task is a simple task that waits for the corresponding message to arrive. Currently, we only implement the Java semantics for this task. When the process reaches the receive task, the process state is saved to storage. It means that the process will wait in this waiting state until the engine receives a specific message, which will trigger the process to continue execution through the receiving task.
insert image description here

5.7 Business Rule Tasks

A business rule user is used to execute one or more rules synchronously. Activiti uses the drools rule engine to enforce business rules. Currently, .drl files containing business rules must be published together with a process definition that contains business rule tasks that execute those rules. It means that all .drl files used by the process must be packaged in the process BAR file, such as task forms.
insert image description here

5.8 Call activity (subprocess) task

The call node refers to a process outside the process definition. The main scenario for using the call node is when the process definition needs to be reused, and this process definition needs to be called by many other process definitions. When the process executes to the call node, a new branch is created, which is the branch of the process that reaches the call node. This branch will be used to execute sub-processes, by default creating parallel sub-processes, just like a normal process. The upper-level process waits for the sub-process to complete before continuing downward.
insert image description here

6 gateways

The gateway is used to control the flow of the process, and the gateway can consume or generate tokens. The gateway is displayed as a diamond shape with a small icon inside. Icons indicate the type of gateway.
insert image description here

6.1 Parallel Gateway

Gateways can also represent parallelism in a process. Parallel gateways allow processes to be divided into multiple branches, and multiple branches can also be brought together. The functionality of the parallel gateway is based on incoming and outgoing sequential flows:

  • Branch: All outgoing sequence flows after parallelization, creating a concurrent branch for each sequence flow.

  • Convergence: All the incoming branches that arrive at the parallel gateway and wait here until all the branches that enter the sequential flow arrive, the flow will pass through the convergent gateway.

If the same parallel gateway has multiple incoming and multiple outgoing sequence flows, it has both branching and converging functions. At this time, the gateway will first aggregate all incoming sequence flows, and then split them into multiple parallel branches. The main difference from other gateways is that parallel gateways do not parse conditions. Even if a condition is defined in the sequence flow, it will be ignored.
insert image description here

6.2 Exclusive Gateway

Exclusive gateways (also called exclusive-or (XOR) gateways, or more technically data-based exclusive gateways) are used to implement decisions in the process. When the process executes to this gateway, all outgoing sequence flows will be processed again. Sequence flows in which the condition resolves to true (or no condition is set, conceptually defining a 'true' on the sequence flow) will be selected to allow the process to continue running.
insert image description here

6.3 Include Gateway

Inclusive gateways can be seen as a combination of exclusive and parallel gateways. As with the exclusive gateway, you can define conditions on the outgoing sequence flow, and the containing gateway will resolve them. But the main difference is that containment gateways can select more than one sequence flow, just like parallel gateways. Functions that include gateways are based on incoming and outgoing sequence flows:

  • Branch: All the conditions of the outgoing sequence flow will be parsed, and the sequence flow whose result is true will continue to execute in parallel, and a branch will be created for each sequence flow.

  • Convergence: All parallel branches arrive at the containing gateway and wait until each branch of the incoming sequence flow containing the process token arrives. This is the biggest difference from parallel gateways. The containing gateway will only wait for incoming sequence flows that are selected for execution. After convergence, the flow continues through the containing gateway.

If the same containing node has multiple incoming and outgoing sequence flows, it will contain both branching and converging functionality. At this time, the gateway will first gather all the incoming sequence flows that have process tokens, and then generate multiple parallel branches for the outgoing sequence flows that are true according to the conditions.
insert image description here

6.4 Based on event gateway

Event-based gateways allow the flow to be determined based on events. Each outgoing sequence flow of the gateway is connected to an intermediate capture event. When a process reaches an event-based gateway, the gateway enters a wait state: execution is suspended. At the same time, relative event subscriptions are created for each outgoing sequence flow.

Note that the outgoing sequence flow based on the event gateway is different from the normal sequence flow. These sequence flows don't actually "execute". Instead, they leave it up to the process engine to decide which events to subscribe to for execution into an event gateway-based process. The following conditions are to be considered:

  • Event-based gateways must have two or more outbound sequence flows.
  • Based on the event gateway, only the intermediateCatchEvent type can be used. (Activiti does not support connecting ReceiveTask based on the event gateway.)
  • There can only be one incoming sequence flow connected to an intermediateCatchEvent based event gateway.
    insert image description here

(10) Getting Started with Activiti7

1 Some thoughts on Activiti (the focus of this article)

  • How to get started with activiti7?
  • How to pass parameters and set values ​​for business parameters?
  • How to use exclusive gateway and parallel gateway?
  • How to use the task listener?

2 Understanding Activiti

insert image description here

2.1 activiti.cfg.xml

Activiti's engine configuration file, including: ProcessEngineConfiguration definition, data source definition, transaction manager, etc. This file is actually a spring configuration file.

2.2 ProcessEngineConfiguration

The workflow engine ProceccEngine can be created through ProcessEngineConfiguration, which is mainly responsible for reading the configuration of activiti.cfg.xml.

public static ProcessEngineConfiguration createProcessEngineConfigurationFromResourceDefault() {
    
    
    return createProcessEngineConfigurationFromResource("activiti.cfg.xml", "processEngineConfiguration");
}

2.3 ProcessEngine

It is equivalent to a facade interface. ProcessEngine is created through ProcessEngineConfiguration, and each service interface is created through ProcessEngine. The specific code logic is implemented by ProcessEngineImpl.class.

public interface ProcessEngine {
    
    
    String VERSION = "7.0.0.0";

    String getName();

    void close();

    RepositoryService getRepositoryService();

    RuntimeService getRuntimeService();

    TaskService getTaskService();

    HistoryService getHistoryService();

    ManagementService getManagementService();

    DynamicBpmnService getDynamicBpmnService();

    ProcessEngineConfiguration getProcessEngineConfiguration();
}

2.4 Service core management class

  • RepositoryService : It is the resource management class of activiti, which provides operations for managing and controlling process release packages and process definitions .
  • RuntimeService : Activiti's process operation management class. You can get a lot of information about process execution from this service class .
  • TaskService : Activiti's task management class. Task information can be obtained from this class .
  • HistoryService : Activiti's history management class, which can query historical information . When executing a process, the engine will save a lot of data (according to configuration), such as the start time of the process instance, the participants of the task, the time to complete the task, and the execution path of each process instance ,etc. This service obtains these data mainly through the query function.
  • ManagementService : Activiti's engine management class provides management and maintenance functions for the Activiti process engine. These functions are not used in workflow-driven applications and are mainly used for the daily maintenance of the Activiti system .
  • FormService : Deprecated, this management class does not conform to the design concept of activiti.
  • IdentityService : Authentication management class, not used for now.

2.5 Database tables

The Activiti process framework has its own set of database tables, a total of 25 tables, through which process management is realized.

This is the explanation of the official database table, the document is applicable to Activiti5~6: Activiti database table structure

  • ACT_RE_ : 'RE' means repository, meaning warehouse. The table with this prefix contains the process definition and process static resources
  • ACT_RU_ : RU stands for runtime, which means runtime. These are runtime tables that contain runtime data for process instances, user tasks, variables, jobs, etc. Activiti only stores runtime data during process instance execution and deletes records when the process instance ends. This keeps running schedules uninterruptedly fast.
  • ACT_ID_ : ID stands for identity, identification. These tables contain identity information such as users, groups, etc.
  • ACT_HI_ : HI stands for history. These are tables containing historical data such as past process instances, variables, tasks, etc.
  • ACT_GE_ : general data, used for various use cases.

2.6

3 Getting Started Configuration Work

3.1 Create a database named: activiti

insert image description here

3.2 Create a maven project and add dependencies

  <properties>
    <slf4j.version>1.6.6</slf4j.version>
    <log4j.version>1.2.12</log4j.version>
    <activiti.version>7.0.0.SR1</activiti.version>
  </properties>

  <dependencies>
    <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>
    <!-- bpmn 模型处理 -->
    <dependency>
      <groupId>org.activiti</groupId>
      <artifactId>activiti-bpmn-model</artifactId>
      <version>${activiti.version}</version>
    </dependency>
    <!-- bpmn 转换 -->
    <dependency>
      <groupId>org.activiti</groupId>
      <artifactId>activiti-bpmn-converter</artifactId>
      <version>${activiti.version}</version>
    </dependency>
    <!-- bpmn json数据转换 -->
    <dependency>
      <groupId>org.activiti</groupId>
      <artifactId>activiti-json-converter</artifactId>
      <version>${activiti.version}</version>
    </dependency>
    <!-- bpmn 布局 -->
    <dependency>
      <groupId>org.activiti</groupId>
      <artifactId>activiti-bpmn-layout</artifactId>
      <version>${activiti.version}</version>
    </dependency>
    <!-- activiti 云支持 -->
    <dependency>
      <groupId>org.activiti.cloud</groupId>
      <artifactId>activiti-cloud-services-api</artifactId>
      <version>7.0.0.Beta1</version>
      <exclusions>
        <exclusion><!-- 排除activiti的mybatis,避免和外面的mybatis-plus冲突 -->
          <!-- 重点坑,不然启动项目会报错mybatisplus缺少类   -->
          <artifactId>mybatis</artifactId>
          <groupId>org.mybatis</groupId>
        </exclusion>
      </exclusions>
    </dependency>
    <!-- mysql驱动 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.27</version>
    </dependency>

    <!-- mybatis -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.4.5</version>
    </dependency>
    <!-- 链接池 -->
    <dependency>
      <groupId>commons-dbcp</groupId>
      <artifactId>commons-dbcp</artifactId>
      <version>1.4</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
    </dependency>
    <!-- log start -->
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>${log4j.version}</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>${slf4j.version}</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>${slf4j.version}</version>
    </dependency>
  </dependencies>

3.3 Activiti configuration file activiti.cfg.xml

insert image description here

  <!--数据库连接池方式配置-->
  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://192.168.1.181/activiti?useSSL=false"/>
    <property name="username" value="root"/>
    <property name="password" value="test@xhkj"/>

    <property name="maxActive" value="3"/>
    <property name="maxIdle" value="1"/>

  </bean>
  <!--同样, 在默认方式下bean的id固定为 processEngineConfiguration-->
  <bean id="processEngineConfiguration"
        class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
    <!--引入上面配置好的 链接池-->
    <property name="dataSource" ref="dataSource"/>
    <property name="databaseSchemaUpdate" value="true"/>
  </bean>

4 Start Activiti

直接见代码,相关注释,代码里都很详细

package com.activiti.test;

import org.activiti.engine.*;
import org.junit.Test;

public class ActivitiCreate {
    
    

    /**
     * 使用activiti提供的默认方式来创建mysql的表
     */
    @Test
    public void testCreateDbTable1() {
    
    
        // 需要使用avtiviti提供的工具类 ProcessEngines ,使用方法getDefaultProcessEngine
        // getDefaultProcessEngine会默认从resources下读取名字为activiti.cfg.xml的文件
        // 创建processEngine时,就会创建mysql的表
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        System.out.println(processEngine);
    }

    @Test
    public void testGetService() {
    
    
        //获取各个service实例
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        RepositoryService repositoryService = processEngine.getRepositoryService();
        RuntimeService runtimeService = processEngine.getRuntimeService();
        TaskService taskService = processEngine.getTaskService();
        HistoryService historyService = processEngine.getHistoryService();
    }

    /**
     * 一般创建方式
     */
    @Test
    public void testCreateDbTable2() {
    
    
		// ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml");
        // 配置文件的名字可以自定义,bean的名字也可以自定义
        ProcessEngineConfiguration processEngineConfiguration = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml","processEngineConfiguration");
        // 获取流程引擎对象
        ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine();
    }
}

5 Simple use of Activiti

直接见代码,相关注释,代码里都很详细

package com.activiti.test;

import org.activiti.engine.*;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.junit.Test;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.List;


public class ActivitiDemo {
    
    

    /**
     * 流程部署
     * `act_re_deployment`   流程部署表,每部署一次会增加一条记录
     * `act_re_procdef`      流程定义表,张三会申请出差流程,李四也会申请出差流程,每个人申请都会增加一条记录
     * act_re_deployment与act_re_procdef是一对多关系
     * `act_ge_bytearray`    流程资源表,每个资源都会增加一条记录
     */
    @Test
    public void testDeployment() throws FileNotFoundException {
    
    

        String evectionBpmnPath = ActivitiDemo.class.getClassLoader().getResource("bpmn/evection.bpmn").getFile();
        File evectionBpmnFile = new File(evectionBpmnPath);
        InputStream evectionBpmnIs = new FileInputStream(evectionBpmnFile);

        String evectionPngPath = ActivitiDemo.class.getClassLoader().getResource("bpmn/evection.png").getFile();
        File evectionPngFile = new File(evectionPngPath);
        InputStream evectionPngIs = new FileInputStream(evectionPngFile);

        // 1、创建ProcessEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

        // 2、获取RepositoryService
        RepositoryService repositoryService = processEngine.getRepositoryService();

        // 3、使用service进行流程的部署,定义一个流程的名字,把bpmn和png部署到数据中
        Deployment deploy = repositoryService.createDeployment()
                .name("出差申请流程")
                .addInputStream("evection.bpmn", evectionBpmnIs)
                .addInputStream("evection.png", evectionPngIs)
                .deploy();

        // 4、输出部署信息
        System.out.println("流程部署id=" + deploy.getId());
        System.out.println("流程部署名字=" + deploy.getName());
    }

    /**
     * 启动流程实例
     * `act_hi_actinst`    流程实例执行历史信息
     * `act_hi_identitylink` 流程参与用户的历史信息
     * `act_hi_procinst`     流程实例的历史信息
     * `act_hi_taskinst`     流程任务的历史信息
     * `act_ru_execution`    流程执行信息
     * `act_ru_identitylink`  流程的正在参与用户信息
     * `act_ru_task`         流程当前任务信息
     */
    @Test
    public void testStartProcess() {
    
    
        // 1、创建ProcessEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 2、获取RunTimeService
        RuntimeService runtimeService = processEngine.getRuntimeService();
        // 3、根据流程定义的id启动流程
        ProcessInstance instance = runtimeService.startProcessInstanceByKey("myEvection");
        // 4、输出内容
        System.out.println("流程定义ID:" + instance.getProcessDefinitionId());
        System.out.println("流程实例ID:" + instance.getId());
        System.out.println("当前活动的ID:" + instance.getActivityId());
    }

    /**
     * 查询个人待执行的任务
     */
    @Test
    public void testFindPersonalTaskList() {
    
    
        // 1、获取流程引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 2、获取taskService
        TaskService taskService = processEngine.getTaskService();
        // 3、根据流程key 和 任务的负责人 查询任务
        List<Task> taskList = taskService.createTaskQuery()
                .processDefinitionKey("myEvection") //流程Key
                .taskAssignee("李四")  //要查询的负责人
                .list();
        // 4、输出
        for (Task task : taskList) {
    
    
            System.out.println("流程实例id=" + task.getProcessInstanceId());
            System.out.println("任务Id=" + task.getId());
            System.out.println("任务负责人=" + task.getAssignee());
            System.out.println("任务名称=" + task.getName());
        }
    }

    /**
     * 完成个人任务 : 创建出差申请
     * `act_ru_task` 这里的 '创建出差申请' 会变为 '经理审批'
     * 也就是也是下一个任务:  经理审批
     * `act_hi_taskinst` 这里的'创建出差申请'这条记录会有开始时间和结束时间
     * 然后还会增加一条记录 '经理审批'
     */
    @Test
    public void completeTask() {
    
    
        // 获取引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 获取操作任务的服务 TaskService
        TaskService taskService = processEngine.getTaskService();
        // 完成任务,参数:任务id,完成 '张三' 的任务
        taskService.complete("2505");
    }

    // 改造: 不把id写死,通过查询,然后执行任务
    // 完成经理'李四'的任务
    @Test
    public void completeTask2() {
    
    
        // 获取引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 获取操作任务的服务 TaskService
        TaskService taskService = processEngine.getTaskService();
        // 完成 经理 '李四' 的任务
        Task task = taskService.createTaskQuery()
                .processDefinitionKey("myEvection")
                .taskAssignee("李四")
                .singleResult();

        System.out.println("流程实例id=" + task.getProcessInstanceId());
        System.out.println("任务Id=" + task.getId());
        System.out.println("任务负责人=" + task.getAssignee());
        System.out.println("任务名称=" + task.getName());

        taskService.complete(task.getId());
    }

    // 完成总经理'老马'的任务
    @Test
    public void completeTask3() {
    
    
        // 获取引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 获取操作任务的服务 TaskService
        TaskService taskService = processEngine.getTaskService();
        // 完成 经理 '李四' 的任务
        Task task = taskService.createTaskQuery()
                .processDefinitionKey("myEvection")
                .taskAssignee("老马")
                .singleResult();

        System.out.println("流程实例id=" + task.getProcessInstanceId());
        System.out.println("任务Id=" + task.getId());
        System.out.println("任务负责人=" + task.getAssignee());
        System.out.println("任务名称=" + task.getName());

        taskService.complete(task.getId());
    }

    // 完成财务'小王'的任务
    @Test
    public void completeTask4() {
    
    
        // 获取引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 获取操作任务的服务 TaskService
        TaskService taskService = processEngine.getTaskService();
        // 完成 经理 '李四' 的任务
        Task task = taskService.createTaskQuery()
                .processDefinitionKey("myEvection")
                .taskAssignee("小王")
                .singleResult();

        System.out.println("流程实例id=" + task.getProcessInstanceId());
        System.out.println("任务Id=" + task.getId());
        System.out.println("任务负责人=" + task.getAssignee());
        System.out.println("任务名称=" + task.getName());

        taskService.complete(task.getId());
    }

    // 如果一个流程中有多个任务,可以用list
    @Test
    public void completeTask5() {
    
    
        // 获取引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        // 获取操作任务的服务 TaskService
        TaskService taskService = processEngine.getTaskService();
        // 完成 经理 '李四' 的任务
        List<Task> tasks = taskService.createTaskQuery()
                .processDefinitionKey("myEvection")
                .taskAssignee("李四")
                .list();  // 多个任务,用list

        for (Task task : tasks) {
    
    
            if (true) {
    
    
                // 判断是哪任务
                System.out.println("流程实例id=" + task.getProcessInstanceId());
                System.out.println("任务Id=" + task.getId());
                System.out.println("任务负责人=" + task.getAssignee());
                System.out.println("任务名称=" + task.getName());
                taskService.complete(task.getId());
            }
        }
    }
}

(11) Activiti7 actual combat

1 Basic process

1.1 Flowchart

此处每个 userTask 均需设置执行人
insert image description here

1.2 BPMN files

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tns="http://www.activiti.org/testm1625990337688" xmlns:xsd="http://www.w3.org/2001/XMLSchema" id="m1625990337688" name="" targetNamespace="http://www.activiti.org/testm1625990337688">
  <process id="zhanleai_approve_base" name="测试出差申请流程" processType="None" isClosed="false" isExecutable="true">
    <documentation />
    <startEvent id="_2" name="StartEvent" />
    <userTask id="_3" name="创建出差申请" activiti:assignee="张三" activiti:exclusive="true">
      <documentation />
      <extensionElements>
        <activiti:taskListener event="create" class="com.gh.maintenance.controller.activiti_example.ActivitiApproveListener" />
      </extensionElements>
    </userTask>
    <userTask id="_4" name="经理审批" activiti:assignee="李四" activiti:exclusive="true">
      <documentation />
      <extensionElements />
    </userTask>
    <userTask id="_5" name="总经理审批" activiti:assignee="老马" activiti:exclusive="true">
      <documentation />
      <extensionElements />
    </userTask>
    <userTask id="_6" name="财务审批" activiti:assignee="小王" activiti:exclusive="true" />
    <endEvent id="_7" name="EndEvent" />
    <sequenceFlow id="_8" sourceRef="_2" targetRef="_3" />
    <sequenceFlow id="_9" sourceRef="_4" targetRef="_5" />
    <sequenceFlow id="_10" sourceRef="_5" targetRef="_6" />
    <sequenceFlow id="_11" sourceRef="_6" targetRef="_7" />
    <sequenceFlow id="_12" sourceRef="_3" targetRef="_4" />
  </process>
  <bpmndi:BPMNDiagram id="Diagram-_1" name="New Diagram" documentation="background=#3C3F41;count=1;horizontalcount=1;orientation=0;width=842.4;height=1195.2;imageableWidth=832.4;imageableHeight=1185.2;imageableX=5.0;imageableY=5.0">
    <bpmndi:BPMNPlane bpmnElement="zhanleai_approve_base">
      <bpmndi:BPMNEdge id="BPMNEdge__12" bpmnElement="_12" sourceElement="_3" targetElement="_4">
        <di:waypoint x="650" y="13" />
        <di:waypoint x="700" y="13" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="0" height="0" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="BPMNEdge__11" bpmnElement="_11" sourceElement="_6" targetElement="_7">
        <di:waypoint x="1110" y="13" />
        <di:waypoint x="1162" y="13" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="0" height="0" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="BPMNEdge__10" bpmnElement="_10" sourceElement="_5" targetElement="_6">
        <di:waypoint x="960" y="13" />
        <di:waypoint x="1010" y="13" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="0" height="0" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="BPMNEdge__9" bpmnElement="_9" sourceElement="_4" targetElement="_5">
        <di:waypoint x="800" y="13" />
        <di:waypoint x="860" y="13" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="0" height="0" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="BPMNEdge__8" bpmnElement="_8" sourceElement="_2" targetElement="_3">
        <di:waypoint x="501" y="13" />
        <di:waypoint x="550" y="13" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="0" height="0" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="Shape-_2" bpmnElement="_2">
        <dc:Bounds x="465" y="-5" width="32" height="32" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="393" y="6" width="53" height="14" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Shape-_3" bpmnElement="_3">
        <dc:Bounds x="550" y="-27" width="100" height="80" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="85" height="55" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Shape-_4" bpmnElement="_4">
        <dc:Bounds x="700" y="-27" width="100" height="80" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="85" height="55" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Shape-_5" bpmnElement="_5">
        <dc:Bounds x="860" y="-27" width="100" height="80" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="85" height="55" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Shape-_6" bpmnElement="_6">
        <dc:Bounds x="1010" y="-27" width="100" height="80" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="85" height="55" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Shape-_7" bpmnElement="_7">
        <dc:Bounds x="1162" y="-5" width="36" height="36" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="1215" y="6" width="49" height="14" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

1.3 Start process instance

/**
 * 启动流程实例
 * `act_hi_actinst`    流程实例执行历史信息
 * `act_hi_identitylink` 流程参与用户的历史信息
 * `act_hi_procinst`     流程实例的历史信息
 * `act_hi_taskinst`     流程任务的历史信息
 * `act_ru_execution`    流程执行信息
 * `act_ru_identitylink`  流程的正在参与用户信息
 * `act_ru_task`         流程当前任务信息
 */
@RequestMapping("/startProcess")
public String startProcess(String key) {
    
    

    // 1、根据流程定义的id启动流程
    ProcessInstance instance = runtimeService.startProcessInstanceByKey(key);
    // 2、输出内容
    log.info("流程定义ID:" + instance.getProcessDefinitionId());
    log.info("流程实例ID:" + instance.getId());
    log.info("当前活动的ID:" + instance.getActivityId());

    return "出差申请流程启动成功!";
}

1.4 Query personal pending tasks

/**
 * 查询个人待执行的任务
 */
@RequestMapping("/getUnfinishTaskList")
public List<Map<String,String>> getUnfinishTaskList(String key, String taskAssignee) {
    
    

    // 1、根据流程key 和 任务的负责人 查询任务
    List<Task> taskList = taskService.createTaskQuery()
            .processDefinitionKey(key) //流程Key
            .taskAssignee(taskAssignee)  //要查询的负责人
            .list();

    List<Map<String,String>> taskMapList = new ArrayList<>();
    // 2、输出
    for (Task task : taskList) {
    
    
        Map<String,String> taskMap = new HashMap<>();
        taskMap.put("processInstanceId",task.getProcessInstanceId());               //流程实例
        taskMap.put("id",task.getId());                                             //任务Id
        taskMap.put("assignee",task.getAssignee());                                 //任务负责人
        taskMap.put("name",task.getName());                                         //任务名称

        //取流程中设置的变量值(基本类型)
//            taskMap.put("taskAssignee", Objects.nonNull(taskService.getVariable(task.getId(),"出差申请人"))?taskService.getVariable(task.getId(),"出差申请人").toString():"");           //出差申请人
//            taskMap.put("days", Objects.nonNull(taskService.getVariable(task.getId(),"请假天数"))?taskService.getVariable(task.getId(),"请假天数").toString():"");                      //出差天数
//            taskMap.put("evaluateFee", Objects.nonNull(taskService.getVariable(task.getId(),"出差预计费用"))?taskService.getVariable(task.getId(),"出差预计费用").toString():"");        //出差预计费用
//            taskMap.put("startDate", Objects.nonNull(taskService.getVariable(task.getId(),"出差开始日期"))?taskService.getVariable(task.getId(),"出差开始日期").toString():"");          //出差开始日期

        //取流程中设置的变量值(bean类型)
        taskMap.put("approveInfo",Objects.nonNull(taskService.getVariable(task.getId(),"出差信息"))?JSONObject.toJSONString(taskService.getVariable(task.getId(),"出差信息")):"");

        taskMapList.add(taskMap);
    }

    return taskMapList;
}

1.5 Complete personal tasks

/**
 * (以张三创建出差申请为例)
 * 完成个人任务 : 创建出差申请
 * `act_ru_task` 这里的 '创建出差申请' 会变为 '经理审批'
 * 也就是也是下一个任务:  经理审批
 * `act_hi_taskinst` 这里的'创建出差申请'这条记录会有开始时间和结束时间
 * 然后还会增加一条记录 '经理审批'
 */
@RequestMapping("/completeTask")
public String completeTask(String key,String taskAssignee,String days,String evaluateFee, String startDate) {
    
    

    // 查找该负责人下所有的任务
    Task task = taskService.createTaskQuery()
            .processDefinitionKey(key)
            .taskAssignee(taskAssignee)
            .singleResult();

    //设置流程变量(基本类型),前提是该流程实例必须未执行完成 —— 存在 act_ru_variable 表中,对应取值即可
//        taskService.setVariable(task.getId(), "请假人", taskAssignee);
//        taskService.setVariable(task.getId(), "出差天数",days);
//        taskService.setVariable(task.getId(), "出差预计费用", evaluateFee);
//        taskService.setVariable(task.getId(), "出差开始日期", startDate);

    //设置流程变量(JavaBean类型),前提是该流程实例必须未执行完成 —— 存在 act_ru_variable 表中,但是实际值存在 act_ge_bytearray 表中
    ApproveInfo approveInfo = ApproveInfo.builder()
            .taskAssignee(taskAssignee)
            .days(days)
            .evaluateFee(evaluateFee)
            .startDate(startDate)
            .build();

    taskService.setVariable(task.getId(),"出差信息",approveInfo);

    //执行流程
    taskService.complete(task.getId());

    return "当前," + task.getAssignee() + "已完成任务:" + task.getName() ;
}

1.6 Complete the task according to the task ID

/**
 * 根据任务ID来完成任务
 */
@RequestMapping("/completeTaskByTaskId")
public String completeTaskByTaskId(String taskId) {
    
    
    Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
    taskService.complete(taskId);
    return "当前已完成任务:" + task.getName() ;
}

2 exclusive gateway

部分代码跟前面简单流程的有重复,我这部分就只贴流程图、bpmn文件和核心代码了

2.1 Flowchart

排他网关需要配置出口的表达式,然后通过 activiti 参数传值的形式会自动识别到该值,自动进行程序流转。
insert image description here

2.2 BPMN files

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tns="http://www.activiti.org/testm1625990337688" xmlns:xsd="http://www.w3.org/2001/XMLSchema" id="m1625990337688" name="" targetNamespace="http://www.activiti.org/testm1625990337688">
  <process id="zhanleai_approve_reject" name="测试出差申请流程" processType="None" isClosed="false" isExecutable="true">
    <documentation />
    <startEvent id="_1" name="流程开始">
      <documentation />
      <extensionElements />
      <outgoing>Flow_11ejhh5</outgoing>
    </startEvent>
    <userTask id="_2" name="创建出差申请" activiti:assignee="张三" activiti:exclusive="true">
      <documentation />
      <extensionElements />
      <incoming>Flow_08ru13p</incoming>
      <incoming>Flow_11ejhh5</incoming>
      <outgoing>Flow_1b2032e</outgoing>
    </userTask>
    <userTask id="_4" name="经理审批" activiti:assignee="李四" activiti:exclusive="true">
      <documentation />
      <extensionElements />
      <incoming>Flow_1fc8hv7</incoming>
      <outgoing>Flow_13ro4z7</outgoing>
    </userTask>
    <userTask id="_5" name="总经理审批" activiti:assignee="老马" activiti:exclusive="true">
      <documentation />
      <extensionElements />
      <incoming>Flow_13ro4z7</incoming>
      <outgoing>Flow_1jpug20</outgoing>
    </userTask>
    <userTask id="_7" name="财务审批" activiti:assignee="小王" activiti:exclusive="true">
      <documentation />
      <extensionElements />
      <incoming>Flow_09owxd3</incoming>
      <outgoing>Flow_1m3z2le</outgoing>
    </userTask>
    <sequenceFlow id="_11" sourceRef="_7" targetRef="_9" />
    <userTask id="_8" name="大老板审批" activiti:assignee="老赵" activiti:exclusive="true">
      <documentation />
      <extensionElements />
      <incoming>Flow_0p8i7yo</incoming>
      <outgoing>Flow_149ikim</outgoing>
      <outgoing>Flow_1h2q7h5</outgoing>
    </userTask>
    <sequenceFlow id="Flow_149ikim" sourceRef="_8" targetRef="_9" />
    <exclusiveGateway id="_6">
      <documentation />
      <incoming>Flow_1jpug20</incoming>
      <outgoing>Flow_09owxd3</outgoing>
      <outgoing>Flow_08ru13p</outgoing>
    </exclusiveGateway>
    <sequenceFlow id="Flow_1jpug20" sourceRef="_5" targetRef="_6" />
    <sequenceFlow id="Flow_09owxd3" name="同意审批" sourceRef="_6" targetRef="_7">
      <documentation />
      <conditionExpression xsi:type="tFormalExpression">${status==1}</conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="Flow_08ru13p" name="驳回申请" sourceRef="_6" targetRef="_2">
      <documentation />
      <conditionExpression xsi:type="tFormalExpression">${status==0}</conditionExpression>
    </sequenceFlow>
    <exclusiveGateway id="_3" name="">
      <documentation />
      <incoming>Flow_1b2032e</incoming>
      <outgoing>Flow_1fc8hv7</outgoing>
      <outgoing>Flow_0p8i7yo</outgoing>
    </exclusiveGateway>
    <sequenceFlow id="Flow_11ejhh5" sourceRef="_1" targetRef="_2" />
    <sequenceFlow id="Flow_1b2032e" sourceRef="_2" targetRef="_3">
      <documentation />
    </sequenceFlow>
    <sequenceFlow id="Flow_1fc8hv7" name="非特殊情况" sourceRef="_3" targetRef="_4">
      <documentation />
      <conditionExpression xsi:type="tFormalExpression">${status==0}</conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="Flow_0p8i7yo" name="特殊情况" sourceRef="_3" targetRef="_8">
      <documentation />
      <conditionExpression xsi:type="tFormalExpression">${status==1}</conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="Flow_1h2q7h5" sourceRef="_8" targetRef="_9">
      <documentation />
    </sequenceFlow>
    <sequenceFlow id="Flow_1m3z2le" sourceRef="_7" targetRef="_9" />
    <sequenceFlow id="Flow_13ro4z7" sourceRef="_4" targetRef="_5" />
    <endEvent id="_9" name="EndEvent">
      <documentation />
      <incoming>Flow_1h2q7h5</incoming>
      <incoming>Flow_1m3z2le</incoming>
      <terminateEventDefinition id="TerminateEventDefinition_1q47fr4" />
    </endEvent>
  </process>
  <bpmndi:BPMNDiagram id="Diagram-_1" name="New Diagram" documentation="background=#3C3F41;count=1;horizontalcount=1;orientation=0;width=842.4;height=1195.2;imageableWidth=832.4;imageableHeight=1185.2;imageableX=5.0;imageableY=5.0">
    <bpmndi:BPMNPlane bpmnElement="zhanleai_approve_reject">
      <bpmndi:BPMNEdge id="Flow_13ro4z7_di" bpmnElement="Flow_13ro4z7">
        <di:waypoint x="485" y="310" />
        <di:waypoint x="485" y="340" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_1m3z2le_di" bpmnElement="Flow_1m3z2le">
        <di:waypoint x="485" y="640" />
        <di:waypoint x="485" y="750" />
        <di:waypoint x="622" y="750" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_1h2q7h5_di" bpmnElement="Flow_1h2q7h5">
        <di:waypoint x="800" y="330" />
        <di:waypoint x="800" y="750" />
        <di:waypoint x="658" y="750" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_0p8i7yo_di" bpmnElement="Flow_0p8i7yo">
        <di:waypoint x="640" y="125" />
        <di:waypoint x="640" y="190" />
        <di:waypoint x="800" y="190" />
        <di:waypoint x="800" y="250" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="698" y="165" width="44" height="14" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_1fc8hv7_di" bpmnElement="Flow_1fc8hv7">
        <di:waypoint x="640" y="125" />
        <di:waypoint x="640" y="190" />
        <di:waypoint x="485" y="190" />
        <di:waypoint x="485" y="230" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="535" y="165" width="55" height="14" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_1b2032e_di" bpmnElement="Flow_1b2032e">
        <di:waypoint x="640" y="30" />
        <di:waypoint x="640" y="75" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_11ejhh5_di" bpmnElement="Flow_11ejhh5">
        <di:waypoint x="640" y="-82" />
        <di:waypoint x="640" y="-50" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_08ru13p_di" bpmnElement="Flow_08ru13p">
        <di:waypoint x="460" y="480" />
        <di:waypoint x="360" y="480" />
        <di:waypoint x="360" y="-10" />
        <di:waypoint x="590" y="-10" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="353" y="225" width="44" height="14" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_09owxd3_di" bpmnElement="Flow_09owxd3">
        <di:waypoint x="485" y="505" />
        <di:waypoint x="485" y="560" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="478" y="523" width="45" height="14" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_1jpug20_di" bpmnElement="Flow_1jpug20">
        <di:waypoint x="485" y="420" />
        <di:waypoint x="485" y="455" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="Shape-_2" bpmnElement="_1">
        <dc:Bounds x="622" y="-118" width="36" height="36" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="618" y="-147" width="44" height="14" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Shape-_3" bpmnElement="_2">
        <dc:Bounds x="590" y="-50" width="100" height="80" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="85" height="55" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Shape-_4" bpmnElement="_4">
        <dc:Bounds x="435" y="230" width="100" height="80" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="85" height="55" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Shape-_5" bpmnElement="_5">
        <dc:Bounds x="435" y="340" width="100" height="80" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="85" height="55" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Shape-_6" bpmnElement="_7">
        <dc:Bounds x="435" y="560" width="100" height="80" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="85" height="55" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Activity_19lsvak_di" bpmnElement="_8">
        <dc:Bounds x="750" y="250" width="100" height="80" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Gateway_06brwuq_di" bpmnElement="_6" isMarkerVisible="true">
        <dc:Bounds x="460" y="455" width="50" height="50" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Gateway_1izdxic_di" bpmnElement="_3" isMarkerVisible="true">
        <dc:Bounds x="615" y="75" width="50" height="50" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="607" y="125" width="66" height="14" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Event_1y7rj7q_di" bpmnElement="_9">
        <dc:Bounds x="622" y="732" width="36" height="36" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="616" y="775" width="49" height="14" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

2.3 Exclusive gateway core code

其实也就是流程图配置参数,然后调用的时候从前端传进来,后端代码需要将这个放到 variableMap 对象中,activiti 会自动根据 status 的值来判断,从而自动流转。
insert image description here

3 Specify any node

流程图和 bpmn 复用排他网关

3.1 Core Process Points

用户在前端页面的操作如下:

  • Query the executed task node information [front end]
  • Select node information [front end]
  • Change node information [front end]

3.2 Query the executed task node information according to the process instance ID

/**
 *  根据流程实例ID查询已执行的任务节点信息
 *
 *  act_hi_taskinst:历史流程任务表
 * 
 * @Date 2023/6/14 15:03
 * @Param [processInstanceId]
 * @return 
 **/
@RequestMapping("/getRunNodesByProcInstId")
public Map<String,String> getRunNodesByProcInstId(String processInstanceId) {
    
    
    Map<String,String> map = new LinkedHashMap();
    // 获取流程历史中已执行节点,并按照节点在流程中执行先后顺序排序
    List<HistoricActivityInstance> historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery()
            .processInstanceId(processInstanceId)
            .activityType("userTask")   //用户任务
            .finished()                 //已经执行的任务节点
            .orderByHistoricActivityInstanceEndTime()
            .asc()
            .list();

    // 已执行的节点ID集合
    if(Objects.nonNull(historicActivityInstanceList) && historicActivityInstanceList.size()>0){
    
    
        for (HistoricActivityInstance historicActivityInstance:historicActivityInstanceList){
    
    
            if(!map.containsKey(historicActivityInstance.getActivityId())){
    
    
                map.put(historicActivityInstance.getActivityId(),historicActivityInstance.getActivityName());
            }
        }
    }
    return map;
}

3.3 Change the node to the specified node

/**
 * 改变节点到指定节点
 * @param rejectNodeId 指定节点ID
 * @param processInstanceId 流程实例ID
 * @param assignee 负责人
 * @return
 */
@RequestMapping("/changeRunNode")
public String changeRunNode(String rejectNodeId, String processInstanceId, String assignee) {
    
    
    // 查找该负责人下所有的任务
    Task task = taskService.createTaskQuery()
            .processInstanceId(processInstanceId)
            .singleResult();

    //如果当前节点处理人不是该用户,就无法进行改变节点操作
    if (!assignee.equals(task.getAssignee())) {
    
    
        return "当前用户无法改变流程节点";
    }

    //获取当前节点
    String currActivityId = task.getTaskDefinitionKey();
    String processDefinitionId = task.getProcessDefinitionId();
    BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
    FlowNode currFlow = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currActivityId);

    if (null == currFlow) {
    
    
        List<SubProcess> subProcessList = bpmnModel.getMainProcess().findFlowElementsOfType(SubProcess.class, true);
        for (SubProcess subProcess : subProcessList) {
    
    
            FlowElement flowElement = subProcess.getFlowElement(currActivityId);
            if (flowElement != null) {
    
    
                currFlow = (FlowNode) flowElement;
                break;
            }
        }
    }
    //获取目标节点
    FlowNode targetFlow = (FlowNode) bpmnModel.getFlowElement(rejectNodeId);

    //如果不是同一个流程(子流程)不能进行改变节点操作
    if (!(currFlow.getParentContainer().equals(targetFlow.getParentContainer()))) {
    
    
        return "不是同一个流程,无法进行改变节点操作";
    }

    //记录原活动方向
    List<SequenceFlow> oriSequenceFlows = Lists.newArrayList();
    oriSequenceFlows.addAll(currFlow.getOutgoingFlows());

    //清理活动方向
    currFlow.getOutgoingFlows().clear();

    //建立新的方向
    List<SequenceFlow> newSequenceFlows = Lists.newArrayList();
    SequenceFlow newSequenceFlow = new SequenceFlow();
    String uuid = UUID.randomUUID().toString().replace("-", "");
    newSequenceFlow.setId(uuid);
    newSequenceFlow.setSourceFlowElement(currFlow);  //原节点
    newSequenceFlow.setTargetFlowElement(targetFlow);  //目标节点
    newSequenceFlows.add(newSequenceFlow);
    currFlow.setOutgoingFlows(newSequenceFlows);

    //审批意见叠加
    //variables 审批意见     act_ru_variable  变量表
    Map<String, Object> variables = task.getProcessVariables();
    variables.put("status","2");
    variables.put("msg","资料填写有误,请重新填写!");

    //完成节点任务
    taskService.complete(task.getId(), variables);
    //恢复原方向
    currFlow.setOutgoingFlows(oriSequenceFlows);

    return "改变流程到指定节点,已完成!";
}

4 parallel gateways

4.1 Flowchart

insert image description here

4.2 BPMN files

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tns="http://www.activiti.org/testm1625990337688" xmlns:xsd="http://www.w3.org/2001/XMLSchema" id="m1625990337688" name="" targetNamespace="http://www.activiti.org/testm1625990337688">
  <process id="zhanleai_approve_parallel" name="测试出差申请流程" processType="None" isClosed="false" isExecutable="true">
    <documentation />
    <startEvent id="_1" name="流程开始">
      <documentation />
      <extensionElements />
      <outgoing>Flow_11ejhh5</outgoing>
    </startEvent>
    <userTask id="_2" name="创建出差申请" activiti:assignee="张三" activiti:exclusive="true">
      <documentation />
      <extensionElements />
      <incoming>Flow_08ru13p</incoming>
      <incoming>Flow_11ejhh5</incoming>
      <outgoing>Flow_1b2032e</outgoing>
    </userTask>
    <userTask id="_4" name="经理审批" activiti:assignee="李四" activiti:exclusive="true">
      <documentation />
      <extensionElements />
      <incoming>Flow_1fc8hv7</incoming>
      <outgoing>Flow_13ro4z7</outgoing>
    </userTask>
    <userTask id="_5" name="总经理审批" activiti:assignee="老马" activiti:exclusive="true">
      <documentation />
      <extensionElements />
      <incoming>Flow_13ro4z7</incoming>
      <outgoing>Flow_1jpug20</outgoing>
    </userTask>
    <userTask id="_7" name="财务审批" activiti:assignee="小王" activiti:exclusive="true">
      <documentation />
      <extensionElements />
      <incoming>Flow_09owxd3</incoming>
      <outgoing>Flow_1m3z2le</outgoing>
    </userTask>
    <sequenceFlow id="_11" sourceRef="_7" targetRef="_9" />
    <userTask id="_8" name="大老板审批" activiti:assignee="老赵" activiti:exclusive="true">
      <documentation />
      <extensionElements />
      <incoming>Flow_0p8i7yo</incoming>
      <outgoing>Flow_149ikim</outgoing>
      <outgoing>Flow_1h2q7h5</outgoing>
    </userTask>
    <sequenceFlow id="Flow_149ikim" sourceRef="_8" targetRef="_9" />
    <exclusiveGateway id="_6">
      <documentation />
      <incoming>Flow_1jpug20</incoming>
      <outgoing>Flow_09owxd3</outgoing>
      <outgoing>Flow_08ru13p</outgoing>
    </exclusiveGateway>
    <sequenceFlow id="Flow_1jpug20" sourceRef="_5" targetRef="_6" />
    <sequenceFlow id="Flow_09owxd3" name="同意审批" sourceRef="_6" targetRef="_7">
      <documentation />
      <conditionExpression xsi:type="tFormalExpression">${status==1}</conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="Flow_08ru13p" name="驳回申请" sourceRef="_6" targetRef="_2">
      <documentation />
      <conditionExpression xsi:type="tFormalExpression">${status==0}</conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="Flow_11ejhh5" sourceRef="_1" targetRef="_2" />
    <sequenceFlow id="Flow_1b2032e" sourceRef="_2" targetRef="_3">
      <documentation />
    </sequenceFlow>
    <sequenceFlow id="Flow_1fc8hv7" name="" sourceRef="_3" targetRef="_4">
      <documentation />
    </sequenceFlow>
    <sequenceFlow id="Flow_0p8i7yo" name="" sourceRef="_3" targetRef="_8">
      <documentation />
    </sequenceFlow>
    <sequenceFlow id="Flow_1h2q7h5" sourceRef="_8" targetRef="_9">
      <documentation />
    </sequenceFlow>
    <sequenceFlow id="Flow_1m3z2le" sourceRef="_7" targetRef="_9" />
    <sequenceFlow id="Flow_13ro4z7" sourceRef="_4" targetRef="_5" />
    <parallelGateway id="_3" name="">
      <documentation />
      <incoming>Flow_1b2032e</incoming>
      <outgoing>Flow_1fc8hv7</outgoing>
      <outgoing>Flow_0p8i7yo</outgoing>
    </parallelGateway>
    <endEvent id="_9" name="结束任务">
      <documentation />
      <incoming>Flow_1h2q7h5</incoming>
      <incoming>Flow_1m3z2le</incoming>
      <terminateEventDefinition id="TerminateEventDefinition_1gxwlzk" />
    </endEvent>
  </process>
  <bpmndi:BPMNDiagram id="Diagram-_1" name="New Diagram" documentation="background=#3C3F41;count=1;horizontalcount=1;orientation=0;width=842.4;height=1195.2;imageableWidth=832.4;imageableHeight=1185.2;imageableX=5.0;imageableY=5.0">
    <bpmndi:BPMNPlane bpmnElement="zhanleai_approve_parallel">
      <bpmndi:BPMNEdge id="Flow_13ro4z7_di" bpmnElement="Flow_13ro4z7">
        <di:waypoint x="485" y="310" />
        <di:waypoint x="485" y="340" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_1m3z2le_di" bpmnElement="Flow_1m3z2le">
        <di:waypoint x="485" y="640" />
        <di:waypoint x="485" y="750" />
        <di:waypoint x="622" y="750" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_1h2q7h5_di" bpmnElement="Flow_1h2q7h5">
        <di:waypoint x="840" y="350" />
        <di:waypoint x="840" y="750" />
        <di:waypoint x="658" y="750" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_0p8i7yo_di" bpmnElement="Flow_0p8i7yo">
        <di:waypoint x="640" y="125" />
        <di:waypoint x="640" y="190" />
        <di:waypoint x="840" y="190" />
        <di:waypoint x="840" y="270" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="718" y="165" width="44" height="14" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_1fc8hv7_di" bpmnElement="Flow_1fc8hv7">
        <di:waypoint x="640" y="125" />
        <di:waypoint x="640" y="190" />
        <di:waypoint x="485" y="190" />
        <di:waypoint x="485" y="230" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="535" y="165" width="55" height="14" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_1b2032e_di" bpmnElement="Flow_1b2032e">
        <di:waypoint x="640" y="30" />
        <di:waypoint x="640" y="75" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_11ejhh5_di" bpmnElement="Flow_11ejhh5">
        <di:waypoint x="640" y="-82" />
        <di:waypoint x="640" y="-50" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_08ru13p_di" bpmnElement="Flow_08ru13p">
        <di:waypoint x="460" y="480" />
        <di:waypoint x="360" y="480" />
        <di:waypoint x="360" y="-10" />
        <di:waypoint x="590" y="-10" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="353" y="225" width="44" height="14" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_09owxd3_di" bpmnElement="Flow_09owxd3">
        <di:waypoint x="485" y="505" />
        <di:waypoint x="485" y="560" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="478" y="523" width="45" height="14" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_1jpug20_di" bpmnElement="Flow_1jpug20">
        <di:waypoint x="485" y="420" />
        <di:waypoint x="485" y="455" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="Shape-_2" bpmnElement="_1">
        <dc:Bounds x="622" y="-118" width="36" height="36" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="618" y="-147" width="44" height="14" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Shape-_3" bpmnElement="_2">
        <dc:Bounds x="590" y="-50" width="100" height="80" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="85" height="55" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Shape-_4" bpmnElement="_4">
        <dc:Bounds x="435" y="230" width="100" height="80" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="85" height="55" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Shape-_5" bpmnElement="_5">
        <dc:Bounds x="435" y="340" width="100" height="80" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="85" height="55" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Shape-_6" bpmnElement="_7">
        <dc:Bounds x="435" y="560" width="100" height="80" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="85" height="55" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Activity_19lsvak_di" bpmnElement="_8">
        <dc:Bounds x="790" y="270" width="100" height="80" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Gateway_06brwuq_di" bpmnElement="_6" isMarkerVisible="true">
        <dc:Bounds x="460" y="455" width="50" height="50" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Gateway_02haxla_di" bpmnElement="_3">
        <dc:Bounds x="615" y="75" width="50" height="50" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Event_1mmhxvi_di" bpmnElement="_9">
        <dc:Bounds x="622" y="732" width="36" height="36" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="619" y="775" width="44" height="14" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

4.3 Core actions

There are no special settings for the parallel gateway, just draw the flow chart directly, and the normal flow will end. But you need to pay attention to one thing, you must set the end event to: Terminate End Event, otherwise the whole process will not end.
insert image description here

5 task listener

  • create: Triggered after the task is created
  • Assignment: triggered after task assignment
  • Delete: triggered after the task is completed
  • All: All tasks are triggered

5.1 Flowchart

针对某个 userTask 设置监听器,选择“创建后”,java类设置全路径为:com.gh.maintenance.controller.activiti_example.ActivitiApproveListener
insert image description here

5.2 BPMN files

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tns="http://www.activiti.org/testm1625990337688" xmlns:xsd="http://www.w3.org/2001/XMLSchema" id="m1625990337688" name="" targetNamespace="http://www.activiti.org/testm1625990337688">
  <process id="zhanleai_approve_listener" name="测试出差申请流程" processType="None" isClosed="false" isExecutable="true">
    <documentation />
    <startEvent id="_2" name="StartEvent" />
    <userTask id="_3" name="创建出差申请" activiti:assignee="张三" activiti:exclusive="true">
      <documentation />
      <extensionElements>
        <activiti:taskListener event="create" class="com.gh.maintenance.controller.activiti_example.ActivitiApproveListener" />
      </extensionElements>
    </userTask>
    <userTask id="_4" name="经理审批" activiti:assignee="李四" activiti:exclusive="true">
      <documentation />
      <extensionElements />
    </userTask>
    <userTask id="_5" name="总经理审批" activiti:assignee="老马" activiti:exclusive="true">
      <documentation></documentation>
      <extensionElements />
    </userTask>
    <userTask id="_6" name="财务审批" activiti:assignee="小王" activiti:exclusive="true">
      <documentation></documentation>
      <extensionElements />
    </userTask>
    <endEvent id="_7" name="EndEvent" />
    <sequenceFlow id="_8" sourceRef="_2" targetRef="_3" />
    <sequenceFlow id="_9" sourceRef="_4" targetRef="_5" />
    <sequenceFlow id="_10" sourceRef="_5" targetRef="_6" />
    <sequenceFlow id="_11" sourceRef="_6" targetRef="_7" />
    <sequenceFlow id="_12" sourceRef="_3" targetRef="_4" />
  </process>
  <bpmndi:BPMNDiagram id="Diagram-_1" name="New Diagram" documentation="background=#3C3F41;count=1;horizontalcount=1;orientation=0;width=842.4;height=1195.2;imageableWidth=832.4;imageableHeight=1185.2;imageableX=5.0;imageableY=5.0">
    <bpmndi:BPMNPlane bpmnElement="zhanleai_approve_listener">
      <bpmndi:BPMNEdge id="BPMNEdge__12" bpmnElement="_12" sourceElement="_3" targetElement="_4">
        <di:waypoint x="477.5" y="160" />
        <di:waypoint x="477.5" y="195" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="0" height="0" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="BPMNEdge__11" bpmnElement="_11" sourceElement="_6" targetElement="_7">
        <di:waypoint x="481" y="440" />
        <di:waypoint x="481" y="510" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="0" height="0" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="BPMNEdge__10" bpmnElement="_10" sourceElement="_5" targetElement="_6">
        <di:waypoint x="480" y="340" />
        <di:waypoint x="480" y="385" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="0" height="0" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="BPMNEdge__9" bpmnElement="_9" sourceElement="_4" targetElement="_5">
        <di:waypoint x="477.5" y="250" />
        <di:waypoint x="477.5" y="285" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="0" height="0" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="BPMNEdge__8" bpmnElement="_8" sourceElement="_2" targetElement="_3">
        <di:waypoint x="481" y="27" />
        <di:waypoint x="481" y="105" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="0" height="0" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="Shape-_2" bpmnElement="_2">
        <dc:Bounds x="465" y="-5" width="32" height="32" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="32" height="32" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Shape-_3" bpmnElement="_3">
        <dc:Bounds x="435" y="105" width="85" height="55" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="85" height="55" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Shape-_4" bpmnElement="_4">
        <dc:Bounds x="435" y="195" width="85" height="55" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="85" height="55" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Shape-_5" bpmnElement="_5">
        <dc:Bounds x="435" y="285" width="85" height="55" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="85" height="55" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Shape-_6" bpmnElement="_6">
        <dc:Bounds x="440" y="385" width="85" height="55" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="85" height="55" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Shape-_7" bpmnElement="_7">
        <dc:Bounds x="465" y="510" width="32" height="32" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="0" y="0" width="32" height="32" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

5.3 Core code of task listener

import lombok.extern.slf4j.Slf4j;
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.delegate.TaskListener;

/**
 * @Description: 监听器 【create-任务创建后触发  Assignment-任务分配后触发  Delete-任务完成后触发  All-所有任务都触发】
 *
 * @Author: zhanleai
 * @Date:2023/6/16 11:37
 */
@Slf4j
public class ActivitiApproveListener implements TaskListener {
    
    
    @Override
    public void notify(DelegateTask delegateTask) {
    
    
        log.info(">>>>>>>>>>>>>>>>>>>>>>>>>此监听器触发机制为:"+delegateTask.getEventName());
        log.info(">>>>>>>>>>>>>>>>>>>>>>>>>触发监听任务ID值为:"+delegateTask.getId());
        log.info(">>>>>>>>>>>>>>>>>>>>>>>>>负责人为:"+delegateTask.getAssignee());
        log.info(">>>>>>>>>>>>>>>>>>>>>>>>>任务名称"+delegateTask.getName());
    }
}

Guess you like

Origin blog.csdn.net/qq_23845083/article/details/131257330