!31 feat(flow): 新增巡视整改和巡视整改销号流程模块
* feat(flow): 新增巡视整改和巡视整改销号流程模块 * feat(flow): 新增巡视整改和巡视整改销号流程 YAML 配置 * chore: 添加 localDocs 到 gitignore 并移除已跟踪的 docs 文件
This commit is contained in:
@@ -0,0 +1,244 @@
|
||||
# 流程模块架构审计报告
|
||||
|
||||
> 审计范围:`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<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()` 逻辑矛盾
|
||||
|
||||
```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` |
|
||||
@@ -0,0 +1,139 @@
|
||||
# 流程模块开发指南
|
||||
|
||||
本文档说明如何为业务模块创建对应的流程集成包,基于 `org.jeecg.xispeak` 的设计模式。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
org/jeecg/<module>/
|
||||
├── XxxConfig.java # 配置类,映射 YAML
|
||||
├── XxxFlow.java # 流程表达式 Bean
|
||||
└── listener/
|
||||
├── AfterXxxListener.java # TaskListener / ExecutionListener
|
||||
└── ...
|
||||
```
|
||||
|
||||
## 三步创建流程模块
|
||||
|
||||
### Step 1: 创建 Config 配置类
|
||||
|
||||
读取 `application-flow.yml` 中 `flow-biz.<module-name>` 段落。
|
||||
|
||||
```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<String> 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:
|
||||
<module-name>:
|
||||
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`。
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package org.jeecg.inspectcloseout;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "flow-biz")
|
||||
public class InspectCloseoutConfig {
|
||||
|
||||
private InspectCloseoutProperties inspectCloseout;
|
||||
|
||||
@Data
|
||||
public static class InspectCloseoutProperties {
|
||||
/** 业务主键 key */
|
||||
private String businessKey;
|
||||
/** JSON 数据 key */
|
||||
private String jsonDataKey;
|
||||
/** 表单 URL */
|
||||
private String formUrl;
|
||||
/** 流程编码 */
|
||||
private String flowCode;
|
||||
/** 销号申请人 key */
|
||||
private String applicantKey;
|
||||
/** 销号审批人 key */
|
||||
private String approverKey;
|
||||
/** 整改事项关联 key */
|
||||
private String rectifyItemKey;
|
||||
/** 销号说明 key */
|
||||
private String closeoutDescKey;
|
||||
/** 完成状态 key */
|
||||
private String completionStatusKey;
|
||||
}
|
||||
}
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
package org.jeecg.inspectcloseout;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.jeecg.modules.extbpm.process.common.expression.FlowNodeExpression;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@Component("inspectCloseoutFlow")
|
||||
@RequiredArgsConstructor(onConstructor_ = @Autowired)
|
||||
public class InspectCloseoutFlow {
|
||||
|
||||
private final InspectCloseoutConfig inspectCloseoutConfig;
|
||||
private final FlowNodeExpression flowNodeExpression;
|
||||
|
||||
// ===================================================================
|
||||
// 通用工具方法
|
||||
// ===================================================================
|
||||
|
||||
private String getStringFromData(Object jsonData, String codeName, String logLabel) {
|
||||
if (jsonData == null || StringUtils.isBlank(codeName)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
JSONObject data;
|
||||
if (jsonData instanceof JSONObject) {
|
||||
data = (JSONObject) jsonData;
|
||||
} else if (jsonData instanceof String) {
|
||||
String jsonStr = (String) jsonData;
|
||||
if (StringUtils.isBlank(jsonStr)) return null;
|
||||
data = JSONObject.parseObject(jsonStr);
|
||||
} else {
|
||||
data = JSONObject.parseObject(JSONObject.toJSONString(jsonData));
|
||||
}
|
||||
String result = data.getString(codeName);
|
||||
log.info("{} 解析结果: {}", logLabel, result);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
log.warn("JSON解析失败: label={}, key={}", logLabel, codeName, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> getListFromData(Object jsonData, String codeName, String logLabel) {
|
||||
if (jsonData == null || StringUtils.isBlank(codeName)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
try {
|
||||
JSONObject data;
|
||||
if (jsonData instanceof JSONObject) {
|
||||
data = (JSONObject) jsonData;
|
||||
} else if (jsonData instanceof String) {
|
||||
String jsonStr = (String) jsonData;
|
||||
if (StringUtils.isBlank(jsonStr)) return Collections.emptyList();
|
||||
data = JSONObject.parseObject(jsonStr);
|
||||
} else {
|
||||
data = JSONObject.parseObject(JSONObject.toJSONString(jsonData));
|
||||
}
|
||||
String rawValue = data.getString(codeName);
|
||||
if (StringUtils.isBlank(rawValue)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<String> result = Arrays.stream(rawValue.split(","))
|
||||
.map(String::trim)
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.collect(Collectors.toList());
|
||||
log.info("{} 解析结果: {}", logLabel, result);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
log.warn("JSON解析失败: label={}, key={}", logLabel, codeName, e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// 流程表达式方法 — 用法示例: ${inspectCloseoutFlow.xxx}
|
||||
// ===================================================================
|
||||
|
||||
/**
|
||||
* ${inspectCloseoutFlow.getApproverList(json_data)}
|
||||
* 获取销号审批人列表
|
||||
*/
|
||||
public List<String> getApproverList(Object jsonData) {
|
||||
InspectCloseoutConfig.InspectCloseoutProperties props = inspectCloseoutConfig.getInspectCloseout();
|
||||
return getListFromData(jsonData, props.getApproverKey(), "销号审批人");
|
||||
}
|
||||
|
||||
/**
|
||||
* ${inspectCloseoutFlow.getApproverListLength(json_data)}
|
||||
*/
|
||||
public int getApproverListLength(Object jsonData) {
|
||||
return getApproverList(jsonData).size();
|
||||
}
|
||||
|
||||
/**
|
||||
* ${inspectCloseoutFlow.getApplicant(json_data)}
|
||||
* 获取销号申请人
|
||||
*/
|
||||
public String getApplicant(Object jsonData) {
|
||||
InspectCloseoutConfig.InspectCloseoutProperties props = inspectCloseoutConfig.getInspectCloseout();
|
||||
return getStringFromData(jsonData, props.getApplicantKey(), "销号申请人");
|
||||
}
|
||||
|
||||
/**
|
||||
* ${inspectCloseoutFlow.getRectifyItemId(json_data)}
|
||||
* 获取关联的整改事项ID
|
||||
*/
|
||||
public String getRectifyItemId(Object jsonData) {
|
||||
InspectCloseoutConfig.InspectCloseoutProperties props = inspectCloseoutConfig.getInspectCloseout();
|
||||
return getStringFromData(jsonData, props.getRectifyItemKey(), "整改事项ID");
|
||||
}
|
||||
|
||||
/**
|
||||
* ${inspectCloseoutFlow.getListSize(data)}
|
||||
* 计算逗号分隔字符串的元素个数
|
||||
*/
|
||||
public int getListSize(String data) {
|
||||
if (StringUtils.isEmpty(data)) {
|
||||
return 0;
|
||||
}
|
||||
return (int) Arrays.stream(data.split(","))
|
||||
.map(String::trim)
|
||||
.filter(StringUtils::isNotEmpty)
|
||||
.count();
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package org.jeecg.inspectcloseout.listener;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.flowable.engine.delegate.TaskListener;
|
||||
import org.flowable.task.service.delegate.DelegateTask;
|
||||
import org.jeecg.inspectcloseout.InspectCloseoutConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 巡视整改销号审批通过后,更新关联整改事项状态
|
||||
*/
|
||||
@Slf4j
|
||||
@Component("AfterCloseoutApprovedListener")
|
||||
@RequiredArgsConstructor(onConstructor_ = @Autowired)
|
||||
public class AfterCloseoutApprovedListener implements TaskListener {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final InspectCloseoutConfig inspectCloseoutConfig;
|
||||
|
||||
@Override
|
||||
public void notify(DelegateTask delegateTask) {
|
||||
Object businessKeyObj = delegateTask.getVariable(
|
||||
inspectCloseoutConfig.getInspectCloseout().getBusinessKey());
|
||||
String businessKey = Objects.toString(businessKeyObj, "").trim();
|
||||
|
||||
if (businessKey.isEmpty()) {
|
||||
log.warn("【销号审批监听器】未查询到业务表单ID,跳过。TaskId: {}", delegateTask.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: 根据实际业务实体,更新关联的整改事项状态为"已销号"
|
||||
log.info("【销号审批监听器】业务表单 [id={}] 销号审批通过", businessKey);
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package org.jeecg.inspectimprove;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "inspect-improve")
|
||||
public class InspectImproveConfig {
|
||||
|
||||
private InspectImproveProperties inspectImprove;
|
||||
|
||||
@Data
|
||||
public static class InspectImproveProperties {
|
||||
/** 业务主键 key */
|
||||
private String businessKey;
|
||||
/** JSON 数据 key */
|
||||
private String jsonDataKey;
|
||||
/** 表单 URL */
|
||||
private String formUrl;
|
||||
/** 流程编码 */
|
||||
private String flowCode;
|
||||
/** 措施责任领导 key */
|
||||
private String measureResLeaderKey;
|
||||
/** 措施责任部门 key */
|
||||
private String measureResDeptKey;
|
||||
/** 问题责任领导 key */
|
||||
private String questionResLeaderKey;
|
||||
/** 问题责任部门 key */
|
||||
private String questionResDeptKey;
|
||||
/** 整改措施 key */
|
||||
private String improveMeasureKey;
|
||||
/** 工作进展 key */
|
||||
private String workProgressKey;
|
||||
/** 完成状态 key */
|
||||
private String completionStatusKey;
|
||||
}
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
package org.jeecg.inspectimprove;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.jeecg.modules.dj.inspectimprove.entity.DjInspectImprove;
|
||||
import org.jeecg.modules.dj.inspectimprove.service.IDjInspectImproveService;
|
||||
import org.jeecg.modules.extbpm.process.common.expression.FlowNodeExpression;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@Component("inspectImproveFlow")
|
||||
@RequiredArgsConstructor(onConstructor_ = @Autowired)
|
||||
public class InspectImproveFlow {
|
||||
|
||||
private final InspectImproveConfig inspectImproveConfig;
|
||||
private final IDjInspectImproveService djInspectImproveService;
|
||||
private final FlowNodeExpression flowNodeExpression;
|
||||
|
||||
// ===================================================================
|
||||
// 通用工具方法
|
||||
// ===================================================================
|
||||
|
||||
/**
|
||||
* 从 JSON 数据中解析字符串字段
|
||||
*/
|
||||
private String getStringFromData(Object jsonData, String codeName, String logLabel) {
|
||||
if (jsonData == null || StringUtils.isBlank(codeName)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
JSONObject data;
|
||||
if (jsonData instanceof JSONObject) {
|
||||
data = (JSONObject) jsonData;
|
||||
} else if (jsonData instanceof String) {
|
||||
String jsonStr = (String) jsonData;
|
||||
if (StringUtils.isBlank(jsonStr)) return null;
|
||||
data = JSONObject.parseObject(jsonStr);
|
||||
} else {
|
||||
data = JSONObject.parseObject(JSONObject.toJSONString(jsonData));
|
||||
}
|
||||
String result = data.getString(codeName);
|
||||
log.info("{} 解析结果: {}", logLabel, result);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
log.warn("JSON解析失败: label={}, key={}", logLabel, codeName, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 JSON 数据中解析逗号分隔的列表字段
|
||||
*/
|
||||
private List<String> getListFromData(Object jsonData, String codeName, String logLabel) {
|
||||
if (jsonData == null || StringUtils.isBlank(codeName)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
try {
|
||||
JSONObject data;
|
||||
if (jsonData instanceof JSONObject) {
|
||||
data = (JSONObject) jsonData;
|
||||
} else if (jsonData instanceof String) {
|
||||
String jsonStr = (String) jsonData;
|
||||
if (StringUtils.isBlank(jsonStr)) return Collections.emptyList();
|
||||
data = JSONObject.parseObject(jsonStr);
|
||||
} else {
|
||||
data = JSONObject.parseObject(JSONObject.toJSONString(jsonData));
|
||||
}
|
||||
String rawValue = data.getString(codeName);
|
||||
if (StringUtils.isBlank(rawValue)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<String> result = Arrays.stream(rawValue.split(","))
|
||||
.map(String::trim)
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.collect(Collectors.toList());
|
||||
log.info("{} 解析结果: {}", logLabel, result);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
log.warn("JSON解析失败: label={}, key={}", logLabel, codeName, e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// 流程表达式方法 — 用法示例: ${inspectImproveFlow.xxx}
|
||||
// ===================================================================
|
||||
|
||||
/**
|
||||
* ${inspectImproveFlow.getMeasureResLeaderList(json_data)}
|
||||
* 获取措施责任领导列表
|
||||
*/
|
||||
public List<String> getMeasureResLeaderList(Object jsonData) {
|
||||
InspectImproveConfig.InspectImproveProperties props = inspectImproveConfig.getInspectImprove();
|
||||
return getListFromData(jsonData, props.getMeasureResLeaderKey(), "措施责任领导");
|
||||
}
|
||||
|
||||
/**
|
||||
* ${inspectImproveFlow.getMeasureResLeaderListLength(json_data)}
|
||||
*/
|
||||
public int getMeasureResLeaderListLength(Object jsonData) {
|
||||
return getMeasureResLeaderList(jsonData).size();
|
||||
}
|
||||
|
||||
/**
|
||||
* ${inspectImproveFlow.getQuestionResLeaderList(json_data)}
|
||||
* 获取问题责任领导列表
|
||||
*/
|
||||
public List<String> getQuestionResLeaderList(Object jsonData) {
|
||||
InspectImproveConfig.InspectImproveProperties props = inspectImproveConfig.getInspectImprove();
|
||||
return getListFromData(jsonData, props.getQuestionResLeaderKey(), "问题责任领导");
|
||||
}
|
||||
|
||||
/**
|
||||
* ${inspectImproveFlow.getQuestionResLeaderListLength(json_data)}
|
||||
*/
|
||||
public int getQuestionResLeaderListLength(Object jsonData) {
|
||||
return getQuestionResLeaderList(jsonData).size();
|
||||
}
|
||||
|
||||
/**
|
||||
* ${inspectImproveFlow.getMeasureResDeptList(json_data)}
|
||||
* 获取措施责任部门列表
|
||||
*/
|
||||
public List<String> getMeasureResDeptList(Object jsonData) {
|
||||
InspectImproveConfig.InspectImproveProperties props = inspectImproveConfig.getInspectImprove();
|
||||
return getListFromData(jsonData, props.getMeasureResDeptKey(), "措施责任部门");
|
||||
}
|
||||
|
||||
/**
|
||||
* ${inspectImproveFlow.getMeasureResDeptListLength(json_data)}
|
||||
*/
|
||||
public int getMeasureResDeptListLength(Object jsonData) {
|
||||
return getMeasureResDeptList(jsonData).size();
|
||||
}
|
||||
|
||||
/**
|
||||
* ${inspectImproveFlow.getMeasureResLeader(json_data)}
|
||||
* 获取措施责任领导(单值)
|
||||
*/
|
||||
public String getMeasureResLeader(Object jsonData) {
|
||||
InspectImproveConfig.InspectImproveProperties props = inspectImproveConfig.getInspectImprove();
|
||||
return getStringFromData(jsonData, props.getMeasureResLeaderKey(), "措施责任领导");
|
||||
}
|
||||
|
||||
/**
|
||||
* ${inspectImproveFlow.getListSize(json_data)}
|
||||
* 计算逗号分隔字符串的元素个数
|
||||
*/
|
||||
public int getListSize(String data) {
|
||||
if (StringUtils.isEmpty(data)) {
|
||||
return 0;
|
||||
}
|
||||
return (int) Arrays.stream(data.split(","))
|
||||
.map(String::trim)
|
||||
.filter(StringUtils::isNotEmpty)
|
||||
.count();
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package org.jeecg.inspectimprove.listener;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.flowable.engine.delegate.TaskListener;
|
||||
import org.flowable.task.service.delegate.DelegateTask;
|
||||
import org.jeecg.modules.dj.inspectimprove.entity.DjInspectImprove;
|
||||
import org.jeecg.modules.dj.inspectimprove.service.IDjInspectImproveService;
|
||||
import org.jeecg.inspectimprove.InspectImproveConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 巡视整改流程最终审批通过后,将业务表单状态标记为已完成
|
||||
*/
|
||||
@Slf4j
|
||||
@Component("AfterInspectImproveCompleteListener")
|
||||
@RequiredArgsConstructor(onConstructor_ = @Autowired)
|
||||
public class AfterInspectImproveCompleteListener implements TaskListener {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final InspectImproveConfig inspectImproveConfig;
|
||||
private final IDjInspectImproveService djInspectImproveService;
|
||||
|
||||
@Override
|
||||
public void notify(DelegateTask delegateTask) {
|
||||
Object businessKeyObj = delegateTask.getVariable(
|
||||
inspectImproveConfig.getInspectImprove().getBusinessKey());
|
||||
String businessKey = Objects.toString(businessKeyObj, "").trim();
|
||||
|
||||
if (businessKey.isEmpty()) {
|
||||
log.warn("【巡视整改完成监听器】未查询到业务表单ID,跳过。TaskId: {}", delegateTask.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
DjInspectImprove entity = djInspectImproveService.getById(businessKey);
|
||||
if (entity == null) {
|
||||
log.warn("【巡视整改完成监听器】业务数据不存在,id: {}", businessKey);
|
||||
return;
|
||||
}
|
||||
// TODO: 根据实际业务补充完成状态字段的更新逻辑
|
||||
log.info("【巡视整改完成监听器】业务表单 [id={}] 流程已完成", businessKey);
|
||||
} catch (Exception e) {
|
||||
log.error("【巡视整改完成监听器】更新业务表单异常, businessKey: " + businessKey, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user