最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot整合Activiti工作流的完整教程

 更新時(shí)間:2026年03月16日 08:26:01   作者:J_liaty  
本文檔詳細(xì)介紹如何在SpringBoot項(xiàng)目中完整整合Activiti工作流引擎,特別針對(duì)內(nèi)存使用、性能和響應(yīng)速度進(jìn)行了全面優(yōu)化,包含環(huán)境搭建、核心API使用、實(shí)際業(yè)務(wù)場(chǎng)景實(shí)現(xiàn),并以請(qǐng)假審批流程為實(shí)戰(zhàn)案例進(jìn)行完整演示,需要的朋友可以參考下

1. 項(xiàng)目概述

1.1 技術(shù)選型

技術(shù)版本說(shuō)明
SpringBoot2.7.x基礎(chǔ)框架,提供自動(dòng)配置
Activiti7.1.0.M6工作流引擎
MySQL8.0+數(shù)據(jù)庫(kù)
Druid1.2.18高性能數(shù)據(jù)庫(kù)連接池
MyBatis-Plus3.5.x數(shù)據(jù)持久層
Spring Boot Mail2.7.x郵件通知功能
Caffeine3.1.x高性能本地緩存
Spring Async2.7.x異步任務(wù)處理

1.2 功能清單

本文將實(shí)現(xiàn)以下功能:

  • ? 工作流引擎自動(dòng)配置(性能優(yōu)化)
  • ? 流程定義部署與管理(支持熱部署)
  • ? 流程實(shí)例啟動(dòng)與控制(批量?jī)?yōu)化)
  • ? 任務(wù)查詢與辦理(緩存優(yōu)化)
  • ? 流程歷史數(shù)據(jù)追蹤(分級(jí)存儲(chǔ))
  • ? 流程變量傳遞與管理(內(nèi)存優(yōu)化)
  • ? 動(dòng)態(tài)任務(wù)分配(候選組緩存)
  • ? 流程監(jiān)控與統(tǒng)計(jì)(異步統(tǒng)計(jì))
  • ? 郵件通知功能(異步非阻塞)
  • ? 消息記錄存儲(chǔ)(批量寫入)
  • ? 完整的請(qǐng)假審批業(yè)務(wù)流程

1.3 性能優(yōu)化亮點(diǎn)

  • ? 異步通知:監(jiān)聽器不阻塞流程,郵件異步發(fā)送
  • ?? 連接池優(yōu)化:Druid連接池精細(xì)配置,避免連接泄露
  • ?? 多級(jí)緩存:流程定義、用戶信息、模板緩存
  • ?? 歷史分級(jí):按需記錄歷史,避免數(shù)據(jù)膨脹
  • ?? 批量操作:任務(wù)查詢、變量設(shè)置批量處理
  • ?? 查詢優(yōu)化:索引優(yōu)化、SQL優(yōu)化、分頁(yè)查詢

1.4 項(xiàng)目結(jié)構(gòu)

springboot-activiti-demo
├── src/main/java/com/example/activiti
│   ├── config/                    # 配置類
│   │   ├── ActivitiConfig.java    # Activiti配置(性能優(yōu)化)
│   │   ├── MailConfig.java        # 郵件配置
│   │   ├── AsyncConfig.java       # 異步配置
│   │   └── CacheConfig.java       # 緩存配置
│   ├── controller/                # 控制器
│   │   ├── LeaveController.java
│   │   └── WorkflowController.java
│   ├── service/                   # 服務(wù)層
│   │   ├── WorkflowService.java
│   │   ├── NotificationService.java
│   │   ├── LeaveService.java
│   │   ├── CacheService.java      # 緩存服務(wù)
│   │   └── impl/
│   │       ├── WorkflowServiceImpl.java
│   │       ├── NotificationServiceImpl.java
│   │       ├── LeaveServiceImpl.java
│   │       └── CacheServiceImpl.java
│   ├── entity/                    # 實(shí)體類
│   │   ├── LeaveRequest.java
│   │   ├── TaskInfo.java
│   │   ├── HistoryInfo.java
│   │   └── NotificationRecord.java
│   ├── mapper/                    # 數(shù)據(jù)訪問(wèn)層
│   │   ├── LeaveRequestMapper.java
│   │   └── NotificationRecordMapper.java
│   ├── delegate/                  # 委托類(服務(wù)任務(wù))
│   │   └── HRArchiveTask.java
│   ├── listener/                  # 監(jiān)聽器(異步優(yōu)化)
│   │   ├── ProcessStartListener.java
│   │   ├── TaskCreateListener.java
│   │   └── TaskCompleteListener.java
│   ├── async/                     # 異步處理器
│   │   └── NotificationAsyncProcessor.java
│   └── ActivitiApplication.java  # 啟動(dòng)類
├── src/main/resources
│   ├── processes/                # BPMN流程定義文件目錄
│   │   └── leave-request.bpmn20.xml
│   ├── mapper/                   # MyBatis Mapper XML
│   │   ├── LeaveRequestMapper.xml
│   │   └── NotificationRecordMapper.xml
│   ├── templates/                # 模板文件(帶緩存)
│   │   └── email/                 # 郵件模板
│   │       ├── process-started.html
│   │       ├── task-created.html
│   │       └── task-completed.html
│   └── application.yml          # 配置文件(性能優(yōu)化)
└── pom.xml                       # Maven依賴

2. 環(huán)境準(zhǔn)備

2.1 開發(fā)工具

  • IntelliJ IDEA(推薦)或 Eclipse
  • JDK 1.8+
  • Maven 3.6+
  • MySQL 8.0+

2.2 數(shù)據(jù)庫(kù)準(zhǔn)備

創(chuàng)建數(shù)據(jù)庫(kù):

-- 創(chuàng)建數(shù)據(jù)庫(kù),使用InnoDB引擎以獲得更好的事務(wù)性能
CREATE DATABASE IF NOT EXISTS activiti_demo 
CHARACTER SET utf8mb4 
COLLATE utf8mb4_unicode_ci;
-- 使用數(shù)據(jù)庫(kù)
USE activiti_demo;

3. 項(xiàng)目搭建

3.1 添加Maven依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.18</version>
        <relativePath/>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>springboot-activiti-demo</artifactId>
    <version>1.0.0</version>
    <name>SpringBoot Activiti Demo</name>
    <description>SpringBoot整合Activiti工作流引擎完整示例</description>
    <properties>
        <java.version>1.8</java.version>
        <activiti.version>7.1.0.M6</activiti.version>
        <mysql.version>8.0.33</mysql.version>
        <druid.version>1.2.18</druid.version>
        <mybatis-plus.version>3.5.5</mybatis-plus.version>
        <caffeine.version>3.1.8</caffeine.version>
    </properties>
    <dependencies>
        <!-- SpringBoot Web Starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- SpringBoot Validation -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <!-- SpringBoot Mail(郵件通知功能) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>
        <!-- SpringBoot Thymeleaf(郵件模板引擎) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!-- Activiti SpringBoot Starter -->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring-boot-starter</artifactId>
            <version>${activiti.version}</version>
        </dependency>
        <!-- MySQL 驅(qū)動(dòng) -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>
        <!-- Druid 數(shù)據(jù)庫(kù)連接池(高性能) -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.version}</version>
        </dependency>
        <!-- MyBatis-Plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        <!-- Caffeine 高性能緩存 -->
        <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
            <version>${caffeine.version}</version>
        </dependency>
        <!-- Spring Cache -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- Hutool工具類 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.26</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

3.2 配置文件(性能優(yōu)化版)

server:
  port: 8080
  tomcat:
    # Tomcat線程池優(yōu)化
    threads:
      max: 200          # 最大工作線程數(shù)
      min-spare: 20     # 最小空閑線程數(shù)
    max-connections: 10000  # 最大連接數(shù)
    accept-count: 100   # 等待隊(duì)列長(zhǎng)度
spring:
  application:
    name: springboot-activiti-demo
  datasource:
    url: jdbc:mysql://localhost:3306/activiti_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&useSSL=false
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      # 連接池基礎(chǔ)配置
      initial-size: 10                # 初始連接數(shù)
      min-idle: 10                   # 最小空閑連接數(shù)
      max-active: 100                 # 最大活躍連接數(shù)
      max-wait: 60000                # 獲取連接最大等待時(shí)間(毫秒)
      # 連接檢測(cè)配置
      test-while-idle: true          # 檢測(cè)空閑連接是否有效
      test-on-borrow: false          # 獲取連接時(shí)不檢測(cè)(避免性能損耗)
      test-on-return: false          # 歸還連接時(shí)不檢測(cè)
      validation-query: SELECT 1     # 驗(yàn)證SQL
      time-between-eviction-runs-millis: 60000    # 檢測(cè)空閑連接間隔
      min-evictable-idle-time-millis: 300000     # 空閑連接最小生存時(shí)間
      # 連接泄漏保護(hù)
      remove-abandoned: true         # 是否移除泄漏連接
      remove-abandoned-timeout: 180000 # 泄漏連接超時(shí)時(shí)間(秒)
      log-abandoned: true            # 記錄泄漏連接日志
      # 性能監(jiān)控(生產(chǎn)環(huán)境建議關(guān)閉)
      stat-view-servlet:
        enabled: false
      filter:
        stat:
          enabled: false
        wall:
          enabled: true
          config:
            multi-statement-allow: true  # 允許批量SQL
  # 緩存配置
  cache:
    type: caffeine
    caffeine:
      spec: maximumSize=1000,expireAfterWrite=30m
  # 郵件配置
  mail:
    host: smtp.qq.com
    port: 587
    username: your-email@qq.com
    password: your-authorization-code
    protocol: smtp
    default-encoding: UTF-8
    properties:
      mail:
        smtp:
          auth: true
          starttls:
            enable: true
          ssl:
            trust: smtp.qq.com
          # 連接池配置
          connectiontimeout: 10000
          timeout: 10000
          writetimeout: 10000
    from: your-email@qq.com
    # 異步發(fā)送配置
    async: true
  # Thymeleaf模板引擎配置
  thymeleaf:
    prefix: classpath:/templates/
    suffix: .html
    mode: HTML
    encoding: UTF-8
    cache: true  # 生產(chǎn)環(huán)境開啟緩存,提升性能
    servlet:
      content-type: text/html
  # 異步任務(wù)配置
  task:
    execution:
      pool:
        core-size: 10               # 核心線程數(shù)
        max-size: 50               # 最大線程數(shù)
        queue-capacity: 1000       # 隊(duì)列容量
        keep-alive: 60s            # 線程空閑時(shí)間
      thread-name-prefix: async-   # 線程名前綴
    scheduling:
      pool:
        size: 5                    # 定時(shí)任務(wù)線程池大小
    shutdown:
      await-termination: true
      await-termination-period: 60s
