From 68eec9f2a22d7f21360ddb5aef1522290dbf450f Mon Sep 17 00:00:00 2001 From: wsm <2746563060@侵权。> Date: Wed, 27 May 2026 20:25:43 +0800 Subject: [PATCH] =?UTF-8?q?feat(xispeak):=20=E9=83=A8=E9=97=A8=E5=AE=A1?= =?UTF-8?q?=E6=89=B9=E4=BF=A1=E6=81=AF=E6=94=B9=E4=B8=BA=E6=8C=81=E4=B9=85?= =?UTF-8?q?=E5=8C=96=E5=88=B0=E4=B8=9A=E5=8A=A1=E8=A1=A8=E5=8D=95JSON?= =?UTF-8?q?=EF=BC=8C=E4=B8=8D=E5=86=8D=E4=BE=9D=E8=B5=96=E6=B5=81=E7=A8=8B?= =?UTF-8?q?=E5=BC=95=E6=93=8E=E5=8F=98=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DeptApproveDetail 新增 isEnd/implWorker 字段 - 新增 saveSubApprove 接口,直接写入 approveInfo JSON - XiSpeakFlow: getImplDeptLeaderIsEnd/getDeptWorkerList 改为 JSON 优先 + 流程变量兜底,兼容旧流程实例 - XiSpeakFlow: 新增 getImplDeptIsEnd(execution, deptId) 流程表达式 - XiSpeakFeedbackFlow: 新增 getImplDeptIsEnd/getImplLeaderAsWorker,提炼公共 getDeptApproveDetailByTask - FlowNodeExpression: 新增 getSonProcessVariableInt/getSonProcessVariableBoolean 类型安全工具方法 Co-Authored-By: Claude Opus 4.7 --- .../java/org/jeecg/xispeak/XiSpeakConfig.java | 1 + .../java/org/jeecg/xispeak/XiSpeakFlow.java | 138 ++++++++++++------ .../jeecg/xispeakfb/XiSpeakFeedbackFlow.java | 50 +++++++ .../src/main/resources/application-flow.yml | 1 + .../controller/BgXiSpeakController.java | 20 +++ .../bg/xispeak/entity/DeptApproveDetail.java | 2 + .../bg/xispeak/service/IBgXiSpeakService.java | 6 + .../service/impl/BgXiSpeakServiceImpl.java | 38 +++++ 8 files changed, 212 insertions(+), 44 deletions(-) diff --git a/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeak/XiSpeakConfig.java b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeak/XiSpeakConfig.java index c69c064..41023d5 100644 --- a/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeak/XiSpeakConfig.java +++ b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeak/XiSpeakConfig.java @@ -42,5 +42,6 @@ public class XiSpeakConfig { private String temImplLeaderKey; private String temBgLeaderKey; private String sdwLeaderListKey; + private String isEndKey; } } diff --git a/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeak/XiSpeakFlow.java b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeak/XiSpeakFlow.java index e4f7b93..a5ceb7c 100644 --- a/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeak/XiSpeakFlow.java +++ b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeak/XiSpeakFlow.java @@ -10,6 +10,9 @@ import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.DelegateExecution; import org.jeecg.common.constant.SymbolConstant; import org.jeecg.common.system.api.ISysBaseAPI; +import org.jeecg.modules.bg.xispeak.entity.BgXiSpeak; +import org.jeecg.modules.bg.xispeak.entity.DeptApproveDetail; +import org.jeecg.modules.bg.xispeak.service.IBgXiSpeakService; import org.jeecg.modules.extbpm.process.common.expression.FlowNodeExpression; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -26,6 +29,7 @@ public class XiSpeakFlow { private final ISysBaseAPI iSysBaseAPI; private final RuntimeService runtimeService; private final FlowNodeExpression flowNodeExpression; + private final IBgXiSpeakService bgXiSpeakService; //流程表达式内用法 ${xiSpeakFlow.getBgDeptLdUserIdList()} public List getBgDeptLdUserIdList() { @@ -194,25 +198,6 @@ public class XiSpeakFlow { return flowNodeExpression.getSonProcessHqVariableList(execution,xiSpeakConfig.getXiSpeak().getTemImplWorkerKey()).size(); } - /** - * 流程表达式内用法: ${xiSpeakFlow.getIsEnd(execution)} - */ - public int getIsEnd(DelegateExecution execution) { - Object value = flowNodeExpression.getSonProcessHqVariable(execution, "isEnd"); - if (value == null) { - return 0; - } - if (value instanceof Number) { - return ((Number) value).intValue(); - } - try { - return Integer.parseInt(String.valueOf(value)); - } catch (NumberFormatException e) { - log.warn("isEnd变量值无法解析为int: {}", value); - return 0; - } - } - /** * 流程表达式内用法: ${xiSpeakFlow.getTemImplLeaderList(execution)} */ @@ -242,10 +227,48 @@ public class XiSpeakFlow { } /** - * 流程表达式内用法: ${xiSpeakFlow.getTemBgLeaderListLength(execution)} + * 流程表达式内用法: ${xiSpeakFlow.getImplDeptLeaderIsEnd(execution)=='1'} + * 优先从业务表单 JSON 读取,没有则回退到流程变量(兼容旧流程实例) */ - public int getImplDeptLeaderIsEnd(DelegateExecution execution) { - return flowNodeExpression.getSonProcessHqVariableList(execution,xiSpeakConfig.getXiSpeak().getTemBgLeaderKey()); + public String getImplDeptLeaderIsEnd(DelegateExecution execution) { + DeptApproveDetail detail = getDeptApproveDetail(execution); + if (detail != null && detail.getIsEnd() != null) { + return String.valueOf(detail.getIsEnd()); + } + // 兜底:从流程变量读取(兼容旧流程) + Object value = flowNodeExpression.getSonProcessVariable(execution, xiSpeakConfig.getXiSpeak().getIsEndKey()); + return value == null ? "0" : value.toString(); + } + + /** + * 流程表达式内用法: ${xiSpeakFlow.getImplDeptLeader(execution)} + */ + public String getImplDeptLeader(DelegateExecution execution) { + DeptApproveDetail detail = getDeptApproveDetail(execution); + if (detail != null && detail.getIsEnd() != null) { + return String.valueOf(detail.getImplWorker()); + } + return detail == null ? StringUtils.EMPTY_STRING : detail.getImplWorker(); + } + + /** + * 流程表达式内用法: ${xiSpeakFlow.getImplDeptWorkerListFromJson(execution)} + * 优先从业务表单 JSON 读取,没有则回退到流程变量(兼容旧流程实例) + */ + public List getImplDeptWorkerListFromJson(DelegateExecution execution) { + DeptApproveDetail detail = getDeptApproveDetail(execution); + if (detail != null && detail.getIsEnd() != null) { + return detail.getImplUserNameList(); + } + return Collections.emptyList(); + } + + /** + * 流程表达式内用法: ${xiSpeakFlow.getImplDeptWorkerListLengthFromJson(execution)} + * 优先从业务表单 JSON 读取,没有则回退到流程变量(兼容旧流程实例) + */ + public int getImplDeptWorkerListLengthFromJson(DelegateExecution execution) { + return this.getImplDeptWorkerListFromJson(execution).size(); } @@ -258,6 +281,35 @@ public class XiSpeakFlow { + /** + * 从执行实例中获取当前部门ID并查询业务表单 JSON 中的部门审批详情。 + */ + private DeptApproveDetail getDeptApproveDetail(DelegateExecution execution) { + if (execution == null) { + return null; + } + String businessKey = java.util.Objects.toString( + execution.getVariable(xiSpeakConfig.getXiSpeak().getBusinessKey()), null); + String implDeptKey = xiSpeakConfig.getXiSpeak().getImplDeptCollectionUsedKey(); + Object rawDeptVar = execution.getVariableLocal(implDeptKey); + if (rawDeptVar == null) { + rawDeptVar = execution.getVariable(implDeptKey); + } + if (org.apache.commons.lang.StringUtils.isBlank(businessKey) || rawDeptVar == null) { + return null; + } + String deptId = rawDeptVar.toString().replace("[", "").replace("]", "").trim(); + BgXiSpeak xiSpeak = bgXiSpeakService.getById(businessKey); + if (xiSpeak == null || xiSpeak.getApproveInfo() == null) { + return null; + } + return xiSpeak.getApproveInfo().get(deptId); + } + // 流程表达式用法${xiSpeakFlow.getImplDeptIsEnd(execution)==1} + public int getImplDeptIsEnd(DelegateExecution execution){ + return getDeptApproveDetail(execution).getIsEnd(); + } + /** * 抽取出的公共逻辑:从当前执行实例获取局部变量并转为字符串 */ @@ -341,42 +393,40 @@ public class XiSpeakFlow { return resultList.size(); } + /** + * 优先从业务表单 JSON 读取 implUserNameList / implWorker,没有则回退到流程变量(兼容旧流程实例) + */ public List getDeptWorkerList(DelegateExecution execution) { + DeptApproveDetail detail = getDeptApproveDetail(execution); + if (detail != null) { + // isEnd=0 时读取 implUserNameList + if (detail.getImplUserNameList() != null && !detail.getImplUserNameList().isEmpty()) { + return detail.getImplUserNameList(); + } + // isEnd=1 时读取 implWorker(兼容旧数据) + if (org.apache.commons.lang.StringUtils.isNotBlank(detail.getImplWorker())) { + return Collections.singletonList(detail.getImplWorker()); + } + } + // 兜底:从流程变量读取(兼容旧流程) String variableName = xiSpeakConfig.getXiSpeak().getDeptWorkerKey(); if (execution == null || org.apache.commons.lang.StringUtils.isBlank(variableName)) { return Collections.emptyList(); } - try { - // 1. 优先尝试从当前 Execution 的局部作用域获取 - // 在多实例(会签)中,每个分支特有的变量(如 item 变量)通常存储在这里 Object value = execution.getVariableLocal(variableName); - - // 2. 如果局部没有,直接使用 getVariable 获取 - // 该方法会自动向上追溯:当前 Execution -> 父 Execution -> 流程实例(Global) if (value == null) { value = execution.getVariable(variableName); } - - // 3. 结果处理 if (value != null) { - return splitUsernames(value); + return Arrays.stream(String.valueOf(value).split(SymbolConstant.COMMA)) + .map(String::trim) + .filter(org.apache.commons.lang.StringUtils::isNotBlank) + .collect(Collectors.toList()); } } catch (Exception e) { - log.warn("getSonProcessVariable 失败, executionId={}, variableName={}", - execution.getId(), variableName, e); + log.warn("getDeptWorkerList from process variable failed", e); } - return Collections.emptyList(); } - private List splitUsernames(Object value) { - if (value == null) { - return Collections.emptyList(); - } - return Arrays.stream(String.valueOf(value).split(SymbolConstant.COMMA)) - .map(String::trim) - .filter(org.apache.commons.lang.StringUtils::isNotBlank) - .collect(Collectors.toList()); - } - } diff --git a/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeakfb/XiSpeakFeedbackFlow.java b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeakfb/XiSpeakFeedbackFlow.java index 75c6673..5d8f15f 100644 --- a/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeakfb/XiSpeakFeedbackFlow.java +++ b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeakfb/XiSpeakFeedbackFlow.java @@ -115,6 +115,56 @@ public class XiSpeakFeedbackFlow { .orElse(""); } + // 流程表达式内用法 ${XiSpeakFeedbackFlow.getImplDeptIsEnd(execution)} + public Integer getImplDeptIsEnd(DelegateExecution execution) { + DeptApproveDetail detail = getDeptApproveDetailByTask(execution); + if (detail == null || detail.getIsEnd() == null) { + return 0; + } + return detail.getIsEnd(); + } + + // 流程表达式内用法 ${XiSpeakFeedbackFlow.getImplLeaderAsWorker(execution)} + public String getImplLeaderAsWorker(DelegateExecution execution) { + DeptApproveDetail detail = getDeptApproveDetailByTask(execution); + if (detail == null || StringUtils.isBlank(detail.getImplWorker())) { + return StringUtils.EMPTY; + } + return detail.getImplWorker(); + } + + /** + * 从 execution 获取 businessKey → TaskTask → BgXiSpeak → DeptApproveDetail + */ + private DeptApproveDetail getDeptApproveDetailByTask(DelegateExecution execution) { + String businessKey = execution.getProcessInstanceBusinessKey(); + + if (StringUtils.isBlank(businessKey)) { + log.warn("流程 BusinessKey 缺失,ExecutionId: {}", execution.getId()); + return null; + } + + TaskTask taskTask = taskTaskService.getById(businessKey); + if (taskTask == null || StringUtils.isBlank(taskTask.getBusinessId())) { + log.warn("无法获取任务数据或业务关联ID![taskTaskId: {}]", businessKey); + return null; + } + + BgXiSpeak xiSpeak = bgXiSpeakService.getById(taskTask.getBusinessId()); + if (xiSpeak == null || xiSpeak.getApproveInfo() == null) { + log.warn("业务表单数据或审批配置缺失![xiBusinessId: {}]", taskTask.getBusinessId()); + return null; + } + + String deptId = taskTask.getDeptId(); + if (StringUtils.isBlank(deptId)) { + log.warn("中间表表单部门信息缺失![tasktaskId: {}]", taskTask.getId()); + return null; + } + + return xiSpeak.getApproveInfo().get(deptId); + } + /** * 获取当前流程的经办部门的经办人。 *

用于流程表达式判断,通过 BusinessKey 关联业务主键,获取当前经办人。

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 3430518..dc72fdd 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 @@ -17,6 +17,7 @@ flow-biz: tem-impl-leader-key: "tem_impl_leader" tem-bg-leader-key: "tem_bg_leader" sdw-leader-list-key: "sdwLeaderList" + is-end-key: "isEnd" xi-speak-fb: dept-id: "x" business-id: "business_id" diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/controller/BgXiSpeakController.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/controller/BgXiSpeakController.java index bd686b4..fecc1df 100644 --- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/controller/BgXiSpeakController.java +++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/controller/BgXiSpeakController.java @@ -266,6 +266,26 @@ public class BgXiSpeakController extends JeecgController saveSubApprove(@RequestBody Map params) { + String mainId = (String) params.get("mainId"); + String deptId = (String) params.get("deptId"); + Integer isEnd = params.get("isEnd") != null ? Integer.valueOf(params.get("isEnd").toString()) : null; + String implWorker = (String) params.get("implWorker"); + @SuppressWarnings("unchecked") + List implUserNameList = (List) params.get("implUserNameList"); + if (oConvertUtils.isEmpty(mainId) || oConvertUtils.isEmpty(deptId)) { + return Result.error("主表ID和部门ID不能为空"); + } + bgXiSpeakService.saveSubApprove(mainId, deptId, isEnd, implWorker, implUserNameList); + return Result.OK("保存成功"); + } + /** * 保存业务数据同时更新流程变量jsonData * @param id diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/entity/DeptApproveDetail.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/entity/DeptApproveDetail.java index 1516e20..e067a2a 100644 --- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/entity/DeptApproveDetail.java +++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/entity/DeptApproveDetail.java @@ -14,6 +14,8 @@ public class DeptApproveDetail { private String deptId; // 部门ID private String approverId; // 审批人ID(从监听器获取的) private String approverName; // 审批人姓名(可选) + private Integer isEnd; // 是否办结(0=分配部门经办人,1=办结) + private String implWorker; // 被分配的部门经办人用户名 @JsonAlias("implUserId") private List implUserIdList; // 落实人Id @JsonAlias("implUserName") diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/service/IBgXiSpeakService.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/service/IBgXiSpeakService.java index 0ff70f6..ffb6894 100644 --- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/service/IBgXiSpeakService.java +++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/service/IBgXiSpeakService.java @@ -85,4 +85,10 @@ public interface IBgXiSpeakService extends IService { List getRootListWithDept(String pid); void checkAndMarkCompletedByProcessInstId(String processInstanceId); + + /** + * 保存部门审批信息(isEnd、部门经办人/经办人列表)到业务表单 approveInfo JSON 中 + * isEnd=1 时 implWorker 生效,isEnd=0 时 implUserNameList 生效 + */ + void saveSubApprove(String mainId, String deptId, Integer isEnd, String implWorker, List implUserNameList); } 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 482686a..3156842 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 @@ -8,6 +8,8 @@ import org.jeecg.common.exception.JeecgBootException; import org.jeecg.common.util.oConvertUtils; 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.bg.xispeak.mapper.BgXiSpeakMapper; import org.jeecg.modules.bg.xispeak.service.IBgXiSpeakService; import org.jeecg.modules.taskapprovalopinion.entity.TaskApprovalOpinion; @@ -351,4 +353,40 @@ public class BgXiSpeakServiceImpl extends ServiceImpl implUserNameList) { + BgXiSpeak xiSpeak = this.getById(mainId); + if (xiSpeak == null) { + throw new JeecgBootException("业务表单不存在"); + } + + DeptApproveDetailMap approveMap = xiSpeak.getApproveInfo(); + if (approveMap == null) { + approveMap = new DeptApproveDetailMap(); + } + + DeptApproveDetail detail = approveMap.get(deptId); + if (detail == null) { + detail = new DeptApproveDetail(); + detail.setDeptId(deptId); + detail.setImplUserNameList(new ArrayList<>()); + approveMap.put(deptId, detail); + } + + detail.setIsEnd(isEnd); + if (isEnd != null && isEnd == 1) { + detail.setImplWorker(implWorker); + detail.setImplUserNameList(null); + } else { + detail.setImplWorker(null); + detail.setImplUserNameList(implUserNameList != null ? implUserNameList : new ArrayList<>()); + } + + xiSpeak.setApproveInfo(approveMap); + this.updateById(xiSpeak); + log.info("部门审批信息保存成功 mainId={}, deptId={}, isEnd={}, implWorker={}, implUserNameList={}", + mainId, deptId, isEnd, implWorker, implUserNameList); + } }