SpringBoot与Activiti6.0深度整合:构建企业级请假审批系统实战指南
在当今企业数字化转型浪潮中,业务流程自动化已成为提升运营效率的关键。本文将带您深入探索如何利用SpringBoot与Activiti6.0构建一个功能完备的请假审批系统,从环境搭建到生产部署,全程采用工程化实践方案。
1. 环境准备与基础配置
1.1 技术栈选型与依赖管理
现代Java企业应用开发中,SpringBoot已成为事实上的标准框架,而Activiti作为成熟的BPMN2.0流程引擎,二者的结合能够快速实现复杂业务流程的数字化。以下是核心依赖配置:
<dependencies> <!-- SpringBoot基础依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Activiti集成 --> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring-boot-starter-basic</artifactId> <version>6.0.0</version> </dependency> <!-- 数据库支持 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> </dependencies>1.2 数据库配置策略
Activiti引擎需要特定的数据库表结构支持,正确的初始化策略对系统稳定性至关重要。推荐采用以下配置组合:
| 配置项 | 推荐值 | 适用场景 |
|---|---|---|
| spring.activiti.database-schema-update | true | 开发环境 |
| spring.activiti.database-schema-update | false | 生产环境 |
| spring.activiti.history-level | full | 需要完整审计 |
# 开发环境配置示例 spring.datasource.url=jdbc:mysql://localhost:3306/activiti_demo spring.datasource.username=root spring.datasource.password=yourpassword spring.activiti.database-schema-update=true spring.activiti.history-level=full提示:生产环境务必关闭database-schema-update,避免意外修改表结构
2. 流程建模与部署
2.1 BPMN2.0流程设计规范
请假审批流程通常包含以下核心节点:
- 开始事件:流程触发点
- 用户任务:请假申请节点
- 排他网关:根据请假天数路由
- 用户任务:部门审批节点(条件:天数>3)
- 结束事件:流程终止点
<!-- 简化版BPMN定义示例 --> <process id="vacationProcess" name="请假审批流程"> <startEvent id="startEvent"/> <userTask id="applyTask" name="请假申请"/> <exclusiveGateway id="decisionGateway"/> <sequenceFlow sourceRef="startEvent" targetRef="applyTask"/> <sequenceFlow sourceRef="applyTask" targetRef="decisionGateway"/> </process>2.2 流程部署最佳实践
推荐采用资源目录自动部署方式,将流程定义文件放置在特定目录:
src/main/resources/processes/ ├── vacation-process.bpmn20.xml └── department-approval.bpmn20.xml通过RepositoryService进行动态部署验证:
@Autowired private RepositoryService repositoryService; public void deployProcess() { Deployment deployment = repositoryService.createDeployment() .addClasspathResource("processes/vacation-process.bpmn20.xml") .name("请假流程部署") .deploy(); logger.info("流程部署成功,ID: {}", deployment.getId()); }3. 系统集成与业务实现
3.1 用户体系整合方案
企业现有用户系统与Activiti身份管理的对接是关键挑战。推荐采用以下整合模式:
public void syncUserToActiviti(User user) { // 创建Activiti用户 User activitiUser = identityService.newUser(user.getId().toString()); activitiUser.setFirstName(user.getRealName()); activitiUser.setEmail(user.getEmail()); identityService.saveUser(activitiUser); // 同步角色关系 user.getRoles().forEach(role -> { Group group = identityService.createGroupQuery() .groupId(role.getId().toString()) .singleResult(); if (group == null) { group = identityService.newGroup(role.getId().toString()); group.setName(role.getName()); group.setType(role.getType()); identityService.saveGroup(group); } // 建立用户-组关系 identityService.createMembership( user.getId().toString(), role.getId().toString() ); }); }3.2 业务数据与流程数据分离设计
遵循流程数据与业务数据分离原则,设计数据库表结构:
请假申请表结构示例
| 字段名 | 类型 | 描述 |
|---|---|---|
| id | BIGINT | 主键 |
| process_instance_id | VARCHAR | 流程实例ID |
| applicant_id | BIGINT | 申请人ID |
| start_date | DATE | 开始日期 |
| end_date | DATE | 结束日期 |
| reason | VARCHAR | 请假原因 |
业务服务层实现流程启动逻辑:
public String startVacationProcess(VacationRequest request) { // 1. 保存业务数据 VacationApply apply = new VacationApply(); apply.setApplicantId(request.getUserId()); apply.setDays(request.getDays()); apply = vacationRepository.save(apply); // 2. 启动流程实例 Map<String, Object> variables = new HashMap<>(); variables.put("days", request.getDays()); variables.put("formId", apply.getId()); ProcessInstance instance = runtimeService.startProcessInstanceByKey( "vacationProcess", variables ); // 3. 关联业务与流程数据 apply.setProcessInstanceId(instance.getId()); vacationRepository.save(apply); return instance.getId(); }4. 核心功能实现细节
4.1 动态任务查询与分页
结合Spring Data JPA实现高效的任务查询:
public Page<TaskDTO> getPendingTasks(Long userId, int page, int size) { // 获取用户所在角色组 List<String> groupIds = identityService.createGroupQuery() .groupMember(userId.toString()) .list() .stream() .map(Group::getId) .collect(Collectors.toList()); // 构建任务查询 TaskQuery query = taskService.createTaskQuery() .taskCandidateOrAssigned(userId.toString()) .taskCandidateGroupIn(groupIds) .orderByTaskCreateTime() .desc(); // 分页处理 List<Task> tasks = query.listPage((page-1)*size, size); long total = query.count(); // 转换为DTO List<TaskDTO> dtos = tasks.stream() .map(this::convertToDTO) .collect(Collectors.toList()); return new PageImpl<>(dtos, PageRequest.of(page-1, size), total); }4.2 审批操作完整实现
审批服务需要处理多种业务场景:
@Transactional public void processApproval(ApprovalRequest request) { // 1. 验证任务有效性 Task task = taskService.createTaskQuery() .taskId(request.getTaskId()) .singleResult(); if (task == null) { throw new BusinessException("任务不存在或已完成"); } // 2. 保存审批意见 Approval approval = new Approval(); approval.setTaskId(task.getId()); approval.setComment(request.getComment()); approval.setResult(request.getResult()); approvalRepository.save(approval); // 3. 处理审批结果 Map<String, Object> variables = new HashMap<>(); variables.put("approvalResult", request.getResult()); if (request.getResult() == ApprovalResult.REJECT) { // 拒绝时终止流程 runtimeService.deleteProcessInstance( task.getProcessInstanceId(), "审批拒绝" ); } else { // 认领并完成任务 taskService.claim(task.getId(), request.getUserId().toString()); taskService.complete(task.getId(), variables); } }4.3 流程可视化实现
生成带高亮的流程图需要特殊处理:
public void generateProcessImage(String processInstanceId, HttpServletResponse response) { try { // 获取BPMN模型 ProcessInstance instance = runtimeService.createProcessInstanceQuery() .processInstanceId(processInstanceId) .singleResult(); BpmnModel model = repositoryService.getBpmnModel( instance.getProcessDefinitionId() ); // 获取当前活动节点 List<String> activeIds = runtimeService.getActiveActivityIds(processInstanceId); // 生成图像流 InputStream imageStream = processEngine.getProcessEngineConfiguration() .getProcessDiagramGenerator() .generateDiagram( model, "png", activeIds, Collections.emptyList(), "宋体", "宋体", "宋体", null, 1.0 ); // 输出响应 response.setContentType("image/png"); IOUtils.copy(imageStream, response.getOutputStream()); } catch (Exception e) { logger.error("流程图生成失败", e); throw new RuntimeException("流程图生成失败"); } }5. 生产环境注意事项
5.1 性能优化建议
针对高并发场景的配置调整:
# Activiti异步执行器配置 spring.activiti.async-executor-activate=true spring.activiti.async-executor-core-pool-size=10 spring.activiti.async-executor-max-pool-size=50 spring.activiti.async-executor-queue-capacity=10005.2 监控与日志方案
集成Spring Boot Actuator监控端点:
| 端点 | 功能描述 |
|---|---|
| /actuator/activiti | 流程引擎状态 |
| /actuator/processes | 已部署流程 |
| /actuator/tasks | 运行中任务统计 |
日志记录关键操作:
@Aspect @Component public class ActivitiLogAspect { @AfterReturning( pointcut = "execution(* org.activiti.engine..*.*(..))", returning = "result" ) public void logActivitiOperation(JoinPoint jp, Object result) { String operation = jp.getSignature().getName(); logger.info("Activiti操作 {} 执行成功,结果: {}", operation, result); } }5.3 异常处理机制
统一异常处理增强系统健壮性:
@ControllerAdvice public class ActivitiExceptionHandler { @ExceptionHandler(ActivitiException.class) public ResponseEntity<ErrorResponse> handleActivitiError(ActivitiException e) { ErrorResponse response = new ErrorResponse(); response.setCode("ACTIVITI_ERROR"); response.setMessage(e.getMessage()); return ResponseEntity .status(HttpStatus.INTERNAL_SERVER_ERROR) .body(response); } @ExceptionHandler(ActivitiObjectNotFoundException.class) public ResponseEntity<ErrorResponse> handleNotFound(ActivitiObjectNotFoundException e) { return ResponseEntity .status(HttpStatus.NOT_FOUND) .body(new ErrorResponse("PROCESS_NOT_FOUND", e.getMessage())); } }在项目实际部署中,我们发现流程定义的热更新是个常见需求。通过扩展RepositoryService可以实现不重启服务的流程版本更新,这对7×24小时运行的系统尤为重要。另外,建议对历史流程数据定期归档,避免核心表数据膨胀影响查询性能。