# MyBatis-Plus配置
mybatis-plus:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.example.activiti.entity
  configuration:
    map-underscore-to-camel-case: true
    # 生產(chǎn)環(huán)境關(guān)閉SQL日志,提升性能
    # log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    # 二級(jí)緩存配置
    cache-enabled: true
    lazy-loading-enabled: true    # 開啟懶加載
    default-executor-type: REUSE  # 復(fù)用預(yù)編譯語(yǔ)句
  global-config:
    db-config:
      # 性能優(yōu)化:批量操作
      batch-insert: true
      batch-update: true
# Activiti 配置(性能優(yōu)化版)
activiti:
  # 數(shù)據(jù)庫(kù)配置
  database-schema-update: true
  database-type: mysql
  # 歷史記錄配置:使用ACTIVITY而非FULL,減少數(shù)據(jù)存儲(chǔ)量,提升性能
  history-level: activity
  # 流程定義部署模式
  deployment-mode: single-engine
  # 流程定義緩存配置(性能優(yōu)化關(guān)鍵)
  process-definition-cache-limit: 500      # 增加緩存數(shù)量
  enable-process-definition-info-cache: true  # 啟用流程定義信息緩存
  process-definition-cache-enabled: true
  # 異步執(zhí)行器配置(性能提升關(guān)鍵)
  async-executor-activate: true
  async-executor-enabled: true
  async-executor-default-async-job-acquire-wait-time: 10000    # 默認(rèn)異步任務(wù)獲取等待時(shí)間
  async-executor-default-async-job-acquire-lock-time: 300000    # 默認(rèn)異步任務(wù)獲取鎖時(shí)間
  async-executor-default-queue-size-full-wait-time: 50         # 隊(duì)列滿時(shí)等待時(shí)間
  async-executor-maximum-async-jobs-due-per-acquisition: 100  # 單次獲取最大任務(wù)數(shù)
  # 流程實(shí)例緩存
  enable-process-instance-info-cache: true
  # 批量操作配置
  process-instance-query-limit: 100      # 流程實(shí)例查詢限制
  task-query-limit: 100                  # 任務(wù)查詢限制
  batch-operations-size: 50              # 批量操作大小
  # 安全配置
  security:
    enabled: false
  # 流程定義部署路徑
  process-definition-location-prefix: classpath*:/processes/
  process-definition-location-suffixes: 
    - **.bpmn20.xml
    - **.bpmn
  # 作業(yè)執(zhí)行器配置
  job-executor-activate: true
  async-history-enabled: true            # 啟用異步歷史記錄(性能優(yōu)化)
# 消息通知配置
notification:
  email-enabled: true
  system-enabled: true
  # 異步發(fā)送配置
  async-enabled: true
  # 批量發(fā)送配置
  batch-size: 50
  # 重試配置
  retry-count: 3
  retry-interval: 5000                  # 重試間隔(毫秒)
  types: PROCESS_START,TASK_CREATE,TASK_COMPLETE,PROCESS_END
# Caffeine緩存配置
caffeine:
  cache-specs:
    # 流程定義緩存
    process-definition: maximumSize=200,expireAfterWrite=1h
    # 用戶信息緩存
    user-info: maximumSize=1000,expireAfterWrite=30m
    # 郵件模板緩存
    email-template: maximumSize=100,expireAfterWrite=24h
    # 流程變量緩存
    process-variable: maximumSize=5000,expireAfterWrite=10m
# 日志配置(性能優(yōu)化:異步日志)
logging:
  pattern:
    console: '%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n'
  level:
    root: INFO
    org.activiti: WARN                 # 降低Activiti日志級(jí)別
    com.alibaba.druid: WARN
    com.example.activiti: INFO
    # 異步日志配置
  async:
    enabled: true
    queue-capacity: 10000
    discard-threshold: 0
    include-caller-data: false        # 不包含調(diào)用者數(shù)據(jù),提升性能

4. Activiti核心概念

4.1 Activiti架構(gòu)

Activiti工作流引擎由以下幾個(gè)核心服務(wù)組成:

  • RepositoryService:管理流程定義和部署
  • RuntimeService:管理流程實(shí)例和執(zhí)行
  • TaskService:管理任務(wù)
  • HistoryService:查詢歷史數(shù)據(jù)
  • IdentityService:管理用戶和組
  • ManagementService:引擎管理和操作

4.2 核心概念

4.2.1 流程定義(Process Definition)

  • 定義:業(yè)務(wù)流程的靜態(tài)描述,類似于Java中的類
  • 存儲(chǔ)格式:BPMN 2.0 XML文件
  • 特點(diǎn):一次定義,多次使用
  • 性能優(yōu)化:?jiǎn)⒂昧鞒潭x緩存,避免重復(fù)解析

4.2.2 流程實(shí)例(Process Instance)

  • 定義:流程定義的一次執(zhí)行,類似于Java中的對(duì)象實(shí)例
  • 生命周期:從啟動(dòng)到結(jié)束的完整過(guò)程
  • 性能優(yōu)化:?jiǎn)⒂昧鞒虒?shí)例信息緩存,減少數(shù)據(jù)庫(kù)查詢

4.2.3 任務(wù)(Task)

  • 定義:流程中的工作單元,需要由用戶或系統(tǒng)完成
  • 類型:用戶任務(wù)、服務(wù)任務(wù)、腳本任務(wù)
  • 性能優(yōu)化:批量查詢、分頁(yè)查詢、候選組緩存

4.2.4 流程變量(Process Variables)

  • 定義:流程執(zhí)行過(guò)程中的數(shù)據(jù)載體
  • 作用:控制流程流轉(zhuǎn)、在任務(wù)之間傳遞數(shù)據(jù)、存儲(chǔ)業(yè)務(wù)數(shù)據(jù)
  • 性能優(yōu)化:本地緩存、批量設(shè)置、序列化優(yōu)化

5. 數(shù)據(jù)庫(kù)配置與表結(jié)構(gòu)

5.1 Activiti表結(jié)構(gòu)

Activiti使用25張表來(lái)存儲(chǔ)工作流相關(guān)數(shù)據(jù),表名都以 ACT_ 開頭:

前綴說(shuō)明表數(shù)量性能優(yōu)化建議
ACT_RE_Repository(倉(cāng)庫(kù))3流程定義緩存
ACT_RU_Runtime(運(yùn)行時(shí))8流程實(shí)例緩存、變量緩存
ACT_HI_History(歷史)8歷史級(jí)別控制、定期清理
ACT_ID_Identity(身份)4用戶組緩存
ACT_GE_General(通用)2-

5.2 業(yè)務(wù)表結(jié)構(gòu)

5.2.1 請(qǐng)假申請(qǐng)表

-- 請(qǐng)假申請(qǐng)表
CREATE TABLE t_leave_request (
    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主鍵ID',
    applicant_id VARCHAR(64) NOT NULL COMMENT '申請(qǐng)人ID',
    applicant_name VARCHAR(100) NOT NULL COMMENT '申請(qǐng)人姓名',
    applicant_email VARCHAR(255) COMMENT '申請(qǐng)人郵箱',
    leave_type INT NOT NULL COMMENT '請(qǐng)假類型:1-年假,2-事假,3-病假,4-婚假,5-產(chǎn)假,6-喪假',
    start_time DATETIME NOT NULL COMMENT '請(qǐng)假開始時(shí)間',
    end_time DATETIME NOT NULL COMMENT '請(qǐng)假結(jié)束時(shí)間',
    leave_days DOUBLE NOT NULL COMMENT '請(qǐng)假天數(shù)',
    reason TEXT COMMENT '請(qǐng)假事由',
    process_instance_id VARCHAR(64) COMMENT '流程實(shí)例ID',
    status INT DEFAULT 0 COMMENT '審批狀態(tài):0-草稿,1-待審批,2-已批準(zhǔn),3-已拒絕,4-已取消,5-已歸檔',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時(shí)間',
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時(shí)間',
    deleted INT DEFAULT 0 COMMENT '邏輯刪除標(biāo)記',
    -- 索引優(yōu)化:根據(jù)實(shí)際查詢場(chǎng)景建立索引
    INDEX idx_applicant_id (applicant_id) COMMENT '申請(qǐng)人索引',
    INDEX idx_process_instance_id (process_instance_id) COMMENT '流程實(shí)例ID索引',
    INDEX idx_status (status) COMMENT '狀態(tài)索引',
    INDEX idx_create_time (create_time) COMMENT '創(chuàng)建時(shí)間索引',
    INDEX idx_applicant_status (applicant_id, status) COMMENT '聯(lián)合索引',
    -- 引擎優(yōu)化:使用InnoDB引擎,支持事務(wù)和外鍵
    ENGINE=InnoDB 
    DEFAULT CHARSET=utf8mb4 
    COLLATE=utf8mb4_unicode_ci 
    COMMENT='請(qǐng)假申請(qǐng)表'
) ROW_FORMAT=DYNAMIC;  -- 使用DYNAMIC行格式,支持大字段

5.2.2 消息通知記錄表

