找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2347

积分

0

好友

315

主题
发表于 8 小时前 | 查看: 0| 回复: 0

一、什么是Activiti?

Activiti 是一个开源的工作流引擎,它完整实现了 BPMN 2.0(业务流程模型与标注)规范,专门用于自动化企业中的业务流程。由 Alfresco 软件公司开发并基于 Apache 许可协议发布,它已经成为目前最流行的轻量级工作流引擎解决方案之一。

1.1 核心概念

为了高效使用Activiti,理解其核心概念是第一步:

  • 流程引擎(Process Engine):这是整个Activiti框架的心脏,负责所有流程的执行、管理和调度工作。
  • BPMN:即业务流程建模与标注,它是一种国际标准的、可视化的流程建模语言。
  • 流程定义(Process Definition):业务流程的静态蓝图或模板,通常以 BPMN 2.0 XML 格式进行存储。
  • 流程实例(Process Instance):当某个员工发起一个具体申请(如请假)时,就是根据流程定义创建的一次动态执行过程。
  • 任务(Task):流程中的一个工作单元,需要由特定的用户或系统去完成,例如“部门经理审批”。
  • 服务任务(Service Task):由系统自动执行的任务,无需人工干预,例如“发送通知邮件”。

Activiti核心架构图

二、环境搭建

2.1 Maven依赖配置

在一个标准的 Java 项目中,我们首先需要在 pom.xml 文件中引入必要的依赖。

<!-- 在pom.xml中添加Activiti依赖 -->
<dependencies>
    <!-- Activiti核心依赖 -->
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-engine</artifactId>
        <version>7.1.0.M6</version>
    </dependency>

    <!-- Activiti Spring集成 -->
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-spring</artifactId>
        <version>7.1.0.M6</version>
    </dependency>

    <!-- BPMN建模工具 -->
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-bpmn-model</artifactId>
        <version>7.1.0.M6</version>
    </dependency>

    <!-- MySQL驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.28</version>
    </dependency>

    <!-- 数据库连接池 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.8</version>
    </dependency>
</dependencies>

2.2 配置流程引擎

接下来,我们需要通过配置类来初始化Activiti的流程引擎。这里我们将其与Spring Boot集成。

package com.example.activiti.config;

import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

/**
 * Activiti流程引擎配置类
 */
@Configuration
public class ActivitiConfig {

    @Value("${spring.datasource.url}")
    private String url;

    @Value("${spring.datasource.username}")
    private String username;

    @Value("${spring.datasource.password}")
    private String password;

    @Value("${spring.datasource.driver-class-name}")
    private String driverClassName;

    /**
     * 配置流程引擎
     */
    @Bean
    public ProcessEngineConfiguration processEngineConfiguration() {
        StandaloneProcessEngineConfiguration configuration = new StandaloneProcessEngineConfiguration();

        // 配置数据库连接
        configuration.setJdbcDriver(driverClassName);
        configuration.setJdbcUrl(url);
        configuration.setJdbcUsername(username);
        configuration.setJdbcPassword(password);

        // 数据库策略配置
        // DB_SCHEMA_UPDATE_TRUE:如果表不存在,自动创建表
        // DB_SCHEMA_UPDATE_FALSE:不自动更新表结构
        // DB_SCHEMA_UPDATE_CREATE_DROP:先创建表,关闭时删除表
        configuration.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);

        // 异步执行器配置
        configuration.setAsyncExecutorActivate(true);
        configuration.setAsyncExecutorEnabled(true);

        return configuration;
    }

    /**
     * 创建流程引擎Bean
     */
    @Bean
    public ProcessEngine processEngine() {
        return processEngineConfiguration().buildProcessEngine();
    }
}

2.3 创建数据库初始化配置

Activiti需要将流程定义、运行时数据、历史记录等持久化到数据库中。我们需要配置一个数据源。

package com.example.activiti.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.alibaba.druid.pool.DruidDataSource;

import javax.sql.DataSource;

/**
 * 数据源配置
 */
@Configuration
public class DataSourceConfig {

    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl("jdbc:mysql://localhost:3306/activiti_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");

