SpringBoot 2.6.2 + Flowable 6.7.2 整合实战:从零搭建一个报销审批系统(附源码)
SpringBoot Flowable 报销审批系统实战从流程设计到权限控制的全链路实现在企业的日常运营中报销审批是一个高频且重要的业务流程。传统的手工审批方式效率低下而基于工作流引擎的系统可以实现审批流程的自动化、规范化和可追溯。本文将带你从零开始基于SpringBoot 2.6.2和Flowable 6.7.2构建一个完整的报销审批系统涵盖流程设计、表单开发、权限控制等核心环节。1. 技术选型与环境搭建1.1 为什么选择FlowableFlowable是一个轻量级、高性能的BPMN 2.0流程引擎相比Activiti具有以下优势更活跃的社区支持Flowable是Activiti的分支社区更新更频繁更简洁的API设计减少了冗余配置开发体验更好更好的Spring集成原生支持Spring Boot Starter更完善的功能模块包括Modeler、IDM等配套工具1.2 基础环境配置首先创建Spring Boot项目并添加必要依赖dependencies !-- Flowable核心依赖 -- dependency groupIdorg.flowable/groupId artifactIdflowable-spring-boot-starter/artifactId version6.7.2/version /dependency !-- 数据库相关 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency dependency groupIdcom.alibaba/groupId artifactIddruid-spring-boot-starter/artifactId version1.2.8/version /dependency !-- Web支持 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency /dependenciesapplication.yml配置示例spring: datasource: url: jdbc:mysql://localhost:3306/flowable_expense?useSSLfalseserverTimezoneUTC username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource flowable: database-schema-update: true # 自动创建表结构 async-executor-activate: false # 关闭异步任务执行器 history-level: full # 完整的历史记录提示生产环境应将database-schema-update设置为false避免意外修改表结构2. 报销流程设计与实现2.1 业务流程分析典型的报销审批流程包含以下环节员工提交报销单填写报销金额、事由、附件等部门经理审批金额≤500元时直接审批财务总监审批金额500元时需要额外审批财务处理出纳打款流程结束归档记录2.2 BPMN流程定义使用Flowable Modeler设计报销流程对应的BPMN XML核心部分process idexpense_process name报销审批流程 isExecutabletrue startEvent idstartEvent name开始/startEvent !-- 员工填写报销单 -- userTask idfillExpense name填写报销单 flowable:assignee${applicant} extensionElements flowable:formProperty idamount name报销金额 typedouble requiredtrue/ flowable:formProperty idreason name报销事由 typestring requiredtrue/ /extensionElements /userTask !-- 部门经理审批 -- userTask iddeptManagerApprove name部门经理审批 flowable:candidateGroupsdept_manager extensionElements flowable:formProperty iddeptComment name审批意见 typestring/ /extensionElements /userTask !-- 金额判断网关 -- exclusiveGateway idamountJudge name金额判断/exclusiveGateway !-- 财务总监审批 -- userTask idfinanceDirectorApprove name财务总监审批 flowable:candidateGroupsfinance_director extensionElements flowable:formProperty idfinanceComment name审批意见 typestring/ /extensionElements /userTask !-- 财务处理 -- userTask idfinanceProcess name财务处理 flowable:candidateGroupsfinance_staff extensionElements flowable:formProperty idpaymentInfo name付款信息 typestring/ /extensionElements /userTask endEvent idendEvent name结束/endEvent !-- 序列流定义 -- sequenceFlow idflow1 sourceRefstartEvent targetReffillExpense/ sequenceFlow idflow2 sourceReffillExpense targetRefdeptManagerApprove/ sequenceFlow idflow3 sourceRefdeptManagerApprove targetRefamountJudge/ !-- 金额500流向财务处理 -- sequenceFlow idflow4 sourceRefamountJudge targetReffinanceProcess conditionExpression xsi:typetFormalExpression ${amount 500} /conditionExpression /sequenceFlow !-- 金额500流向财务总监审批 -- sequenceFlow idflow5 sourceRefamountJudge targetReffinanceDirectorApprove conditionExpression xsi:typetFormalExpression ${amount 500} /conditionExpression /sequenceFlow sequenceFlow idflow6 sourceReffinanceDirectorApprove targetReffinanceProcess/ sequenceFlow idflow7 sourceReffinanceProcess targetRefendEvent/ /process2.3 流程部署与启动通过RepositoryService部署流程定义Service public class ProcessDeployService { Autowired private RepositoryService repositoryService; public void deployProcess(String processName, String bpmnPath) { Deployment deployment repositoryService.createDeployment() .name(processName) .addClasspathResource(bpmnPath) .deploy(); System.out.println(流程部署成功部署ID deployment.getId()); } }启动流程实例RestController RequestMapping(/expense) public class ExpenseController { Autowired private RuntimeService runtimeService; PostMapping(/start) public String startExpenseProcess(RequestBody ExpenseRequest request) { MapString, Object variables new HashMap(); variables.put(applicant, request.getApplicant()); variables.put(amount, request.getAmount()); variables.put(reason, request.getReason()); ProcessInstance instance runtimeService.startProcessInstanceByKey( expense_process, variables); return 流程启动成功流程实例ID instance.getId(); } }3. 业务数据与流程的关联3.1 数据库设计为报销系统设计业务表结构CREATE TABLE expense_order ( id bigint(20) NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 报销单号, applicant varchar(64) NOT NULL COMMENT 申请人, amount decimal(10,2) NOT NULL COMMENT 报销金额, reason varchar(255) NOT NULL COMMENT 报销事由, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 状态0-待审批,1-审批中,2-已通过,3-已驳回, process_instance_id varchar(64) DEFAULT NULL COMMENT 流程实例ID, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_process_instance (process_instance_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT报销单表; CREATE TABLE expense_approval ( id bigint(20) NOT NULL AUTO_INCREMENT, order_id bigint(20) NOT NULL COMMENT 报销单ID, approver varchar(64) NOT NULL COMMENT 审批人, approval_result tinyint(4) NOT NULL COMMENT 审批结果1-通过,2-驳回, approval_comment varchar(255) DEFAULT NULL COMMENT 审批意见, task_id varchar(64) DEFAULT NULL COMMENT 任务ID, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_order_id (order_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT审批记录表;3.2 业务与流程的同步使用ExecutionListener实现业务状态同步public class ExpenseStatusListener implements ExecutionListener { Override public void notify(DelegateExecution execution) { String processInstanceId execution.getProcessInstanceId(); String eventName execution.getEventName(); // 获取业务服务Bean ExpenseOrderService orderService SpringContextUtil.getBean(ExpenseOrderService.class); if (start.equals(eventName)) { // 流程启动时更新状态为审批中 orderService.updateStatusByProcessInstance(processInstanceId, 1); } else if (end.equals(eventName)) { // 流程结束时判断最终状态 boolean approved execution.getVariable(approved, Boolean.class); orderService.updateStatusByProcessInstance( processInstanceId, approved ? 2 : 3); } } }4. 审批功能实现4.1 任务查询接口实现获取用户待办任务列表RestController RequestMapping(/task) public class TaskController { Autowired private TaskService taskService; GetMapping(/todo) public ListTaskDTO getTodoTasks( RequestParam String userId, RequestParam(required false) ListString groups) { // 创建任务查询 TaskQuery query taskService.createTaskQuery() .taskAssignee(userId); // 添加候选组查询 if (groups ! null !groups.isEmpty()) { query.taskCandidateGroupIn(groups); } // 执行查询并转换为DTO return query.orderByTaskCreateTime().desc().list() .stream() .map(task - { TaskDTO dto new TaskDTO(); dto.setTaskId(task.getId()); dto.setName(task.getName()); dto.setCreateTime(task.getCreateTime()); dto.setProcessInstanceId(task.getProcessInstanceId()); return dto; }) .collect(Collectors.toList()); } }4.2 任务审批接口实现任务审批通过/驳回操作PostMapping(/approve) public String approveTask(RequestBody ApproveRequest request) { // 获取当前任务 Task task taskService.createTaskQuery() .taskId(request.getTaskId()) .singleResult(); if (task null) { throw new RuntimeException(任务不存在或已完成); } // 设置审批变量 MapString, Object variables new HashMap(); variables.put(approved, request.isApproved()); variables.put(comment, request.getComment()); // 完成任务 taskService.complete(task.getId(), variables); // 记录审批记录 approvalService.recordApproval( task.getProcessInstanceId(), task.getId(), SecurityUtils.getCurrentUserId(), request.isApproved(), request.getComment()); return 审批操作成功; }5. 高级功能实现5.1 动态审批人设置通过TaskListener实现动态审批人分配public class DeptManagerTaskListener implements TaskListener { Override public void notify(DelegateTask delegateTask) { // 获取流程变量 String applicant (String) delegateTask.getVariable(applicant); // 根据申请人查询部门经理 UserService userService SpringContextUtil.getBean(UserService.class); String deptManager userService.getDeptManagerByUser(applicant); // 设置任务处理人 delegateTask.setAssignee(deptManager); } }5.2 驳回功能实现在BPMN中定义驳回路线sequenceFlow idrejectFlow sourceRefdeptManagerApprove targetReffillExpense conditionExpression xsi:typetFormalExpression ${!approved} /conditionExpression /sequenceFlow5.3 流程图表生成实现流程进度图生成接口GetMapping(/diagram) public void generateDiagram( HttpServletResponse response, RequestParam String processInstanceId) throws IOException { ProcessInstance processInstance runtimeService.createProcessInstanceQuery() .processInstanceId(processInstanceId) .singleResult(); if (processInstance null) { return; } // 获取活动节点 ListString activeActivityIds runtimeService.getActiveActivityIds(processInstanceId); // 获取BPMN模型 BpmnModel bpmnModel repositoryService.getBpmnModel(processInstance.getProcessDefinitionId()); // 生成流程图 ProcessDiagramGenerator diagramGenerator processEngineConfiguration.getProcessDiagramGenerator(); InputStream imageStream diagramGenerator.generateDiagram( bpmnModel, png, activeActivityIds, Collections.emptyList(), processEngineConfiguration.getActivityFontName(), processEngineConfiguration.getLabelFontName(), processEngineConfiguration.getAnnotationFontName(), null, 1.0, true); // 输出图片 response.setContentType(image/png); try (OutputStream out response.getOutputStream()) { IOUtils.copy(imageStream, out); } }6. 前端集成与权限控制6.1 集成Flowable Modeler添加Modeler依赖并配置dependency groupIdorg.flowable/groupId artifactIdflowable-ui-modeler-rest/artifactId version6.7.2/version /dependency dependency groupIdorg.flowable/groupId artifactIdflowable-ui-modeler-conf/artifactId version6.7.2/version /dependency配置安全规则绕过授权Configuration Order(SecurityProperties.BASIC_AUTH_ORDER - 1) public class ModelerSecurityConfiguration extends WebSecurityConfigurerAdapter { Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .antMatchers(/modeler/**).permitAll() .anyRequest().authenticated(); } }6.2 自定义用户集成重写用户信息接口RestController RequestMapping(/app) public class CustomUserResource { GetMapping(/rest/account) public UserRepresentation getCurrentUser() { UserRepresentation user new UserRepresentation(); user.setId(SecurityUtils.getCurrentUserId()); user.setFirstName(SecurityUtils.getCurrentUserName()); // 设置权限 ListString privileges new ArrayList(); privileges.add(access-idm); privileges.add(access-modeler); user.setPrivileges(privileges); return user; } }7. 系统优化与扩展7.1 性能优化建议启用异步执行器对于耗时操作启用异步处理flowable: async-executor-activate: true async-executor-thread-pool-size: 10历史数据归档定期归档已完成流程的历史数据缓存流程定义避免频繁读取数据库7.2 扩展功能思路多级审批支持无限级审批层级配置金额分段审批不同金额范围走不同审批路径附件管理集成文件上传服务移动端支持开发微信小程序审批端7.3 常见问题解决方案问题1流程图中中文乱码解决方案配置字体Configuration public class FlowableConfig implements EngineConfigurationConfigurerSpringProcessEngineConfiguration { Override public void configure(SpringProcessEngineConfiguration configuration) { configuration.setActivityFontName(宋体); configuration.setLabelFontName(宋体); configuration.setAnnotationFontName(宋体); } }问题2MyBatis与Flowable冲突解决方案使用多数据源配置为MyBatis指定主数据源MapperScan(basePackages com.example.mapper, sqlSessionFactoryRef businessSqlSessionFactory) Configuration public class BusinessDataSourceConfig { Bean ConfigurationProperties(spring.datasource.business) public DataSource businessDataSource() { return DataSourceBuilder.create().build(); } Bean public SqlSessionFactory businessSqlSessionFactory( Qualifier(businessDataSource) DataSource dataSource) throws Exception { SqlSessionFactoryBean bean new SqlSessionFactoryBean(); bean.setDataSource(dataSource); return bean.getObject(); } }8. 项目部署与测试8.1 测试用例设计编写集成测试验证核心流程SpringBootTest class ExpenseProcessTest { Autowired private RuntimeService runtimeService; Autowired private TaskService taskService; Test void testExpenseProcess() { // 1. 启动流程 MapString, Object vars new HashMap(); vars.put(applicant, user1); vars.put(amount, 1000.00); ProcessInstance instance runtimeService.startProcessInstanceByKey( expense_process, vars); // 2. 验证任务 Task task taskService.createTaskQuery() .processInstanceId(instance.getId()) .singleResult(); assertEquals(填写报销单, task.getName()); // 3. 完成任务 taskService.complete(task.getId()); // 4. 验证下一任务 task taskService.createTaskQuery() .processInstanceId(instance.getId()) .singleResult(); assertEquals(部门经理审批, task.getName()); } }8.2 部署建议数据库生产环境建议使用主从配置应用服务器至少2个节点实现高可用监控集成Prometheus监控流程引擎指标日志集中式日志收集分析9. 总结与源码通过本文的实践我们完成了基于SpringBoot和Flowable的报销审批系统实现了以下功能可视化流程设计通过Flowable Modeler设计业务流程灵活的任务分配支持固定审批人和动态审批人完整的审批功能包括通过、驳回、撤销等操作业务集成流程与业务数据紧密关联权限控制与现有系统用户体系集成项目源码结构flowable-expense/ ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── example/ │ │ │ ├── config/ # 配置类 │ │ │ ├── controller/ # 控制器 │ │ │ ├── listener/ # 流程监听器 │ │ │ ├── model/ # 数据模型 │ │ │ ├── service/ # 业务服务 │ │ │ └── FlowableExpenseApplication.java │ │ └── resources/ │ │ ├── static/ # 静态资源 │ │ ├── templates/ # 模板文件 │ │ ├── processes/ # BPMN流程文件 │ │ └── application.yml # 应用配置 │ └── test/ # 测试代码 ├── pom.xml # Maven配置 └── README.md # 项目说明