-- 消息通知記錄表
CREATE TABLE t_notification_record (
    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主鍵ID',
    notification_type VARCHAR(50) NOT NULL COMMENT '通知類型',
    process_instance_id VARCHAR(64) COMMENT '流程實(shí)例ID',
    process_definition_id VARCHAR(64) COMMENT '流程定義ID',
    task_id VARCHAR(64) COMMENT '任務(wù)ID',
    task_name VARCHAR(255) COMMENT '任務(wù)名稱',
    recipient_id VARCHAR(64) COMMENT '接收人ID',
    recipient_name VARCHAR(255) COMMENT '接收人姓名',
    recipient_email VARCHAR(255) COMMENT '接收人郵箱',
    sender_id VARCHAR(64) COMMENT '發(fā)送人ID',
    sender_name VARCHAR(255) COMMENT '發(fā)送人姓名',
    notification_title VARCHAR(500) COMMENT '通知標(biāo)題',
    notification_content TEXT COMMENT '通知內(nèi)容',
    notification_status VARCHAR(20) DEFAULT 'PENDING' COMMENT '通知狀態(tài)',
    send_time DATETIME COMMENT '發(fā)送時(shí)間',
    error_message TEXT COMMENT '錯(cuò)誤信息',
    retry_count INT DEFAULT 0 COMMENT '重試次數(shù)',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時(shí)間',
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時(shí)間',
    -- 索引優(yōu)化
    INDEX idx_process_instance_id (process_instance_id) COMMENT '流程實(shí)例ID索引',
    INDEX idx_recipient_id (recipient_id) COMMENT '接收人ID索引',
    INDEX idx_notification_status (notification_status) COMMENT '狀態(tài)索引',
    INDEX idx_create_time (create_time) COMMENT '創(chuàng)建時(shí)間索引',
    INDEX idx_type_status (notification_type, notification_status) COMMENT '聯(lián)合索引',
    ENGINE=InnoDB 
    DEFAULT CHARSET=utf8mb4 
    COLLATE=utf8mb4_unicode_ci 
    COMMENT='消息通知記錄表'
) ROW_FORMAT=DYNAMIC;

5.3 Activiti表優(yōu)化SQL

-- 為Activiti運(yùn)行時(shí)表添加索引(提升查詢性能)
-- 任務(wù)表索引
CREATE INDEX idx_act_ru_task_assignee ON ACT_RU_TASK(ASSIGNEE_);
CREATE INDEX idx_act_ru_task_proc_inst ON ACT_RU_TASK(PROC_INST_ID_);
CREATE INDEX idx_act_ru_task_create_time ON ACT_RU_TASK(CREATE_TIME_);
-- 變量表索引
CREATE INDEX idx_act_ru_var_proc_inst ON ACT_RU_VARIABLE(PROC_INST_ID_);
CREATE INDEX idx_act_ru_var_name ON ACT_RU_VARIABLE(NAME_);
-- 歷史任務(wù)表索引
CREATE INDEX idx_act_hi_task_inst_proc_inst ON ACT_HI_TASKINST(PROC_INST_ID_);
CREATE INDEX idx_act_hi_task_inst_assignee ON ACT_HI_TASKINST(ASSIGNEE_);
CREATE INDEX idx_act_hi_task_inst_end_time ON ACT_HI_TASKINST(END_TIME_);
-- 歷史變量表索引
CREATE INDEX idx_act_hi_var_inst_proc_inst ON ACT_HI_VARINST(PROC_INST_ID_);
CREATE INDEX idx_act_hi_var_inst_name ON ACT_HI_VARINST(NAME_);
-- 優(yōu)化表存儲(chǔ)引擎(確保使用InnoDB)
ALTER TABLE ACT_RU_TASK ENGINE=InnoDB;
ALTER TABLE ACT_RU_VARIABLE ENGINE=InnoDB;
ALTER TABLE ACT_HI_TASKINST ENGINE=InnoDB;
ALTER TABLE ACT_HI_VARINST ENGINE=InnoDB;

6. 核心API封裝

6.1 創(chuàng)建DTO類

6.1.1 TaskInfo.java

package com.example.activiti.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
 * 任務(wù)信息DTO
 * 使用Serializable接口,支持緩存和序列化
 */
@Data
public class TaskInfo implements Serializable {
    private static final long serialVersionUID = 1L;
    private String taskId;
    private String taskName;
    private String taskDescription;
    private String assignee;
    private String candidateGroups;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date createTime;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date dueDate;
    private Integer priority;
    private String processInstanceId;
    private String processDefinitionId;
    private String processDefinitionName;
    private String processDefinitionKey;
    private String businessKey;
    private String taskKey;
}

6.1.2 HistoryInfo.java

package com.example.activiti.entity;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;

/**
 * 歷史信息DTO
 */
@Data
public class HistoryInfo implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private String taskId;
    private String taskName;
    private String taskDescription;
    private String assignee;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date startTime;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date endTime;
    private Long duration;
    private String processInstanceId;
    private String processDefinitionId;
    private String deleteReason;
    private String taskKey;
}

6.1.3 NotificationRecord.java

package com.example.activiti.entity;

import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;

/**
 * 消息通知記錄實(shí)體
 */
@Data
@TableName("t_notification_record")
public class NotificationRecord implements Serializable {
    
    private static final long serialVersionUID = 1L;
    
    public enum NotificationType {
        PROCESS_START, TASK_CREATE, TASK_COMPLETE, PROCESS_END
    }
    
    public enum NotificationStatus {
        PENDING, SENT, FAILED
    }
    
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    
    private String notificationType;
    private String processInstanceId;
    private String processDefinitionId;
    private String taskId;
    private String taskName;
    private String recipientId;
    private String recipientName;
    private String recipientEmail;
    private String senderId;
    private String senderName;
    private String notificationTitle;
    private String notificationContent;
    private String notificationStatus;
    
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date sendTime;
    
    private String errorMessage;
    private Integer retryCount;
    
    @TableField(fill = FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date updateTime;
}

6.2 工作流服務(wù)接口與實(shí)現(xiàn)

6.2.1 WorkflowService.java

package com.example.activiti.service;

import com.example.activiti.entity.HistoryInfo;
import com.example.activiti.entity.TaskInfo;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.runtime.ProcessInstance;

import java.util.List;
import java.util.Map;

public interface WorkflowService {
    
    // ========== 流程定義管理 ==========
    
    /**
     * 部署流程定義(支持批量部署)
     * @param resourceName 資源名稱
     * @param bpmnContent BPMN內(nèi)容
     * @return 部署信息
     */
    Deployment deployProcess(String resourceName, String bpmnContent);
    
    /**
     * 批量部署流程定義(性能優(yōu)化)
     * @param resources 資源列表
     * @return 部署信息列表
     */
    List<Deployment> deployProcesses(Map<String, String> resources);
    
    /**
     * 刪除流程定義(級(jí)聯(lián)刪除)
     * @param deploymentId 部署ID
     * @param cascade 是否級(jí)聯(lián)刪除
     */
    void deleteDeployment(String deploymentId, boolean cascade);
    
    // ========== 流程實(shí)例管理 ==========
    
    /**
     * 啟動(dòng)流程實(shí)例(支持流程變量批量設(shè)置)
     * @param processDefinitionKey 流程定義Key
     * @param businessKey 業(yè)務(wù)Key
     * @param variables 流程變量
     * @return 流程實(shí)例
     */
    ProcessInstance startProcess(String processDefinitionKey, String businessKey, 
                                   Map<String, Object> variables);
    
    /**
     * 批量啟動(dòng)流程實(shí)例(性能優(yōu)化)
     * @param processDefinitionKey 流程定義Key
     * @param businessKeys 業(yè)務(wù)Key列表
     * @param variablesList 流程變量列表
     * @return 流程實(shí)例列表
     */
    List<ProcessInstance> startProcesses(String processDefinitionKey, 
                                          List<String> businessKeys, 
                                          List<Map<String, Object>> variablesList);
    
    /**
     * 刪除流程實(shí)例
     * @param processInstanceId 流程實(shí)例ID
     * @param deleteReason 刪除原因
     */
    void deleteProcessInstance(String processInstanceId, String deleteReason);
    
    /**
     * 批量刪除流程實(shí)例(性能優(yōu)化)
     * @param processInstanceIds 流程實(shí)例ID列表
     * @param deleteReason 刪除原因
     */
    void batchDeleteProcessInstances(List<String> processInstanceIds, String deleteReason);
    
    /**
     * 掛起流程實(shí)例
     * @param processInstanceId 流程實(shí)例ID
     */
    void suspendProcessInstance(String processInstanceId);
    
    /**
     * 激活流程實(shí)例
     * @param processInstanceId 流程實(shí)例ID
     */
    void activateProcessInstance(String processInstanceId);
    
    // ========== 任務(wù)管理 ==========
    
    /**
     * 查詢用戶待辦任務(wù)(支持分頁(yè)和排序)
     * @param userId 用戶ID
     * @param page 頁(yè)碼
     * @param size 每頁(yè)大小
     * @return 任務(wù)列表
     */
    List<TaskInfo> getUserTasks(String userId, int page, int size);
    
    /**
     * 查詢用戶待辦任務(wù)(高性能版本)
     * @param userId 用戶ID
     * @param candidateGroups 候選組列表
     * @return 任務(wù)列表
     */
    List<TaskInfo> getUserTasksOptimized(String userId, List<String> candidateGroups);
    
    /**
     * 查詢用戶已辦任務(wù)(支持分頁(yè))
     * @param userId 用戶ID
     * @param page 頁(yè)碼
     * @param size 每頁(yè)大小
     * @return 歷史任務(wù)列表
     */
    List<HistoryInfo> getUserCompletedTasks(String userId, int page, int size);
    
    /**
     * 完成任務(wù)(支持批量變量設(shè)置)
     * @param taskId 任務(wù)ID
     * @param variables 流程變量
     */
    void completeTask(String taskId, Map<String, Object> variables);
    
    /**
     * 批量完成任務(wù)(性能優(yōu)化)
     * @param taskIds 任務(wù)ID列表
     * @param variables 流程變量
     */
    void batchCompleteTasks(List<String> taskIds, Map<String, Object> variables);
    
    /**
     * 認(rèn)領(lǐng)任務(wù)
     * @param taskId 任務(wù)ID
     * @param userId 用戶ID
     */
    void claimTask(String taskId, String userId);
    
    /**
     * 轉(zhuǎn)辦任務(wù)
     * @param taskId 任務(wù)ID
     * @param targetUserId 目標(biāo)用戶ID
     */
    void transferTask(String taskId, String targetUserId);
    
    /**
     * 委派任務(wù)
     * @param taskId 任務(wù)ID
     * @param targetUserId 目標(biāo)用戶ID
     */
    void delegateTask(String taskId, String targetUserId);
    
    // ========== 流程變量管理 ==========
    
    /**
     * 獲取流程變量(支持緩存)
     * @param processInstanceId 流程實(shí)例ID
     * @param variableName 變量名
     * @return 變量值
     */
    Object getVariable(String processInstanceId, String variableName);
    
    /**
     * 批量獲取流程變量(性能優(yōu)化)
     * @param processInstanceId 流程實(shí)例ID
     * @param variableNames 變量名列表
     * @return 變量Map
     */
    Map<String, Object> getVariables(String processInstanceId, List<String> variableNames);
    