        // 连接池配置
        dataSource.setInitialSize(5);
        dataSource.setMinIdle(5);
        dataSource.setMaxActive(20);
        dataSource.setMaxWait(60000);

        return dataSource;
    }
}

三、核心API详解

3.1 流程引擎服务架构

Activiti通过一组清晰划分职责的服务接口来提供所有功能。理解这些服务是进行二次开发的基础。

Activiti服务架构图

3.2 核心服务接口

让我们通过一个示例服务类,直观地了解这些核心API的用法。

package com.example.activiti.service;

import org.activiti.engine.*;
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.stereotype.Service;

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

/**
 * Activiti核心服务使用示例
 */
@Service
public class ActivitiCoreService {

    @Autowired
    private ProcessEngine processEngine;

    /**
     * 获取流程定义服务 - 管理流程定义和部署
     */
    public void repositoryServiceExample() {
        RepositoryService repositoryService = processEngine.getRepositoryService();

        // 部署流程定义
        Deployment deployment = repositoryService.createDeployment()
                .addClasspathResource("processes/leave-request.bpmn20.xml")
                .name("请假流程")
                .deploy();

        System.out.println("流程部署ID: " + deployment.getId());
        System.out.println("流程部署名称: " + deployment.getName());
    }

    /**
     * 获取运行时服务 - 启动流程实例、查询流程实例
     */
    public String runtimeServiceExample() {
        RuntimeService runtimeService = processEngine.getRuntimeService();

        // 设置流程变量
        Map<String, Object> variables = new HashMap<>();
        variables.put("applicant", "张三");
        variables.put("days", 3);

        // 启动流程实例
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(
                "leaveRequest",
                variables
        );

        System.out.println("流程实例ID: " + processInstance.getId());
        System.out.println("流程定义ID: " + processInstance.getProcessDefinitionId());

        return processInstance.getId();
    }

    /**
     * 获取任务服务 - 查询、完成用户任务
     */
    public void taskServiceExample(String processInstanceId) {
        TaskService taskService = processEngine.getTaskService();

        // 查询待办任务
        List<Task> tasks = taskService.createTaskQuery()
                .processInstanceId(processInstanceId)
                .list();

        for (Task task : tasks) {
            System.out.println("任务ID: " + task.getId());
            System.out.println("任务名称: " + task.getName());
            System.out.println("任务办理人: " + task.getAssignee());

            // 设置任务变量
            Map<String, Object> taskVariables = new HashMap<>();
            taskVariables.put("approved", true);
            taskVariables.put("comment", "同意请假");

            // 完成任务
            taskService.complete(task.getId(), taskVariables);
        }
    }

    /**
     * 获取历史服务 - 查询历史数据
     */
    public void historyServiceExample(String processInstanceId) {
        HistoryService historyService = processEngine.getHistoryService();

        // 查询已完成的历史任务
        List<org.activiti.engine.task.history.HistoricTaskInstance> historicTasks =
                historyService.createHistoricTaskInstanceQuery()
                        .processInstanceId(processInstanceId)
                        .finished()
                        .orderByHistoricTaskInstanceEndTime()
                        .asc()
                        .list();

        for (org.activiti.engine.task.history.HistoricTaskInstance historicTask : historicTasks) {
            System.out.println("历史任务ID: " + historicTask.getId());
            System.out.println("历史任务名称: " + historicTask.getName());
            System.out.println("完成时间: " + historicTask.getEndTime());
        }
    }

    /**
     * 获取管理服务 - 执行管理和运维操作
     */
    public void managementServiceExample() {
        ManagementService managementService = processEngine.getManagementService();

        // 执行自定义SQL查询
        long tableCount = managementService.createTableCountQuery().count();
        System.out.println("Activiti表数量: " + tableCount);

        // 获取数据库表元数据
        List<String> tableNames = managementService.getTableNames();
        System.out.println("Activiti表名列表: " + tableNames);
    }

