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

SpringBoot 2.6.2 + Flowable 6.7.2 整合实战:从零搭建一个报销审批系统(附源码)

SpringBoot + Flowable 报销审批系统实战:从流程设计到权限控制的全链路实现

在企业的日常运营中,报销审批是一个高频且重要的业务流程。传统的手工审批方式效率低下,而基于工作流引擎的系统可以实现审批流程的自动化、规范化和可追溯。本文将带你从零开始,基于SpringBoot 2.6.2和Flowable 6.7.2构建一个完整的报销审批系统,涵盖流程设计、表单开发、权限控制等核心环节。

1. 技术选型与环境搭建

1.1 为什么选择Flowable

Flowable是一个轻量级、高性能的BPMN 2.0流程引擎,相比Activiti具有以下优势:

  • 更活跃的社区支持:Flowable是Activiti的分支,社区更新更频繁
  • 更简洁的API设计:减少了冗余配置,开发体验更好
  • 更好的Spring集成:原生支持Spring Boot Starter
  • 更完善的功能模块:包括Modeler、IDM等配套工具

1.2 基础环境配置

首先创建Spring Boot项目并添加必要依赖:

<dependencies> <!-- Flowable核心依赖 --> <dependency> <groupId>org.flowable</groupId> <artifactId>flowable-spring-boot-starter</artifactId> <version>6.7.2</version> </dependency> <!-- 数据库相关 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.8</version> </dependency> <!-- Web支持 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>

application.yml配置示例:

spring: datasource: url: jdbc:mysql://localhost:3306/flowable_expense?useSSL=false&serverTimezone=UTC 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 业务流程分析

典型的报销审批流程包含以下环节:

  1. 员工提交报销单:填写报销金额、事由、附件等
  2. 部门经理审批:金额≤500元时直接审批
  3. 财务总监审批:金额>500元时需要额外审批
  4. 财务处理:出纳打款
  5. 流程结束:归档记录

2.2 BPMN流程定义

使用Flowable Modeler设计报销流程,对应的BPMN XML核心部分:

<process id="expense_process" name="报销审批流程" isExecutable="true"> <startEvent id="startEvent" name="开始"></startEvent> <!-- 员工填写报销单 --> <userTask id="fillExpense" name="填写报销单" flowable:assignee="${applicant}"> <extensionElements> <flowable:formProperty id="amount" name="报销金额" type="double" required="true"/> <flowable:formProperty id="reason" name="报销事由" type="string" required="true"/> </extensionElements> </userTask> <!-- 部门经理审批 --> <userTask id="deptManagerApprove" name="部门经理审批" flowable:candidateGroups="dept_manager"> <extensionElements> <flowable:formProperty id="deptComment" name="审批意见" type="string"/> </extensionElements> </userTask> <!-- 金额判断网关 --> <exclusiveGateway id="amountJudge" name="金额判断"></exclusiveGateway> <!-- 财务总监审批 --> <userTask id="financeDirectorApprove" name="财务总监审批" flowable:candidateGroups="finance_director"> <extensionElements> <flowable:formProperty id="financeComment" name="审批意见" type="string"/> </extensionElements> </userTask> <!-- 财务处理 --> <userTask id="financeProcess" name="财务处理" flowable:candidateGroups="finance_staff"> <extensionElements> <flowable:formProperty id="paymentInfo" name="付款信息" type="string"/> </extensionElements> </userTask> <endEvent id="endEvent" name="结束"></endEvent> <!-- 序列流定义 --> <sequenceFlow id="flow1" sourceRef="startEvent" targetRef="fillExpense"/> <sequenceFlow id="flow2" sourceRef="fillExpense" targetRef="deptManagerApprove"/> <sequenceFlow id="flow3" sourceRef="deptManagerApprove" targetRef="amountJudge"/> <!-- 金额<=500流向财务处理 --> <sequenceFlow id="flow4" sourceRef="amountJudge" targetRef="financeProcess"> <conditionExpression xsi:type="tFormalExpression"> ${amount <= 500} </conditionExpression> </sequenceFlow> <!-- 金额>500流向财务总监审批 --> <sequenceFlow id="flow5" sourceRef="amountJudge" targetRef="financeDirectorApprove"> <conditionExpression xsi:type="tFormalExpression"> ${amount > 500} </conditionExpression> </sequenceFlow> <sequenceFlow id="flow6" sourceRef="financeDirectorApprove" targetRef="financeProcess"/> <sequenceFlow id="flow7" sourceRef="financeProcess" targetRef="endEvent"/> </process>

