news 2026/4/16 11:54:55

别再手动改状态字段了!用Spring Boot + Activiti7快速搞定请假审批流程(附完整代码)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
别再手动改状态字段了!用Spring Boot + Activiti7快速搞定请假审批流程(附完整代码)

Spring Boot + Activiti7:从零构建企业级审批工作流引擎

1. 传统状态字段管理的困境与工作流引擎的价值

在传统OA/ERP系统开发中,我们经常使用状态字段(如status)来跟踪业务流程。比如请假审批流程,可能会设计这样的状态流转:

public class LeaveRequest { private Integer status; //0-草稿 1-提交 2-部门审批通过 3-人事归档 4-驳回 }

这种实现方式存在明显缺陷:

  • 硬编码流程逻辑:每次流程变更都需要修改代码并重新部署
  • 缺乏可视化监控:无法直观查看流程当前节点和历史轨迹
  • 权限控制复杂:需要手动编写不同状态下的操作权限校验
  • 扩展性差:并行审批、会签等复杂场景实现困难

工作流引擎的核心优势

对比维度状态字段方式Activiti工作流引擎
流程可视化图形化流程设计器
流程变更需改代码修改流程定义文件无需重启
历史追踪需自行实现自动记录完整流程历史
复杂流程支持实现困难内置并行网关、会签等特性
系统耦合度低(流程与业务解耦)

2. Activiti7核心架构与Spring Boot集成

2.1 Activiti7核心服务

Activiti7通过六大服务接口提供完整的工作流能力:

// 流程仓库服务(部署、查询流程定义) RepositoryService repositoryService = processEngine.getRepositoryService(); // 运行时服务(启动、查询流程实例) RuntimeService runtimeService = processEngine.getRuntimeService(); // 任务服务(查询、办理任务) TaskService taskService = processEngine.getTaskService(); // 历史服务(查询历史数据) HistoryService historyService = processEngine.getHistoryService(); // 表单服务(处理动态表单) FormService formService = processEngine.getFormService(); // 管理服务(系统管理功能) ManagementService managementService = processEngine.getManagementService();

2.2 Spring Boot集成配置

Maven依赖配置

<dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring-boot-starter</artifactId> <version>7.1.0.M6</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency>

application.yml配置

spring: activiti: database-schema-update: true # 自动更新数据库结构 history-level: full # 完整历史记录 check-process-definitions: true # 启动时检查流程定义 async-executor-activate: false # 关闭异步执行器(开发环境)

3. 请假审批流程实战开发

3.1 流程定义设计

使用Activiti Modeler设计请假审批流程:

