diff --git a/.gitignore b/.gitignore index f9cf829..1973d68 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ localDocs **/.claude **/yml *.bundle +jeecg-module-system/jeecg-system-start/config/ ## front **/*.lock diff --git a/jeecg-boot-module/jeecg-module-flow/pom.xml b/jeecg-boot-module/jeecg-module-flow/pom.xml deleted file mode 100644 index c7725bd..0000000 --- a/jeecg-boot-module/jeecg-module-flow/pom.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - 4.0.0 - - jeecg-boot-module - org.jeecgframework.boot - ${jeecgProjectVersion} - - - jeecg-module-flow - - - 21 - 21 - UTF-8 - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - false - - - - - - - - org.jeecgframework.boot - jeecg-boot-base-core - - - org.jeecgframework.boot - jeecg-system-local-api - - - org.jeecgframework.boot - jeecg-boot-module-bpm-flowable - 3.8.0 - compile - - - org.jeecgframework.boot - jeecg-system-biz - - - org.jeecgframework.boot - jeecg-module-supervision - - - - \ No newline at end of file diff --git a/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/FLOW-MODULE-AUDIT.md b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/FLOW-MODULE-AUDIT.md deleted file mode 100644 index 083294a..0000000 --- a/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/FLOW-MODULE-AUDIT.md +++ /dev/null @@ -1,244 +0,0 @@ -# 流程模块架构审计报告 - -> 审计范围:`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 行: - -```java -if(org.apache.commons.lang.StringUtil.isEmpty(url)) { -``` - -Apache Commons Lang 中不存在 `StringUtil` 类,应为 `StringUtils`。 - -**影响**:该行不会通过编译;如果通过(如 IDE 自动导入),运行时会抛出 `NoClassDefFoundError`。 - -### 2.2 链式调用直接 NPE(3 处) - -**`XiSpeakFlow.java`:** - -1. **行 166**:`xiSpeakConfig.getXiSpeak().getNeedSdwApproveCode()` — `getXiSpeak()` 可能返回 null(yml 缺失此段落时 Spring Boot 不会注入默认值,导致属性为 null) -2. **行 172**:`xiSpeakConfig.getXiSpeak().getFeedbackRightNowCode()` — 同上 -3. **行 178**:`xiSpeakConfig.getXiSpeak().getSdwLeaderListKey()` — 同上 - -同类方法 `getBgDeptLdUserIdList()` 行 37-41 做了守卫判断,但上述 3 处遗漏。 - -**`XiSpeakFlow.java` 行 310:** - -```java -public int getImplDeptIsEnd(DelegateExecution execution) { - return getDeptApproveDetail(execution).getIsEnd(); -} -``` - -`getDeptApproveDetail()` 可能返回 null(当 businessKey 为空、deptVar 为 null、entity 不存在时),直接调 `.getIsEnd()` 必抛 NPE。 - -### 2.3 `InspectImproveFlow` / `InspectCloseoutFlow` 所有公开方法配置 NPE - -```java -public List 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` | -| `XiSpeakFlow` | `getImplDeptNums` | `Integer` (变体) | -| `InspectImproveFlow` | `getStringFromData` | `String` | -| `InspectImproveFlow` | `getListFromData` | `List` | -| `InspectCloseoutFlow` | `getStringFromData` | `String` | -| `InspectCloseoutFlow` | `getListFromData` | `List` | - -**建议**:抽取为 `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()` 逻辑矛盾 - -```java -// 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. 补上 `AfterInspectImproveCompleteListener` 和 `AfterCloseoutApprovedListener` 的 TODO - -### 6.2 中期(下个迭代) - -5. 将 `getStringFromData` / `getListFromData` / `getListSize` 提取为共用 `FlowJsonParser` 工具类 -6. 将 2 级父 Execution 攀爬逻辑提取为可复用的模板方法或工具方法 -7. 统一 `parseDeptId` 到完整版 -8. 统一 `StringUtils` 引用为 `org.apache.commons.lang3.StringUtils` -9. 清理未使用的 import - -### 6.3 长期(架构层面) - -10. 考虑引入流程模块 SDK 层(`flow-module-sdk`),包含: - - 基类 `AbstractFlowConfig` / `AbstractFlowExpression` - - 泛型 JSON 解析器 - - 通用 Listener 模板(变量攀爬、审批记录持久化) -11. 统一事务策略文档:哪些 Listener 需要 `@Transactional`,何时用悲观锁 vs 乐观锁 -12. 建立模块创建 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` | diff --git a/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/FLOW-MODULE-GUIDE.md b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/FLOW-MODULE-GUIDE.md deleted file mode 100644 index c17319b..0000000 --- a/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/FLOW-MODULE-GUIDE.md +++ /dev/null @@ -1,139 +0,0 @@ -# 流程模块开发指南 - -本文档说明如何为业务模块创建对应的流程集成包,基于 `org.jeecg.xispeak` 的设计模式。 - -## 目录结构 - -``` -org/jeecg// -├── XxxConfig.java # 配置类,映射 YAML -├── XxxFlow.java # 流程表达式 Bean -└── listener/ - ├── AfterXxxListener.java # TaskListener / ExecutionListener - └── ... -``` - -## 三步创建流程模块 - -### Step 1: 创建 Config 配置类 - -读取 `application-flow.yml` 中 `flow-biz.` 段落。 - -```java -@Data -@Component -@ConfigurationProperties(prefix = "flow-biz") -public class XxxConfig { - private XxxProperties xxx; // 对应 yml 中 xxx 节点 - - @Data - public static class XxxProperties { - private String businessKey; // 业务主键变量名 - private String jsonDataKey; // JSON 数据变量名 - private String formUrl; // 业务表单路径 - private String flowCode; // 流程定义编码(可选) - // ... 按需添加字段名配置 - } -} -``` - -**要点:** -- 外层类 `@ConfigurationProperties(prefix = "flow-biz")`,内层属性名对应 YAML 键名 -- Spring 自动做中划线 → 驼峰映射(`business-key` → `businessKey`) -- 将业务字段名配置化,避免硬编码,方便流程设计调整 - -### Step 2: 创建 Flow 表达式 Bean - -供 Flowable 流程表达式 `${xxxFlow.method(args)}` 调用。 - -```java -@Slf4j -@Component("xxxFlow") // Bean 名即流程表达式前缀 -@RequiredArgsConstructor(onConstructor_ = @Autowired) -public class XxxFlow { - - private final XxxConfig xxxConfig; - // 注入需要的 Service - - // 提供流程表达式方法 - public List getApproverList(Object jsonData) { ... } - public int getApproverListLength(Object jsonData) { ... } -} -``` - -**常用方法模式:** -| 方法 | 用途 | 表达式示例 | -|------|------|-----------| -| `getXxxList(jsonData)` | 从 JSON 解析列表 | `${xxxFlow.getApproverList(json_data)}` | -| `getXxxListLength(jsonData)` | 返回列表长度 | `${xxxFlow.getApproverListLength(json_data) > 0}` | -| `getXxx(jsonData)` | 从 JSON 解析单值 | `${xxxFlow.getApplicant(json_data)}` | -| `getListSize(str)` | 逗号分隔字符串计数 | `${xxxFlow.getListSize(someVar)}` | - -**要点:** -- Bean 名要有命名空间意识,不可与其它模块冲突 -- 所有方法需防御 null/空字符串输入 -- Length 类方法用于流程网关条件判断 - -### Step 3: 创建 Listener - -监听流程节点事件,执行业务回调。 - -```java -@Slf4j -@Component("AfterXxxListener") // Bean 名供 Flowable 设计器引用 -@RequiredArgsConstructor(onConstructor_ = @Autowired) -public class AfterXxxListener implements TaskListener { - - private final XxxConfig xxxConfig; - - @Override - public void notify(DelegateTask delegateTask) { - // 1. 读取流程变量 - // 2. 执行业务逻辑 - // 3. 异常时慎重决定是否 rethrow - } -} -``` - -**Listener 类型:** -- `TaskListener` — 用户任务完成/创建时触发 -- `ExecutionListener` — 流程实例/节点生命周期事件 - -**要点:** -- 必须在 `notify` 开头做 null 防御(assignee、变量等可能为空) -- 需要读写业务表时加 `@Transactional(rollbackFor = Exception.class)` -- 异常处理:阻断流程 rethrow,不阻断 catch 后 log - -### Step 4: 添加 YAML 配置 - -在 `application-flow.yml` 的 `flow-biz` 下新增配置节: - -```yaml -flow-biz: - : - business-key: "business_id" - json-data-key: "json_data" - form-url: "path/to/BPMForm" - flow-code: "process_xxx" - # ... 模块专属字段 -``` - -## 已有模块速查 - -| 模块 | 包路径 | YAML 节点 | Flow Bean | 对应业务实体 | -|------|--------|-----------|-----------|-------------| -| 习讲话 | `org.jeecg.xispeak` | `xi-speak` | `xiSpeakFlow` | `BgXiSpeak` | -| 巡视整改 | `org.jeecg.inspectimprove` | `inspect-improve` | `inspectImproveFlow` | `DjInspectImprove` | -| 巡视整改销号 | `org.jeecg.inspectcloseout` | `inspect-closeout` | `inspectCloseoutFlow` | 待定 | - -## 流程表达式调试 - -在 Flowable 流程设计器中,表达式写法为 `${beanName.methodName(args)}`: - -``` -${xiSpeakFlow.getNeedSdwApproval(json_data) == 1} -${inspectImproveFlow.getMeasureResLeaderListLength(json_data) > 0} -${inspectCloseoutFlow.getApproverListLength(json_data) == 0} -``` - -`json_data` 变量由子流程启动监听器(如 `AfterSDWApproveHqListener`)从主流程注入,具体变量名见各模块 YAML 的 `json-data-key`。 diff --git a/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/Main.java b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/Main.java deleted file mode 100644 index 28f7a57..0000000 --- a/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/Main.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.jeecg; - -//TIP 要运行代码,请按 或 -// 点击装订区域中的 图标。 -public class Main { - public static void main(String[] args) { - //TIP 当文本光标位于高亮显示的文本处时按 - // 查看 IntelliJ IDEA 建议如何修正。 - System.out.printf("Hello and welcome!"); - - for (int i = 1; i <= 5; i++) { - //TIP 按 开始调试代码。我们已经设置了一个 断点 - // 但您始终可以通过按 添加更多断点。 - System.out.println("i = " + i); - } - } -} \ No newline at end of file diff --git a/jeecg-boot-module/pom.xml b/jeecg-boot-module/pom.xml index 364b0ad..bb73d50 100644 --- a/jeecg-boot-module/pom.xml +++ b/jeecg-boot-module/pom.xml @@ -18,7 +18,6 @@ jeecg-boot-module-airag - jeecg-module-flow jeecg-module-tools jeecg-module-im diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/Main.java b/jeecg-module-supervision/src/main/java/org/jeecg/Main.java deleted file mode 100644 index 28f7a57..0000000 --- a/jeecg-module-supervision/src/main/java/org/jeecg/Main.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.jeecg; - -//TIP 要运行代码,请按 或 -// 点击装订区域中的 图标。 -public class Main { - public static void main(String[] args) { - //TIP 当文本光标位于高亮显示的文本处时按 - // 查看 IntelliJ IDEA 建议如何修正。 - System.out.printf("Hello and welcome!"); - - for (int i = 1; i <= 5; i++) { - //TIP 按 开始调试代码。我们已经设置了一个 断点 - // 但您始终可以通过按 添加更多断点。 - System.out.println("i = " + i); - } - } -} \ No newline at end of file diff --git a/jeecg-module-system/jeecg-system-start/pom.xml b/jeecg-module-system/jeecg-system-start/pom.xml index ef6e8ed..b545667 100644 --- a/jeecg-module-system/jeecg-system-start/pom.xml +++ b/jeecg-module-system/jeecg-system-start/pom.xml @@ -33,10 +33,6 @@ jeecg-module-im ${jeecgProjectVersion} - - org.jeecgframework.boot - jeecg-module-flow - org.jeecgframework.boot diff --git a/pom.xml b/pom.xml index ce0e4d7..dfbffae 100644 --- a/pom.xml +++ b/pom.xml @@ -218,11 +218,6 @@ jeecg-system-biz ${jeecgProjectVersion} - - org.jeecgframework.boot - jeecg-module-flow - ${jeecgProjectVersion} - org.jeecgframework.boot