2.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) { Map<String, 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`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 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`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 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 List<TaskDTO> getTodoTasks( @RequestParam String userId, @RequestParam(required = false) List<String> 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("任务不存在或已完成"); } // 设置审批变量 Map<String, 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 id="rejectFlow" sourceRef="deptManagerApprove" targetRef="fillExpense"> <conditionExpression xsi:type="tFormalExpression"> ${!approved} </conditionExpression> </sequenceFlow>

5.3 流程图表生成

实现流程进度图生成接口:

@GetMapping("/diagram") public void generateDiagram( HttpServletResponse response, @RequestParam String processInstanceId) throws IOException { ProcessInstance processInstance = runtimeService.createProcessInstanceQuery() .processInstanceId(processInstanceId) .singleResult(); if (processInstance == null) { return; } // 获取活动节点 List<String> 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> <groupId>org.flowable</groupId> <artifactId>flowable-ui-modeler-rest</artifactId> <version>6.7.2</version> </dependency> <dependency> <groupId>org.flowable</groupId> <artifactId>flowable-ui-modeler-conf</artifactId> <version>6.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()); // 设置权限 List<String> 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 EngineConfigurationConfigurer<SpringProcessEngineConfiguration> { @Override public void configure(SpringProcessEngineConfiguration configuration) { configuration.setActivityFontName("宋体"); configuration.setLabelFontName("宋体"); configuration.setAnnotationFontName("宋体"); } }

问题2:MyBatis与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. 启动流程 Map<String, 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 # 项目说明
http://www.cnnetsun.cn/news/2032495.html

相关文章:

  • BERT在命名实体识别(NER)中的实践与优化
  • AI模型容器化总失败?揭秘Docker 24.0+版本中cgroup v2、seccomp与nvidia-container-toolkit的3大隐性冲突
  • 告别Navicat手动改表名!Oracle到PostgreSQL迁移后,一键批量处理大小写字段的实战脚本
  • 紫光同创FPGA构建多源视频处理系统:OV/HDMI输入转HDMI输出的PDS工程详解
  • 几何数据格式的革命性突破:stltostp重新定义STL到STEP的无缝转换范式
  • Qwen3-4B-Thinking应用案例:如何用它快速生成营销文案和编程代码?
  • Qwen-Image-Edit-2509在电商场景的应用:自动优化商品主图实操
  • 别再瞎猜了!用JMeter的Stepping Thread Group插件,精准定位你的接口到底能扛多少用户
  • KKS-HF_Patch 终极指南:轻松解锁完整游戏体验与数百个功能增强
  • 为什么3DS玩家需要JKSM:守护你游戏进度的数字保险箱
  • 深夜加班如何快速保存B站教程?BilibiliDown帮你告别视频收藏难题
  • 手把手教你用Python脚本绕过SQL过滤,在BUUCTF靶场实战GetShell
  • 告别虚拟机!在Win11/Win10上5分钟搞定WSL2,用Miniconda3搭建你的第一个生信分析环境
  • 百度网盘下载加速终极指南:BaiduPCS-Web与KinhDown免费高速下载方案
  • 终极指南:用Python轻松解锁通达信金融数据宝库
  • 别再只懂泊松分布了:用Python实战模拟用户点击流(从均匀分布到点过程生成)
  • 3分钟掌握Windows窗口隐身术:Boss-Key老板键深度使用指南
  • py-googletrans完全指南:3大技巧教你免费实现批量文本翻译自动化
  • FanControl终极指南:5大技术架构解析与Windows风扇控制深度配置
  • Python文件移动踩坑实录:shutil.move的3个报错与我的实战解决方案
  • 免费文档下载工具终极指南:30+平台文档一键获取完整教程
  • 禾川X2E伺服位置模式参数设置保姆级教程:从惯量比识别到电子齿轮比计算
  • 开源模型真的追上闭源了吗?Kimi K2.6 给了我不一样的答案
  • 告别Windows激活烦恼:KMS_VL_ALL_AIO一键激活全攻略
  • NVIDIA RTX PRO 6000 Blackwell加速蛋白质结构预测实战
  • STGCN 实战:从环境配置到模型训练全流程解析(Pytorch)
  • Python聊天机器人开发实战:从ChatterBot入门到部署
  • 为什么国家级数字农场禁用docker run --privileged?——农业场景下Docker安全配置的11条铁律
  • Phi-3.5-mini-instruct环境配置:transformers 4.46.3 + trust_remote_code实践
  • F3D技术架构深度解析:高性能3D渲染引擎的模块化设计实现