From fa0212467234ea576a57b087ed7819e9ea086550 Mon Sep 17 00:00:00 2001 From: wsm <2746563060@侵权。> Date: Fri, 22 May 2026 16:30:00 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9EAfterImplDeptLeaderStoreJsonL?= =?UTF-8?q?istener=E7=9B=91=E5=90=AC=E5=99=A8=E7=94=A8=E4=BA=8E=E5=AD=98?= =?UTF-8?q?=E5=82=A8json=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AfterImplDeptLeaderStoreJsonListener.java | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeak/listener/AfterImplDeptLeaderStoreJsonListener.java diff --git a/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeak/listener/AfterImplDeptLeaderStoreJsonListener.java b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeak/listener/AfterImplDeptLeaderStoreJsonListener.java new file mode 100644 index 0000000..fa463f9 --- /dev/null +++ b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeak/listener/AfterImplDeptLeaderStoreJsonListener.java @@ -0,0 +1,138 @@ +package org.jeecg.xispeak.listener; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang.StringUtils; +import org.flowable.task.service.delegate.DelegateTask; +import org.flowable.engine.delegate.TaskListener; // 🎯 1. 必须引入标准接口 +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.service.IBgXiSpeakService; +import org.jeecg.xispeak.XiSpeakConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Objects; + +@Slf4j +@Component("AfterImplDeptLeaderStoreJsonListener") +@RequiredArgsConstructor(onConstructor_ = @Autowired) +// 🎯 2. 显式实现 TaskListener 接口,确保 Flowable 引擎能正常识别并回调 +public class AfterImplDeptLeaderStoreJsonListener implements TaskListener { + + private final XiSpeakConfig xiSpeakConfig; + private final IBgXiSpeakService bgXiSpeakService; + + @Override // 🎯 3. 明确标记重写 + @Transactional(rollbackFor = Exception.class) + public void notify(DelegateTask delegateTask) { + String currentWorkerName = delegateTask.getAssignee(); + Object rawBusinessKey = delegateTask.getVariable(xiSpeakConfig.getXiSpeak().getBusinessKey()); + String taskId = delegateTask.getId(); + + // 1. 基础边界防御性校验 + if (StringUtils.isEmpty(currentWorkerName) || rawBusinessKey == null) { + log.warn("【任务通知】无法获取有效的审批人或业务ID,略过处理。TaskId: {}, Assignee: {}, BusinessKey: {}", + taskId, currentWorkerName, rawBusinessKey); + return; + } + + String businessKeyStr = rawBusinessKey.toString(); + + try { + // 2. 🎯 并发安全提示: + // 如果此节点属于会签/并行节点,建议 bgXiSpeakService.getById() 内部实体类带有 MyBatis-Plus 的 @Version 乐观锁。 + // 或者此处若业务允许,仍旧推荐使用悲观锁(如 selectForUpdate),工作流死锁通常可以通过优化流程设计或统一加锁顺序解决。 + BgXiSpeak xiSpeak = bgXiSpeakService.getById(businessKeyStr); + if (xiSpeak == null) { + log.error("【任务通知】业务数据不存在,终止处理。BusinessKey: {}", businessKeyStr); + return; + } + + // 3. 部门变量安全解析 + String implDeptKey = xiSpeakConfig.getXiSpeak().getImplDeptCollectionUsedKey(); + Object rawDeptVar = delegateTask.getVariableLocal(implDeptKey); + if (rawDeptVar == null) { + rawDeptVar = delegateTask.getVariable(implDeptKey); + } + + if (rawDeptVar == null) { + log.error("【任务通知】流程数据异常:未找到实施部门ID变量: {}, BusinessKey: {}", implDeptKey, businessKeyStr); + return; + } + + // 安全解析 + String cleanDeptId = parseDeptId(rawDeptVar); + if (StringUtils.isEmpty(cleanDeptId)) { + log.error("【任务通知】解析部门ID为空,跳过更新。RawDeptVar: {}, BusinessKey: {}", rawDeptVar, businessKeyStr); + return; + } + + // 4. 安全获取或创建部门审批详情 + DeptApproveDetailMap approveMap = xiSpeak.getApproveInfo(); + if (approveMap == null) { + approveMap = new DeptApproveDetailMap(); + } + + DeptApproveDetail detail = approveMap.get(cleanDeptId); + if (detail == null) { + detail = new DeptApproveDetail(); + detail.setDeptId(cleanDeptId); + detail.setImplUserNameList(new ArrayList<>()); + approveMap.put(cleanDeptId, detail); + } + + // 5. 状态比对与幂等更新 + if (!Objects.equals(detail.getApproverName(), currentWorkerName)) { + detail.setApproverName(currentWorkerName); + + // 写回并更新 + xiSpeak.setApproveInfo(approveMap); + + // 🎯 如果有乐观锁,更新失败会抛出 OptimisticLockException,从而触发事务回滚与 Flowable 重试机制 + bgXiSpeakService.updateById(xiSpeak); + + log.info("【任务通知】业务单据 [{}] 部门 [{}] 经办人审批记录成功更新为: {}", + businessKeyStr, cleanDeptId, currentWorkerName); + } else { + log.info("【任务通知】业务单据 [{}] 部门 [{}] 经办人未发生变化 [{}], 跳过数据库更新", + businessKeyStr, cleanDeptId, currentWorkerName); + } + + } catch (Exception e) { + log.error(String.format("【任务通知】处理任务结束监听器异常。TaskId: %s, BusinessKey: %s", taskId, businessKeyStr), e); + throw e; + } + } + + /** + * 辅助方法:安全解析部门ID + */ + private String parseDeptId(Object rawDeptVar) { + if (rawDeptVar == null) { + return ""; + } + // 如果原本就是集合 + if (rawDeptVar instanceof Collection) { + Collection collection = (Collection) rawDeptVar; + if (!collection.isEmpty()) { + return Objects.toString(collection.iterator().next(), "").trim(); + } + return ""; + } + + String str = rawDeptVar.toString().trim(); + // 🎯 增强防御:如果是 "[1001,1002]" 这种拼成的字符串,只取第一个元素 + if (str.startsWith("[") && str.endsWith("]")) { + str = str.substring(1, str.length() - 1); + if (str.contains(",")) { + str = str.split(",")[0]; + } + } + return str.trim(); + } +} \ No newline at end of file