news 2026/4/23 23:14:53

Activiti 7.x 实战:用 TaskListener 实现审批流程的自动抄送与通知(Spring Boot 集成)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Activiti 7.x 实战:用 TaskListener 实现审批流程的自动抄送与通知(Spring Boot 集成)

Activiti 7.x 实战:用 TaskListener 实现审批流程的自动抄送与通知(Spring Boot 集成)

在企业的日常运营中,审批流程无处不在。从简单的请假申请到复杂的项目立项,每个环节都需要高效、准确的审批机制。传统的审批流程往往依赖于人工操作,不仅效率低下,还容易出现遗漏和错误。而 Activiti 作为一款强大的工作流引擎,为我们提供了自动化处理这些流程的能力。本文将重点介绍如何利用 Activiti 7.x 的 TaskListener 功能,实现审批流程中的自动抄送与通知机制,特别是在 Spring Boot 环境下的集成实践。

想象这样一个场景:员工提交请假申请后,系统需要自动通知部门经理审批,同时抄送给HR备案;审批通过后,系统需要自动通知申请人结果,并更新相关考勤记录。这些看似简单的需求,如果完全依赖人工操作,不仅耗时耗力,还容易出错。而通过 Activiti 的 TaskListener,我们可以将这些操作自动化,大大提高工作效率和准确性。

1. TaskListener 基础与核心事件

TaskListener 是 Activiti 提供的一个强大接口,允许我们在任务生命周期的关键节点插入自定义逻辑。理解这些事件触发的时机和顺序,是正确使用 TaskListener 的前提。

1.1 四大核心事件解析

Activiti 的 TaskListener 主要监听四种事件类型,每种事件对应任务生命周期中的不同阶段:

  • create:任务创建完成且所有参数设置完毕后触发。这是最常用的事件之一,适合执行初始化操作。
  • assignment:任务被分配给具体人员时触发。值得注意的是,assignment 事件会在 create 事件之前触发。
  • complete:任务完成但尚未从运行时数据中删除前触发。这是另一个常用事件,适合执行后处理逻辑。
  • delete:任务即将被删除前触发。即使是正常完成任务(completeTask)也会触发此事件。

重要提示:assignment 事件在 create 之前触发这一设计看似违反直觉,但实际上是为了确保在 create 事件触发时,所有任务参数(包括办理人)都已经设置完成。

1.2 DelegateTask 关键API

在 TaskListener 的实现中,我们可以通过 DelegateTask 对象访问和操作任务的各种属性。以下是一些最常用的方法:

// 获取任务基本信息 String taskId = delegateTask.getId(); String taskName = delegateTask.getName(); String assignee = delegateTask.getAssignee(); // 操作任务属性 delegateTask.setPriority(80); // 设置任务优先级 delegateTask.setDueDate(new Date()); // 设置截止日期 // 管理候选人 delegateTask.addCandidateUser("user1"); delegateTask.addCandidateGroup("group1");

理解这些 API 的用法,是构建复杂审批逻辑的基础。在实际开发中,我们通常会结合流程变量(Process Variables)来实现更动态的控制。

2. Spring Boot 集成实践

将 Activiti 与 Spring Boot 集成,可以充分利用 Spring 的依赖注入和事务管理能力。下面我们来看具体的集成步骤和最佳实践。

2.1 基础环境配置

首先,在 Spring Boot 项目中添加 Activiti 依赖:

<dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring-boot-starter</artifactId> <version>7.1.0.M6</version> </dependency>

然后,配置基本的数据库和 Activiti 属性:

spring: datasource: url: jdbc:mysql://localhost:3306/activiti_db username: root password: password driver-class-name: com.mysql.cj.jdbc.Driver activiti: database-schema-update: true history-level: full async-executor-activate: true

2.2 监听器的三种实现方式