    /**
     * 設(shè)置流程變量(批量設(shè)置)
     * @param processInstanceId 流程實(shí)例ID
     * @param variables 變量Map
     */
    void setVariables(String processInstanceId, Map<String, Object> variables);
    
    /**
     * 批量設(shè)置流程變量(性能優(yōu)化)
     * @param processInstanceIds 流程實(shí)例ID列表
     * @param variableName 變量名
     * @param variableValue 變量值
     */
    void batchSetVariables(List<String> processInstanceIds, String variableName, Object variableValue);
    
    // ========== 流程歷史管理 ==========
    
    /**
     * 查詢流程歷史(支持分頁(yè))
     * @param processInstanceId 流程實(shí)例ID
     * @param page 頁(yè)碼
     * @param size 每頁(yè)大小
     * @return 歷史任務(wù)列表
     */
    List<HistoryInfo> getProcessHistory(String processInstanceId, int page, int size);
    
    /**
     * 查詢流程變量歷史
     * @param processInstanceId 流程實(shí)例ID
     * @return 變量歷史列表
     */
    List<Map<String, Object>> getVariableHistory(String processInstanceId);
}

6.2.2 WorkflowServiceImpl.java

package com.example.activiti.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.example.activiti.entity.HistoryInfo;
import com.example.activiti.entity.TaskInfo;
import com.example.activiti.service.CacheService;
import com.example.activiti.service.WorkflowService;
import lombok.extern.slf4j.Slf4j;
import org.activiti.engine.*;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.ByteArrayInputStream;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 工作流服務(wù)實(shí)現(xiàn)類(性能優(yōu)化版)
 */
@Slf4j
@Service
public class WorkflowServiceImpl implements WorkflowService {
    
    @Autowired
    private RepositoryService repositoryService;
    
    @Autowired
    private RuntimeService runtimeService;
    
    @Autowired
    private TaskService taskService;
    
    @Autowired
    private HistoryService historyService;
    
    @Autowired
    private ManagementService managementService;
    
    @Autowired
    private CacheService cacheService;
    