  1. 开始事件→ 2.填写请假单→ 3.部门审批
    • 4a.人事归档(≤3天)
    • 4b.总经理审批→ 5.人事归档(>3天)→ 6.结束事件

对应的BPMN XML核心部分:

<process id="leaveApproval" name="请假审批流程"> <startEvent id="startEvent"/> <userTask id="fillLeave" name="填写请假单" activiti:assignee="${applicant}"/> <userTask id="deptApprove" name="部门经理审批" activiti:candidateGroups="deptManager"/> <exclusiveGateway id="decisionGateway"/> <sequenceFlow id="flow1" sourceRef="startEvent" targetRef="fillLeave"/> <sequenceFlow id="flow2" sourceRef="fillLeave" targetRef="deptApprove"/> <sequenceFlow id="flow3" sourceRef="deptApprove" targetRef="decisionGateway"/> <sequenceFlow id="flow4" sourceRef="decisionGateway" targetRef="hrArchive"> <conditionExpression xsi:type="tFormalExpression"> ${days <= 3} </conditionExpression> </sequenceFlow> <sequenceFlow id="flow5" sourceRef="decisionGateway" targetRef="ceoApprove"> <conditionExpression xsi:type="tFormalExpression"> ${days > 3} </conditionExpression> </sequenceFlow> <userTask id="ceoApprove" name="总经理审批" activiti:candidateGroups="ceo"/> <sequenceFlow id="flow6" sourceRef="ceoApprove" targetRef="hrArchive"/> <userTask id="hrArchive" name="人事归档" activiti:candidateGroups="hr"/> <sequenceFlow id="flow7" sourceRef="hrArchive" targetRef="endEvent"/> <endEvent id="endEvent"/> </process>

3.2 流程部署与启动

部署流程定义

@Service public class ProcessDeployer { @Autowired private RepositoryService repositoryService; public void deployProcess() { Deployment deployment = repositoryService.createDeployment() .addClasspathResource("processes/leave-approval.bpmn20.xml") .name("请假审批流程") .deploy(); logger.info("流程部署成功,ID: {}", deployment.getId()); } }

启动流程实例

public class LeaveService { @Autowired private RuntimeService runtimeService; public String startLeaveProcess(LeaveDTO leaveDTO) { // 设置流程变量 Map<String, Object> variables = new HashMap<>(); variables.put("days", leaveDTO.getDays()); variables.put("reason", leaveDTO.getReason()); variables.put("applicant", leaveDTO.getApplicant()); // 关联业务Key ProcessInstance instance = runtimeService.startProcessInstanceByKey( "leaveApproval", leaveDTO.getLeaveId(), variables ); return instance.getId(); } }

3.3 任务处理实现

查询待办任务

public List<TaskDTO> getTasksByUser(String userId, String group) { // 个人任务 List<Task> personalTasks = taskService.createTaskQuery() .taskAssignee(userId) .list(); // 组任务 List<Task> groupTasks = taskService.createTaskQuery() .taskCandidateUser(userId) .taskCandidateGroup(group) .list(); return Stream.concat(personalTasks.stream(), groupTasks.stream()) .map(this::convertToDTO) .collect(Collectors.toList()); }

办理任务

@Transactional public void completeTask(String taskId, String userId, boolean approved, String comment) { // 校验任务权限 Task task = taskService.createTaskQuery() .taskId(taskId) .taskCandidateOrAssigned(userId) .singleResult(); if (task == null) { throw new BusinessException("无权操作该任务"); } // 拾取组任务 if (task.getAssignee() == null) { taskService.claim(taskId, userId); } // 添加批注 if (StringUtils.isNotBlank(comment)) { taskService.addComment(taskId, task.getProcessInstanceId(), comment); } // 设置审批结果变量 Map<String, Object> variables = new HashMap<>(); variables.put("approved", approved); // 完成任务 taskService.complete(taskId, variables); }

4. 高级特性实现

4.1 动态任务分配

通过监听器实现动态任务分配:

public class DeptManagerAssignmentListener implements TaskListener { @Override public void notify(DelegateTask delegateTask) { // 从业务系统获取部门经理 String deptId = (String) delegateTask.getVariable("deptId"); String manager = departmentService.getManagerByDept(deptId); // 设置任务负责人 delegateTask.setAssignee(manager); // 设置任务到期时间(3个工作日内审批) delegateTask.setDueDate(DateUtils.addBusinessDays(new Date(), 3)); } }

在BPMN中配置:

<userTask id="deptApprove" name="部门审批"> <extensionElements> <activiti:taskListener event="create" class="com.example.workflow.DeptManagerAssignmentListener"/> </extensionElements> </userTask>

4.2 会签实现

多人并行会签场景配置:

<userTask id="multiApprove" name="多部门会签"> <extensionElements> <activiti:multiInstanceLoopCharacteristics isSequential="false" activiti:collection="${approvers}" activiti:elementVariable="approver"> <activiti:completionCondition> ${nrOfCompletedInstances/nrOfInstances >= 0.6} </activiti:completionCondition> </activiti:multiInstanceLoopCharacteristics> </extensionElements> </userTask>

4.3 业务关联与历史查询

业务关联查询

public LeaveVO getLeaveWithProcess(String leaveId) { // 查询业务数据 Leave leave = leaveRepository.findById(leaveId); // 查询关联的流程实例 ProcessInstance instance = runtimeService.createProcessInstanceQuery() .processInstanceBusinessKey(leaveId) .singleResult(); // 查询审批历史 List<Comment> comments = taskService.getProcessInstanceComments(instance.getId()); return assembleVO(leave, instance, comments); }

历史流程查询

public List<HistoricProcessInstance> getFinishedProcesses(String applicant) { return historyService.createHistoricProcessInstanceQuery() .processDefinitionKey("leaveApproval") .variableValueEquals("applicant", applicant) .finished() .orderByProcessInstanceEndTime().desc() .list(); }

5. 生产环境最佳实践

5.1 性能优化方案

数据库优化

-- 创建常用查询索引 CREATE INDEX idx_act_ru_task_procinst ON act_ru_task(proc_inst_id_); CREATE INDEX idx_act_ru_execution_parent ON act_ru_execution(parent_id_);

异步任务配置

spring: activiti: async-executor-activate: true async-executor-thread-pool-size: 10 async-failed-job-wait-time: 300 # 失败重试间隔(秒)

5.2 监控与运维

Spring Boot Actuator端点

/actuator/activiti # 流程引擎状态 /actuator/processes # 流程定义列表 /actuator/tasks # 运行中任务统计

自定义监控看板

@RestController @RequestMapping("/monitor") public class WorkflowMonitorController { @GetMapping("/stats") public WorkflowStats getStats() { long running = runtimeService.createProcessInstanceQuery().count(); long completed = historyService.createHistoricProcessInstanceQuery() .finished().count(); return new WorkflowStats(running, completed); } }

5.3 异常处理策略

常见异常处理

@ControllerAdvice public class WorkflowExceptionHandler { @ExceptionHandler(ActivitiObjectNotFoundException.class) public ResponseEntity<String> handleNotFound(ActivitiObjectNotFoundException ex) { return ResponseEntity.status(404).body("流程资源不存在"); } @ExceptionHandler(ActivitiTaskAlreadyClaimedException.class) public ResponseEntity<String> handleTaskClaimed(ActivitiTaskAlreadyClaimedException ex) { return ResponseEntity.status(409).body("任务已被他人领取"); } }

事务边界控制

@Transactional(propagation = Propagation.REQUIRES_NEW) public void completeTaskWithTransaction(String taskId) { // 任务操作与业务更新放在同一事务 taskService.complete(taskId); businessService.updateStatus(taskId); }

6. 扩展与集成方案

6.1 与消息系统集成

审批通知实现

public class TaskNotificationListener implements TaskListener { @Autowired private MessageQueueProducer messageProducer; @Override public void notify(DelegateTask delegateTask) { TaskNotificationMessage message = new TaskNotificationMessage( delegateTask.getId(), delegateTask.getName(), delegateTask.getAssignee() ); messageProducer.send("task.notification", message); } }

6.2 与规则引擎集成

public class ApprovalRuleEvaluator { @Autowired private KieContainer kieContainer; public boolean evaluate(LeaveRequest request) { KieSession kieSession = kieContainer.newKieSession(); kieSession.insert(request); kieSession.fireAllRules(); return request.isApproved(); } }

6.3 微服务架构下的实现

跨服务流程调用

@FeignClient(name = "hr-service") public interface HRServiceClient { @PostMapping("/leave/approve") void approveLeave(@RequestBody ApproveDTO dto); } public class HRTaskListener implements JavaDelegate { @Autowired private HRServiceClient hrClient; @Override public void execute(DelegateExecution execution) { String leaveId = (String) execution.getVariable("leaveId"); boolean approved = (boolean) execution.getVariable("approved"); hrClient.approveLeave(new ApproveDTO(leaveId, approved)); } }

在实际项目中引入工作流引擎后,某企业的请假审批流程平均处理时间从原来的3.2天缩短到1.5天,流程异常率下降68%。特别是在应对突发性流程变更时,开发团队不再需要频繁发布系统更新,只需通过调整流程定义即可快速响应业务变化。

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

OBS Multi RTMP:如何一键开启多平台直播新时代

OBS Multi RTMP&#xff1a;如何一键开启多平台直播新时代 【免费下载链接】obs-multi-rtmp OBS複数サイト同時配信プラグイン 项目地址: https://gitcode.com/gh_mirrors/ob/obs-multi-rtmp 你是否曾经为了在不同平台直播而不得不重复配置OBS&#xff1f;或者因为切换平…

作者头像 李华
网站建设 2026/4/16 11:53:34

UniApp安卓MQTT集成实战:原生插件与WebSocket方案深度对比

1. 为什么要在UniApp中集成MQTT&#xff1f; MQTT协议作为物联网领域的"普通话"&#xff0c;凭借其轻量级、低功耗、高实时性的特点&#xff0c;已经成为智能硬件通信的事实标准。我在开发智能家居控制系统时&#xff0c;就遇到过这样的场景&#xff1a;需要同时控制…

作者头像 李华
网站建设 2026/4/16 11:52:21

Go语言的sync-atomic.Value原子值与接口类型在并发存储中的类型安全

Go语言中的sync/atomic.Value原子值与接口类型在并发存储中的类型安全 在并发编程中&#xff0c;类型安全是一个至关重要的议题。Go语言的sync/atomic.Value提供了一种高效且线程安全的存储机制&#xff0c;尤其适合在多个goroutine之间共享数据。当与空接口类型&#xff08;i…

作者头像 李华
网站建设 2026/4/16 11:52:20

别再死记硬背LLC公式了!手把手教你用K值和Q值搞定电源设计

别再死记硬背LLC公式了&#xff01;手把手教你用K值和Q值搞定电源设计 作为一名电源工程师&#xff0c;你是否曾在设计LLC谐振变换器时&#xff0c;面对一堆复杂的公式和曲线图感到无从下手&#xff1f;K值和Q值这两个关键参数&#xff0c;常常让工程师们既爱又恨——它们决定了…

作者头像 李华
网站建设 2026/4/16 11:51:11

Spring Boot 异步调用性能优化

Spring Boot 异步调用性能优化 在现代高并发应用中&#xff0c;性能优化是开发者必须面对的挑战之一。Spring Boot作为Java生态中广泛使用的框架&#xff0c;其异步调用能力能够显著提升系统吞吐量&#xff0c;但如何高效利用这一特性仍需深入探讨。本文将从线程池配置、异步方…

作者头像 李华
网站建设 2026/4/16 11:50:19

为什么IPXWrapper能让经典游戏在Windows 11上重获联机新生?

为什么IPXWrapper能让经典游戏在Windows 11上重获联机新生&#xff1f; 【免费下载链接】ipxwrapper 项目地址: https://gitcode.com/gh_mirrors/ip/ipxwrapper 在现代Windows系统中重温《红色警戒》、《星际争霸》、《暗黑破坏神2》等经典游戏的联机乐趣&#xff0c;I…

作者头像 李华