    /**
     * 获取动态BPMN服务 - 动态修改流程
     */
    public void dynamicBpmnServiceExample() {
        DynamicBpmnService dynamicBpmnService = processEngine.getDynamicBpmnService();

        // 动态修改流程定义的某些属性,而不需要重新部署
        // 例如:修改任务名称、添加监听器等
        System.out.println("动态BPMN服务可用于运行时修改流程定义");
    }
}

四、BPMN流程设计

4.1 创建请假流程

理论结合实践,我们设计一个经典的请假审批流程。以下是其BPMN 2.0 XML定义。

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:activiti="http://activiti.org/bpmn"
             xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
             typeLanguage="http://www.w3.org/2001/XMLSchema"
             expressionLanguage="http://www.w3.org/1999/XPath"
             targetNamespace="http://www.example.org/process">

    <!-- 流程定义 -->
    <process id="leaveRequest" name="请假审批流程" isExecutable="true">

        <!-- 开始事件 -->
        <startEvent id="startEvent" name="开始"/>

        <!-- 用户任务:提交请假申请 -->
        <userTask id="submitLeave" name="提交请假申请" activiti:assignee="${applicant}">
            <documentation>员工提交请假申请</documentation>
        </userTask>

        <!-- 用户任务:部门经理审批 -->
        <userTask id="managerApproval" name="部门经理审批" activiti:candidateGroups="managers">
            <documentation>部门经理审批请假申请</documentation>
        </userTask>

        <!-- 网关:排他网关,根据请假天数判断 -->
        <exclusiveGateway id="gateway" name="天数判断"/>

        <!-- 用户任务:总经理审批(请假天数大于3天) -->
        <userTask id="gmApproval" name="总经理审批" activiti:candidateGroups="general_managers">
            <documentation>总经理审批长期请假</documentation>
        </userTask>

        <!-- 服务任务:发送通知 -->
        <serviceTask id="sendNotification" name="发送通知"
                     activiti:class="com.example.activiti.delegate.SendNotificationDelegate"/>

        <!-- 结束事件:审批通过 -->
        <endEvent id="endEventApproved" name="审批通过"/>

        <!-- 结束事件:审批拒绝 -->
        <endEvent id="endEventRejected" name="审批拒绝"/>

        <!-- 流程连线 -->
        <sequenceFlow id="flow1" sourceRef="startEvent" targetRef="submitLeave"/>
        <sequenceFlow id="flow2" sourceRef="submitLeave" targetRef="managerApproval"/>
        <sequenceFlow id="flow3" sourceRef="managerApproval" targetRef="gateway"/>

        <sequenceFlow id="flow4" sourceRef="gateway" targetRef="gmApproval">
            <conditionExpression xsi:type="tFormalExpression">
                <![CDATA[${days > 3}]]>
            </conditionExpression>
        </sequenceFlow>

        <sequenceFlow id="flow5" sourceRef="gateway" targetRef="sendNotification">
            <conditionExpression xsi:type="tFormalExpression">
                <![CDATA[${days <= 3}]]>
            </conditionExpression>
        </sequenceFlow>

        <sequenceFlow id="flow6" sourceRef="gmApproval" targetRef="sendNotification"/>

        <sequenceFlow id="flow7" sourceRef="sendNotification" targetRef="endEventApproved">
            <conditionExpression xsi:type="tFormalExpression">
                <![CDATA[${approved == true}]]>
            </conditionExpression>
        </sequenceFlow>

        <sequenceFlow id="flow8" sourceRef="sendNotification" targetRef="endEventRejected">
            <conditionExpression xsi:type="tFormalExpression">
                <![CDATA[${approved == false}]]>
            </conditionExpression>
        </sequenceFlow>
    </process>

    <!-- BPMN图形信息 -->
    <bpmndi:BPMNDiagram id="BPMNDiagram_leaveRequest">
        <bpmndi:BPMNPlane bpmnElement="leaveRequest" id="BPMNPlane_leaveRequest">
            <!-- 这里包含图形布局信息 -->
        </bpmndi:BPMNPlane>
    </bpmndi:BPMNDiagram>
</definitions>

请假审批流程图

