* feat(flow): 新增巡视整改和巡视整改销号流程模块 * feat(flow): 新增巡视整改和巡视整改销号流程 YAML 配置 * chore: 添加 localDocs 到 gitignore 并移除已跟踪的 docs 文件
11 KiB
流程模块架构审计报告
审计范围:
org.jeecg下 4 个流程模块(xispeak / xispeakfb / inspectimprove / inspectcloseout) 共 18 个 Java 文件,含 4 个 Config、4 个 Flow、10 个 Listener
一、模块概览
| 模块 | 文件数 | Flow 行数 | 业务完整度 | DI 健康度 |
|---|---|---|---|---|
| xispeak | 9 (1C+1F+7L) | 432 | 完整 | 4/7 正常,3/7 有问题 |
| xispeakfb | 3 (1C+1F+1L) | 322 | 完整 | 正常 |
| inspectimprove | 3 (1C+1F+1L) | 164 | TODO 未完成 | 正常 |
| inspectcloseout | 3 (1C+1F+1L) | 132 | TODO 未完成 | 正常 |
二、P0 致命问题
2.1 StringUtil 拼写错误 — 编译/运行时报错
文件 AfterSDWApproveHqListener.java 第 50 行:
if(org.apache.commons.lang.StringUtil.isEmpty(url)) {
Apache Commons Lang 中不存在 StringUtil 类,应为 StringUtils。
影响:该行不会通过编译;如果通过(如 IDE 自动导入),运行时会抛出 NoClassDefFoundError。
2.2 链式调用直接 NPE(3 处)
XiSpeakFlow.java:
- 行 166:
xiSpeakConfig.getXiSpeak().getNeedSdwApproveCode()—getXiSpeak()可能返回 null(yml 缺失此段落时 Spring Boot 不会注入默认值,导致属性为 null) - 行 172:
xiSpeakConfig.getXiSpeak().getFeedbackRightNowCode()— 同上 - 行 178:
xiSpeakConfig.getXiSpeak().getSdwLeaderListKey()— 同上
同类方法 getBgDeptLdUserIdList() 行 37-41 做了守卫判断,但上述 3 处遗漏。
XiSpeakFlow.java 行 310:
public int getImplDeptIsEnd(DelegateExecution execution) {
return getDeptApproveDetail(execution).getIsEnd();
}
getDeptApproveDetail() 可能返回 null(当 businessKey 为空、deptVar 为 null、entity 不存在时),直接调 .getIsEnd() 必抛 NPE。
2.3 InspectImproveFlow / InspectCloseoutFlow 所有公开方法配置 NPE
public List<String> getMeasureResLeaderList(Object jsonData) {
InspectImproveConfig.InspectImproveProperties props = inspectImproveConfig.getInspectImprove();
return getListFromData(jsonData, props.getMeasureResLeaderKey(), "...");
}
getInspectImprove() 无 null 守卫。如果 yml 中 inspect-improve 或 inspect-closeout 配置节缺失,所有公开方法调用时接连抛出 NPE。
三、P1 高危问题
3.1 JSON 解析方法大面织重复(~180 行)
同一个 "JSONObject / JSONString / POJO 三种类型兼容解析" 逻辑在以下 7 个方法中独立实现:
| 类 | 方法 | 返回类型 |
|---|---|---|
XiSpeakFlow |
getCodeValueFromData |
Integer |
XiSpeakFlow |
getListCodeValueFromData |
List<String> |
XiSpeakFlow |
getImplDeptNums |
Integer (变体) |
InspectImproveFlow |
getStringFromData |
String |
InspectImproveFlow |
getListFromData |
List<String> |
InspectCloseoutFlow |
getStringFromData |
String |
InspectCloseoutFlow |
getListFromData |
List<String> |
建议:抽取为 FlowJsonParser 工具类,提供 getString(jsonData, key) / getInteger(jsonData, key, default) / getStringList(jsonData, key) 三个通用方法。
3.2 2 级父 Execution 攀爬逻辑三重复制(~135 行)
三个 Listener 中有逐行相同的 45 行核心逻辑,仅变量名不同:
| Listener | 存储变量名 |
|---|---|
AfterImplDeptLeaderApproveListener |
硬编码 "tem_impl_leader" |
AfterImplWorkerTemStoreListener |
硬编码 "tem_impl_worker" |
AfterBgLeaderApproveListener |
配置 props.getTemBgLeaderKey() |
建议:提取抽象模板方法,参数化变量名和日志前缀。
3.3 getListSize() 重复定义(3 处)
XiSpeakFlow 行 340、InspectImproveFlow 行 155、InspectCloseoutFlow 行 123 都有语义完全相同的实现。
建议:移入共用工具类。
3.4 依赖注入混乱(3 个 Listener)
| Listener | 注入方式 | 问题 |
|---|---|---|
AfterSDWApproveHqListener |
@RequiredArgsConstructor(2 final) + @Autowired 字段注入 RuntimeService |
混合注入,风格撕裂 |
AfterImplDeptLeaderApproveListener |
@RequiredArgsConstructor 零 final + static { SpringContextUtils.getBean(...) } |
注解虚设,static 块获取 Bean 有容器未就绪风险 |
AfterImplWorkerTemStoreListener |
同上 | 同上 |
建议:统一为 @RequiredArgsConstructor(onConstructor_ = @Autowired) + private final 字段。
3.5 事务注解缺失
AfterBgWorkerFinalApproveListener 执行 bgXiSpeakService.updateById(entity) 却没有 @Transactional。
虽然 MyBatis-Plus 的 updateById 自身在一条 SQL 中完成,但如果后续有人在此方法中增加第二个 DB 操作,不会感知到缺少事务边界。
3.6 悲观锁 vs 无锁策略不一致
| Listener | 读方法 | 锁 |
|---|---|---|
AfterImplWorkerApproveListener |
getByIdForUpdate |
悲观锁 (SELECT FOR UPDATE) |
AfterImplDeptLeaderStoreJsonListener |
getById |
无锁 |
xispeakfb AfterImplDeptLeaderApproveListener |
getById |
无锁 |
多实例并行会签场景下,无锁的 getById →修改→ updateById 存在丢失更新风险。
四、P2 中危问题
4.1 parseDeptId 两个版本功能不对等
- xispeak 版 (
AfterImplDeptLeaderStoreJsonListener行 115-137):处理 null / Collection / 普通字符串 /[1001,1002]数组字符串 - xispeakfb 版 (
AfterImplDeptLeaderApproveListener行 115-123):仅处理 Collection / 普通字符串,缺失数组字符串兼容
如果 xispeakfb 流程中传入 [deptA, deptB] 格式的变量值(这在 xispeak 流程中很常见),解析结果会包含方括号导致后续匹配失败。
4.2 流程变量 fallback 模式重复(4 处)
同一 "先 getVariableLocal 再 getVariable" 的 4 行模式出现在 4 个不同位置,无统一封装。
4.3 AfterSDWApproveHqListener 日志硬编码字段名
行 80 的日志 "解析 JSON 字段 pending_depts_id 失败" 硬编码了特定字段名,与运行时通过 props.getImplDeptKey() 读取的实际字段名可能不一致,降低排查效率。
4.4 StringUtils 库引用混乱
| 类 | 使用的 StringUtils |
|---|---|
XiSpeakFlow |
org.apache.shiro.util.StringUtils、org.apache.commons.lang.StringUtils、org.apache.commons.lang3.StringUtils — 三个库混用 |
XiSpeakFeedbackFlow |
org.apache.commons.lang3.StringUtils 统一 |
InspectImproveFlow |
org.apache.commons.lang.StringUtils (lang) |
InspectCloseoutFlow |
org.apache.commons.lang.StringUtils (lang) |
| 各 Listener | lang 和 lang3 混用 |
同一类中三种 StringUtils 混用增加混淆。Apache Commons Lang (lang) 是旧版,lang3 是新版,shiro 是第三方。
五、P3 低危问题
5.1 两个新的 Listener 含 TODO 未实现
AfterInspectImproveCompleteListener行 44:只输出日志,未更新业务表AfterCloseoutApprovedListener行 35:同上
5.2 大量未使用的 Import
约 7 个 Listener 包含从模板复刻过来的未使用 import(如 LambdaQueryWrapper、FlowNodeExpression、WorkFlowGlobals、TaskTask、ITaskTaskService),推测是从其他文件复制后未清理。
5.3 缺少 @Override
xispeakfb AfterImplDeptLeaderApproveListener 的 notify() 方法缺少 @Override,失去编译期接口契约校验。
5.4 硬编码变量名 vs 配置化
AfterImplDeptLeaderApproveListener 和 AfterImplWorkerTemStoreListener 分别硬编码 "tem_impl_leader" 和 "tem_impl_worker",而 AfterBgLeaderApproveListener 正确地通过 Config 读取。行为不一致。
5.5 getImplDeptLeader() 逻辑矛盾
// XiSpeakFlow 行 246-252
public String getImplDeptLeader(DelegateExecution execution) {
DeptApproveDetail detail = getDeptApproveDetail(execution);
if (detail != null && detail.getIsEnd() != null) {
return String.valueOf(detail.getImplWorker()); // isEnd 非 null → 取 implWorker
}
return detail == null ? "" : detail.getImplWorker(); // isEnd 为 null → 也取 implWorker
}
两个分支返回同一个字段,isEnd 判断形同虚设(可能是条件写反)。
六、架构演进建议
6.1 短期(本迭代)
- 修复 4 处 P0 致命问题(
StringUtil拼写、3 处 NPE) - 统一
AfterSDWApproveHqListener注入方式 - 统一两处错误注入(static 块 → 构造注入)
- 补上
AfterInspectImproveCompleteListener和AfterCloseoutApprovedListener的 TODO
6.2 中期(下个迭代)
- 将
getStringFromData/getListFromData/getListSize提取为共用FlowJsonParser工具类 - 将 2 级父 Execution 攀爬逻辑提取为可复用的模板方法或工具方法
- 统一
parseDeptId到完整版 - 统一
StringUtils引用为org.apache.commons.lang3.StringUtils - 清理未使用的 import
6.3 长期(架构层面)
- 考虑引入流程模块 SDK 层(
flow-module-sdk),包含:- 基类
AbstractFlowConfig/AbstractFlowExpression - 泛型 JSON 解析器
- 通用 Listener 模板(变量攀爬、审批记录持久化)
- 基类
- 统一事务策略文档:哪些 Listener 需要
@Transactional,何时用悲观锁 vs 乐观锁 - 建立模块创建 CheckList,新模块上线前逐项验证(DI 一致性、NPE 防御、事务标注、无硬编码等)
七、CheckList:新流程模块自查项
| # | 检查项 | 说明 |
|---|---|---|
| 1 | Config 注入方式 | @Component + @ConfigurationProperties(prefix = "flow-biz") |
| 2 | Config 内嵌类 | 属性名与 yml key 中划线自动映射驼峰 |
| 3 | Flow 注入方式 | @RequiredArgsConstructor(onConstructor_ = @Autowired) + private final |
| 4 | Flow Bean 命名 | @Component("xxxFlow"),避免与其它模块冲突 |
| 5 | 所有公开方法守卫 null | Flow 方法中检查 getXxx() 返回 null |
| 6 | 是否复用 getStringFromData / getListFromData |
优先使用共用工具类,不复写 |
| 7 | Listener 注入方式 | 统一构造注入,禁止 static 块和字段注入 |
| 8 | Listener 类型选择 | 需在任务创建前初始化变量 → ExecutionListener;任务完成回调 → TaskListener |
| 9 | 事务注解 | 有 DB 写操作加 @Transactional(rollbackFor = Exception.class) |
| 10 | 锁策略 | 并发写同一行 → getByIdForUpdate;无并发 → getById |
| 11 | 异常处理策略 | 需阻断流程 → rethrow;可容忍 → catch + log + 返回默认值 |
| 12 | StringUtils |
统一使用 org.apache.commons.lang3.StringUtils |
| 13 | 无硬编码变量名 | 变量名统一从 Config 读取 |
| 14 | 无未使用 import | 提交前 IDE 自动优化 imports |
| 15 | @Override |
所有 notify() 方法加 @Override |
| 16 | yml 配置节 | 与 Config 内嵌类属性一一对应 |
| 17 | 无 TODO | 功能完整的 Listener 不应留 TODO |
| 18 | 日志规范 | 异常用 log.error、业务用 log.info、跳过/降级用 log.warn |