Activiti 提供了三种方式来实现 TaskListener,各有适用场景:

  1. 类方式:实现 TaskListener 接口

    @Component public class ApprovalNotificationListener implements TaskListener { @Override public void notify(DelegateTask delegateTask) { // 通知逻辑实现 } }
  2. 表达式方式:使用 EL 表达式

    <activiti:taskListener event="create" expression="${notificationService.sendCreateNotification(task)}"/>
  3. 委托表达式:结合 Spring 容器

    <activiti:taskListener event="complete" delegateExpression="${approvalCompleteListener}"/>

对于 Spring Boot 项目,推荐使用委托表达式方式,因为它可以充分利用 Spring 的依赖注入功能,使代码更加清晰和可测试。

3. 审批流程自动抄送实现

自动抄送是审批流程中常见的需求,下面我们以实现请假审批流程为例,详细介绍实现方法。

3.1 动态确定抄送人

抄送人的确定通常基于业务规则,可能包括:

  • 申请人的直属领导
  • 部门HR
  • 相关项目负责人
  • 流程中指定的其他相关人员

我们可以通过流程变量和业务规则相结合的方式动态确定抄送人:

public void notify(DelegateTask delegateTask) { // 获取流程变量 String applicant = (String) delegateTask.getVariable("applicant"); String department = (String) delegateTask.getVariable("department"); // 业务规则确定抄送人 List<String> ccUsers = new ArrayList<>(); ccUsers.add(getDepartmentManager(department)); ccUsers.add(getHRForDepartment(department)); // 添加额外的抄送人(如果有) if (delegateTask.hasVariable("extraCCUsers")) { ccUsers.addAll((List<String>) delegateTask.getVariable("extraCCUsers")); } // 设置抄送人 delegateTask.setVariable("ccUsers", ccUsers); }

3.2 多通道通知集成

现代企业通常使用多种通知渠道,我们需要根据接收者的偏好选择合适的通知方式。以下是一个集成邮件和企业微信通知的示例:

@Service public class NotificationServiceImpl implements NotificationService { @Autowired private JavaMailSender mailSender; @Autowired private WeChatService weChatService; public void sendNotification(String userId, String message, NotificationType type) { UserPreference preference = getUserPreference(userId); switch (preference.getPreferredChannel()) { case EMAIL: sendEmail(preference.getEmail(), message); break; case WECHAT: weChatService.sendMessage(preference.getWeChatId(), message); break; case BOTH: sendEmail(preference.getEmail(), message); weChatService.sendMessage(preference.getWeChatId(), message); break; } } private void sendEmail(String to, String content) { SimpleMailMessage message = new SimpleMailMessage(); message.setTo(to); message.setSubject("审批流程通知"); message.setText(content); mailSender.send(message); } }

4. 性能优化与工程实践

在实际生产环境中使用 TaskListener 时,需要注意一些性能和安全方面的最佳实践。

4.1 避免常见陷阱

  1. 耗时操作异步化:不要在监听器中直接执行耗时操作(如调用外部API、复杂计算等),应该使用 Activiti 的异步执行器或消息队列。

    // 不推荐 - 同步调用外部服务 externalService.callSlowAPI(); // 推荐 - 异步处理 @Async public void handleTaskCompletion(String taskId) { // 耗时操作 }
  2. 事务边界管理:监听器中的操作通常在同一事务中执行,失败会导致整个操作回滚。需要仔细考虑事务边界。

  3. 异常处理:完善的异常处理机制可以防止单个监听器失败影响整个流程。

    try { // 业务逻辑 } catch (BusinessException e) { // 记录业务异常,不影响流程继续 log.error("Business error in listener", e); } catch (Exception e) { // 系统异常可能需要终止流程 throw new ActivitiException("Listener failed", e); }

4.2 性能优化技巧

  1. 批量处理:当需要处理大量任务时,考虑批量操作而非单个处理。

    // 批量添加抄送人 delegateTask.addCandidateUsers(batchUsers);
  2. 缓存利用:频繁访问的外部数据应该缓存。

    @Cacheable("managers") public String getDepartmentManager(String departmentId) { // 查询数据库或外部服务 }
  3. 懒加载:对于可能不需要的数据,采用懒加载策略。

5. 实战:请假审批全流程实现

让我们通过一个完整的请假审批流程示例,将前面介绍的概念和技术串联起来。

5.1 流程定义

首先,定义一个简单的请假审批流程BPMN:

<process id="leaveApproval" name="请假审批流程"> <startEvent id="start"/> <userTask id="applyLeave" name="申请请假"> <extensionElements> <activiti:taskListener event="create" delegateExpression="${leaveApplyListener}"/> </extensionElements> </userTask> <userTask id="managerApproval" name="经理审批"> <extensionElements> <activiti:taskListener event="complete" delegateExpression="${managerApprovalListener}"/> </extensionElements> </userTask> <endEvent id="end"/> <sequenceFlow sourceRef="start" targetRef="applyLeave"/> <sequenceFlow sourceRef="applyLeave" targetRef="managerApproval"/> <sequenceFlow sourceRef="managerApproval" targetRef="end"/> </process>

5.2 监听器实现

实现申请和审批两个关键监听器:

@Component("leaveApplyListener") public class LeaveApplyListener implements TaskListener { @Autowired private NotificationService notificationService; @Override public void notify(DelegateTask delegateTask) { // 设置默认审批人(直属经理) String applicant = (String) delegateTask.getVariable("applicant"); String manager = getManager(applicant); delegateTask.setAssignee(manager); // 设置抄送人(HR) List<String> ccUsers = Collections.singletonList(getDepartmentHR(applicant)); delegateTask.setVariable("ccUsers", ccUsers); // 发送通知 notificationService.notifyManager(manager, applicant); } } @Component("managerApprovalListener") public class ManagerApprovalListener implements TaskListener { @Override public void notify(DelegateTask delegateTask) { boolean approved = (boolean) delegateTask.getVariable("approved"); String applicant = (String) delegateTask.getVariable("applicant"); if (approved) { // 审批通过,更新考勤系统 updateAttendanceSystem(applicant, delegateTask.getVariable("leaveDays")); } // 无论是否通过,都通知申请人 notifyApplicant(applicant, approved); } }

5.3 流程测试与调试

编写测试用例验证流程:

@SpringBootTest public class LeaveApprovalTest { @Autowired private RuntimeService runtimeService; @Test public void testLeaveApprovalProcess() { Map<String, Object> variables = new HashMap<>(); variables.put("applicant", "employee1"); variables.put("leaveDays", 3); ProcessInstance instance = runtimeService .startProcessInstanceByKey("leaveApproval", variables); // 模拟经理审批 Task task = taskService.createTaskQuery() .processInstanceId(instance.getId()) .singleResult(); Map<String, Object> approvalVars = new HashMap<>(); approvalVars.put("approved", true); taskService.complete(task.getId(), approvalVars); // 验证流程结束 assertThat(runtimeService.createProcessInstanceQuery() .processInstanceId(instance.getId()) .count()).isEqualTo(0); } }

在实际项目中,除了单元测试外,还应该考虑集成测试和端到端测试,确保整个流程在各种场景下都能正常工作。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/23 23:11:19

告别硬编码!用SAP标准函数FREE_SELECTIONS_DIALOG,5分钟搞定动态查询弹窗

5分钟实现ABAP动态查询弹窗&#xff1a;FREE_SELECTIONS_DIALOG高阶实战 当我们需要在报表执行过程中临时弹出筛选窗口时&#xff0c;传统做法往往需要硬编码选择屏幕字段。这种开发方式不仅耗时耗力&#xff0c;后期维护更是噩梦。SAP标准函数FREE_SELECTIONS_DIALOG提供了一种…

作者头像 李华
网站建设 2026/4/23 23:07:24

用MATLAB的rand函数和蒙特卡洛法,快速画出你的六轴机器人工作空间(附完整代码)

蒙特卡洛法在六轴机器人工作空间可视化中的实战应用 第一次接触六轴机器人工作空间分析时&#xff0c;我被那些复杂的数学公式和理论推导吓退了。直到发现蒙特卡洛方法——这个用随机数就能解决问题的神奇工具&#xff0c;才让我真正开始享受机器人仿真的乐趣。本文将分享如何用…

作者头像 李华
网站建设 2026/4/23 23:04:19

MTK Filogic 630方案首秀:中兴E1630拆解看MT7916的升级点

MT7916芯片深度解析&#xff1a;Filogic 630方案的技术跃迁与市场前景 拆开中兴E1630路由器的那一刻&#xff0c;我意识到手中握着的不仅是台AX3000设备&#xff0c;更是联发科无线通信技术迭代的活体标本。作为首款搭载MT7916&#xff08;Filogic 630方案&#xff09;的消费级…

作者头像 李华
网站建设 2026/4/23 23:03:16

解密ExtractorSharp:游戏资源编辑新手指南与实战秘籍

解密ExtractorSharp&#xff1a;游戏资源编辑新手指南与实战秘籍 【免费下载链接】ExtractorSharp Game Resources Editor 项目地址: https://gitcode.com/gh_mirrors/ex/ExtractorSharp 还在为游戏资源修改而烦恼吗&#xff1f;想象一下&#xff0c;当别人在游戏中拥有…

作者头像 李华
网站建设 2026/4/23 23:02:16

ARM64 缓存指令实战:DC CIVAC 与 IC IVAU 在驱动开发中的协同应用

1. ARM64缓存指令基础&#xff1a;理解DC与IC的核心作用 在ARM64架构的驱动开发中&#xff0c;缓存管理就像交通指挥员协调车辆流动一样关键。DC&#xff08;Data Cache&#xff09;和IC&#xff08;Instruction Cache&#xff09;这两条指令&#xff0c;分别掌管着数据高速公路…

作者头像 李华