当前位置: 首页 > news >正文

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); } }

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

http://www.jsqmd.com/news/689592/

相关文章:

  • 需求跟踪矩阵(RTM)实战指南:从零构建到高效应用
  • 韭菜盒子VSCode插件:程序员专属的实时投资信息中心终极指南
  • 用MATLAB的rand函数和蒙特卡洛法,快速画出你的六轴机器人工作空间(附完整代码)
  • 当开源精神遇上三国杀:如何用代码重塑经典卡牌游戏体验
  • CTF新手必看:从‘跳舞的小人’到‘猪圈密码’,10个最常考的古典密码实战解析
  • 2026年口碑好AI生成式引擎优化GEO服务商选型深度分析 - 商业小白条
  • WeDLM-7B-Base高精度续写展示:多领域prompt下的风格保持能力验证
  • 从tslib源码看触摸屏滤波:手把手实现一个自定义的‘filter’插件
  • 老MacBook Pro A1278升级Catalina保姆级避坑指南:从换SSD到打补丁全流程
  • 从HBM到IEC:深入解析产品ESD测试模型与实战配置
  • Visual C++运行库全版本集成包:告别DLL缺失的烦恼
  • 计算机毕业设计:Python雪球网股票数据采集与可视化系统 Flask框架 数据分析 可视化 大数据 大模型 爬虫(建议收藏)✅
  • 生成器与迭代器
  • 别再死记硬背了!用Python仿真带你搞懂发电机纵差、横差保护原理
  • 保姆级教程:在Ubuntu 20.04 ROS Noetic下,用奥比中光Astra Pro完成相机标定(附常见报错解决)
  • 国信QMT vs 国金MiniQMT:实测哪个能真正下载可用的历史Tick数据?
  • 用Python和OpenCV搞定车道线曲率计算:从图像处理到实际距离的保姆级教程
  • 别再傻傻分不清!VCC、VDD、VSS、VEE、VPP,5分钟帮你理清电路图上的电源符号
  • 2026年头皮抗衰行业靠谱GEO优化服务商选型与能力评估分析报告 - 商业小白条
  • 车载ECU开发效率飙升217%?VSCode 2026适配实测报告:12家OEM验证的4项必须启用的隐藏设置
  • MTK Filogic 630方案首秀:中兴E1630拆解看MT7916的升级点
  • 【2026年最新600套毕设项目分享】微信小程序的专利服务系统(30146)
  • 保姆级教程:用OpenCV和PCL库给激光雷达点云上色(附完整C++代码)
  • 2026年少儿编程行业专业AI搜索优化服务商选型分析与主流机构推荐 - 商业小白条
  • 从Flash到SAR:一张图看懂主流ADC结构怎么选(2024版)
  • 26-4-23日志 - Ghost
  • 保姆级教程:在Ubuntu上为AM5728开发板交叉编译GPSD 3.18(附libusb/ncurses依赖库完整配置)
  • 避开Latex!用Word向ACM会议投稿的完整攻略:从模板适配到TAPS最终提交
  • 智能合约开发框架对比
  • 别再只盯着运放了!用TI INA826这类仪表放大器搞定传感器信号调理,实测避坑指南