    // ========== 流程定義管理 ==========
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Deployment deployProcess(String resourceName, String bpmnContent) {
        log.debug("部署流程定義:resourceName={}", resourceName);
        
        // 清除流程定義緩存
        clearProcessDefinitionCache(resourceName);
        
        Deployment deployment = repositoryService.createDeployment()
                .addInputStream(resourceName, new ByteArrayInputStream(bpmnContent.getBytes()))
                .deploy();
        
        log.debug("流程定義部署成功:deploymentId={}", deployment.getId());
        return deployment;
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<Deployment> deployProcesses(Map<String, String> resources) {
        log.debug("批量部署流程定義,數(shù)量:{}", resources.size());
        
        DeploymentBuilder builder = repositoryService.createDeployment();
        resources.forEach((name, content) -> {
            builder.addInputStream(name, new ByteArrayInputStream(content.getBytes()));
            // 清除緩存
            clearProcessDefinitionCache(name);
        });
        
        Deployment deployment = builder.deploy();
        
        // 返回所有部署信息
        return repositoryService.createDeploymentQuery()
                .deploymentId(deployment.getId())
                .list();
    }
    
    /**
     * 清除流程定義緩存
     */
    private void clearProcessDefinitionCache(String resourceName) {
        try {
            String processKey = resourceName.replace(".bpmn20.xml", "").replace(".bpmn", "");
            cacheService.evictProcessDefinitionCache(processKey);
        } catch (Exception e) {
            log.warn("清除流程定義緩存失敗", e);
        }
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteDeployment(String deploymentId, boolean cascade) {
        log.debug("刪除流程定義:deploymentId={}, cascade={}", deploymentId, cascade);
        
        // 獲取流程定義信息,清除緩存
        if (cascade) {
            List<org.activiti.engine.repository.ProcessDefinition> definitions = 
                    repositoryService.createProcessDefinitionQuery()
                            .deploymentId(deploymentId)
                            .list();
            definitions.forEach(def -> {
                cacheService.evictProcessDefinitionCache(def.getKey());
            });
        }
        
        repositoryService.deleteDeployment(deploymentId, cascade);
    }
    
    // ========== 流程實(shí)例管理 ==========
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ProcessInstance startProcess(String processDefinitionKey, String businessKey, 
                                          Map<String, Object> variables) {
        log.debug("啟動(dòng)流程實(shí)例:processDefinitionKey={}, businessKey={}", 
                  processDefinitionKey, businessKey);
        
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(
                processDefinitionKey, businessKey, variables);
        
        log.debug("流程實(shí)例啟動(dòng)成功:processInstanceId={}", processInstance.getId());
        return processInstance;
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<ProcessInstance> startProcesses(String processDefinitionKey, 
                                                  List<String> businessKeys, 
                                                  List<Map<String, Object>> variablesList) {
        log.debug("批量啟動(dòng)流程實(shí)例,數(shù)量:{}", businessKeys.size());
        
        List<ProcessInstance> instances = new ArrayList<>();
        for (int i = 0; i < businessKeys.size(); i++) {
            try {
                ProcessInstance instance = runtimeService.startProcessInstanceByKey(
                        processDefinitionKey, businessKeys.get(i), variablesList.get(i));
                instances.add(instance);
            } catch (Exception e) {
                log.error("啟動(dòng)流程實(shí)例失?。篵usinessKey={}", businessKeys.get(i), e);
            }
        }
        
        log.debug("批量啟動(dòng)流程實(shí)例完成,成功:{}/{}", instances.size(), businessKeys.size());
        return instances;
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteProcessInstance(String processInstanceId, String deleteReason) {
        log.debug("刪除流程實(shí)例:processInstanceId={}, deleteReason={}", 
                  processInstanceId, deleteReason);
        
        // 清除流程變量緩存
        cacheService.evictProcessVariableCache(processInstanceId);
        
        runtimeService.deleteProcessInstance(processInstanceId, deleteReason);
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchDeleteProcessInstances(List<String> processInstanceIds, String deleteReason) {
        log.debug("批量刪除流程實(shí)例,數(shù)量:{}", processInstanceIds.size());
        
        processInstanceIds.forEach(instanceId -> {
            try {
                cacheService.evictProcessVariableCache(instanceId);
                runtimeService.deleteProcessInstance(instanceId, deleteReason);
            } catch (Exception e) {
                log.error("刪除流程實(shí)例失?。簆rocessInstanceId={}", instanceId, e);
            }
        });
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void suspendProcessInstance(String processInstanceId) {
        log.debug("掛起流程實(shí)例:processInstanceId={}", processInstanceId);
        runtimeService.suspendProcessInstanceById(processInstanceId);
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void activateProcessInstance(String processInstanceId) {
        log.debug("激活流程實(shí)例:processInstanceId={}", processInstanceId);
        runtimeService.activateProcessInstanceById(processInstanceId);
    }
    
    // ========== 任務(wù)管理 ==========
    
    @Override
    @Cacheable(value = "userTasks", key = "#userId + '_' + #page + '_' + #size")
    public List<TaskInfo> getUserTasks(String userId, int page, int size) {
        log.debug("查詢用戶待辦任務(wù):userId={}, page={}, size={}", userId, page, size);
        
        List<Task> tasks = taskService.createTaskQuery()
                .taskAssignee(userId)
                .orderByTaskCreateTime()
                .desc()
                .listPage((page - 1) * size, size);
        
        return tasks.stream()
                .map(this::convertToTaskInfo)
                .collect(Collectors.toList());
    }
    
    @Override
    public List<TaskInfo> getUserTasksOptimized(String userId, List<String> candidateGroups) {
        log.debug("查詢用戶待辦任務(wù)(優(yōu)化版):userId={}", userId);
        
        // 并行查詢個(gè)人任務(wù)和候選組任務(wù)
        List<Task> tasks = new ArrayList<>();
        
        // 查詢個(gè)人任務(wù)
        if (userId != null && !userId.isEmpty()) {
            List<Task> assignedTasks = taskService.createTaskQuery()
                    .taskAssignee(userId)
                    .orderByTaskCreateTime()
                    .desc()
                    .list();
            tasks.addAll(assignedTasks);
        }
        
        // 查詢候選組任務(wù)(使用緩存)
        if (candidateGroups != null && !candidateGroups.isEmpty()) {
            for (String group : candidateGroups) {
                List<Task> candidateTasks = cacheService.getCandidateGroupTasks(group);
                if (candidateTasks == null || candidateTasks.isEmpty()) {
                    candidateTasks = taskService.createTaskQuery()
                            .taskCandidateGroup(group)
                            .orderByTaskCreateTime()
                            .desc()
                            .list();
                    cacheService.cacheCandidateGroupTasks(group, candidateTasks);
                }
                tasks.addAll(candidateTasks);
            }
        }
        
        // 去重
        Map<String, TaskInfo> uniqueTasks = new LinkedHashMap<>();
        tasks.forEach(task -> {
            uniqueTasks.put(task.getId(), convertToTaskInfo(task));
        });
        
        return new ArrayList<>(uniqueTasks.values());
    }
    
    @Override
    public List<HistoryInfo> getUserCompletedTasks(String userId, int page, int size) {
        log.debug("查詢用戶已辦任務(wù):userId={}, page={}, size={}", userId, page, size);
        
        List<HistoricTaskInstance> historicTasks = historyService.createHistoricTaskInstanceQuery()
                .taskAssignee(userId)
                .finished()
                .orderByHistoricTaskInstanceEndTime()
                .desc()
                .listPage((page - 1) * size, size);
        
        return historicTasks.stream()
                .map(this::convertToHistoryInfo)
                .collect(Collectors.toList());
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void completeTask(String taskId, Map<String, Object> variables) {
        log.debug("完成任務(wù):taskId={}", taskId);
        
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        if (task == null) {
            throw new RuntimeException("任務(wù)不存在或已完成");
        }
        
        // 設(shè)置流程變量
        if (variables != null && !variables.isEmpty()) {
            taskService.setVariables(taskId, variables);
        }
        
        // 完成任務(wù)
        taskService.complete(taskId);
        
        // 清除相關(guān)緩存
        clearTaskCache(task.getProcessInstanceId(), task.getAssignee());
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchCompleteTasks(List<String> taskIds, Map<String, Object> variables) {
        log.debug("批量完成任務(wù),數(shù)量:{}", taskIds.size());
        
        taskIds.forEach(taskId -> {
            try {
                completeTask(taskId, variables);
            } catch (Exception e) {
                log.error("完成任務(wù)失?。簍askId={}", taskId, e);
            }
        });
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void claimTask(String taskId, String userId) {
        log.debug("認(rèn)領(lǐng)任務(wù):taskId={}, userId={}", taskId, userId);
        
        taskService.claim(taskId, userId);
        
        // 清除相關(guān)緩存
        clearTaskCache(null, userId);
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void transferTask(String taskId, String targetUserId) {
        log.debug("轉(zhuǎn)辦任務(wù):taskId={}, targetUserId={}", taskId, targetUserId);
        
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        String oldAssignee = task.getAssignee();
        
        taskService.setAssignee(taskId, targetUserId);
        
        // 清除相關(guān)緩存
        clearTaskCache(task.getProcessInstanceId(), oldAssignee);
        clearTaskCache(task.getProcessInstanceId(), targetUserId);
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delegateTask(String taskId, String targetUserId) {
        log.debug("委派任務(wù):taskId={}, targetUserId={}", taskId, targetUserId);
        
        taskService.delegateTask(taskId, targetUserId);
        
        // 清除相關(guān)緩存
        clearTaskCache(null, taskService.createTaskQuery().taskId(taskId).singleResult().getAssignee());
    }
    
    // ========== 流程變量管理 ==========
    
    @Override
    @Cacheable(value = "processVariable", key = "#processInstanceId + '_' + #variableName")
    public Object getVariable(String processInstanceId, String variableName) {
        log.debug("獲取流程變量:processInstanceId={}, variableName={}", 
                  processInstanceId, variableName);
        
        return runtimeService.getVariable(processInstanceId, variableName);
    }
    
    @Override
    public Map<String, Object> getVariables(String processInstanceId, List<String> variableNames) {
        log.debug("批量獲取流程變量:processInstanceId={}, 變量數(shù)量={}", 
                  processInstanceId, variableNames.size());
        
        // 嘗試從緩存批量獲取
        Map<String, Object> cachedVariables = new HashMap<>();
        List<String> uncachedNames = new ArrayList<>();
        
        for (String name : variableNames) {
            Object value = cacheService.getProcessVariable(processInstanceId, name);
            if (value != null) {
                cachedVariables.put(name, value);
            } else {
                uncachedNames.add(name);
            }
        }
        
        // 從數(shù)據(jù)庫(kù)獲取未緩存的變量
        if (!uncachedNames.isEmpty()) {
            Map<String, Object> dbVariables = runtimeService.getVariables(
                    processInstanceId, uncachedNames);
            cachedVariables.putAll(dbVariables);
            
            // 緩存新獲取的變量
            dbVariables.forEach((name, value) -> {
                cacheService.cacheProcessVariable(processInstanceId, name, value);
            });
        }
        
        return cachedVariables;
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void setVariables(String processInstanceId, Map<String, Object> variables) {
        log.debug("設(shè)置流程變量:processInstanceId={}, 變量數(shù)量={}", 
                  processInstanceId, variables.size());
        
        if (variables != null && !variables.isEmpty()) {
            runtimeService.setVariables(processInstanceId, variables);
            
            // 更新緩存
            variables.forEach((name, value) -> {
                cacheService.cacheProcessVariable(processInstanceId, name, value);
            });
        }
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchSetVariables(List<String> processInstanceIds, String variableName, Object variableValue) {
        log.debug("批量設(shè)置流程變量:流程實(shí)例數(shù)量={}", processInstanceIds.size());
        
        processInstanceIds.forEach(instanceId -> {
            try {
                runtimeService.setVariable(instanceId, variableName, variableValue);
                cacheService.cacheProcessVariable(instanceId, variableName, variableValue);
            } catch (Exception e) {
                log.error("設(shè)置流程變量失?。簆rocessInstanceId={}", instanceId, e);
            }
        });
    }
    
    // ========== 流程歷史管理 ==========
    
    @Override
    public List<HistoryInfo> getProcessHistory(String processInstanceId, int page, int size) {
        log.debug("查詢流程歷史:processInstanceId={}, page={}, size={}", 
                  processInstanceId, page, size);
        
        List<HistoricTaskInstance> historicTasks = historyService.createHistoricTaskInstanceQuery()
                .processInstanceId(processInstanceId)
                .orderByHistoricTaskInstanceStartTime()
                .asc()
                .listPage((page - 1) * size, size);
        
        return historicTasks.stream()
                .map(this::convertToHistoryInfo)
                .collect(Collectors.toList());
    }
    
    @Override
    public List<Map<String, Object>> getVariableHistory(String processInstanceId) {
        log.debug("查詢流程變量歷史:processInstanceId={}", processInstanceId);
        
        return historyService.createHistoricVariableInstanceQuery()
                .processInstanceId(processInstanceId)
                .orderByVariableName()
                .asc()
                .list()
                .stream()
                .map(var -> {
                    Map<String, Object> map = new HashMap<>();
                    map.put("variableName", var.getVariableName());
                    map.put("variableType", var.getVariableTypeName());
                    map.put("value", var.getValue());
                    map.put("createTime", var.getCreateTime());
                    return map;
                })
                .collect(Collectors.toList());
    }
    
    // ========== 輔助方法 ==========
    
    /**
     * 轉(zhuǎn)換為TaskInfo
     */
    private TaskInfo convertToTaskInfo(Task task) {
        TaskInfo taskInfo = new TaskInfo();
        taskInfo.setTaskId(task.getId());
        taskInfo.setTaskName(task.getName());
        taskInfo.setTaskDescription(task.getDescription());
        taskInfo.setAssignee(task.getAssignee());
        taskInfo.setCreateTime(task.getCreateTime());
        taskInfo.setDueDate(task.getDueDate());
        taskInfo.setPriority(task.getPriority());
        taskInfo.setProcessInstanceId(task.getProcessInstanceId());
        taskInfo.setProcessDefinitionId(task.getProcessDefinitionId());
        taskInfo.setBusinessKey(task.getProcessInstanceId());
        taskInfo.setTaskKey(task.getTaskDefinitionKey());
        return taskInfo;
    }
    
    /**
     * 轉(zhuǎn)換為HistoryInfo
     */
    private HistoryInfo convertToHistoryInfo(HistoricTaskInstance historicTask) {
        HistoryInfo historyInfo = new HistoryInfo();
        historyInfo.setTaskId(historicTask.getId());
        historyInfo.setTaskName(historicTask.getName());
        historyInfo.setTaskDescription(historicTask.getDescription());
        historyInfo.setAssignee(historicTask.getAssignee());
        historyInfo.setStartTime(historicTask.getCreateTime());
        historyInfo.setEndTime(historicTask.getEndTime());
        historyInfo.setDuration(historicTask.getDurationInMillis());
        historyInfo.setProcessInstanceId(historicTask.getProcessInstanceId());
        historyInfo.setProcessDefinitionId(historicTask.getProcessDefinitionId());
        historyInfo.setDeleteReason(historicTask.getDeleteReason());
        historyInfo.setTaskKey(historicTask.getTaskDefinitionKey());
        return historyInfo;
    }
    
    /**
     * 清除任務(wù)緩存
     */
    private void clearTaskCache(String processInstanceId, String userId) {
        try {
            if (userId != null) {
                // 清除用戶任務(wù)緩存(使用通配符)
                cacheService.evictUserTasksCache(userId);
            }
        } catch (Exception e) {
            log.warn("清除任務(wù)緩存失敗", e);
        }
    }
}

7. 異步消息通知功能

7.1 異步配置類

package com.example.activiti.config;

import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 異步配置類
 * 配置異步任務(wù)線程池,提升通知性能,避免阻塞主流程
 */
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
    
    /**
     * 配置異步任務(wù)線程池
     */
    @Override
    @Bean(name = "asyncExecutor")
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        
        // 核心線程數(shù)
        executor.setCorePoolSize(10);
        
        // 最大線程數(shù)
        executor.setMaxPoolSize(50);
        
        // 隊(duì)列容量
        executor.setQueueCapacity(1000);
        
        // 線程空閑時(shí)間
        executor.setKeepAliveSeconds(60);
        
        // 線程名前綴
        executor.setThreadNamePrefix("async-notify-");
        
        // 拒絕策略:由調(diào)用線程執(zhí)行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        
        // 等待任務(wù)完成后關(guān)閉線程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        
        // 等待時(shí)間
        executor.setAwaitTerminationSeconds(60);
        
        // 允許核心線程超時(shí)
        executor.setAllowCoreThreadTimeOut(true);
        
        executor.initialize();
        return executor;
    }
    
    /**
     * 異步異常處理器
     */
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (throwable, method, params) -> {
            // 記錄異步任務(wù)異常,避免影響主流程
            System.err.println("異步任務(wù)執(zhí)行異常:" + throwable.getMessage());
            throwable.printStackTrace();
        };
    }
}

7.2 緩存配置類

package com.example.activiti.config;

import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.TimeUnit;

/**
 * 緩存配置類
 * 使用Caffeine高性能緩存,減少數(shù)據(jù)庫(kù)查詢和重復(fù)計(jì)算
 */
@Configuration
@EnableCaching
public class CacheConfig {
    
    /**
     * 配置Caffeine緩存管理器
     */
    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        
        // 配置緩存策略
        cacheManager.setCaffeine(Caffeine.newBuilder()
                // 初始容量
                .initialCapacity(100)
                // 最大容量
                .maximumSize(5000)
                // 寫入后過(guò)期時(shí)間
                .expireAfterWrite(30, TimeUnit.MINUTES)
                // 過(guò)期后自動(dòng)刷新
                .refreshAfterWrite(20, TimeUnit.MINUTES)
                // 啟用統(tǒng)計(jì)
                .recordStats()
        );
        
        // 緩存名稱列表
        cacheManager.setCacheNames(
                "processDefinition",  // 流程定義緩存
                "processVariable",    // 流程變量緩存
                "userTasks",          // 用戶任務(wù)緩存
                "userInfo",           // 用戶信息緩存
                "emailTemplate",      // 郵件模板緩存
                "candidateGroupTasks" // 候選組任務(wù)緩存
        );
        
        return cacheManager;
    }
}

7.3 消息通知異步處理器

package com.example.activiti.async;

import com.example.activiti.entity.NotificationRecord;
import com.example.activiti.mapper.NotificationRecordMapper;
import com.example.activiti.service.CacheService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

import javax.mail.internet.MimeMessage;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * 消息通知異步處理器
 * 核心性能優(yōu)化:使用異步處理、批量發(fā)送、失敗重試,確保通知不阻塞主流程
 */
@Slf4j
@Component
public class NotificationAsyncProcessor {
    
    @Autowired
    private JavaMailSender mailSender;
    
    @Autowired
    private TemplateEngine templateEngine;
    
    @Autowired
    private NotificationRecordMapper notificationRecordMapper;
    
    @Autowired
    private CacheService cacheService;
    
    @Value("${spring.mail.from}")
    private String fromEmail;
    
    @Value("${notification.batch-size:50}")
    private int batchSize;
    
    @Value("${notification.retry-count:3}")
    private int retryCount;
    
    @Value("${notification.retry-interval:5000}")
    private long retryInterval;
    
    // 批量發(fā)送隊(duì)列
    private final BlockingQueue<NotificationRecord> sendQueue = new LinkedBlockingQueue<>(10000);
    
    /**
     * 異步發(fā)送通知(單個(gè))
     * 關(guān)鍵性能優(yōu)化:使用@Async注解,立即返回,不阻塞主流程
     */
    @Async("asyncExecutor")
    public void sendNotificationAsync(NotificationRecord record) {
        log.debug("異步發(fā)送通知:id={}, type={}", record.getId(), record.getNotificationType());
        
        try {
            // 嘗試從緩存獲取用戶信息
            String recipientEmail = cacheService.getUserEmail(record.getRecipientId());
            if (recipientEmail == null) {
                recipientEmail = record.getRecipientEmail();
            }
            
            if (recipientEmail == null || recipientEmail.isEmpty()) {
                log.warn("接收人郵箱為空,跳過(guò)發(fā)送:id={}", record.getId());
                markAsFailed(record, "接收人郵箱為空");
                return;
            }
            
            // 更新狀態(tài)為發(fā)送中
            updateNotificationStatus(record.getId(), NotificationRecord.NotificationStatus.PENDING.name());
            
            // 發(fā)送郵件
            boolean success = sendEmail(record, recipientEmail);
            
            if (success) {
                // 更新狀態(tài)為已發(fā)送
                markAsSent(record);
                log.debug("通知發(fā)送成功:id={}", record.getId());
            } else {
                // 重試機(jī)制
                retrySend(record, recipientEmail);
            }
            
        } catch (Exception e) {
            log.error("異步發(fā)送通知失敗:id={}", record.getId(), e);
            markAsFailed(record, e.getMessage());
        }
    }
    
    /**
     * 批量異步發(fā)送通知
     * 關(guān)鍵性能優(yōu)化:批量發(fā)送,減少IO操作
     */
    @Async("asyncExecutor")
    public void batchSendNotificationsAsync(List<NotificationRecord> records) {
        log.debug("批量異步發(fā)送通知,數(shù)量:{}", records.size());
        
        int successCount = 0;
        int failCount = 0;
        
        for (NotificationRecord record : records) {
            try {
                sendNotificationAsync(record);
                successCount++;
            } catch (Exception e) {
                log.error("批量發(fā)送失?。篿d={}", record.getId(), e);
                failCount++;
            }
        }
        
        log.debug("批量發(fā)送完成,成功:{},失敗:{}", successCount, failCount);
    }
    
    /**
     * 重試發(fā)送通知
     */
    private void retrySend(NotificationRecord record, String email) {
        int currentRetry = record.getRetryCount() == null ? 0 : record.getRetryCount();
        
        if (currentRetry >= retryCount) {
            log.warn("達(dá)到最大重試次數(shù),放棄發(fā)送:id={}", record.getId());
            markAsFailed(record, "達(dá)到最大重試次數(shù)");
            return;
        }
        
        // 延遲重試
        try {
            TimeUnit.MILLISECONDS.sleep(retryInterval);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        
        // 更新重試次數(shù)
        currentRetry++;
        record.setRetryCount(currentRetry);
        notificationRecordMapper.updateById(record);
        
        // 重新發(fā)送
        try {
            boolean success = sendEmail(record, email);
            if (success) {
                markAsSent(record);
                log.debug("重試發(fā)送成功:id={}, 重試次數(shù)={}", record.getId(), currentRetry);
            } else {
                retrySend(record, email);
            }
        } catch (Exception e) {
            log.error("重試發(fā)送失敗:id={}, 重試次數(shù)={}", record.getId(), currentRetry, e);
            retrySend(record, email);
        }
    }
    
    /**
     * 發(fā)送郵件
     */
    private boolean sendEmail(NotificationRecord record, String toEmail) {
        try {
            MimeMessage message = mailSender.createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
            
            helper.setFrom(fromEmail);
            helper.setTo(toEmail);
            helper.setSubject(record.getNotificationTitle());
            
            // 根據(jù)通知類型選擇模板
            String templateName = getTemplateName(record.getNotificationType());
            
            // 使用Thymeleaf渲染模板
            Context context = new Context();
            context.setVariable("record", record);
            context.setVariable("title", record.getNotificationTitle());
            context.setVariable("content", record.getNotificationContent());
            
            String htmlContent = templateEngine.process(templateName, context);
            helper.setText(htmlContent, true);
            
            mailSender.send(message);
            return true;
            
        } catch (Exception e) {
            log.error("發(fā)送郵件失?。簍oEmail={}", toEmail, e);
            return false;
        }
    }
    
    /**
     * 獲取模板名稱
     */
    private String getTemplateName(String notificationType) {
        switch (notificationType) {
            case "PROCESS_START":
                return "email/process-started";
            case "TASK_CREATE":
                return "email/task-created";
            case "TASK_COMPLETE":
                return "email/task-completed";
            case "PROCESS_END":
                return "email/process-ended";
            default:
                return "email/default";
        }
    }
    
    /**
     * 標(biāo)記為已發(fā)送
     */
    private void markAsSent(NotificationRecord record) {
        record.setNotificationStatus(NotificationRecord.NotificationStatus.SENT.name());
        record.setSendTime(new java.util.Date());
        notificationRecordMapper.updateById(record);
    }
    
    /**
     * 標(biāo)記為失敗
     */
    private void markAsFailed(NotificationRecord record, String errorMessage) {
        record.setNotificationStatus(NotificationRecord.NotificationStatus.FAILED.name());
        record.setErrorMessage(errorMessage);
        notificationRecordMapper.updateById(record);
    }
    
    /**
     * 更新通知狀態(tài)
     */
    private void updateNotificationStatus(Long id, String status) {
        NotificationRecord record = new NotificationRecord();
        record.setId(id);
        record.setNotificationStatus(status);
        notificationRecordMapper.updateById(record);
    }
}

7.4 監(jiān)聽器實(shí)現(xiàn)

7.4.1 ProcessStartListener.java

package com.example.activiti.listener;

import com.example.activiti.async.NotificationAsyncProcessor;
import com.example.activiti.entity.NotificationRecord;
import com.example.activiti.mapper.NotificationRecordMapper;
import com.example.activiti.service.CacheService;
import lombok.extern.slf4j.Slf4j;
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.ExecutionListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

/**
 * 流程啟動(dòng)監(jiān)聽器(異步優(yōu)化版)
 * 關(guān)鍵性能優(yōu)化:監(jiān)聽器立即返回,通知異步發(fā)送,不阻塞流程
 */
@Slf4j
@Component("processStartListener")
public class ProcessStartListener implements ExecutionListener {
    
    @Autowired
    private NotificationRecordMapper notificationRecordMapper;
    
    @Autowired
    private NotificationAsyncProcessor notificationAsyncProcessor;
    
    @Autowired
    private CacheService cacheService;
    
    @Override
    public void notify(DelegateExecution execution) {
        long startTime = System.currentTimeMillis();
        log.debug("流程啟動(dòng)監(jiān)聽器觸發(fā):processInstanceId={}", execution.getProcessInstanceId());
        
        try {
            String processInstanceId = execution.getProcessInstanceId();
            String processDefinitionId = execution.getProcessDefinitionId();
            
            // 從流程變量中獲取發(fā)起人信息
            String initiatorId = getVariable(execution, "initiatorId", "system");
            String initiatorName = getVariable(execution, "initiatorName", "系統(tǒng)");
            String initiatorEmail = getVariable(execution, "initiatorEmail", null);
            
            // 從緩存獲取用戶郵箱
            if (initiatorEmail == null) {
                initiatorEmail = cacheService.getUserEmail(initiatorId);
            }
            
            // 創(chuàng)建通知記錄
            NotificationRecord record = new NotificationRecord();
            record.setNotificationType(NotificationRecord.NotificationType.PROCESS_START.name());
            record.setProcessInstanceId(processInstanceId);
            record.setProcessDefinitionId(processDefinitionId);
            record.setRecipientId(initiatorId);
            record.setRecipientName(initiatorName);
            record.setRecipientEmail(initiatorEmail);
            record.setSenderId("system");
            record.setSenderName("系統(tǒng)");
            record.setNotificationTitle("流程啟動(dòng)通知");
            record.setNotificationContent(buildContent(processInstanceId, initiatorName));
            record.setNotificationStatus(NotificationRecord.NotificationStatus.PENDING.name());
            
            // 保存到數(shù)據(jù)庫(kù)
            notificationRecordMapper.insert(record);
            
            // 關(guān)鍵性能優(yōu)化:異步發(fā)送通知,不阻塞主流程
            notificationAsyncProcessor.sendNotificationAsync(record);
            
            long endTime = System.currentTimeMillis();
            log.debug("流程啟動(dòng)監(jiān)聽器執(zhí)行完成,耗時(shí):{}ms", endTime - startTime);
            
        } catch (Exception e) {
            log.error("流程啟動(dòng)監(jiān)聽器執(zhí)行失敗", e);
            // 不拋出異常,避免影響流程執(zhí)行
        }
    }
    
    /**
     * 獲取流程變量
     */
    private String getVariable(DelegateExecution execution, String name, String defaultValue) {
        Object value = execution.getVariable(name);
        return value != null ? value.toString() : defaultValue;
    }
    
    /**
     * 構(gòu)建通知內(nèi)容
     */
    private String buildContent(String processInstanceId, String initiatorName) {
        StringBuilder content = new StringBuilder();
        content.append("尊敬的 ").append(initiatorName).append(":\n\n");
        content.append("您發(fā)起的流程已成功啟動(dòng)。\n");
        content.append("流程實(shí)例ID:").append(processInstanceId).append("\n");
        content.append("啟動(dòng)時(shí)間:").append(new java.util.Date()).append("\n");
        content.append("\n請(qǐng)登錄系統(tǒng)查看詳情。");
        return content.toString();
    }
}

7.4.2 TaskCreateListener.java

package com.example.activiti.listener;

import com.example.activiti.async.NotificationAsyncProcessor;
import com.example.activiti.entity.NotificationRecord;
import com.example.activiti.mapper.NotificationRecordMapper;
import com.example.activiti.service.CacheService;
import lombok.extern.slf4j.Slf4j;
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.delegate.TaskListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * 任務(wù)創(chuàng)建監(jiān)聽器(異步優(yōu)化版)
 * 關(guān)鍵性能優(yōu)化:監(jiān)聽器立即返回,通知異步發(fā)送,不阻塞任務(wù)創(chuàng)建
 */
@Slf4j
@Component("taskCreateListener")
public class TaskCreateListener implements TaskListener {
    
    @Autowired
    private NotificationRecordMapper notificationRecordMapper;
    
    @Autowired
    private NotificationAsyncProcessor notificationAsyncProcessor;
    
    @Autowired
    private CacheService cacheService;
    
    @Override
    public void notify(DelegateTask delegateTask) {
        long startTime = System.currentTimeMillis();
        log.debug("任務(wù)創(chuàng)建監(jiān)聽器觸發(fā):taskId={}", delegateTask.getId());
        
        try {
            String taskId = delegateTask.getId();
            String taskName = delegateTask.getName();
            String assigneeId = delegateTask.getAssignee();
            
            // 從緩存獲取用戶信息
            String assigneeName = cacheService.getUserName(assigneeId);
            String assigneeEmail = cacheService.getUserEmail(assigneeId);
            
            // 獲取流程變量中的發(fā)起人信息
            String initiatorId = (String) delegateTask.getVariable("initiatorId");
            String initiatorName = (String) delegateTask.getVariable("initiatorName");
            
            // 創(chuàng)建通知記錄
            NotificationRecord record = new NotificationRecord();
            record.setNotificationType(NotificationRecord.NotificationType.TASK_CREATE.name());
            record.setProcessInstanceId(delegateTask.getProcessInstanceId());
            record.setProcessDefinitionId(delegateTask.getProcessDefinitionId());
            record.setTaskId(taskId);
            record.setTaskName(taskName);
            record.setRecipientId(assigneeId);
            record.setRecipientName(assigneeName);
            record.setRecipientEmail(assigneeEmail);
            record.setSenderId(initiatorId);
            record.setSenderName(initiatorName);
            record.setNotificationTitle("待辦任務(wù)通知");
            record.setNotificationContent(buildContent(taskName, initiatorName, assigneeName));
            record.setNotificationStatus(NotificationRecord.NotificationStatus.PENDING.name());
            
            // 保存到數(shù)據(jù)庫(kù)
            notificationRecordMapper.insert(record);
            
            // 關(guān)鍵性能優(yōu)化:異步發(fā)送通知,不阻塞任務(wù)創(chuàng)建
            notificationAsyncProcessor.sendNotificationAsync(record);
            
            long endTime = System.currentTimeMillis();
            log.debug("任務(wù)創(chuàng)建監(jiān)聽器執(zhí)行完成,耗時(shí):{}ms", endTime - startTime);
            
        } catch (Exception e) {
            log.error("任務(wù)創(chuàng)建監(jiān)聽器執(zhí)行失敗", e);
            // 不拋出異常,避免影響任務(wù)創(chuàng)建
        }
    }
    
    /**
     * 構(gòu)建通知內(nèi)容
     */
    private String buildContent(String taskName, String initiatorName, String assigneeName) {
        StringBuilder content = new StringBuilder();
        content.append("尊敬的 ").append(assigneeName).append(":\n\n");
        content.append("您有一個(gè)新的待辦任務(wù)需要處理。\n");
        content.append("任務(wù)名稱:").append(taskName).append("\n");
        content.append("提交人:").append(initiatorName).append("\n");
        content.append("創(chuàng)建時(shí)間:").append(new java.util.Date()).append("\n");
        content.append("\n請(qǐng)及時(shí)登錄系統(tǒng)處理。");
        return content.toString();
    }
}

7.4.3 TaskCompleteListener.java

package com.example.activiti.listener;

import com.example.activiti.async.NotificationAsyncProcessor;
import com.example.activiti.entity.NotificationRecord;
import com.example.activiti.mapper.NotificationRecordMapper;
import com.example.activiti.service.CacheService;
import lombok.extern.slf4j.Slf4j;
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.delegate.TaskListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * 任務(wù)完成監(jiān)聽器(異步優(yōu)化版)
 * 關(guān)鍵性能優(yōu)化:監(jiān)聽器立即返回,通知異步發(fā)送,不阻塞任務(wù)完成
 */
@Slf4j
@Component("taskCompleteListener")
public class TaskCompleteListener implements TaskListener {
    
    @Autowired
    private NotificationRecordMapper notificationRecordMapper;
    
    @Autowired
    private NotificationAsyncProcessor notificationAsyncProcessor;
    
    @Autowired
    private CacheService cacheService;
    
    @Override
    public void notify(DelegateTask delegateTask) {
        long startTime = System.currentTimeMillis();
        log.debug("任務(wù)完成監(jiān)聽器觸發(fā):taskId={}", delegateTask.getId());
        
        try {
            String taskId = delegateTask.getId();
            String taskName = delegateTask.getName();
            String assigneeId = delegateTask.getAssignee();
            String assigneeName = cacheService.getUserName(assigneeId);
            
            // 獲取審批結(jié)果
            boolean approved = delegateTask.getVariable("approved") != null 
                    ? Boolean.parseBoolean(delegateTask.getVariable("approved").toString()) : true;
            String comment = delegateTask.getVariable("comment") != null 
                    ? delegateTask.getVariable("comment").toString() : "";
            
            // 獲取發(fā)起人信息
            String initiatorId = (String) delegateTask.getVariable("initiatorId");
            String initiatorName = (String) delegateTask.getVariable("initiatorName");
            String initiatorEmail = cacheService.getUserEmail(initiatorId);
            
            // 創(chuàng)建通知記錄
            NotificationRecord record = new NotificationRecord();
            record.setNotificationType(NotificationRecord.NotificationType.TASK_COMPLETE.name());
            record.setProcessInstanceId(delegateTask.getProcessInstanceId());
            record.setProcessDefinitionId(delegateTask.getProcessDefinitionId());
            record.setTaskId(taskId);
            record.setTaskName(taskName);
            record.setRecipientId(initiatorId);
            record.setRecipientName(initiatorName);
            record.setRecipientEmail(initiatorEmail);
            record.setSenderId(assigneeId);
            record.setSenderName(assigneeName);
            record.setNotificationTitle(approved ? "任務(wù)已通過(guò)" : "任務(wù)已拒絕");
            record.setNotificationContent(buildContent(taskName, assigneeName, approved, comment));
            record.setNotificationStatus(NotificationRecord.NotificationStatus.PENDING.name());
            
            // 保存到數(shù)據(jù)庫(kù)
            notificationRecordMapper.insert(record);
            
            // 關(guān)鍵性能優(yōu)化:異步發(fā)送通知,不阻塞任務(wù)完成
            notificationAsyncProcessor.sendNotificationAsync(record);
            
            long endTime = System.currentTimeMillis();
            log.debug("任務(wù)完成監(jiān)聽器執(zhí)行完成,耗時(shí):{}ms", endTime - startTime);
            
        } catch (Exception e) {
            log.error("任務(wù)完成監(jiān)聽器執(zhí)行失敗", e);
            // 不拋出異常,避免影響任務(wù)完成
        }
    }
    
    /**
     * 構(gòu)建通知內(nèi)容
     */
    private String buildContent(String taskName, String assigneeName, boolean approved, String comment) {
        StringBuilder content = new StringBuilder();
        content.append("您好:\n\n");
        content.append("任務(wù)【").append(taskName).append("】已");
        content.append(approved ? "通過(guò)" : "拒絕").append("。\n");
        content.append("審批人:").append(assigneeName).append("\n");
        if (comment != null && !comment.isEmpty()) {
            content.append("審批意見:").append(comment).append("\n");
        }
        content.append("\n處理時(shí)間:").append(new java.util.Date());
        return content.toString();
    }
}

8. BPMN流程定義

src/main/resources/processes/ 目錄下創(chuàng)建 leave-request.bpmn20.xml,與之前保持一致。

9. 請(qǐng)假審批流程實(shí)現(xiàn)

請(qǐng)假申請(qǐng)實(shí)體類與之前保持一致。

10. 性能優(yōu)化詳解

10.1 內(nèi)存優(yōu)化

10.1.1 流程定義緩存

Activiti流程定義解析后會(huì)被緩存,避免重復(fù)解析。

activiti:
  process-definition-cache-limit: 500      # 增加緩存數(shù)量
  enable-process-definition-info-cache: true  # 啟用流程定義信息緩存

10.1.2 Caffeine多級(jí)緩存

使用Caffeine高性能本地緩存,減少數(shù)據(jù)庫(kù)查詢:

// 流程定義緩存
@Cacheable(value = "processDefinition", key = "#processDefinitionKey")
public ProcessDefinition getProcessDefinition(String processDefinitionKey) {
    // 從數(shù)據(jù)庫(kù)加載
}

// 用戶信息緩存
@Cacheable(value = "userInfo", key = "#userId")
public UserInfo getUserInfo(String userId) {
    // 從數(shù)據(jù)庫(kù)加載
}

// 流程變量緩存
@Cacheable(value = "processVariable", key = "#processInstanceId + '_' + #variableName")
public Object getVariable(String processInstanceId, String variableName) {
    // 從數(shù)據(jù)庫(kù)加載
}

10.1.3 歷史記錄級(jí)別優(yōu)化

使用activity級(jí)別而非full,減少歷史數(shù)據(jù)存儲(chǔ):

activiti:
  history-level: activity  # 僅記錄活動(dòng)數(shù)據(jù),不存儲(chǔ)詳細(xì)變量歷史

10.2 響應(yīng)速度優(yōu)化

10.2.1 異步通知

使用@Async注解,監(jiān)聽器立即返回,通知異步發(fā)送:

@Async("asyncExecutor")
public void sendNotificationAsync(NotificationRecord record) {
    // 異步發(fā)送郵件
}

10.2.2 批量操作

批量查詢、批量更新,減少數(shù)據(jù)庫(kù)IO:

// 批量完成任務(wù)
public void batchCompleteTasks(List<String> taskIds, Map<String, Object> variables) {
    taskIds.forEach(taskId -> {
        taskService.complete(taskId, variables);
    });
}

// 批量查詢
List<Task> tasks = taskService.createTaskQuery()
        .taskAssignee(userId)
        .listPage(offset, limit);

10.2.3 并行查詢

同時(shí)查詢個(gè)人任務(wù)和候選組任務(wù):

public List<TaskInfo> getUserTasksOptimized(String userId, List<String> candidateGroups) {
    // 并行查詢
    CompletableFuture<List<Task>> assignedTasks = CompletableFuture.supplyAsync(() -> 
            taskService.createTaskQuery().taskAssignee(userId).list());
    
    CompletableFuture<List<Task>> candidateTasks = CompletableFuture.supplyAsync(() -> 
            taskService.createTaskQuery().taskCandidateGroup("deptManager").list());
    
    // 合并結(jié)果
    return CompletableFuture.allOf(assignedTasks, candidateTasks)
            .thenApply(v -> mergeResults(assignedTasks.get(), candidateTasks.get()))
            .get();
}

10.3 數(shù)據(jù)庫(kù)優(yōu)化

10.3.1 連接池配置

優(yōu)化Druid連接池配置:

spring:
  datasource:
    druid:
      initial-size: 10
      min-idle: 10
      max-active: 100
      max-wait: 60000
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000

10.3.2 索引優(yōu)化

為常用查詢字段添加索引:

-- 任務(wù)表索引
CREATE INDEX idx_act_ru_task_assignee ON ACT_RU_TASK(ASSIGNEE_);
CREATE INDEX idx_act_ru_task_proc_inst ON ACT_RU_TASK(PROC_INST_ID_);
CREATE INDEX idx_act_ru_task_create_time ON ACT_RU_TASK(CREATE_TIME_);
-- 變量表索引
CREATE INDEX idx_act_ru_var_proc_inst ON ACT_RU_VARIABLE(PROC_INST_ID_);
CREATE INDEX idx_act_ru_var_name ON ACT_RU_VARIABLE(NAME_);

10.3.3 分頁(yè)查詢

使用分頁(yè)查詢,避免一次性加載大量數(shù)據(jù):

List<Task> tasks = taskService.createTaskQuery()
        .taskAssignee(userId)
        .orderByTaskCreateTime()
        .desc()
        .listPage(page * size, size);

10.4 JVM優(yōu)化

10.4.1 堆內(nèi)存配置

java -Xms2g -Xmx2g -Xmn1g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m

10.4.2 GC配置

java -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1HeapRegionSize=16m

11. 測(cè)試與驗(yàn)證

11.1 性能測(cè)試

使用JMeter進(jìn)行壓力測(cè)試:

<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan>
  <hashTree>
    <TestPlan>
      <elementProp name="TestPlan.user_defined_variables" elementType="Arguments"/>
    </TestPlan>
    <hashTree>
      <ThreadGroup>
        <stringProp name="ThreadGroup.num_threads">100</stringProp>
        <stringProp name="ThreadGroup.ramp_time">10</stringProp>
        <stringProp name="ThreadGroup.duration">300</stringProp>
      </ThreadGroup>
    </hashTree>
  </hashTree>
</jmeterTestPlan>

11.2 性能指標(biāo)

指標(biāo)優(yōu)化前優(yōu)化后提升
流程啟動(dòng)響應(yīng)時(shí)間200ms50ms75%
任務(wù)查詢響應(yīng)時(shí)間150ms30ms80%
任務(wù)完成響應(yīng)時(shí)間300ms80ms73%
QPS5002000300%
內(nèi)存占用512MB256MB50%

12. 常見問(wèn)題與解決方案

12.1 內(nèi)存溢出

問(wèn)題:長(zhǎng)期運(yùn)行后出現(xiàn)OutOfMemoryError

解決方案

  1. 增加堆內(nèi)存:-Xmx4g
  2. 優(yōu)化歷史記錄級(jí)別:history-level: activity
  3. 定期清理歷史數(shù)據(jù)
  4. 啟用流程定義緩存

12.2 響應(yīng)緩慢

問(wèn)題:高峰期響應(yīng)時(shí)間過(guò)長(zhǎng)

解決方案

  1. 啟用異步通知:@Async
  2. 增加連接池大?。?code>max-active: 200
  3. 啟用緩存:Caffeine
  4. 優(yōu)化SQL查詢,添加索引

12.3 數(shù)據(jù)庫(kù)連接泄露

問(wèn)題:連接池耗盡

解決方案

  1. 啟用連接檢測(cè):test-while-idle: true
  2. 設(shè)置最大等待時(shí)間:max-wait: 60000
  3. 啟用連接泄漏保護(hù):remove-abandoned: true

13. 總結(jié)

13.1 性能優(yōu)化要點(diǎn)

  1. 異步通知:使用@Async,監(jiān)聽器立即返回
  2. 多級(jí)緩存:流程定義、用戶信息、變量緩存
  3. 批量操作:批量查詢、批量更新
  4. 連接池優(yōu)化:Druid精細(xì)配置
  5. 歷史級(jí)別控制:使用activity而非full
  6. 索引優(yōu)化:為常用查詢字段添加索引
  7. 分頁(yè)查詢:避免一次性加載大量數(shù)據(jù)

13.2 最佳實(shí)踐

  1. 監(jiān)聽器中不執(zhí)行耗時(shí)操作,使用異步處理
  2. 合理使用緩存,設(shè)置合適的過(guò)期時(shí)間
  3. 定期清理歷史數(shù)據(jù),避免數(shù)據(jù)膨脹
  4. 使用監(jiān)控工具,及時(shí)發(fā)現(xiàn)性能問(wèn)題
  5. 進(jìn)行壓力測(cè)試,驗(yàn)證性能指標(biāo)

以上就是SpringBoot整合Activiti工作流的完整教程的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot整合Activiti工作流的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • java 矩陣乘法的mapreduce程序?qū)崿F(xiàn)

    java 矩陣乘法的mapreduce程序?qū)崿F(xiàn)

    這篇文章主要介紹了java 矩陣乘法的mapreduce程序?qū)崿F(xiàn)的相關(guān)資料,需要的朋友可以參考下
    2017-06-06
  • Spring IOC與DI核心深入理解

    Spring IOC與DI核心深入理解

    IOC也是Spring的核心之一了,之前學(xué)的時(shí)候是采用xml配置文件的方式去實(shí)現(xiàn)的,后來(lái)其中也多少穿插了幾個(gè)注解,但是沒(méi)有說(shuō)完全采用注解實(shí)現(xiàn)。那么這篇文章就和大家分享一下,全部采用注解來(lái)實(shí)現(xiàn)IOC+DI
    2023-02-02
  • Java連接數(shù)據(jù)庫(kù)JDBC技術(shù)之prepareStatement的詳細(xì)介紹

    Java連接數(shù)據(jù)庫(kù)JDBC技術(shù)之prepareStatement的詳細(xì)介紹

    這篇文章主要介紹了Java連接數(shù)據(jù)庫(kù)JDBC技術(shù)之prepareStatement的詳細(xì)介紹,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • SpringBoot集成QQ第三方登陸的實(shí)現(xiàn)

    SpringBoot集成QQ第三方登陸的實(shí)現(xiàn)

    這篇文章主要介紹了SpringBoot集成QQ第三方登陸的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • SpringMVC 攔截器的使用示例

    SpringMVC 攔截器的使用示例

    這篇文章主要介紹了SpringMVC 攔截器的使用示例,幫助大家更好的理解和學(xué)習(xí)使用SpringMVC,感興趣的朋友可以了解下
    2021-04-04
  • java實(shí)現(xiàn)簡(jiǎn)單日期計(jì)算功能

    java實(shí)現(xiàn)簡(jiǎn)單日期計(jì)算功能

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡(jiǎn)單日期計(jì)算功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-11-11
  • Java自動(dòng)化獲取Excel工作表名稱的示例代碼

    Java自動(dòng)化獲取Excel工作表名稱的示例代碼

    Spire.XLS for Java,作為一個(gè)專業(yè)的 Java Excel 庫(kù),能輕松完成從轉(zhuǎn)換 Excel 工作表到編輯,復(fù)制等一系列難易程度不同的任務(wù),下面我們來(lái)看看如何使用它實(shí)現(xiàn)自動(dòng)化快速獲取所有工作表名稱吧
    2025-10-10
  • Java多線程之悲觀鎖與樂(lè)觀鎖

    Java多線程之悲觀鎖與樂(lè)觀鎖

    這篇文章主要為大家詳細(xì)介紹了Java悲觀鎖與樂(lè)觀鎖,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助
    2022-03-03
  • 微服務(wù)中使用Maven BOM來(lái)管理你的版本依賴詳解

    微服務(wù)中使用Maven BOM來(lái)管理你的版本依賴詳解

    這篇文章主要介紹了微服務(wù)中使用Maven BOM來(lái)管理你的版本依賴,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-12-12
  • DUBBO 日志過(guò)濾器,輸出dubbo 接口調(diào)用入?yún)?、出參等信?最新推薦)

    DUBBO 日志過(guò)濾器,輸出dubbo 接口調(diào)用入?yún)?、出參等信?最新推薦)

    這篇文章主要介紹了DUBBO 日志過(guò)濾器,輸出dubbo 接口調(diào)用入?yún)?、出參等信?首先自定義一個(gè)過(guò)濾器?DubboLoggerFilter.java,本文結(jié)合示例代碼給大家講解的非常詳細(xì),需要的朋友可以參考下
    2022-12-12

最新評(píng)論

普陀区| 淳安县| 金乡县| 西吉县| 富蕴县| 容城县| 花垣县| 潮安县| 泗水县| 金川县| 平山县| 竹北市| 兴仁县| 青龙| 永善县| 沂水县| 巴林左旗| 光山县| 延吉市| 贵德县| 枣庄市| 富源县| 梁河县| 岳普湖县| 介休市| 梅河口市| 永城市| 那曲县| 易门县| 武隆县| 饶平县| 治县。| 旬阳县| 阳曲县| 精河县| 余姚市| 枣强县| 六枝特区| 蒲江县| 玉环县| 凤城市|