Files
supervision-test-repo/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/FLOW-MODULE-AUDIT.md
T
new_new_new 0bc279957f !31 feat(flow): 新增巡视整改和巡视整改销号流程模块
* feat(flow): 新增巡视整改和巡视整改销号流程模块
* feat(flow): 新增巡视整改和巡视整改销号流程 YAML 配置
* chore: 添加 localDocs 到 gitignore 并移除已跟踪的 docs 文件
2026-06-03 09:22:12 +00:00

11 KiB
Raw Blame History

流程模块架构审计报告

审计范围: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 链式调用直接 NPE3 处)

XiSpeakFlow.java

  1. 行 166xiSpeakConfig.getXiSpeak().getNeedSdwApproveCode()getXiSpeak() 可能返回 nullyml 缺失此段落时 Spring Boot 不会注入默认值,导致属性为 null)
  2. 行 172xiSpeakConfig.getXiSpeak().getFeedbackRightNowCode() — 同上
  3. 行 178xiSpeakConfig.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-improveinspect-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 处)

同一 "先 getVariableLocalgetVariable" 的 4 行模式出现在 4 个不同位置,无统一封装。

4.3 AfterSDWApproveHqListener 日志硬编码字段名

行 80 的日志 "解析 JSON 字段 pending_depts_id 失败" 硬编码了特定字段名,与运行时通过 props.getImplDeptKey() 读取的实际字段名可能不一致,降低排查效率。

4.4 StringUtils 库引用混乱

使用的 StringUtils
XiSpeakFlow org.apache.shiro.util.StringUtilsorg.apache.commons.lang.StringUtilsorg.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(如 LambdaQueryWrapperFlowNodeExpressionWorkFlowGlobalsTaskTaskITaskTaskService),推测是从其他文件复制后未清理。

5.3 缺少 @Override

xispeakfb AfterImplDeptLeaderApproveListenernotify() 方法缺少 @Override,失去编译期接口契约校验。

5.4 硬编码变量名 vs 配置化

AfterImplDeptLeaderApproveListenerAfterImplWorkerTemStoreListener 分别硬编码 "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 短期(本迭代)

  1. 修复 4 处 P0 致命问题(StringUtil 拼写、3 处 NPE
  2. 统一 AfterSDWApproveHqListener 注入方式
  3. 统一两处错误注入(static 块 → 构造注入)
  4. 补上 AfterInspectImproveCompleteListenerAfterCloseoutApprovedListener 的 TODO

6.2 中期(下个迭代)

  1. getStringFromData / getListFromData / getListSize 提取为共用 FlowJsonParser 工具类
  2. 将 2 级父 Execution 攀爬逻辑提取为可复用的模板方法或工具方法
  3. 统一 parseDeptId 到完整版
  4. 统一 StringUtils 引用为 org.apache.commons.lang3.StringUtils
  5. 清理未使用的 import

6.3 长期(架构层面)

  1. 考虑引入流程模块 SDK 层(flow-module-sdk),包含:
    • 基类 AbstractFlowConfig / AbstractFlowExpression
    • 泛型 JSON 解析器
    • 通用 Listener 模板(变量攀爬、审批记录持久化)
  2. 统一事务策略文档:哪些 Listener 需要 @Transactional,何时用悲观锁 vs 乐观锁
  3. 建立模块创建 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