diff --git a/.gitignore b/.gitignore
index cc93aad..e345e9b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
**/.idea
*.iml
rebel.xml
+localDocs
## backend
**/target
diff --git a/docs/流程引擎架构分析.md b/docs/流程引擎架构分析.md
deleted file mode 100644
index bf38c08..0000000
--- a/docs/流程引擎架构分析.md
+++ /dev/null
@@ -1,423 +0,0 @@
-# 流程引擎架构分析
-
-基于 Flowable 6.8.0,Jeecg 在之上封装了自定义的流程元数据管理层,Flovable 仅作为 BPMN 执行内核使用。
-
----
-
-## 整体分层
-
-```
-┌──────────────────────────────────────────────────┐
-│ 前端 (Vue 3) │
-│ 流程设计器 │ 待办任务 │ 审批表单 │ 流程列表 │
-└──────────────────┬───────────────────────────────┘
- │ REST API
-┌──────────────────┴───────────────────────────────┐
-│ ActTaskController │
-│ /act/task/list /act/task/processComplete ... │
-│ /act/process/deploy /act/process/list ... │
-└──────────────────┬───────────────────────────────┘
- │
-┌──────────────────┴───────────────────────────────┐
-│ 业务门面层 (Facade) │
-│ BpmBaseExtApiImpl — 对外统一入口 │
-│ ExtActProcessServiceImpl — 核心编排 (1493行) │
-└──────────────────┬───────────────────────────────┘
- │
- ┌────────────┼────────────┐
- ▼ ▼ ▼
-┌──────────┐ ┌──────────┐ ┌──────────┐
-│ ext_act_ │ │ Flowable │ │ 业务表单 │
-│ 元数据层 │ │ 执行引擎 │ │ 数据层 │
-└──────────┘ └──────────┘ └──────────┘
-```
-
----
-
-## 一、自定义元数据层(ext_act_* 14 张表)
-
-### 1.1 流程定义 — ext_act_process
-
-**Entity:** `ExtActProcess.java`
-
-双格式存储 BPMN 定义,同时支持可视化设计器和"简流"低代码 JSON 格式:
-
-| 字段 | 类型 | 说明 |
-|------|------|------|
-| `id` | String | 主键 |
-| `processKey` | String | 流程标识,跨版本唯一 |
-| `processName` | String | 流程名称 |
-| `processXml` | byte[] | 标准 BPMN 2.0 XML |
-| `processJson` | String | "简流"低代码设计器 JSON |
-| `startType` | Integer | 触发方式:手动/表事件/定时器/信号/按钮 |
-| `processStatus` | Integer | 0=未部署, 1=已部署 |
-| `deploymentId` | String | 关联 Flowable `act_re_deployment.ID_` |
-| `processDeployTime` | Date | 最近一次部署时间 |
-| `categoryId` | String | 流程分类 |
-| `updateCount` | Integer | @Version 乐观锁 |
-
-**版本管理:** `ext_act_process` 不维护版本号。版本由 Flowable 原生管理——每次 `deployProcess()` 在 `act_re_procdef` 中自动递增 `VERSION_`。历史版本仅在 Flowable 引擎表中保留。
-
-### 1.2 表单绑定 — ext_act_process_form
-
-**Entity:** `ExtActProcessForm.java`
-
-流程与业务表单的映射关系,运行时启动流程时以此表为依据:
-
-| 字段 | 说明 |
-|------|------|
-| `relationCode` | 流程编码(如 `bg_xi_speak_001`),作为流程启动的唯一标识 |
-| `processId` | 关联 `ext_act_process.id` |
-| `formType` | 1=Online在线表单, 2=设计器表单, 3=自定义开发 |
-| `formTableName` | 业务数据表名或设计器表名 |
-| `flowStatusCol` | 业务表中存储流程状态的字段名 |
-| `titleExp` | 标题表达式,如 `{name}的请假申请` |
-| `triggerAction` | 自动触发时机:add/update/delete |
-| `startSignalName` | 信号启动名称模式 |
-
-### 1.3 节点配置(三层结构)
-
-#### ext_act_process_node — 实时配置
-
-运行时直接读取此表。每个节点可配置:
-
-| 配置项 | 字段 | 说明 |
-|--------|------|------|
-| 表单可编辑 | `formEditStatus` | 审批时表单是否可修改 |
-| 抄送 | `ccStatus` | 是否允许抄送 |
-| 自选下一节点 | `selnextUserStatus` | 是否允许选择下一审批人 |
-| 消息通知 | `msgStatus` | 是否发送通知 |
-| 转办 | `transferStatus` | 是否允许转办 |
-| 加签 | `addSignStatus` | 是否允许加签 |
-| 驳回 | `rejectStatus` | 是否允许驳回 |
-| 计数器加签 | `allowCounterSignAddUser` | 会签中是否允许添加会签人 |
-| 扩展配置 | `nodeConfigJson` | 邮件/钉钉/系统通知的详细配置 |
-
-#### ext_act_process_node_deploy — 部署快照
-
-`deployProcess()` 时从 `ext_act_process_node` 复制,按 `deploymentId` 隔离。设计上是为了版本锁定,但**当前代码实际运行时读取的是实时 `ext_act_process_node`**,并非快照。代码注释标注了已知问题:
-
-```java
-// ActTaskController.java line 322
-// TODO bug, 这里读取的不是发布后的配置
-```
-
-#### ext_act_process_node_auth — 字段权限
-
-按节点+表单字段维度控制:可见/禁用/必填。支持 Online 表单(formType=1)和设计器表单(formType=2)。
-
-| 字段 | 说明 |
-|------|------|
-| `ruleType` | 1=显示, 2=禁用 |
-| `required` | 是否必填 |
-| `desformComKey` | 设计器表单控件 key |
-
-### 1.4 运行时关联 — ext_act_flow_data
-
-**Entity:** `ExtActFlowData.java`
-
-业务记录与 Flowable 流程实例的桥接表,是运行时状态的核心:
-
-```
-formDataId + processKey + relationCode → processInstId
-```
-
-生命周期:
-
-```
-草稿 (bpmStatus="1")
- → 创建记录,processInstId = NULL
-
-运行中 (bpmStatus="2")
- → runtimeService.startProcessInstanceByKey() 后写入 processInstId
- → 同步回写业务表 flowStatusCol
-
-完成 (bpmStatus="3")
- → ProcessEndListener 正常结束时更新
-
-作废 (bpmStatus="4")
- → 流程被作废时更新
-
-撤回 (bpmStatus="callbackProcess")
- → 删除 ext_act_flow_data 记录,业务表状态重置
-```
-
-### 1.5 其他辅助表
-
-| 表名 | Entity | 用途 |
-|------|--------|------|
-| `ext_act_bpm_log` | `ExtActBpmLog` | 审批操作日志,每个节点审批时写入一条 |
-| `ext_act_bpm_file` | `ExtActBpmFile` | 审批附件,关联 `bpm_log_id` |
-| `ext_act_task_cc` | `ExtActTaskCc` | 任务抄送记录 |
-| `ext_act_task_notification` | `ExtActTaskNotification` | 催办/通知记录 |
-| `ext_act_listener` | `ExtActListener` | 监听器注册表,供设计器选配 |
-| `ext_act_expression` | `ExtActExpression` | 表达式库,候选人公式等 |
-| `ext_act_design_flow_data` | `ExtActDesignFlowData` | 设计器表单与流程的运行时关联 |
-
----
-
-## 二、核心流程:从设计到执行
-
-### 2.1 设计阶段
-
-```
-┌──────────────┐ saveProcess() ┌───────────────────────┐
-│ 流程设计器 │ ─────────────────────→ │ ext_act_process │
-│ (前端Vue) │ │ .processXml (BPMN) │
-└──────────────┘ │ .processJson (简流) │
- └───────────┬───────────┘
- │ BpmnXMLConverter
- │ 解析 UserTask 节点
- ▼
- ┌───────────────────────┐
- │ ext_act_process_node │
- │ 自动创建 + 默认配置 │
- └───────────────────────┘
-
- deployProcess()
-┌──────────────────────────────────────────────────────────────┐
-│ 1. repositoryService.createDeployment(processXml) │
-│ → act_re_deployment + act_re_procdef + act_ge_bytearray │
-│ 2. 快照 ext_act_process_node → ext_act_process_node_deploy │
-│ 3. 更新 processStatus = 1, processDeployTime │
-└──────────────────────────────────────────────────────────────┘
-```
-
-### 2.2 启动阶段
-
-```
-BpmBaseExtApiImpl.startMutilProcess(flowCode, formDataId, formUrl, username, jsonData)
- │
- ├─ 1. 查 ext_act_process_form WHERE relationCode = flowCode
- ├─ 2. 查业务数据 (SQL / MongoDB)
- ├─ 3. 组装流程变量:
- │ BPM_DATA_ID = formDataId
- │ BPM_FORM_KEY = flowCode
- │ BPM_FORM_CONTENT_URL = formUrl
- │ BPM_BIZ_TITLE = 标题表达式解析结果
- │ BPM_STATUS = "2" (处理中)
- │ BPM_FORM_TYPE = 1/2/3
- │ json_data = 业务表单数据 JSON
- │
- ├─ 4. identityService.setAuthenticatedUserId(initiator)
- ├─ 5. runtimeService.startProcessInstanceByKey(processKey, businessKey, variables)
- │ → 返回 processInstanceId
- │
- ├─ 6. 创建/更新 ext_act_flow_data (businessKey → processInstanceId 映射)
- └─ 7. 回写业务表 flowStatusCol = "2"
-```
-
-### 2.3 审批阶段
-
-```
-┌─────────────────────────────────────────────────────────────┐
-│ GET /act/task/list │
-│ → ActivitiServiceImpl.findPriTodoTasks() │
-│ 返回当前用户的待办任务列表 │
-└─────────────────────────────────────────────────────────────┘
-
-┌─────────────────────────────────────────────────────────────┐
-│ GET /act/task/getProcessTaskTransInfo?taskId=xxx │
-│ → 查询 ext_act_process_node(实时配置) │
-│ 返回: │
-│ formEditStatus — 表单是否可编辑 │
-│ ccStatus — 是否允许抄送 │
-│ selnextUserStatus — 是否允许自选下一节点 │
-│ rejectStatus — 是否允许驳回 │
-│ transferStatus — 是否允许转办 │
-│ addSignStatus — 是否允许加签 │
-│ bpmLogList — 审批历史时间线 │
-│ transitionList — 可选的下一步操作 │
-└─────────────────────────────────────────────────────────────┘
-
-┌─────────────────────────────────────────────────────────────┐
-│ POST /act/task/processComplete │
-│ → ExtActTaskCcServiceImpl.processComplete() │
-│ │
-│ 1. 设置 BPM_STATUS = "2" │
-│ 2. 写入审批结果变量 approve_result_{taskDefKey} │
-│ 3. 判断会签 → 设置候选人列表变量 │
-│ 4. 路由分发: │
-│ · model="1" 单分支 → handTaskComplete() │
-│ └ taskService.complete(taskId, variables) │
-│ · model="2" 多分支 → SuperTaskService.goProcessTaskNode()│
-│ └ runtimeService.createChangeActivityStateBuilder() │
-│ .moveActivityIdTo(nextNode) │
-│ · 驳回 → backToStepTask() │
-│ └ .moveActivityIdTo(历史节点) │
-│ 5. 处理加签/委派子任务 │
-│ 6. saveTaskHandBpmLog() → ext_act_bpm_log │
-│ 7. TaskUpdateFormDataListener 刷新流程变量 │
-└─────────────────────────────────────────────────────────────┘
-```
-
-### 2.4 结束阶段
-
-```
-流程到达结束事件
- → ProcessEndListener.notify(execution)
- │
- ├─ BPM_STATUS = "callbackProcess"
- │ → delete ext_act_flow_data
- │ → 业务表状态重置为 "1"
- │
- ├─ BPM_STATUS = "invalidProcess"
- │ → ext_act_flow_data.bpmStatus = "4"
- │ → 业务表 flowStatusCol = "4"
- │
- └─ 其他(正常完成)
- → ext_act_flow_data.bpmStatus = "3"
- → 业务表 flowStatusCol = "3"
-```
-
----
-
-## 三、两种流程定义方式
-
-| 维度 | DB 定义流程 | Java 定义流程 |
-|------|------------|-------------|
-| **BPMN 来源** | 可视化设计器 → `ext_act_process.processXml` | 手工编写 BPMN XML |
-| **监听器** | XML 中注册全限定类名 | `@Component` Spring Bean |
-| **表达式** | 直接写在 XML 条件中 | Java 方法:`${xiSpeakFlow.getNeedSdwApproval()}` |
-| **节点配置** | 前端设计器编辑 → `ext_act_process_node` | 同左或手动插入 |
-| **示例** | 通用审批流程 | `XiSpeakFlow`、`XiSpeakFeedbackFlow` |
-| **部署方式** | `deployProcess()` | 同样 `deployProcess()` |
-| **代码位置** | — | `jeecg-module-flow` |
-
-### XiSpeak 示例
-
-`XiSpeakFlow.java` 注册为 Spring Bean `"xiSpeakFlow"`,在 BPMN XML 中通过表达式回调:
-
-```xml
-
- ${xiSpeakFlow.getNeedSdwApproval(jsonData)}
-
-```
-
-业务监听器在 BPMN XML 中以全限定类名注册:
-
-```xml
-
-```
-
----
-
-## 四、特殊流程能力
-
-| 能力 | 实现方式 | 关键类 |
-|------|---------|--------|
-| **会签** | Flowable `multiInstanceLoopCharacteristics`,运行时 XML 解析判断 | `SuperTaskService.checkUserTaskIsHuiQian()` |
-| **加签** | Flowable `Command` 动态修改 BPMN 模型,插入新 UserTask | `AfterSignUserTaskCmd` |
-| **驳回** | `runtimeService.createChangeActivityStateBuilder().moveActivityIdTo(历史节点)` | `ExtActTaskCcServiceImpl.backToStepTask()` |
-| **转办** | `taskService.setAssignee(taskId, newUserId)` | `ActTaskController.complaint()` |
-| **委派** | `taskService.delegateTask(taskId, delegateUser)` | `ActTaskController.delegate()` |
-| **撤回** | 设 `BPM_STATUS = "callbackProcess"` → `actProcessService.stopProcessInstanceById()` | `ActTaskController.callbackProcess()` |
-| **任务变量刷新** | 每个 UserTask 创建时从业务库拉最新表单数据注入流程变量 | `TaskUpdateFormDataListener` |
-| **信号启动** | 低代码"简流"模式,通过信号事件触发子流程 | `SignalProcessStartListener` |
-
----
-
-## 五、数据库表全景
-
-### Flowable 引擎表(47张)
-
-| 类别 | 前缀 | 数量 | 说明 |
-|------|------|------|------|
-| 运行时 | `act_ru_*` | 13 | 运行中的流程实例、任务、变量 |
-| 历史 | `act_hi_*` | 10 | 已完成的流程实例历史 |
-| 仓库 | `act_re_*` | 3 | 流程定义、部署、模型 |
-| 通用 | `act_ge_*` | 2 | 字节数组、属性 |
-| 身份 | `act_id_*` | 8 | 用户、组、权限 |
-| 事件 | `act_evt_*` / `flw_*` | 11 | 事件日志、事件定义、批量 |
-
-### 自定义表(14张)
-
-| 类别 | 表名 | 用途 |
-|------|------|------|
-| 流程定义 | `ext_act_process` | 流程定义(BPMN XML + 简流 JSON) |
-| 表单绑定 | `ext_act_process_form` | 流程与业务表单映射 |
-| 节点配置 | `ext_act_process_node` | 节点实时配置 |
-| 节点快照 | `ext_act_process_node_deploy` | 部署时节点配置快照 |
-| 字段权限 | `ext_act_process_node_auth` | 节点字段级权限 |
-| 运行时关联 | `ext_act_flow_data` | 业务数据 ↔ 流程实例 |
-| 设计器运行时 | `ext_act_design_flow_data` | 设计器表单 ↔ 流程实例 |
-| 审批日志 | `ext_act_bpm_log` | 审批操作记录 |
-| 审批附件 | `ext_act_bpm_file` | 审批附件 |
-| 抄送 | `ext_act_task_cc` | 任务抄送 |
-| 催办通知 | `ext_act_task_notification` | 催办记录 |
-| 监听器 | `ext_act_listener` | 监听器注册表 |
-| 表达式 | `ext_act_expression` | 表达式库 |
-
-### 业务表(与流程绑定)
-
-| 表名 | 模块 | 说明 |
-|------|------|------|
-| `task_task` | tasktask | 事项任务计划表,核心督办任务记录 |
-| `bg_xi_speak` | xispeak | 习总书记重要讲话指示批示 |
-| `bg_xi_speak_feedback` | xispeak | 讲话指示批示反馈 |
-| `task_approval_opinion` | taskapprovalopinion | 审批意见快照 |
-| `dj_inspect_improve` | inspectimprove | 巡视整改台账 |
-
----
-
-## 六、关键文件速查
-
-```
-jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/
-├── extbpm/process/
-│ ├── entity/
-│ │ ├── ExtActProcess.java ext_act_process 流程定义
-│ │ ├── ExtActProcessForm.java ext_act_process_form 表单绑定
-│ │ ├── ExtActProcessNode.java ext_act_process_node 节点实时配置
-│ │ ├── ExtActProcessNodeDeployment.java ext_act_process_node_deploy 部署快照
-│ │ ├── ExtActProcessNodePermission.java ext_act_process_node_auth 字段权限
-│ │ ├── ExtActFlowData.java ext_act_flow_data 运行时关联
-│ │ ├── ExtActBpmLog.java ext_act_bpm_log 审批日志
-│ │ ├── ExtActBpmFile.java ext_act_bpm_file 审批附件
-│ │ ├── ExtActTaskCc.java ext_act_task_cc 抄送
-│ │ └── ExtActListener.java ext_act_listener 监听器注册
-│ ├── service/impl/
-│ │ ├── ExtActProcessServiceImpl.java ★ 核心编排服务 (1493行)
-│ │ ├── ExtActTaskCcServiceImpl.java 审批完成+路由逻辑
-│ │ ├── ExtActBpmLogServiceImpl.java 审批日志双写(旧表+新意见表)
-│ │ └── BpmBaseExtApiImpl.java 对外门面 Facade
-│ ├── listener/execution/
-│ │ ├── ProcessEndListener.java 流程结束状态同步
-│ │ ├── TaskUpdateFormDataListener.java 任务创建时刷新表单变量
-│ │ └── SignalProcessStartListener.java 简流信号启动
-│ └── common/
-│ └── WorkFlowGlobals.java 流程常量定义
-├── bpm/
-│ ├── controller/
-│ │ ├── ActTaskController.java 任务操作全部REST端点 (1633行)
-│ │ └── ActProcessInstanceController.java 流程实例管理端点
-│ ├── service/impl/
-│ │ ├── ActivitiServiceImpl.java 任务查询+多实例+加签
-│ │ ├── ActProcessService.java Flowable原生部署/激活/挂起
-│ │ └── SuperTaskService.java 节点跳跃+会签判断+转办
-│ └── cmd/
-│ └── AfterSignUserTaskCmd.java 动态加签 Command
-└── taskapprovalopinion/
- ├── entity/TaskApprovalOpinion.java task_approval_opinion 审批意见快照
- └── service/impl/ CRUD 实现
-
-jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/
-├── xispeak/
-│ ├── XiSpeakFlow.java 表达 Bean ($xiSpeakFlow)
-│ ├── XiSpeakConfig.java YAML 配置
-│ └── listener/ 7个业务监听器
-└── xispeakfb/
- ├── XiSpeakFeedbackFlow.java 反馈流程表达 Bean
- └── listener/ 反馈流程业务监听器
-```
-
----
-
-## 七、关键设计决策
-
-1. **双层定义**:`ext_act_process` 存 BPMN XML + 简流 JSON,同时服务可视化设计器和低代码平台
-2. **实时 vs 快照**:节点配置分 `ext_act_process_node`(实时)和 `ext_act_process_node_deploy`(快照),当前运行时实际读取实时表
-3. **双写审批记录**:`ExtActBpmLog`(旧表)+ `TaskApprovalOpinion`(新表),旧表写入成功后尽力双写新表,新表失败不影响主流程
-4. **门面模式**:`BpmBaseExtApiImpl` 是外部系统调用流程能力的唯一入口,隔离了内部实现细节
-5. **Java Bean 表达式**:XiSpeak 流程通过 Spring Bean 方法注入业务逻辑到 BPMN 条件表达式中,实现比 UEL 表达式更复杂的动态计算
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
new file mode 100644
index 0000000..083294a
--- /dev/null
+++ b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/FLOW-MODULE-AUDIT.md
@@ -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 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
new file mode 100644
index 0000000..c17319b
--- /dev/null
+++ b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/FLOW-MODULE-GUIDE.md
@@ -0,0 +1,139 @@
+# 流程模块开发指南
+
+本文档说明如何为业务模块创建对应的流程集成包,基于 `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/inspectcloseout/InspectCloseoutConfig.java b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/inspectcloseout/InspectCloseoutConfig.java
new file mode 100644
index 0000000..99f51e8
--- /dev/null
+++ b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/inspectcloseout/InspectCloseoutConfig.java
@@ -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;
+ }
+}
diff --git a/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/inspectcloseout/InspectCloseoutFlow.java b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/inspectcloseout/InspectCloseoutFlow.java
new file mode 100644
index 0000000..8ca5d22
--- /dev/null
+++ b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/inspectcloseout/InspectCloseoutFlow.java
@@ -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 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 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 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();
+ }
+}
diff --git a/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/inspectcloseout/listener/AfterCloseoutApprovedListener.java b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/inspectcloseout/listener/AfterCloseoutApprovedListener.java
new file mode 100644
index 0000000..bb0f00c
--- /dev/null
+++ b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/inspectcloseout/listener/AfterCloseoutApprovedListener.java
@@ -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);
+ }
+}
diff --git a/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/inspectimprove/InspectConstant.java b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/inspectimprove/InspectConstant.java
new file mode 100644
index 0000000..1e1ea12
--- /dev/null
+++ b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/inspectimprove/InspectConstant.java
@@ -0,0 +1,15 @@
+package org.jeecg.inspectimprove;
+
+import org.jeecg.modules.demo.dqinspecttask.entity.DqInspectTask;
+
+public class InspectConstant {
+ public static final String NEED_APPROVE_STR = "1";
+ public static final String NOT_NEED_APPROVE_STR = "0";
+ public static final String IMPL_DEPT_LEADER_KEY = "impl_dept_leader";
+ public static final String DQ_DEPT_LEADER_KEY = "dq_dept_leader";
+ public static final String IS_END_KEY = "is_end";
+ public static final String SUB_APPROVE_USER_KEY = "sub_approve_user";
+ public static final String SLD_DEPT_ID = "2044677562460508161";
+ public static final String SDWSJ_ROLE_ID = "2062796937607499777";
+ public static final int ZERO_INDEX = 0;
+}
diff --git a/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/inspectimprove/InspectImproveConfig.java b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/inspectimprove/InspectImproveConfig.java
new file mode 100644
index 0000000..a17211c
--- /dev/null
+++ b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/inspectimprove/InspectImproveConfig.java
@@ -0,0 +1,41 @@
+package org.jeecg.inspectimprove;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+@Data
+@Component
+@ConfigurationProperties(prefix = "flow-biz")
+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;
+ private String dqDeptId;
+ private String ldRoleId;
+ }
+}
diff --git a/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/inspectimprove/InspectImproveFlow.java b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/inspectimprove/InspectImproveFlow.java
new file mode 100644
index 0000000..173f7bd
--- /dev/null
+++ b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/inspectimprove/InspectImproveFlow.java
@@ -0,0 +1,350 @@
+package org.jeecg.inspectimprove;
+
+import com.alibaba.fastjson.JSONObject;
+import com.jeecg.weibo.exception.BusinessException;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.StringUtils;
+import org.flowable.engine.RuntimeService;
+import org.flowable.engine.delegate.DelegateExecution;
+import org.jeecg.common.system.api.ISysBaseAPI;
+import org.jeecg.modules.demo.dqinspecttask.entity.DqInspectTask;
+import org.jeecg.modules.demo.dqinspecttask.service.IDqInspectTaskService;
+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.jeecg.xispeak.XiSpeakConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+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;
+ private final XiSpeakConfig xiSpeakConfig;
+ private final ISysBaseAPI iSysBaseAPI;
+ private final IDqInspectTaskService dqInspectTaskService;
+ private final RuntimeService runtimeService;
+
+ //流程表达式内用法 ${inspectImproveFlow.getDqDeptLdUserIdList()}
+ public List getDqDeptLdUserIdList() {
+ InspectImproveConfig.InspectImproveProperties props = inspectImproveConfig.getInspectImprove();
+ if (props == null || !org.apache.shiro.util.StringUtils.hasText(props.getDqDeptId())) {
+ log.error("【巡视整改-流程表达式】流程配置缺失, key: flow-biz.inspect-improve.dq-dept-id");
+ return Collections.emptyList();
+ }
+
+ log.info("【巡视整改-流程表达式】查询部门角色用户列表, deptId: {}, roleId: {}", props.getDqDeptId(), props.getLdRoleId());
+ List userList = iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi(
+ props.getDqDeptId(),
+ props.getLdRoleId()
+ );
+ log.info("【巡视整改-流程表达式】查询到用户列表, userList: {}", userList);
+ return userList;
+ }
+
+ // 流程表达式内用法 ${inspectImproveFlow.getSDWSJ()}
+ public String getSDWSJ() {
+ String deptId = InspectConstant.SLD_DEPT_ID;
+ String roleId = InspectConstant.SDWSJ_ROLE_ID;
+
+ log.info("【巡视整改-流程表达式】开始查询所党委书记用户, deptId: {}, roleId: {}", deptId, roleId);
+
+ List userList = iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi(deptId, roleId);
+
+ // 1. 强校验:未查询到任何用户
+ if (CollectionUtils.isEmpty(userList)) {
+ log.error("【巡视整改-流程表达式getSDWSJ】异常:未查询到所党委书记用户!deptId: {}, roleId: {}", deptId, roleId);
+ throw new BusinessException("流程流转失败:未配置所党委书记用户,请联系管理员!");
+ }
+
+ // 2. 强校验:查询到多个用户(不唯一)
+ if (userList.size() > 1) {
+ log.error("【巡视整改-流程表达式getSDWSJ】严重异常:期望唯一用户,但查询到多个党委书记用户!数量: {}, 用户列表: {}", userList.size(), userList);
+ throw new BusinessException("流程流转失败:系统检测到存在多个党委书记用户,数据不唯一,请检查系统角色配置!");
+ }
+
+ // 3. 唯一性通过,校验提取出的值是否有效
+ String targetUser = userList.get(InspectConstant.ZERO_INDEX);
+ if (StringUtils.isEmpty(targetUser)) {
+ log.error("【巡视整改-流程表达式getSDWSJ】异常:查询到的党委书记用户名为空字符串!");
+ throw new BusinessException("流程流转失败:党委书记用户名数据非法!");
+ }
+
+ log.info("【巡视整改-流程表达式getSDWSJ】成功匹配到唯一的所党委书记用户: {}", targetUser);
+ return targetUser;
+ }
+
+ //流程表达式内用法 ${inspectImproveFlow.getDqDeptLdUserIdListLength()}
+ public int getDqDeptLdUserIdListLength() {
+ List dqDeptLdUserIdList = this.getDqDeptLdUserIdList();
+ if (dqDeptLdUserIdList != null && !dqDeptLdUserIdList.isEmpty()) {
+ return dqDeptLdUserIdList.size();
+ }
+ log.warn("【巡视整改-流程表达式】dq部门ld角色没有对应用户");
+ return 0;
+ }
+
+ // 流程表达式内用法 ${inspectImproveFlow.getNeedDqLeaderApprove(execution)}
+ public String getNeedDqLeaderApprove(DelegateExecution execution) {
+ DqInspectTask temDqInspectTask = this.getDqInspectTask(execution);
+
+ // 如果上一步返回了 null(说明数据缺失),为了流程安全,这里直接抛出异常,阻止流程往错误的方向流转
+ if (temDqInspectTask == null) {
+ throw new IllegalStateException("无法判定是否需要审批:未找到对应的业务表单数据,流程暂停。");
+ }
+
+ // 字符串比对的优雅写法:把常量放在前面,天然防范 NPE
+ if (InspectConstant.NOT_NEED_APPROVE_STR.equals(temDqInspectTask.getIsNeedAppro())
+ || StringUtils.isBlank(temDqInspectTask.getIsNeedAppro())) {
+ return InspectConstant.NOT_NEED_APPROVE_STR;
+ }
+
+ return InspectConstant.NEED_APPROVE_STR;
+ }
+
+ // 流程表达式内用法:${inspectImproveFlow.getImplDeptLeaderList(execution)}
+ public List getImplDeptLeaderList(DelegateExecution execution) {
+
+ DqInspectTask temDqInspectTask = this.getDqInspectTask(execution);
+ if (temDqInspectTask == null) {
+ throw new IllegalStateException("无法获取审批人:未找到对应的业务表单数据,流程暂停。");
+ }
+
+ String measureDeptId = temDqInspectTask.getMeasureResDept();
+ if (StringUtils.isBlank(measureDeptId)) {
+ String errorMsg = String.format("流程流转失败: 业务表单[ID:%s]中的整改责任部门(measureResDept)为空,无法匹配审批人。", temDqInspectTask.getId());
+ log.error(errorMsg);
+ throw new IllegalStateException(errorMsg); // 保持严谨,核心数据缺失直接抛异常阻断
+ }
+
+
+ InspectImproveConfig.InspectImproveProperties props = inspectImproveConfig.getInspectImprove();
+ log.info("正在查询整改责任部门: {} 下的角色: {} 的领导列表", measureDeptId, props.getLdRoleId());
+ List leaderList = iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi(
+ measureDeptId,
+ props.getLdRoleId()
+ );
+
+ if (leaderList == null || leaderList.isEmpty()) {
+ log.warn("【节点预警】整改责任部门: {} 下未配置角色: {} 的用户,审批人列表为空!", measureDeptId, props.getLdRoleId());
+ return Collections.emptyList();
+ }
+
+ log.info("查询到的整改责任部门领导为:{}", leaderList);
+ return leaderList;
+ }
+
+ // 流程表达式内用法:${inspectImproveFlow.getImplDeptLeaderListLength(execution)}
+ public int getImplDeptLeaderListLength(DelegateExecution execution) {
+ return this.getImplDeptLeaderList(execution).size();
+ }
+
+ // 流程表达式内用法:${inspectImproveFlow.getIsEnd(execution)}
+ public String getIsEnd(DelegateExecution execution) {
+ Object value = runtimeService.getVariable(execution.getProcessInstanceId(), InspectConstant.IS_END_KEY);
+ return value != null ? value.toString() : null;
+ }
+
+ // 流程表达式内用法:${inspectImproveFlow.getSubApproveUser(execution)}
+ public String getSubApproveUser(DelegateExecution execution) {
+ Object value = runtimeService.getVariable(execution.getProcessInstanceId(), InspectConstant.SUB_APPROVE_USER_KEY);
+ return value != null ? value.toString() : null;
+ }
+
+ // 流程表达式内用法:${inspectImproveFlow.getDqDeptLeader(execution)}
+ public String getDqDeptLeader(DelegateExecution execution) {
+ Object value = runtimeService.getVariable(execution.getProcessInstanceId(), InspectConstant.DQ_DEPT_LEADER_KEY);
+ return value != null ? value.toString() : null;
+ }
+
+ // 流程表达式内用法:${inspectImproveFlow.getImplDeptLeader(execution)}
+ public String getImplDeptLeader(DelegateExecution execution) {
+ Object value = runtimeService.getVariable(execution.getProcessInstanceId(), InspectConstant.IMPL_DEPT_LEADER_KEY);
+ return value != null ? value.toString() : null;
+ }
+
+ // 流程表达式内用法:${inspectImproveFlow.getSLD(execution)}
+ public String getSLD(DelegateExecution execution) {
+ DqInspectTask tempDqInspectTask = this.getDqInspectTask(execution);
+ if (tempDqInspectTask == null) {
+ log.error("【巡视整改-流程表达式】未获取到巡视整改任务, businessKey: {}", execution.getProcessInstanceBusinessKey());
+ return null;
+ }
+ String sldUserStr = tempDqInspectTask.getChargeLeaderId();
+ if (StringUtils.isBlank(sldUserStr)) {
+ log.error("【巡视整改-流程表达式】未获取到所领导, businessKey: {}", execution.getProcessInstanceBusinessKey());
+ return null;
+ }
+ return sldUserStr;
+ }
+
+
+
+ private DqInspectTask getDqInspectTask(DelegateExecution execution) {
+ Object inspectTaskId = execution.getVariable(inspectImproveConfig.getInspectImprove().getBusinessKey());
+
+ // 统一校验 null 和空字符串
+ if (inspectTaskId == null || StringUtils.isBlank(inspectTaskId.toString())) {
+ log.error("【巡视整改-流程表达式】未获取到有效的业务表单ID, processInstanceId: {}, businessKey: {}", execution.getProcessInstanceId(), inspectTaskId);
+ return null; // 明确返回 null,不给上游返回空壳对象
+ }
+
+ // 如果 getById 没查到,MyBatis-Plus 也会返回 null
+ DqInspectTask task = dqInspectTaskService.getById(inspectTaskId.toString());
+ if (task == null) {
+ log.error("【巡视整改-流程表达式】未查询到业务表单记录, businessKey: {}, processInstanceId: {}", inspectTaskId, execution.getProcessInstanceId());
+ } else {
+ log.info("【巡视整改-流程表达式】已查询到业务表单, businessKey: {}", inspectTaskId);
+ }
+ return task;
+ }
+
+ // ===================================================================
+ // 通用工具方法
+ // ===================================================================
+
+ /**
+ * 从 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 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 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 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 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 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();
+ }
+}
diff --git a/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/inspectimprove/listener/AfterDqLeaderApproveListener.java b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/inspectimprove/listener/AfterDqLeaderApproveListener.java
new file mode 100644
index 0000000..10b7909
--- /dev/null
+++ b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/inspectimprove/listener/AfterDqLeaderApproveListener.java
@@ -0,0 +1,30 @@
+package org.jeecg.inspectimprove.listener;
+
+import lombok.RequiredArgsConstructor;
+import org.flowable.engine.RuntimeService;
+import org.flowable.engine.delegate.TaskListener;
+import org.flowable.task.service.delegate.DelegateTask;
+import org.jeecg.inspectimprove.InspectConstant;
+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;
+
+/**
+ * 巡视整改流程党群领导审批通过后,将党群领导写入流程变量
+ */
+@Component("InspectAfterDqLeaderApproveListener")
+@RequiredArgsConstructor(onConstructor_ = @Autowired)
+public class AfterDqLeaderApproveListener implements TaskListener {
+
+ private static final long serialVersionUID = 1L;
+ private final InspectImproveConfig inspectImproveConfig;
+ private final IDjInspectImproveService djInspectImproveService;
+ private final RuntimeService runtimeService;
+
+ @Override
+ public void notify(DelegateTask delegateTask) {
+ LeaderApproveHelper.handle(delegateTask, runtimeService,
+ InspectConstant.DQ_DEPT_LEADER_KEY, "【巡视整改-党群领导审批监听器】");
+ }
+}
diff --git a/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/inspectimprove/listener/AfterImplDeptLeaderApproveListener.java b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/inspectimprove/listener/AfterImplDeptLeaderApproveListener.java
new file mode 100644
index 0000000..4ad07a3
--- /dev/null
+++ b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/inspectimprove/listener/AfterImplDeptLeaderApproveListener.java
@@ -0,0 +1,30 @@
+package org.jeecg.inspectimprove.listener;
+
+import lombok.RequiredArgsConstructor;
+import org.flowable.engine.RuntimeService;
+import org.flowable.engine.delegate.TaskListener;
+import org.flowable.task.service.delegate.DelegateTask;
+import org.jeecg.inspectimprove.InspectConstant;
+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;
+
+/**
+ * 巡视整改流程部门领导审批通过后,将部门领导写入流程变量
+ */
+@Component("InspectAfterImplDeptLeaderApproveListener")
+@RequiredArgsConstructor(onConstructor_ = @Autowired)
+public class AfterImplDeptLeaderApproveListener implements TaskListener {
+
+ private static final long serialVersionUID = 1L;
+ private final InspectImproveConfig inspectImproveConfig;
+ private final IDjInspectImproveService djInspectImproveService;
+ private final RuntimeService runtimeService;
+
+ @Override
+ public void notify(DelegateTask delegateTask) {
+ LeaderApproveHelper.handle(delegateTask, runtimeService,
+ InspectConstant.IMPL_DEPT_LEADER_KEY, "【巡视整改-部门领导审批监听器】");
+ }
+}
diff --git a/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/inspectimprove/listener/AfterInspectImproveCompleteListener.java b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/inspectimprove/listener/AfterInspectImproveCompleteListener.java
new file mode 100644
index 0000000..bbb8b2f
--- /dev/null
+++ b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/inspectimprove/listener/AfterInspectImproveCompleteListener.java
@@ -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);
+ }
+ }
+}
diff --git a/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/inspectimprove/listener/LeaderApproveHelper.java b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/inspectimprove/listener/LeaderApproveHelper.java
new file mode 100644
index 0000000..9467875
--- /dev/null
+++ b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/inspectimprove/listener/LeaderApproveHelper.java
@@ -0,0 +1,24 @@
+package org.jeecg.inspectimprove.listener;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.flowable.engine.RuntimeService;
+import org.flowable.task.service.delegate.DelegateTask;
+
+@Slf4j
+public final class LeaderApproveHelper {
+
+ private LeaderApproveHelper() {}
+
+ public static void handle(DelegateTask delegateTask, RuntimeService runtimeService,
+ String variableKey, String logPrefix) {
+ String assignee = delegateTask.getAssignee();
+ if (StringUtils.isEmpty(assignee)) {
+ log.error("{}未获取到审批人, taskId: {}", logPrefix, delegateTask.getId());
+ return;
+ }
+ runtimeService.setVariable(delegateTask.getProcessInstanceId(), variableKey, assignee);
+ log.info("{}将审批人 {} 写入流程变量, processInstanceId: {}, 变量名: {}",
+ logPrefix, assignee, delegateTask.getProcessInstanceId(), variableKey);
+ }
+}
diff --git a/jeecg-boot-module/jeecg-module-flow/src/main/resources/application-flow.yml b/jeecg-boot-module/jeecg-module-flow/src/main/resources/application-flow.yml
index dc72fdd..d2f5639 100644
--- a/jeecg-boot-module/jeecg-module-flow/src/main/resources/application-flow.yml
+++ b/jeecg-boot-module/jeecg-module-flow/src/main/resources/application-flow.yml
@@ -12,12 +12,36 @@ flow-biz:
business-key: "business_id"
dept-worker-key: "subApproveUser"
xi-speak-fb-flowcode: "process_1778315114392"
- form-url: "bg/xispeak/components/BgXiSpeakBPMForm"
+ form-url: "bg/xispeak/components/BgXiSpeakBPMForm?showFeedbackInfo=1&showJiJianFields=1"
tem-impl-worker-key: "tem_impl_worker"
tem-impl-leader-key: "tem_impl_leader"
tem-bg-leader-key: "tem_bg_leader"
sdw-leader-list-key: "sdwLeaderList"
is-end-key: "isEnd"
+ inspect-improve:
+ dq-dept-id: "2044677628281720834"
+ ld-role-id: "2044680793306591234"
+ business-key: "business_id"
+ json-data-key: "json_data"
+ form-url: "dj/inspectimprove/components/DjInspectImproveBPMForm"
+ flow-code: ""
+ measure-res-leader-key: "measureResLeader"
+ measure-res-dept-key: "measureResDept"
+ question-res-leader-key: "questionResLeader"
+ question-res-dept-key: "questionResDept"
+ improve-measure-key: "improveMeasure"
+ work-progress-key: "workProgress"
+ completion-status-key: "completionStatus"
+ inspect-closeout:
+ business-key: "business_id"
+ json-data-key: "json_data"
+ form-url: "dj/inspectcloseout/components/InspectCloseoutBPMForm"
+ flow-code: ""
+ applicant-key: "applicant"
+ approver-key: "approver"
+ rectify-item-key: "rectifyItemId"
+ closeout-desc-key: "closeoutDesc"
+ completion-status-key: "completionStatus"
xi-speak-fb:
dept-id: "x"
business-id: "business_id"
diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/entity/BgXiSpeak.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/entity/BgXiSpeak.java
index 25bfaf4..e7f2ede 100644
--- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/entity/BgXiSpeak.java
+++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/entity/BgXiSpeak.java
@@ -165,7 +165,8 @@ public class BgXiSpeak implements Serializable {
@Schema(description = "督办次数")
private java.lang.Integer supervisionCount;
/**dw领导userId字段*/
- @Excel(name = "领导userId字段", width = 15)
+ @Excel(name = "领导userId字段", width = 15, dictTable = "sys_user", dicText = "realname", dicCode = "username")
+ @Dict(dictTable = "sys_user", dicText = "realname", dicCode = "username")
@Schema(description = "领导userId字段")
private java.lang.String sdwLeaderList;
/**dw领导userId字段*/
diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/service/impl/BgXiSpeakServiceImpl.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/service/impl/BgXiSpeakServiceImpl.java
index 3156842..ebd3408 100644
--- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/service/impl/BgXiSpeakServiceImpl.java
+++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/service/impl/BgXiSpeakServiceImpl.java
@@ -3,6 +3,7 @@ package org.jeecg.modules.bg.xispeak.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.flowable.engine.HistoryService;
import org.flowable.engine.RuntimeService;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.util.oConvertUtils;
@@ -10,6 +11,8 @@ import org.jeecg.common.system.vo.SelectTreeModel;
import org.jeecg.modules.bg.xispeak.entity.BgXiSpeak;
import org.jeecg.modules.bg.xispeak.entity.DeptApproveDetail;
import org.jeecg.modules.bg.xispeak.entity.DeptApproveDetailMap;
+import org.jeecg.modules.extbpm.process.entity.ExtActTaskCc;
+import org.jeecg.modules.extbpm.process.service.IExtActTaskCcService;
import org.jeecg.modules.bg.xispeak.mapper.BgXiSpeakMapper;
import org.jeecg.modules.bg.xispeak.service.IBgXiSpeakService;
import org.jeecg.modules.taskapprovalopinion.entity.TaskApprovalOpinion;
@@ -40,7 +43,9 @@ public class BgXiSpeakServiceImpl extends ServiceImpl()
.eq(TaskApprovalOpinion::getProcessInstId, procId));
+
+ // 删除抄送记录
+ extActTaskCcService.remove(
+ new LambdaQueryWrapper()
+ .eq(ExtActTaskCc::getProcInstId, procId));
}
// 4. 重新创建一个干净的 Wrapper,绝不能带 for update!
diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/demo/dqinspecttask/controller/DqInspectProgressController.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/demo/dqinspecttask/controller/DqInspectProgressController.java
new file mode 100644
index 0000000..5447d53
--- /dev/null
+++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/demo/dqinspecttask/controller/DqInspectProgressController.java
@@ -0,0 +1,166 @@
+package org.jeecg.modules.demo.dqinspecttask.controller;
+
+import java.util.Arrays;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.jeecg.common.api.vo.Result;
+import org.jeecg.common.system.query.QueryGenerator;
+import org.jeecg.modules.demo.dqinspecttask.entity.DqInspectProgress;
+import org.jeecg.modules.demo.dqinspecttask.service.IDqInspectProgressService;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.extern.slf4j.Slf4j;
+
+import org.jeecgframework.poi.excel.ExcelImportUtil;
+import org.jeecgframework.poi.excel.def.NormalExcelConstants;
+import org.jeecgframework.poi.excel.entity.ExportParams;
+import org.jeecgframework.poi.excel.entity.ImportParams;
+import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
+import org.jeecg.common.system.base.controller.JeecgController;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.multipart.MultipartHttpServletRequest;
+import org.springframework.web.servlet.ModelAndView;
+import com.alibaba.fastjson.JSON;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Operation;
+import org.jeecg.common.aspect.annotation.AutoLog;
+
+/**
+ * @Description: 工作进展反馈
+ * @Author: jeecg-boot
+ * @Date: 2026-05-28
+ * @Version: V1.0
+ */
+@Tag(name="工作进展反馈")
+@RestController
+@RequestMapping("/dqinspecttask/dqInspectTask")
+@Slf4j
+public class DqInspectProgressController extends JeecgController {
+ @Autowired
+ private IDqInspectProgressService dqInspectProgressService;
+
+ /**
+ * 分页列表查询
+ *
+ * @param dqInspectProgress
+ * @param pageNo
+ * @param pageSize
+ * @param req
+ * @return
+ */
+ @Operation(summary="工作进展反馈-分页列表查询")
+ @GetMapping(value = "/listDqInspectProgressByMainId")
+ public Result> queryPageList(DqInspectProgress dqInspectProgress,
+ @RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
+ @RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
+ HttpServletRequest req) {
+ QueryWrapper queryWrapper = QueryGenerator.initQueryWrapper(dqInspectProgress, req.getParameterMap());
+ Page page = new Page(pageNo, pageSize);
+ IPage pageList = dqInspectProgressService.page(page, queryWrapper);
+ return Result.OK(pageList);
+ }
+
+ /**
+ * 添加
+ *
+ * @param dqInspectProgress
+ * @return
+ */
+ @AutoLog(value = "工作进展反馈-添加")
+ @Operation(summary="工作进展反馈-添加")
+ @PostMapping(value = "/addDqInspectProgress")
+ public Result add(@RequestBody DqInspectProgress dqInspectProgress) {
+ dqInspectProgressService.save(dqInspectProgress);
+ return Result.OK("添加成功!");
+ }
+
+ /**
+ * 编辑
+ *
+ * @param dqInspectProgress
+ * @return
+ */
+ @AutoLog(value = "工作进展反馈-编辑")
+ @Operation(summary="工作进展反馈-编辑")
+ @PostMapping(value = "/editDqInspectProgress")
+ public Result edit(@RequestBody DqInspectProgress dqInspectProgress) {
+ dqInspectProgressService.updateById(dqInspectProgress);
+ return Result.OK("编辑成功!");
+ }
+
+ /**
+ * 通过id删除
+ *
+ * @param id
+ * @return
+ */
+ @AutoLog(value = "工作进展反馈-通过id删除")
+ @Operation(summary="工作进展反馈-通过id删除")
+ @DeleteMapping(value = "/deleteDqInspectProgress")
+ public Result delete(@RequestParam(name="id",required=true) String id) {
+ dqInspectProgressService.removeById(id);
+ return Result.OK("删除成功!");
+ }
+
+ /**
+ * 批量删除
+ *
+ * @param ids
+ * @return
+ */
+ @AutoLog(value = "工作进展反馈-批量删除")
+ @Operation(summary="工作进展反馈-批量删除")
+ @DeleteMapping(value = "/deleteBatchDqInspectProgress")
+ public Result deleteBatch(@RequestParam(name="ids",required=true) String ids) {
+ this.dqInspectProgressService.removeByIds(Arrays.asList(ids.split(",")));
+ return Result.OK("批量删除成功!");
+ }
+
+ /**
+ * 通过id查询
+ *
+ * @param id
+ * @return
+ */
+ @Operation(summary="工作进展反馈-通过id查询")
+ @GetMapping(value = "/queryDqInspectProgressById")
+ public Result queryById(@RequestParam(name="id",required=true) String id) {
+ DqInspectProgress dqInspectProgress = dqInspectProgressService.getById(id);
+ if(dqInspectProgress==null) {
+ return Result.error("未找到对应数据");
+ }
+ return Result.OK(dqInspectProgress);
+ }
+
+ /**
+ * 导出excel
+ *
+ * @param request
+ * @param dqInspectProgress
+ */
+ @RequestMapping(value = "/exportDqInspectProgress")
+ public ModelAndView exportXls(HttpServletRequest request, DqInspectProgress dqInspectProgress) {
+ return super.exportXls(request, dqInspectProgress, DqInspectProgress.class, "工作进展反馈");
+ }
+
+ /**
+ * 通过excel导入数据
+ *
+ * @param request
+ * @param response
+ * @return
+ */
+ @RequestMapping(value = "/importDqInspectProgress", method = RequestMethod.POST)
+ public Result> importExcel(HttpServletRequest request, HttpServletResponse response) {
+ return super.importExcel(request, response, DqInspectProgress.class);
+ }
+
+}
diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/demo/dqinspecttask/entity/DqInspectProgress.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/demo/dqinspecttask/entity/DqInspectProgress.java
index 849c5e0..df6e7a5 100644
--- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/demo/dqinspecttask/entity/DqInspectProgress.java
+++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/demo/dqinspecttask/entity/DqInspectProgress.java
@@ -67,9 +67,9 @@ public class DqInspectProgress implements Serializable {
@Schema(description = "反馈人姓名")
private java.lang.String feedbackPersonName;
/**实际完成时间*/
- @Excel(name = "实际完成时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
- @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
- @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+ @Excel(name = "实际完成时间", width = 20, format = "yyyy-MM-dd")
+ @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd")
+ @DateTimeFormat(pattern="yyyy-MM-dd")
@Schema(description = "实际完成时间")
private java.util.Date actualTime;
/**工作进展*/
diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/demo/dqinspecttask/entity/DqInspectTask.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/demo/dqinspecttask/entity/DqInspectTask.java
index 12df303..9bd525b 100644
--- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/demo/dqinspecttask/entity/DqInspectTask.java
+++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/demo/dqinspecttask/entity/DqInspectTask.java
@@ -104,6 +104,11 @@ public class DqInspectTask implements Serializable {
@Dict(dictTable = "sys_user", dicText = "realname", dicCode = "username")
@Schema(description = "党群领导")
private java.lang.String supDeptleaderid;
+ /**分管所领导*/
+ @Excel(name = "分管所领导", width = 15, dictTable = "sys_user", dicText = "realname", dicCode = "username")
+ @Dict(dictTable = "sys_user", dicText = "realname", dicCode = "username")
+ @Schema(description = "分管所领导")
+ private java.lang.String chargeLeaderId;
/**检查情况*/
@Excel(name = "检查情况", width = 15)
@Schema(description = "检查情况")
diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/demo/dqinspecttask/vo/DqInspectTaskPage.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/demo/dqinspecttask/vo/DqInspectTaskPage.java
index 2d8b0dd..0552ce2 100644
--- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/demo/dqinspecttask/vo/DqInspectTaskPage.java
+++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/demo/dqinspecttask/vo/DqInspectTaskPage.java
@@ -82,6 +82,11 @@ public class DqInspectTaskPage {
@Dict(dictTable = "sys_depart", dicText = "depart_name", dicCode = "id")
@Schema(description = "措施责任部门")
private java.lang.String measureResDept;
+ /**分管所领导*/
+ @Excel(name = "分管所领导", width = 15, dictTable = "sys_user", dicText = "realname", dicCode = "username")
+ @Dict(dictTable = "sys_user", dicText = "realname", dicCode = "username")
+ @Schema(description = "分管所领导")
+ private java.lang.String chargeLeaderId;
/**整改交付成果*/
@Excel(name = "整改交付成果", width = 15)
@Schema(description = "整改交付成果")
diff --git a/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/quartz/job/TaskJob.java b/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/quartz/job/TaskJob.java
index 57aa655..9b0a7cc 100644
--- a/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/quartz/job/TaskJob.java
+++ b/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/quartz/job/TaskJob.java
@@ -45,14 +45,15 @@ public class TaskJob implements Job {
long startTime = System.currentTimeMillis();
LocalDateTime startDateTime = LocalDateTime.now();
-// 1. 查询建议:只查询必要的字段,避免 select *
+ // 1. 查询建议:只查询必要的字段,避免 select *
List executeTaskTaskList = taskTaskServiceImpl
.lambdaQuery()
.select(TaskTask::getId, TaskTask::getFlowCode, TaskTask::getJsonData,
TaskTask::getFormUrl, TaskTask::getCreateBy)
.eq(TaskTask::getBpmStatus, 1)
.eq(TaskTask::getTriggerType, FlowConstants.Strategy.RECURRING_ONLY)
- .lt(TaskTask::getStartTime, startDateTime)
+ .ge(TaskTask::getStartTime, startDateTime.toLocalDate().atStartOfDay())
+ .lt(TaskTask::getStartTime, startDateTime.toLocalDate().plusDays(1).atStartOfDay())
.list();
// 2. 集合判空实践