4.2 流程设计最佳实践

  1. 命名规范:流程定义ID建议使用驼峰命名(如 leaveRequest),而任务名称则使用明确的中文业务描述。
  2. 网关使用:根据业务逻辑合理选择排他网关(XOR)、并行网关(AND)和包容网关(OR)。
  3. 任务分配:灵活运用候选人(candidateUsers)和候选组(candidateGroups)来实现动态的任务指派。
  4. 流程变量:谨慎规划流程变量的使用,只传递必要的数据,避免过度耦合。

五、生产环境实战案例

5.1 系统整体架构

在一个真实的OA系统中,Activiti通常作为工作流层被集成。下面是一个典型的微服务架构设计。

OA系统集成架构图

5.2 请假审批完整实现

5.2.1 实体类设计

首先,我们需要一个与数据库表对应的业务实体类。

package com.example.activiti.entity;

import java.util.Date;

/**
 * 请假申请实体
 */
public class LeaveRequest {

    private Long id;
    private String processInstanceId;
    private String applicant;
    private Integer days;
    private String reason;
    private Date startTime;
    private Date endTime;
    private String status;
    private Date createTime;
    private Date updateTime;

    // Getter和Setter方法
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getProcessInstanceId() {
        return processInstanceId;
    }

    public void setProcessInstanceId(String processInstanceId) {
        this.processInstanceId = processInstanceId;
    }

    public String getApplicant() {
        return applicant;
    }

    public void setApplicant(String applicant) {
        this.applicant = applicant;
    }

    public Integer getDays() {
        return days;
    }

    public void setDays(Integer days) {
        this.days = days;
    }

    public String getReason() {
        return reason;
    }

    public void setReason(String reason) {
        this.reason = reason;
    }

    public Date getStartTime() {
        return startTime;
    }

    public void setStartTime(Date startTime) {
        this.startTime = startTime;
    }

    public Date getEndTime() {
        return endTime;
    }

    public void setEndTime(Date endTime) {
        this.endTime = endTime;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public Date getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }
}
5.2.2 服务层实现

这是业务逻辑的核心,封装了所有与请假流程相关的操作。

package com.example.activiti.service;

import com.example.activiti.entity.LeaveRequest;
import org.activiti.engine.*;
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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

/**
 * 请假审批服务
 */
@Service
public class LeaveRequestService {

    @Autowired
    private ProcessEngine processEngine;

    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private TaskService taskService;

    @Autowired
    private HistoryService historyService;

    /**
     * 部署流程定义
     */
    public void deployProcess() {
        Deployment deployment = repositoryService.createDeployment()
                .addClasspathResource("processes/leave-request.bpmn20.xml")
                .name("请假审批流程")
                .category("OA")
                .deploy();

        System.out.println("流程部署成功,ID: " + deployment.getId());
    }

    /**
     * 启动请假流程
     */
    @Transactional(rollbackFor = Exception.class)
    public String startLeaveProcess(LeaveRequest leaveRequest) {
        // 准备流程变量
        Map<String, Object> variables = new HashMap<>();
        variables.put("applicant", leaveRequest.getApplicant());
        variables.put("days", leaveRequest.getDays());
        variables.put("reason", leaveRequest.getReason());
        variables.put("startTime", leaveRequest.getStartTime());
        variables.put("endTime", leaveRequest.getEndTime());

        // 启动流程实例
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(
                "leaveRequest",
                variables
        );

        // 保存流程实例ID到业务表
        leaveRequest.setProcessInstanceId(processInstance.getId());
        leaveRequest.setStatus("PENDING");
        // 这里应该调用DAO保存到数据库
        // leaveRequestDao.insert(leaveRequest);

        return processInstance.getId();
    }

    /**
     * 查询待办任务
     */
    public List<Task> findPendingTasks(String assignee) {
        return taskService.createTaskQuery()
                .taskAssignee(assignee)
                .orderByTaskCreateTime()
                .desc()
                .list();
    }

    /**
     * 查询候选任务(组任务)
     */
    public List<Task> findCandidateTasks(String candidateGroup) {
        return taskService.createTaskQuery()
                .taskCandidateGroup(candidateGroup)
                .orderByTaskCreateTime()
                .desc()
                .list();
    }

