从‘查不到数据’到‘自动流转’:手把手调试RuoYi-Flowable动态审批人逻辑(附完整测试类写法)

张开发
2026/4/3 12:14:18 15 分钟阅读
从‘查不到数据’到‘自动流转’:手把手调试RuoYi-Flowable动态审批人逻辑(附完整测试类写法)
从‘查不到数据’到‘自动流转’RuoYi-Flowable动态审批人全流程调试指南1. 问题定位与调试环境搭建当你在RuoYi-Flowable项目中实现动态审批人功能时最令人头疼的莫过于流程表达式配置正确但运行时却返回null或报错。这种情况往往源于三个关键环节的配置问题典型报错场景分析Unknown property used in expression服务类未被Spring容器正确识别NullPointerExceptionMyBatis查询结果封装失败No such method流程表达式调用方法签名不匹配提示调试前请确保已安装Lombok插件否则部分代码可能无法正常编译我们先搭建一个最小化的测试环境。在src/test/java下创建测试类结构SpringBootTest RunWith(SpringRunner.class) public class FlowableDynamicAssigneeTest { Autowired private ISysUserService userService; Autowired private RuntimeService runtimeService; }注意测试类必须与主启动类在相同包路径下否则需要显式指定启动类SpringBootTest(classes RuoYiApplication.class)2. Service层单元测试实战2.1 解决MyBatis结果封装为null的问题当selectDirectLeaderByPostCode方法始终返回null时首先检查Mapper层结果映射。以下是两种解决方案的对比方案配置方式优点缺点全局配置在application.yml中添加mybatis.configuration.map-underscore-to-camel-casetrue一次性解决所有映射问题无法处理复杂映射关系XML映射在Mapper.xml中定义resultMap可精确控制每个字段映射需要为每个查询单独配置推荐使用XML映射方案示例resultMap idUserResult typecom.ruoyi.system.domain.SysUser id propertyuserId columnuser_id / result propertydeptId columndept_id / result propertyuserName columnuser_name / /resultMap select idselectUsersByDeptId resultMapUserResult SELECT * FROM sys_user WHERE dept_id #{deptId} /select2.2 循环依赖问题破解若测试时遇到Circular dependency警告可通过以下方式解决将测试类移到admin模块主模块下使用Lazy注解延迟加载依赖重构代码消除循环引用推荐方案1这是最彻底的解决方案。3. 流程表达式深度解析3.1 两种动态审批人实现方式对比固定变量方式variables.put(headTeacherId, 12345);BPMN配置formalExpression${headTeacherId}/formalExpression动态查询方式formalExpression${userService.getHeadTeacher(execution.getVariable(initiator))}/formalExpression性能与灵活性对比维度固定变量动态查询执行效率★★★★☆★★☆☆☆实时性★☆☆☆☆★★★★☆配置复杂度★★☆☆☆★★★★☆维护成本★★☆☆☆★★★☆☆3.2 表达式服务注册机制要使流程引擎识别自定义Service必须确保类上有Service注解首字母小写的bean名称与表达式中的引用一致在Flowable配置中显式注册Configuration public class FlowableConfig { Bean public SpringProcessEngineConfiguration springProcessEngineConfiguration( UserService userService) { SpringProcessEngineConfiguration config new SpringProcessEngineConfiguration(); config.getBeans().put(userService, userService); return config; } }常见错误排查清单检查Service注解的value是否与表达式中的名称一致确认方法访问权限为public验证方法参数类型与流程变量类型匹配4. 完整测试类编写指南4.1 集成测试示例Test public void testDynamicApprovalFlow() { // 1. 准备测试数据 Long studentId 123L; String processDefinitionKey leaveApproval; // 2. 设置流程变量 MapString, Object variables new HashMap(); variables.put(initiator, studentId); // 3. 启动流程实例 ProcessInstance instance runtimeService.startProcessInstanceByKey( processDefinitionKey, variables); // 4. 验证任务分配 Task task taskService.createTaskQuery() .processInstanceId(instance.getId()) .singleResult(); Long expectedTeacherId userService.selectDirectLeaderByPostCode( studentId, class_teacher); assertEquals(expectedTeacherId.toString(), task.getAssignee()); }4.2 测试数据准备技巧使用Sql注解初始化测试数据Test Sql(scripts /sql/init_teacher_student.sql) public void testWithRealData() { // 测试逻辑 }init_teacher_student.sql示例INSERT INTO sys_dept VALUES(1, 计算机学院, 0, 001); INSERT INTO sys_user VALUES(100, 1, 张老师, teacher); INSERT INTO sys_user VALUES(101, 1, 李同学, student); INSERT INTO sys_user_post VALUES(100, 1); -- 班主任岗位5. 生产环境优化建议性能优化为dept_id和post_code字段添加索引考虑缓存班主任信息减少数据库查询批量查询替代循环中的单条查询健壮性增强public Long selectDirectLeaderByPostCode(Long userId, String postCode) { ListSysUser users this.selectDeptUsersByUserId(userId); if(CollectionUtils.isEmpty(users)) { throw new BusinessException(部门下没有用户); } return users.stream() .filter(u - this.checkIncludePostCode( postMapper.selectPostCodeListByUserId(u.getUserId()), postCode)) .findFirst() .orElseThrow(() - new BusinessException(未找到指定岗位负责人)) .getUserId(); }事务管理Transactional(rollbackFor Exception.class) public Long assignHeadTeacher(Long deptId, Long userId) { // 先解除原班主任关联 postMapper.deleteByDeptAndPost(deptId, class_teacher); // 设置新班主任 return postMapper.insertUserPost(userId, class_teacher); }在调试过程中记得充分利用Flowable的调试日志# application.yml logging.level.org.flowableDEBUG当看到控制台输出流程变量和SQL语句时就像拥有了X光透视能力可以清晰看到整个审批流转的内部运作机制。

更多文章