保姆级教程:在RuoYi-Vue-Pro项目中,从零搭建一个完整的请假审批工作流(Flowable实战)
从零构建RuoYi-Vue-Pro请假审批工作流全流程实战1. 工作流开发环境准备在开始构建请假审批工作流之前我们需要确保开发环境配置正确。RuoYi-Vue-Pro作为一款基于Spring Boot和Vue.js的前后端分离快速开发框架已经内置了对Flowable工作流引擎的支持。基础环境要求JDK 1.8Maven 3.5MySQL 5.7Redis 5.0Node.js 12首先我们需要在项目中引入Flowable相关依赖。在pom.xml中添加以下配置!-- Flowable工作流引擎 -- dependency groupIdorg.flowable/groupId artifactIdflowable-spring-boot-starter/artifactId version6.7.2/version /dependency数据库配置方面Flowable会自动创建所需的表结构。在application.yml中配置数据源spring: datasource: url: jdbc:mysql://localhost:3306/ruoyi_flowable?useUnicodetruecharacterEncodingutf8zeroDateTimeBehaviorconvertToNulluseSSLtrueserverTimezoneGMT%2B8 username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.DriverFlowable的表结构主要分为以下几类ACT_RE_*: 存储流程定义和静态资源ACT_RU_*: 存储运行时流程实例数据ACT_HI_*: 存储历史流程数据ACT_ID_*: 存储身份信息2. 请假表单设计与实现请假审批流程的第一步是设计请假表单。在RuoYi-Vue-Pro中我们可以使用内置的表单设计器或自定义表单。数据库表设计CREATE TABLE bpm_oa_leave ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键ID, user_id bigint(20) NOT NULL COMMENT 申请人ID, dept_id bigint(20) DEFAULT NULL COMMENT 部门ID, leave_type tinyint(4) NOT NULL COMMENT 请假类型(1事假 2病假 3年假 4调休), reason varchar(200) NOT NULL COMMENT 请假原因, start_time datetime NOT NULL COMMENT 开始时间, end_time datetime NOT NULL COMMENT 结束时间, duration decimal(10,1) NOT NULL COMMENT 请假时长(天), status tinyint(4) DEFAULT 0 COMMENT 状态(0待提交 1审批中 2已通过 3已拒绝), create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENTOA请假申请表;前端表单实现template el-form refform :modelform :rulesrules label-width100px el-form-item label请假类型 propleaveType el-select v-modelform.leaveType placeholder请选择请假类型 el-option label事假 :value1/el-option el-option label病假 :value2/el-option el-option label年假 :value3/el-option el-option label调休 :value4/el-option /el-select /el-form-item el-form-item label开始时间 propstartTime el-date-picker v-modelform.startTime typedatetime placeholder选择开始时间 /el-date-picker /el-form-item el-form-item label结束时间 propendTime el-date-picker v-modelform.endTime typedatetime placeholder选择结束时间 /el-date-picker /el-form-item el-form-item label请假原因 propreason el-input typetextarea :rows3 placeholder请输入请假原因 v-modelform.reason /el-input /el-form-item /el-form /template3. 流程模型设计与配置在RuoYi-Vue-Pro中我们可以通过内置的Flowable Modeler进行可视化流程设计。以下是请假审批流程的关键节点设计开始节点流程起点关联请假表单部门领导审批第一个审批环节HR审批第二个审批环节结束节点流程终点BPMN XML核心配置process idleave_approval name请假审批流程 isExecutabletrue startEvent idstartEvent name开始 extensionElements flowable:formProperty idleaveType name请假类型 typeenum requiredtrue flowable:value id1 name事假/ flowable:value id2 name病假/ flowable:value id3 name年假/ flowable:value id4 name调休/ /flowable:formProperty /extensionElements /startEvent userTask iddeptLeaderAudit name部门领导审批 extensionElements flowable:taskListener eventcreate classcom.ruoyi.flowable.listener.LeaveTaskListener/ /extensionElements /userTask userTask idhrAudit nameHR审批 extensionElements flowable:taskListener eventcreate classcom.ruoyi.flowable.listener.LeaveTaskListener/ /extensionElements /userTask endEvent idendEvent name结束/ sequenceFlow idflow1 sourceRefstartEvent targetRefdeptLeaderAudit/ sequenceFlow idflow2 sourceRefdeptLeaderAudit targetRefhrAudit/ sequenceFlow idflow3 sourceRefhrAudit targetRefendEvent/ /process任务分配规则配置在RuoYi-Vue-Pro中可以通过bpm_task_assign_rule表配置任务分配规则。例如部门领导审批任务可以分配给申请人的部门领导// 在流程部署时设置任务分配规则 BpmTaskAssignRule rule new BpmTaskAssignRule(); rule.setModelId(modelId); rule.setProcessDefinitionId(processDefinitionId); rule.setTaskDefinitionKey(deptLeaderAudit); rule.setType(BpmTaskAssignRuleTypeEnum.DEPT_LEADER.getType()); rule.setOptions(Collections.singletonList(leaveApply.getDeptId().toString())); bpmTaskAssignRuleService.createTaskAssignRule(rule);4. 流程部署与实例启动设计好流程模型后我们需要将其部署到Flowable引擎中并实现流程实例的启动逻辑。流程部署代码实现public String deployProcess(String modelId) { // 获取模型数据 Model modelData repositoryService.getModel(modelId); byte[] bpmnBytes repositoryService.getModelEditorSource(modelData.getId()); // 部署流程 Deployment deployment repositoryService.createDeployment() .name(modelData.getName()) .key(modelData.getKey()) .addBytes(modelData.getKey() .bpmn20.xml, bpmnBytes) .deploy(); // 设置流程分类 ProcessDefinition processDefinition repositoryService.createProcessDefinitionQuery() .deploymentId(deployment.getId()) .singleResult(); repositoryService.setProcessDefinitionCategory(processDefinition.getId(), modelData.getCategory()); return processDefinition.getId(); }启动流程实例public String startLeaveProcess(LeaveApplyDTO dto) { // 1. 保存请假申请 LeaveApply apply convertDTOToEntity(dto); leaveApplyMapper.insert(apply); // 2. 设置流程变量 MapString, Object variables new HashMap(); variables.put(leaveId, apply.getId()); variables.put(userId, apply.getUserId()); variables.put(deptId, apply.getDeptId()); variables.put(leaveType, apply.getLeaveType()); variables.put(startTime, apply.getStartTime()); variables.put(endTime, apply.getEndTime()); variables.put(reason, apply.getReason()); // 3. 启动流程实例 ProcessInstance instance runtimeService.startProcessInstanceByKey( leave_approval, leave_ apply.getId(), variables); // 4. 更新请假申请状态 apply.setProcessInstanceId(instance.getId()); apply.setStatus(1); // 审批中 leaveApplyMapper.updateById(apply); return instance.getId(); }5. 审批功能实现与任务处理审批功能是工作流系统的核心我们需要实现任务查询、审批通过和审批拒绝等功能。待办任务查询public PageResultTodoTaskVO getTodoTasks(TodoTaskQuery query) { // 1. 查询Flowable任务 TaskQuery taskQuery taskService.createTaskQuery() .taskAssignee(query.getUserId().toString()) .active() .orderByTaskCreateTime() .desc(); // 2. 分页查询 ListTask tasks taskQuery.listPage( query.getPageSize() * (query.getPageNum() - 1), query.getPageSize()); // 3. 转换为VO并补充业务数据 ListTodoTaskVO vos tasks.stream().map(task - { TodoTaskVO vo new TodoTaskVO(); vo.setTaskId(task.getId()); vo.setTaskName(task.getName()); vo.setProcessInstanceId(task.getProcessInstanceId()); vo.setCreateTime(task.getCreateTime()); // 查询业务数据 ProcessInstance instance runtimeService.createProcessInstanceQuery() .processInstanceId(task.getProcessInstanceId()) .singleResult(); LeaveApply apply leaveApplyMapper.selectOne( new LambdaQueryWrapperLeaveApply() .eq(LeaveApply::getProcessInstanceId, instance.getBusinessKey())); vo.setBusinessKey(instance.getBusinessKey()); vo.setLeaveType(apply.getLeaveType()); vo.setStartTime(apply.getStartTime()); vo.setEndTime(apply.getEndTime()); vo.setReason(apply.getReason()); return vo; }).collect(Collectors.toList()); return new PageResult( vos, taskQuery.count(), query.getPageNum(), query.getPageSize()); }审批通过处理Transactional public void approveTask(TaskApproveDTO dto) { // 1. 查询任务 Task task taskService.createTaskQuery() .taskId(dto.getTaskId()) .singleResult(); if (task null) { throw new RuntimeException(任务不存在或已完成); } // 2. 完成任务 MapString, Object variables new HashMap(); variables.put(approved, true); variables.put(comment, dto.getComment()); taskService.complete(task.getId(), variables); // 3. 记录审批日志 TaskComment comment new TaskComment(); comment.setTaskId(task.getId()); comment.setUserId(dto.getUserId()); comment.setType(approve); comment.setContent(dto.getComment()); comment.setFullMessage(审批通过 dto.getComment()); taskCommentMapper.insert(comment); // 4. 如果是最后一个审批节点更新请假状态 if (isProcessEnded(task.getProcessInstanceId())) { LeaveApply apply leaveApplyMapper.selectOne( new LambdaQueryWrapperLeaveApply() .eq(LeaveApply::getProcessInstanceId, task.getProcessInstanceId())); apply.setStatus(2); // 已通过 leaveApplyMapper.updateById(apply); } }审批拒绝处理Transactional public void rejectTask(TaskRejectDTO dto) { // 1. 查询任务 Task task taskService.createTaskQuery() .taskId(dto.getTaskId()) .singleResult(); if (task null) { throw new RuntimeException(任务不存在或已完成); } // 2. 设置拒绝变量并完成任务 MapString, Object variables new HashMap(); variables.put(approved, false); variables.put(comment, dto.getComment()); taskService.complete(task.getId(), variables); // 3. 记录审批日志 TaskComment comment new TaskComment(); comment.setTaskId(task.getId()); comment.setUserId(dto.getUserId()); comment.setType(reject); comment.setContent(dto.getComment()); comment.setFullMessage(审批拒绝 dto.getComment()); taskCommentMapper.insert(comment); // 4. 更新请假状态 LeaveApply apply leaveApplyMapper.selectOne( new LambdaQueryWrapperLeaveApply() .eq(LeaveApply::getProcessInstanceId, task.getProcessInstanceId())); apply.setStatus(3); // 已拒绝 leaveApplyMapper.updateById(apply); }6. 流程监控与历史数据查询完善的流程监控和历史数据查询功能对于系统运维和数据分析至关重要。运行中流程监控public PageResultRunningProcessVO getRunningProcesses(ProcessQuery query) { // 1. 查询运行中的流程实例 ProcessInstanceQuery instanceQuery runtimeService.createProcessInstanceQuery() .orderByProcessInstanceStartTime() .desc(); if (StringUtils.isNotBlank(query.getProcessDefinitionKey())) { instanceQuery.processDefinitionKey(query.getProcessDefinitionKey()); } // 2. 分页查询 ListProcessInstance instances instanceQuery.listPage( query.getPageSize() * (query.getPageNum() - 1), query.getPageSize()); // 3. 转换为VO并补充业务数据 ListRunningProcessVO vos instances.stream().map(instance - { RunningProcessVO vo new RunningProcessVO(); vo.setProcessInstanceId(instance.getId()); vo.setProcessDefinitionId(instance.getProcessDefinitionId()); vo.setStartTime(instance.getStartTime()); vo.setStartUserId(instance.getStartUserId()); // 查询当前任务 ListTask tasks taskService.createTaskQuery() .processInstanceId(instance.getId()) .list(); vo.setCurrentTasks(tasks.stream().map(Task::getName).collect(Collectors.toList())); // 查询业务数据 LeaveApply apply leaveApplyMapper.selectOne( new LambdaQueryWrapperLeaveApply() .eq(LeaveApply::getProcessInstanceId, instance.getId())); if (apply ! null) { vo.setBusinessKey(apply.getId().toString()); vo.setLeaveType(apply.getLeaveType()); vo.setApplicant(apply.getUserId()); } return vo; }).collect(Collectors.toList()); return new PageResult( vos, instanceQuery.count(), query.getPageNum(), query.getPageSize()); }历史流程查询public PageResultHistoricProcessVO getHistoricProcesses(ProcessQuery query) { // 1. 查询历史流程实例 HistoricProcessInstanceQuery historicQuery historyService.createHistoricProcessInstanceQuery() .orderByProcessInstanceEndTime() .desc(); if (StringUtils.isNotBlank(query.getProcessDefinitionKey())) { historicQuery.processDefinitionKey(query.getProcessDefinitionKey()); } // 2. 分页查询 ListHistoricProcessInstance instances historicQuery.listPage( query.getPageSize() * (query.getPageNum() - 1), query.getPageSize()); // 3. 转换为VO并补充业务数据 ListHistoricProcessVO vos instances.stream().map(instance - { HistoricProcessVO vo new HistoricProcessVO(); vo.setProcessInstanceId(instance.getId()); vo.setProcessDefinitionId(instance.getProcessDefinitionId()); vo.setStartTime(instance.getStartTime()); vo.setEndTime(instance.getEndTime()); vo.setDuration(instance.getDurationInMillis()); vo.setStartUserId(instance.getStartUserId()); // 查询业务数据 LeaveApply apply leaveApplyMapper.selectOne( new LambdaQueryWrapperLeaveApply() .eq(LeaveApply::getProcessInstanceId, instance.getId())); if (apply ! null) { vo.setBusinessKey(apply.getId().toString()); vo.setLeaveType(apply.getLeaveType()); vo.setApplicant(apply.getUserId()); vo.setStatus(apply.getStatus()); } return vo; }).collect(Collectors.toList()); return new PageResult( vos, historicQuery.count(), query.getPageNum(), query.getPageSize()); }流程轨迹展示public ProcessTraceVO getProcessTrace(String processInstanceId) { ProcessTraceVO traceVO new ProcessTraceVO(); // 1. 获取流程定义 HistoricProcessInstance instance historyService.createHistoricProcessInstanceQuery() .processInstanceId(processInstanceId) .singleResult(); BpmnModel bpmnModel repositoryService.getBpmnModel(instance.getProcessDefinitionId()); // 2. 获取已完成的活动 ListHistoricActivityInstance finishedActivities historyService .createHistoricActivityInstanceQuery() .processInstanceId(processInstanceId) .finished() .orderByHistoricActivityInstanceStartTime() .asc() .list(); // 3. 获取当前活动 ListHistoricActivityInstance unfinishedActivities historyService .createHistoricActivityInstanceQuery() .processInstanceId(processInstanceId) .unfinished() .list(); // 4. 构建流程轨迹 ListActivityTrace traces new ArrayList(); for (HistoricActivityInstance activity : finishedActivities) { ActivityTrace trace new ActivityTrace(); trace.setActivityId(activity.getActivityId()); trace.setActivityName(activity.getActivityName()); trace.setActivityType(activity.getActivityType()); trace.setStartTime(activity.getStartTime()); trace.setEndTime(activity.getEndTime()); trace.setDuration(activity.getDurationInMillis()); trace.setAssignee(activity.getAssignee()); trace.setFinished(true); // 获取审批意见 if (userTask.equals(activity.getActivityType())) { ListComment comments taskService.getTaskComments(activity.getTaskId()); if (!comments.isEmpty()) { trace.setComment(comments.get(0).getFullMessage()); } } traces.add(trace); } for (HistoricActivityInstance activity : unfinishedActivities) { ActivityTrace trace new ActivityTrace(); trace.setActivityId(activity.getActivityId()); trace.setActivityName(activity.getActivityName()); trace.setActivityType(activity.getActivityType()); trace.setStartTime(activity.getStartTime()); trace.setFinished(false); traces.add(trace); } traceVO.setTraces(traces); // 5. 获取流程图 ProcessDiagramGenerator diagramGenerator new DefaultProcessDiagramGenerator(); InputStream diagram diagramGenerator.generateDiagram( bpmnModel, png, finishedActivities.stream().map(HistoricActivityInstance::getActivityId).collect(Collectors.toList()), unfinishedActivities.stream().map(HistoricActivityInstance::getActivityId).collect(Collectors.toList()), 宋体, 宋体, 宋体); traceVO.setDiagram(Base64.getEncoder().encodeToString(IOUtils.toByteArray(diagram))); return traceVO; }7. 系统集成与扩展功能在实际企业应用中工作流系统通常需要与其他系统集成并提供扩展功能。消息通知集成Component public class LeaveTaskListener implements TaskListener { Autowired private ISysNoticeService noticeService; Override public void notify(DelegateTask delegateTask) { // 1. 获取流程变量 Long leaveId (Long) delegateTask.getVariable(leaveId); Long userId (Long) delegateTask.getVariable(userId); // 2. 查询请假申请 LeaveApply apply leaveApplyMapper.selectById(leaveId); // 3. 发送通知 SysNotice notice new SysNotice(); notice.setNoticeTitle(您有新的待办任务); notice.setNoticeType(2); // 待办通知 notice.setNoticeContent(String.format( 您有一个新的请假审批任务%s的%s请假申请, apply.getUserName(), getLeaveTypeName(apply.getLeaveType()))); notice.setStatus(0); // 未读 notice.setCreateBy(system); notice.setCreateTime(new Date()); // 设置接收人 if (delegateTask.getAssignee() ! null) { notice.setUserId(Long.valueOf(delegateTask.getAssignee())); } else { // 如果是候选任务发送给所有候选人 ListIdentityLink candidates delegateTask.getCandidates(); for (IdentityLink candidate : candidates) { if (candidate.getUserId() ! null) { notice.setUserId(Long.valueOf(candidate.getUserId())); noticeService.insertNotice(notice); } } return; } noticeService.insertNotice(notice); } private String getLeaveTypeName(int type) { switch (type) { case 1: return 事假; case 2: return 病假; case 3: return 年假; case 4: return 调休; default: return 其他; } } }数据权限控制InterceptorIgnore(tenantLine true) public DataScope getDataScope(Long userId) { // 1. 获取用户角色 ListSysRole roles roleService.selectRolesByUserId(userId); // 2. 构建数据权限 DataScope dataScope new DataScope(); for (SysRole role : roles) { if (DataScopeEnum.ALL.getCode().equals(role.getDataScope())) { dataScope.setAllData(true); break; } else if (DataScopeEnum.CUSTOM.getCode().equals(role.getDataScope())) { dataScope.getDeptIds().addAll( Arrays.stream(role.getDeptIds().split(,)) .map(Long::valueOf) .collect(Collectors.toList())); } else if (DataScopeEnum.DEPT.getCode().equals(role.getDataScope())) { dataScope.setSelfDept(true); } else if (DataScopeEnum.DEPT_AND_CHILD.getCode().equals(role.getDataScope())) { dataScope.setSelfAndChildDept(true); } } return dataScope; } public ListLeaveApplyVO selectLeaveApplyList(LeaveApplyQuery query, DataScope dataScope) { // 构建查询条件 LambdaQueryWrapperLeaveApply wrapper new LambdaQueryWrapper(); // 数据权限过滤 if (!dataScope.isAllData()) { if (dataScope.isSelfDept() || dataScope.isSelfAndChildDept()) { SysUser user SecurityUtils.getLoginUser().getUser(); if (dataScope.isSelfDept()) { wrapper.eq(LeaveApply::getDeptId, user.getDeptId()); } else { ListLong deptIds deptService.selectDeptList( new SysDeptQuery().setParentId(user.getDeptId())) .stream() .map(SysDept::getDeptId) .collect(Collectors.toList()); deptIds.add(user.getDeptId()); wrapper.in(LeaveApply::getDeptId, deptIds); } } else if (!dataScope.getDeptIds().isEmpty()) { wrapper.in(LeaveApply::getDeptId, dataScope.getDeptIds()); } } // 其他查询条件 if (query.getLeaveType() ! null) { wrapper.eq(LeaveApply::getLeaveType, query.getLeaveType()); } if (query.getStatus() ! null) { wrapper.eq(LeaveApply::getStatus, query.getStatus()); } if (StringUtils.isNotBlank(query.getReason())) { wrapper.like(LeaveApply::getReason, query.getReason()); } // 执行查询 ListLeaveApply applies leaveApplyMapper.selectList(wrapper); return applies.stream().map(this::convertToVO).collect(Collectors.toList()); }多租户支持public class TenantInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // 从请求头或token中获取租户ID String tenantId request.getHeader(X-Tenant-Id); if (StringUtils.isBlank(tenantId)) { tenantId SecurityUtils.getTenantId(); // 从登录信息获取 } if (StringUtils.isNotBlank(tenantId)) { // 设置当前租户上下文 TenantContext.setCurrentTenant(tenantId); // 设置Flowable租户 IdentityService identityService SpringUtils.getBean(IdentityService.class); identityService.setAuthenticatedUserId(SecurityUtils.getUsername()); identityService.setTenantId(tenantId); } return true; } Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { // 清除租户上下文 TenantContext.clear(); } } // 在流程启动时设置租户 public String startProcessWithTenant(StartProcessDTO dto) { // 设置当前租户 TenantContext.setCurrentTenant(dto.getTenantId()); // 启动流程 ProcessInstance instance runtimeService.createProcessInstanceBuilder() .processDefinitionKey(dto.getProcessKey()) .businessKey(dto.getBusinessKey()) .variables(dto.getVariables()) .tenantId(dto.getTenantId()) .start(); return instance.getId(); }8. 性能优化与最佳实践在实际生产环境中工作流系统可能会面临性能挑战。以下是一些优化建议和实践经验1. 数据库优化-- 为常用查询字段添加索引 CREATE INDEX idx_act_ru_task_assignee ON ACT_RU_TASK(ASSIGNEE_); CREATE INDEX idx_act_ru_exec_procinst ON ACT_RU_EXECUTION(PROC_INST_ID_); CREATE INDEX idx_act_hi_procinst_buskey ON ACT_HI_PROCINST(BUSINESS_KEY_); -- 定期归档历史数据 INSERT INTO ACT_HI_PROCINST_ARCHIVE SELECT * FROM ACT_HI_PROCINST WHERE END_TIME_ IS NOT NULL AND END_TIME_ DATE_SUB(NOW(), INTERVAL 3 MONTH); DELETE FROM ACT_HI_PROCINST WHERE END_TIME_ IS NOT NULL AND END_TIME_ DATE_SUB(NOW(), INTERVAL 3 MONTH);2. 缓存策略Cacheable(value processDefinitionCache, key #processDefinitionId) public ProcessDefinition getProcessDefinition(String processDefinitionId) { return repositoryService.createProcessDefinitionQuery() .processDefinitionId(processDefinitionId) .singleResult(); } Cacheable(value processDiagramCache, key #processInstanceId) public String getProcessDiagram(String processInstanceId) { // 生成流程图并返回Base64编码 // ... } CacheEvict(value {processDefinitionCache, processDiagramCache}, allEntries true) public void clearProcessCache() { // 缓存清除方法 }3. 批量操作优化public void batchApproveTasks(ListString taskIds, Long userId) { // 1. 批量查询任务 ListTask tasks taskService.createTaskQuery() .taskIds(new HashSet(taskIds)) .list(); // 2. 批量完成任务 tasks.forEach(task - { MapString, Object variables new HashMap(); variables.put(approved, true); variables.put(comment, 批量审批通过); variables.put(approver, userId.toString()); taskService.complete(task.getId(), variables); }); // 3. 批量记录审批日志 ListTaskComment comments tasks.stream().map(task - { TaskComment comment new TaskComment(); comment.setTaskId(task.getId()); comment.setUserId(userId); comment.setType(approve); comment.setContent(批量审批通过); comment.setFullMessage(批量审批通过); return comment; }).collect(Collectors.toList()); taskCommentMapper.batchInsert(comments); }4. 异步处理Async(flowableAsyncExecutor) public void asyncCompleteTask(String taskId, MapString, Object variables) { try { taskService.complete(taskId, variables); } catch (Exception e) { log.error(异步完成任务失败, e); // 记录失败日志或重试 } } // 配置异步线程池 Configuration EnableAsync public class AsyncConfig implements AsyncConfigurer { Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); executor.setMaxPoolSize(50); executor.setQueueCapacity(1000); executor.setThreadNamePrefix(FlowableAsync-); executor.initialize(); return executor; } }5. 监控与告警Scheduled(fixedRate 60000) // 每分钟执行一次 public void monitorFlowableJobs() { // 检查失败的作业 long failedJobs managementService.createDeadLetterJobQuery().count(); if (failedJobs 0) { alertService.sendAlert( Flowable作业告警, String.format(当前有%d个失败的Flowable作业需要处理, failedJobs)); } // 检查长时间运行的任务 ListTask longRunningTasks taskService.createTaskQuery() .taskCreatedBefore(DateUtils.addDays(new Date(), -1)) .list(); if (!longRunningTasks.isEmpty()) { alertService.sendAlert( 长时间任务告警, String.format(有%d个任务运行超过24小时未完成, longRunningTasks.size())); } }