    /**
     * 完成任务
     */
    @Transactional(rollbackFor = Exception.class)
    public void completeTask(String taskId, boolean approved, String comment) {
        // 设置审批结果
        Map<String, Object> variables = new HashMap<>();
        variables.put("approved", approved);
        variables.put("comment", comment);

        // 添加评论
        taskService.addComment(taskId, null, comment);

        // 完成任务
        taskService.complete(taskId, variables);

        // 更新业务状态
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        String processInstanceId = task.getProcessInstanceId();

        // 根据审批结果更新业务表
        // LeaveRequest leaveRequest = leaveRequestDao.findByProcessInstanceId(processInstanceId);
        // leaveRequest.setStatus(approved ? "APPROVED" : "REJECTED");
        // leaveRequestDao.update(leaveRequest);
    }

    /**
     * 查询流程历史
     */
    public Map<String, Object> getProcessHistory(String processInstanceId) {
        // 查询流程实例历史
        org.activiti.engine.history.HistoricProcessInstance historicProcessInstance =
                historyService.createHistoricProcessInstanceQuery()
                        .processInstanceId(processInstanceId)
                        .singleResult();

        // 查询历史任务
        List<org.activiti.engine.task.history.HistoricTaskInstance> historicTasks =
                historyService.createHistoricTaskInstanceQuery()
                        .processInstanceId(processInstanceId)
                        .orderByHistoricTaskInstanceEndTime()
                        .asc()
                        .list();

        // 查询历史变量
        List<org.activiti.engine.history.HistoricVariableInstance> historicVariables =
                historyService.createHistoricVariableInstanceQuery()
                        .processInstanceId(processInstanceId)
                        .list();

        Map<String, Object> result = new HashMap<>();
        result.put("processInstance", historicProcessInstance);
        result.put("tasks", historicTasks);
        result.put("variables", historicVariables);

        return result;
    }

    /**
     * 撤销流程
     */
    @Transactional(rollbackFor = Exception.class)
    public void cancelProcess(String processInstanceId, String reason) {
        // 删除流程实例
        runtimeService.deleteProcessInstance(processInstanceId, reason);

        // 更新业务表状态
        // LeaveRequest leaveRequest = leaveRequestDao.findByProcessInstanceId(processInstanceId);
        // leaveRequest.setStatus("CANCELLED");
        // leaveRequestDao.update(leaveRequest);
    }

    /**
     * 挂起流程
     */
    public void suspendProcess(String processInstanceId) {
        runtimeService.suspendProcessInstanceById(processInstanceId);
    }

    /**
     * 激活流程
     */
    public void activateProcess(String processInstanceId) {
        runtimeService.activateProcessInstanceById(processInstanceId);
    }
}

5.3 任务监听器

监听器可以在任务生命周期的各个节点(创建、分配、完成等)插入自定义逻辑。

package com.example.activiti.listener;

import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.delegate.TaskListener;
import org.springframework.stereotype.Component;

/**
 * 任务创建监听器
 */
@Component
public class TaskCreateListener implements TaskListener {

    @Override
    public void notify(DelegateTask delegateTask) {
        String taskName = delegateTask.getName();
        String assignee = delegateTask.getAssignee();

        System.out.println("任务创建: " + taskName);
        System.out.println("办理人: " + assignee);

        // 发送通知
        sendNotification(assignee, taskName);

        // 记录日志
        logTask(delegateTask);
    }

    private void sendNotification(String assignee, String taskName) {
        // 实现发送邮件、短信等通知逻辑
        System.out.println("发送通知给: " + assignee + ", 任务: " + taskName);
    }

    private void logTask(DelegateTask delegateTask) {
        // 记录任务创建日志
        System.out.println("任务ID: " + delegateTask.getId());
        System.out.println("流程实例ID: " + delegateTask.getProcessInstanceId());
    }
}

5.4 执行监听器

执行监听器作用于流程实例或活动(节点)的流转事件上。

package com.example.activiti.listener;

import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.ExecutionListener;
import org.springframework.stereotype.Component;

/**
 * 流程开始监听器
 */
@Component
public class ProcessStartListener implements ExecutionListener {

