基础架构到实战重构:Flowable 工作流"多状态"动态查询方案
1. 项目背景与环境配置
在企业级数智化项目中,工作流引擎(Flowable)是核心组件。本项目采用标准的 Spring Boot 2.7.6 多模块架构进行构建。
- 父工程 (
flow-parent) :负责统一管理Flowable 6.8.0、MyBatis-Plus 3.5.2、Druid等核心依赖版本,确保全项目依赖的一致性。 - 核心模块 (
flow-core) :不仅集成了流程引擎,还包含了 UI 设计器逻辑。通过自定义build配置,实现了Mapper.xml与 Java 代码同目录打包,提升了开发体验。
2. 需求痛点:如何优雅地支持"多状态"过滤?
原本的"我发起的流程"接口仅支持单一状态过滤。但在实际业务中,用户经常需要在一个页签(如"已结束")中同时看到以下几种状态的数据:
- BJ (办结)
- BH (驳回)
- CH (撤回)
- ZZ (终止)
2.1 数据表关联逻辑
该查询关联了 6 张表,包括 Flowable 核心历史表(act_hi_procinst)、流程定义表(act_re_procdef)以及业务扩展表(tbl_flow_extend_hisprocinst)。
3. 后端重构:动态 SQL 的演进
3.1 参数模型扩展
在 InstanceQueryParamsVo 中新增 processStatusList 字段,用于接收前端传来的状态集合。
java
@Data
public class InstanceQueryParamsVo implements Serializable {
// ... 原有字段 ...
@ApiModelProperty(value = "流程状态集合,如:['BH','CH','BJ','ZZ']")
private List<String> processStatusList;
}
3.2 MyBatis 动态 SQL 实现
利用 <foreach> 标签动态构建 IN 子句。这种方式既支持前端自由组合状态,又通过硬编码白名单防御了注入风险。
xml
<select id="findMyProcessInstancesPagerModel" ...>
SELECT DISTINCT t1.PROC_INST_ID_, t1.NAME_, t4.process_status ...
FROM act_hi_procinst t1
INNER JOIN tbl_flow_extend_hisprocinst t4 ON t1.PROC_INST_ID_ = t4.process_instance_id
WHERE 1=1
<if test="params.processStatusList != null and params.processStatusList.size() > 0">
AND t4.process_status IN
<foreach collection="params.processStatusList" item="status" open="(" separator="," close=")">
#{status}
</foreach>
</if>
<if test="sqlOrderBy != null">
ORDER BY
<foreach collection="sqlOrderBy" item="order" index="field" separator=",">
<choose>
<when test="field == 'startTime'">t1.START_TIME_</when>
<when test="field == 'processStatus'">t4.process_status</when>
<otherwise>t1.START_TIME_</otherwise>
</choose>
${order.name()}
</foreach>
</if>
</select>
4. 前端实践:Tab 驱动与类型挑战
4.1 逻辑适配
前端基于 activeTab 状态,在请求 entity 中动态注入参数。这样可以复用同一个查询接口,极大减轻了维护成本。
typescript
const requestData = {
entity: {
...params,
// 仅在"已完成"页签注入多状态集合
...(activeTab === 'completed' && {
processStatusList: ['BH', 'CH', 'BJ', 'ZZ']
}),
},
};
4.2 避坑指南:TypeScript 类型守卫
报错信息 :This comparison appears to be unintentional because the types 'TabType' and '"completed"' have no overlap. 原因 :activeTab 定义的联合类型中缺少新状态。 解决 :扩展 TabType 定义,或者在逻辑判断中使用类型断言 (activeTab as string) === 'completed'。
5. 进阶:性能与安全深度优化
随着流程数据量增长,简单的实现往往隐藏着性能隐患:
5.1 索引预警:模糊查询的隐患
我们使用了 LIKE CONCAT('%', #{keyword}, '%')。
- 风险 :左模糊查询会导致 B+Tree 索引失效。
- 优化 :在大数据场景下,建议使用右模糊查询
keyword%或引入 Elasticsearch 进行搜索。
5.2 安全防护:排序字段白名单
在 ORDER BY 中,必须使用 ${}。
- 对策 :通过 MyBatis 的
<choose>标签硬编码字段映射,防止攻击者通过字段名进行 SQL 注入。
5.3 架构思考:减少多表 Join
- 痛点:跨表关联 6 张表在百万级数据下 IO 压力巨大。
- 建议 :在
tbl_flow_extend_hisprocinst扩展表中冗余存储高频字段(如应用名、发起人姓名),将"6 表 Join"降级为"单表或双表查询"。
6. 总结
本次重构通过"后端提供能力、前端定义视图"的思路,以最小的代码变动实现了高度灵活的查询。
核心收益:
- 高复用性:一个接口支撑了待办、已办、发起、结束等多个业务视图。
- 安全性:通过动态 SQL 标签规避了拼接风险。
- 可扩展性:未来新增状态,只需前端更新数组,无需后端发布。