    @Override
    public void notify(DelegateExecution execution) {
        String processInstanceId = execution.getProcessInstanceId();
        String processDefinitionId = execution.getProcessDefinitionId();

        System.out.println("流程启动: " + processInstanceId);
        System.out.println("流程定义: " + processDefinitionId);

        // 初始化流程变量
        execution.setVariable("startTime", new java.util.Date());
        execution.setVariable("status", "RUNNING");

        // 记录流程启动日志
        logProcessStart(execution);
    }

    private void logProcessStart(DelegateExecution execution) {
        // 记录流程启动日志
        System.out.println("记录流程启动日志");
    }
}

5.5 Java Delegate任务

Java Delegate用于实现服务任务(Service Task)的自定义业务逻辑。

package com.example.activiti.delegate;

import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.JavaDelegate;
import org.springframework.stereotype.Component;

/**
 * 发送通知委托类
 */
@Component("sendNotificationDelegate")
public class SendNotificationDelegate implements JavaDelegate {

    @Override
    public void execute(DelegateExecution execution) {
        String processInstanceId = execution.getProcessInstanceId();

        // 获取流程变量
        String applicant = (String) execution.getVariable("applicant");
        Integer days = (Integer) execution.getVariable("days");
        Boolean approved = (Boolean) execution.getVariable("approved");
        String comment = (String) execution.getVariable("comment");

        // 构建通知内容
        StringBuilder message = new StringBuilder();
        message.append("请假审批通知\n");
        message.append("申请人: ").append(applicant).append("\n");
        message.append("请假天数: ").append(days).append("\n");
        message.append("审批结果: ").append(approved ? "通过" : "拒绝").append("\n");
        if (comment != null) {
            message.append("审批意见: ").append(comment);
        }

        // 发送通知
        sendEmail(applicant, message.toString());
        sendSMS(applicant, message.toString());

        System.out.println("通知已发送: " + message);
    }

    private void sendEmail(String to, String content) {
        // 实现邮件发送逻辑
        System.out.println("发送邮件给: " + to);
    }

    private void sendSMS(String to, String content) {
        // 实现短信发送逻辑
        System.out.println("发送短信给: " + to);
    }
}

六、流程交互时序图

从用户操作到系统内部调用的完整交互过程,可以通过时序图清晰地展现。

请假审批流程交互时序图

七、REST API设计

将核心服务封装成RESTful API,方便前端或其他系统调用。

7.1 控制器实现

package com.example.activiti.controller;

import com.example.activiti.entity.LeaveRequest;
import com.example.activiti.service.LeaveRequestService;
import org.activiti.engine.task.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

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

/**
 * 请假审批控制器
 */
@RestController
@RequestMapping("/api/leave")
public class LeaveRequestController {

    @Autowired
    private LeaveRequestService leaveRequestService;

    /**
     * 提交请假申请
     * POST /api/leave/submit
     */
    @PostMapping("/submit")
    public Map<String, Object> submitLeaveRequest(@RequestBody LeaveRequest leaveRequest) {
        Map<String, Object> result = new HashMap<>();

        try {
            String processInstanceId = leaveRequestService.startLeaveProcess(leaveRequest);
            result.put("success", true);
            result.put("processInstanceId", processInstanceId);
            result.put("message", "请假申请提交成功");
        } catch (Exception e) {
            result.put("success", false);
            result.put("message", "提交失败: " + e.getMessage());
        }

        return result;
    }

    /**
     * 查询待办任务
     * GET /api/leave/tasks/pending?assignee=xxx
     */
    @GetMapping("/tasks/pending")
    public List<Task> getPendingTasks(@RequestParam String assignee) {
        return leaveRequestService.findPendingTasks(assignee);
    }

    /**
     * 查询候选任务
     * GET /api/leave/tasks/candidate?group=xxx
     */
    @GetMapping("/tasks/candidate")
    public List<Task> getCandidateTasks(@RequestParam String group) {
        return leaveRequestService.findCandidateTasks(group);
    }

    /**
     * 审批任务
     * POST /api/leave/tasks/{taskId}/complete
     */
    @PostMapping("/tasks/{taskId}/complete")
    public Map<String, Object> completeTask(
            @PathVariable String taskId,
            @RequestParam boolean approved,
            @RequestParam String comment) {

        Map<String, Object> result = new HashMap<>();

        try {
            leaveRequestService.completeTask(taskId, approved, comment);
            result.put("success", true);
            result.put("message", "任务审批完成");
        } catch (Exception e) {
            result.put("success", false);
            result.put("message", "审批失败: " + e.getMessage());
        }

        return result;
    }

    /**
     * 查询流程历史
     * GET /api/leave/history/{processInstanceId}
     */
    @GetMapping("/history/{processInstanceId}")
    public Map<String, Object> getProcessHistory(@PathVariable String processInstanceId) {
        return leaveRequestService.getProcessHistory(processInstanceId);
    }

    /**
     * 撤销流程
     * DELETE /api/leave/process/{processInstanceId}
     */
    @DeleteMapping("/process/{processInstanceId}")
    public Map<String, Object> cancelProcess(
            @PathVariable String processInstanceId,
            @RequestParam String reason) {

        Map<String, Object> result = new HashMap<>();

        try {
            leaveRequestService.cancelProcess(processInstanceId, reason);
            result.put("success", true);
            result.put("message", "流程已撤销");
        } catch (Exception e) {
            result.put("success", false);
            result.put("message", "撤销失败: " + e.getMessage());
        }

        return result;
    }
}

八、常见问题与解决方案

8.1 流程无法启动

问题:调用 startProcessInstanceByKey 时抛出异常。

原因

  • 流程定义未部署。
  • 流程定义key不匹配。
  • 数据库表未正确创建。

解决方案

// 检查流程是否已部署
long count = repositoryService.createProcessDefinitionQuery()
        .processDefinitionKey("leaveRequest")
        .count();

if (count == 0) {
    // 部署流程
    deployProcess();
}

8.2 任务查询不到

问题:查询待办任务返回空列表。

原因

  • 任务已分配给其他用户。
  • 任务已在其他会话中完成。
  • 流程已结束。

解决方案

// 使用多种方式查询
List<Task> tasks = taskService.createTaskQuery()
        .taskAssignee(assignee)  // 指定办理人
        .taskCandidateGroup("managers")  // 候选组
        .taskCandidateUser(assignee)  // 候选人
        .includeIdentityLinks()  // 包含身份关联
        .list();

8.3 流程变量丢失

问题:设置的流程变量在后续节点无法获取。

原因

  • 变量作用域设置错误(流程实例级 vs 任务级)。
  • 变量被后续操作覆盖。
  • 流程变量未正确持久化。

解决方案

// 使用全局变量(流程实例级别)
runtimeService.setVariable(processInstanceId, "varName", value);

// 使用本地变量(仅当前任务可见)
taskService.setVariableLocal(taskId, "varName", value);

九、总结

通过本文的详细讲解,我们完成了一次从零开始的Activiti工作流引擎集成之旅。我们不仅学会了如何搭建环境和配置引擎,更重要的是,掌握了如何使用其核心API来驱动业务,并设计出了完整的、可投入生产的请假审批OA模块。

核心收获包括:

  1. 环境与配置:快速搭建基于Spring Boot和 MySQL 的Activiti开发环境。
  2. API精通:深入理解并实践了RepositoryService、RuntimeService、TaskService等七大核心服务。
  3. 流程设计:使用标准的BPMN 2.0 XML设计和实现了一个包含条件分支的复杂业务流程。
  4. 生产实战:构建了包含业务实体、服务层、监听器、REST API的完整解决方案,并解决了常见疑难问题。

希望这份指南能成为你探索Activiti和业务流程自动化领域的坚实起点。如果想深入学习更多架构设计模式或查阅其他 技术文档,欢迎持续关注相关的技术社区。




上一篇:嵌入式MCU交互式Shell CherrySH解析:零动态内存、命令注册与移植实战
下一篇:LZ压缩算法简史:从HTTP的Gzip到Zip文件的底层原理
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-24 16:34 , Processed in 0.237851 second(s), 43 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表