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 7629dec..e6c7a3d 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 @@ -37,5 +37,9 @@ public class XiSpeakConfig { private String deptWorkerKey; private String xiSpeakFbFlowCode; private String formUrl; + private String feedbackRightNowCode; + private String temImplWorkerKey; + private String temImplLeaderKey; + private String temBgLeaderKey; } } 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 12a6fe5..5f282d1 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 @@ -4,11 +4,13 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; + import org.apache.shiro.util.StringUtils; 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.extbpm.process.common.expression.FlowNodeExpression; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -23,6 +25,7 @@ public class XiSpeakFlow { private final XiSpeakConfig xiSpeakConfig; private final ISysBaseAPI iSysBaseAPI; private final RuntimeService runtimeService; + private final FlowNodeExpression flowNodeExpression; //流程表达式内用法 ${xiSpeakFlow.getBgDeptLdUserIdList()} public List getBgDeptLdUserIdList() { @@ -67,37 +70,129 @@ public class XiSpeakFlow { return getBgDeptLdUserIdList().size(); } - public Integer getNeedSdwApproval(Object jsonData) { - XiSpeakConfig.XiSpeakProperties props = xiSpeakConfig.getXiSpeak(); - if (jsonData == null || org.apache.commons.lang.StringUtils.isBlank(props.getNeedSdwApproveCode())) { + /** + * 提取后的公共解析方法 + * @param jsonData 原始数据对象 + * @param codeName 配置项对应的 Key + * @param logLabel 日志标识名(用于区分不同业务的日志输出) + */ + private Integer getCodeValueFromData(Object jsonData, String codeName, String logLabel) { + // 1. 基础校验:如果配置的 Key 为空,直接返回 0 + if (jsonData == null || org.apache.commons.lang.StringUtils.isBlank(codeName)) { return 0; } - String codeName = props.getNeedSdwApproveCode(); - if (org.apache.commons.lang.StringUtils.isBlank(codeName)) return 0; + try { JSONObject data; + // 2. 统一转换为 JSONObject (此处逻辑复用) if (jsonData instanceof JSONObject) { data = (JSONObject) jsonData; } else if (jsonData instanceof String) { - if (org.apache.commons.lang.StringUtils.isBlank((String) jsonData)) { - return 0; - } - data = JSONObject.parseObject((String) jsonData); + String jsonStr = (String) jsonData; + if (org.apache.commons.lang.StringUtils.isBlank(jsonStr)) return 0; + data = JSONObject.parseObject(jsonStr); } else { + // 处理普通 POJO 对象 data = JSONObject.parseObject(JSONObject.toJSONString(jsonData)); } - int finalResult = Optional.ofNullable(data) - .map(d -> d.getInteger(codeName)) - .orElse(0); - log.info("needSdwApproval结果为{}", finalResult); + // 3. 提取结果 + Integer result = data.getInteger(codeName); + int finalResult = (result == null) ? 0 : result; + + log.info("{} 业务解析结果: {}", logLabel, finalResult); return finalResult; } catch (Exception e) { - log.warn("getJsondata parse failed, codeName={}, jsonData={}", codeName, jsonData, e); + log.warn("JSON解析失败: 业务={}, codeKey={}, 数据={}", logLabel, codeName, jsonData, e); return 0; } } +// --- 调用处 --- + + //流程表达式内用法${xiSpeakFlow.getNeedSdwApproval(jsonData)} + public Integer getNeedSdwApproval(Object jsonData) { + String codeName = xiSpeakConfig.getXiSpeak().getNeedSdwApproveCode(); + return getCodeValueFromData(jsonData, codeName, "需SDW审批"); + } + + //流程表达式内用法${xiSpeakFlow.getFeedBackRightNow(jsonData)} + public Integer getFeedBackRightNow(Object jsonData) { + String codeName = xiSpeakConfig.getXiSpeak().getFeedbackRightNowCode(); + return getCodeValueFromData(jsonData, codeName, "立即反馈"); + } + + /** + * 流程表达式内用法: ${xiSpeakFlow.getTemImplWorkerList(execution)} + */ + public List getTemImplWorkerList(DelegateExecution execution) { + return flowNodeExpression.getSonProcessHqVariableList(execution,xiSpeakConfig.getXiSpeak().getTemImplWorkerKey()); + } + + /** + * 流程表达式内用法: ${xiSpeakFlow.getTemImplWorkerListLength(execution)} + */ + public int getTemImplWorkerListLength(DelegateExecution execution) { + return flowNodeExpression.getSonProcessHqVariableList(execution,xiSpeakConfig.getXiSpeak().getTemImplWorkerKey()).size(); + } + + /** + * 流程表达式内用法: ${xiSpeakFlow.getTemImplLeaderList(execution)} + */ + public List getTemImplLeaderList(DelegateExecution execution) { + return flowNodeExpression.getSonProcessHqVariableList(execution,xiSpeakConfig.getXiSpeak().getTemImplLeaderKey()); + } + + /** + * 流程表达式内用法: ${xiSpeakFlow.getTemImplLeaderListLength(execution)} + */ + public int getTemImplLeaderListLength(DelegateExecution execution) { + return flowNodeExpression.getSonProcessHqVariableList(execution,xiSpeakConfig.getXiSpeak().getTemImplLeaderKey()).size(); + } + + /** + * 流程表达式内用法: ${xiSpeakFlow.getTemBgLeaderList(execution)} + */ + public List getTemBgLeaderList(DelegateExecution execution) { + return flowNodeExpression.getSonProcessHqVariableList(execution,xiSpeakConfig.getXiSpeak().getTemBgLeaderKey()); + } + + /** + * 流程表达式内用法: ${xiSpeakFlow.getTemBgLeaderListLength(execution)} + */ + public int getTemBgLeaderListLength(DelegateExecution execution) { + return flowNodeExpression.getSonProcessHqVariableList(execution,xiSpeakConfig.getXiSpeak().getTemBgLeaderKey()).size(); + } + + + /** + * 流程表达式内用法: ${xiSpeakFlow.getTemBgLeader(execution)} + */ + public String getTemBgLeader(DelegateExecution execution) { + return getLocalVarAsString(execution, xiSpeakConfig.getXiSpeak().getTemBgLeaderKey()); + } + + + + /** + * 抽取出的公共逻辑:从当前执行实例获取局部变量并转为字符串 + */ + private String getLocalVarAsString(DelegateExecution execution, String variableKey) { + if (execution == null || org.apache.commons.lang3.StringUtils.isBlank(variableKey)) { + return StringUtils.EMPTY_STRING; + } + + // 在 DelegateExecution 环境下,getId() 永远不为空,直接取变量即可 + Object obj = execution.getVariableLocal(variableKey); + + if (obj instanceof String) { + return (String) obj; + } + + // 如果变量不存在或类型不对,返回空字符串(防止流程引擎解析报错) + return StringUtils.EMPTY_STRING; + } + public List getImplDeptLdsList(String deptId) { return iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi(deptId, xiSpeakConfig.getXiSpeak().getLdRoleId()); } @@ -199,4 +294,5 @@ public class XiSpeakFlow { .filter(org.apache.commons.lang.StringUtils::isNotBlank) .collect(Collectors.toList()); } + } diff --git a/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeak/listener/AfterBgLeaderApproveListener.java b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeak/listener/AfterBgLeaderApproveListener.java new file mode 100644 index 0000000..22d4804 --- /dev/null +++ b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeak/listener/AfterBgLeaderApproveListener.java @@ -0,0 +1,93 @@ +package org.jeecg.xispeak.listener; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang.StringUtils; +import org.flowable.engine.delegate.TaskListener; +import org.flowable.engine.runtime.Execution; +import org.flowable.task.service.delegate.DelegateTask; +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 lombok.RequiredArgsConstructor; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.ExecutionListener; +import org.jeecg.common.util.oConvertUtils; +import org.jeecg.modules.extbpm.process.common.WorkFlowGlobals; +import org.jeecg.modules.tasktask.entity.TaskTask; +import org.jeecg.modules.tasktask.service.ITaskTaskService; +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.*; +import java.util.stream.Collectors; + +@Slf4j +@Component("AfterBgLeaderApproveListener") +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class AfterBgLeaderApproveListener implements TaskListener { + + private static final long serialVersionUID = 1L; + private final FlowNodeExpression flowNodeExpression; + private final RuntimeService runtimeService; + private final XiSpeakConfig xiSpeakConfig; + private final ITaskTaskService taskTaskService; + private final IBgXiSpeakService bgXiSpeakService; + + @Override + public void notify(DelegateTask delegateTask) { + + XiSpeakConfig.XiSpeakProperties props = xiSpeakConfig.getXiSpeak(); + + if (delegateTask == null) return; + + String assignee = delegateTask.getAssignee(); + if (StringUtils.isBlank(assignee)) return; + + String currentExecutionId = delegateTask.getExecutionId(); + if (StringUtils.isBlank(currentExecutionId)) return; + + // 2. 先通过 runtimeService 查询当前的执行实例对象 + Execution currentExecution = runtimeService.createExecutionQuery() + .executionId(currentExecutionId) + .singleResult(); + + if (currentExecution == null) return; + + // 2. 向上寻找第一层父级 (比如:内层会签容器/子流程) + String parentId = currentExecution.getParentId(); + if (StringUtils.isBlank(parentId)) { + log.warn("无法找到父级执行实例,当前已是顶层"); + return; + } + + // 3. 核心:通过 runtimeService 获取第一层父级的实体对象,从而拿到第二层的 ID + // 只有拿到实体对象,才能调用 getParentId() 继续向上爬 + Execution parentExecution = runtimeService.createExecutionQuery() + .executionId(parentId) + .singleResult(); + + if (parentExecution != null) { + // 4. 获取第二层父级 ID (大会签容器/外层作用域) + String grandParentId = parentExecution.getParentId(); + + // 5. 确定最终存储目标 + // 如果有第二层父级,则存入第二层;如果没有,则退而求其次存入第一层 + String targetId = StringUtils.isNotBlank(grandParentId) ? grandParentId : parentId; + + try { + // 6. 执行存储 + runtimeService.setVariableLocal(targetId, props.getTemBgLeaderKey() , assignee); + + log.info("变量已存入向上两层级:TargetId={}, OriginalId={}, Assignee={}", + targetId, currentExecution.getId(), assignee); + } catch (Exception e) { + log.error("设置嵌套局部变量失败", e); + } + } + } +} \ No newline at end of file diff --git a/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeak/listener/AfterImplDeptLeaderApproveListener.java b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeak/listener/AfterImplDeptLeaderApproveListener.java index c40f381..d9cef0c 100644 --- a/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeak/listener/AfterImplDeptLeaderApproveListener.java +++ b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeak/listener/AfterImplDeptLeaderApproveListener.java @@ -4,7 +4,9 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; import org.flowable.engine.delegate.TaskListener; +import org.flowable.engine.runtime.Execution; import org.flowable.task.service.delegate.DelegateTask; +import org.jeecg.common.util.SpringContextUtils; import org.jeecg.modules.bg.xispeak.entity.BgXiSpeak; import org.jeecg.modules.bg.xispeak.entity.DeptApproveDetail; import org.jeecg.modules.bg.xispeak.service.IBgXiSpeakService; @@ -31,46 +33,61 @@ import java.util.stream.Collectors; public class AfterImplDeptLeaderApproveListener implements TaskListener { private static final long serialVersionUID = 1L; - private final FlowNodeExpression flowNodeExpression; - private final RuntimeService runtimeService; - private final XiSpeakConfig xiSpeakConfig; - private final ITaskTaskService taskTaskService; - private final IBgXiSpeakService bgXiSpeakService; + private static final String VARIABLE_NAME = "tem_impl_leader"; - @Transactional(rollbackFor = Exception.class) + private static RuntimeService runtimeService; + + static { + runtimeService = SpringContextUtils.getBean(RuntimeService.class); + } + + @Override public void notify(DelegateTask delegateTask) { - // 1. 获取基础数据 - String currentAssignee = delegateTask.getAssignee(); - Object rawTaskTaskId = delegateTask.getVariable(xiSpeakConfig.getXiSpeak().getBusinessKey()); - String implDeptKey = xiSpeakConfig.getXiSpeak().getImplDeptCollectionUsedKey(); - String rawDeptVar = delegateTask.getVariable(implDeptKey).toString(); - String cleanDeptId = rawDeptVar.replace("[", "").replace("]", "").trim(); + if (delegateTask == null) return; - // 2. 【核心】使用行锁查询,确保拿到的是数据库此刻最真实、最新的数据 - // 同时也让其他并发的审批线程在这里“排队”等待 - BgXiSpeak xiSpeak = bgXiSpeakService.getByIdForUpdate(rawTaskTaskId.toString()); - if (xiSpeak == null) return; + String assignee = delegateTask.getAssignee(); + if (StringUtils.isBlank(assignee)) return; - // 3. 操作 Map - Map map = xiSpeak.getApproveInfo(); - if (map == null) map = new HashMap<>(); + String currentExecutionId = delegateTask.getExecutionId(); + if (StringUtils.isBlank(currentExecutionId)) return; - // 4. 获取或创建详情对象 - // 使用你定义的 POJO - DeptApproveDetail detail = map.getOrDefault(cleanDeptId, new DeptApproveDetail()); + // 2. 先通过 runtimeService 查询当前的执行实例对象 + Execution currentExecution = runtimeService.createExecutionQuery() + .executionId(currentExecutionId) + .singleResult(); - // 只有没审批过才记录(防止重复触发覆盖已有数据) - if (StringUtils.isBlank(detail.getApproverName())) { - detail.setDeptId(cleanDeptId); - detail.setApproverName(currentAssignee); - // 这里可以根据需要设置其他字段,比如 detail.setApproverId(...) + if (currentExecution == null) return; - map.put(cleanDeptId, detail); - xiSpeak.setApproveInfo(map); + // 2. 向上寻找第一层父级 (比如:内层会签容器/子流程) + String parentId = currentExecution.getParentId(); + if (StringUtils.isBlank(parentId)) { + log.warn("无法找到父级执行实例,当前已是顶层"); + return; + } - // 5. 写回数据库 - // 因为有了 FOR UPDATE,这里的更新绝对是基于最新数据的增量合并 - bgXiSpeakService.updateById(xiSpeak); + // 3. 核心:通过 runtimeService 获取第一层父级的实体对象,从而拿到第二层的 ID + // 只有拿到实体对象,才能调用 getParentId() 继续向上爬 + Execution parentExecution = runtimeService.createExecutionQuery() + .executionId(parentId) + .singleResult(); + + if (parentExecution != null) { + // 4. 获取第二层父级 ID (大会签容器/外层作用域) + String grandParentId = parentExecution.getParentId(); + + // 5. 确定最终存储目标 + // 如果有第二层父级,则存入第二层;如果没有,则退而求其次存入第一层 + String targetId = StringUtils.isNotBlank(grandParentId) ? grandParentId : parentId; + + try { + // 6. 执行存储 + runtimeService.setVariableLocal(targetId, VARIABLE_NAME, assignee); + + log.info("变量已存入向上两层级:TargetId={}, OriginalId={}, Assignee={}", + targetId, currentExecution.getId(), assignee); + } catch (Exception e) { + log.error("设置嵌套局部变量失败", e); + } } } } \ No newline at end of file diff --git a/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeak/listener/AfterImplWorkerApproveListener.java b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeak/listener/AfterImplWorkerApproveListener.java index 017011e..e89cfa7 100644 --- a/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeak/listener/AfterImplWorkerApproveListener.java +++ b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeak/listener/AfterImplWorkerApproveListener.java @@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; import org.flowable.engine.delegate.TaskListener; +import org.flowable.engine.runtime.Execution; import org.flowable.task.service.delegate.DelegateTask; import org.jeecg.modules.bg.xispeak.entity.BgXiSpeak; import org.jeecg.modules.bg.xispeak.entity.DeptApproveDetail; @@ -32,6 +33,7 @@ public class AfterImplWorkerApproveListener implements TaskListener { private static final long serialVersionUID = 1L; private final XiSpeakConfig xiSpeakConfig; private final IBgXiSpeakService bgXiSpeakService; + private final RuntimeService runtimeService; // 引入 Jackson 的 ObjectMapper 用于处理类型转换失败的情况 private final com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper(); @@ -39,9 +41,19 @@ public class AfterImplWorkerApproveListener implements TaskListener { @Override @Transactional(rollbackFor = Exception.class) public void notify(DelegateTask delegateTask) { + String currentWorkerName = delegateTask.getAssignee(); Object rawBusinessKey = delegateTask.getVariable(xiSpeakConfig.getXiSpeak().getBusinessKey()); + String executionId = delegateTask.getExecutionId(); + if(StringUtils.isBlank(executionId)){ + log.warn("任务通知监听器:获取执行实例ID失败,跳过流程变量设置。任务ID: {}, 办理人: {}", delegateTask.getId(), currentWorkerName); + }else{ + runtimeService.setVariableLocal(executionId,xiSpeakConfig.getXiSpeak().getTemImplWorkerKey() , currentWorkerName); + log.info("任务通知监听器:成功存入执行实例局部变量 -> 执行ID: [{}], 变量名: [{}], 变量值: [{}]", + executionId, xiSpeakConfig.getXiSpeak().getTemImplWorkerKey(), currentWorkerName); + } + if (StringUtils.isEmpty(currentWorkerName) || rawBusinessKey == null) { log.warn("无法获取有效的审批人或业务ID,略过处理"); return; diff --git a/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeak/listener/AfterImplWorkerTemStoreListener.java b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeak/listener/AfterImplWorkerTemStoreListener.java new file mode 100644 index 0000000..a01a4ef --- /dev/null +++ b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeak/listener/AfterImplWorkerTemStoreListener.java @@ -0,0 +1,93 @@ +package org.jeecg.xispeak.listener; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang.StringUtils; +import org.flowable.engine.delegate.TaskListener; +import org.flowable.engine.runtime.Execution; +import org.flowable.task.service.delegate.DelegateTask; +import org.jeecg.common.util.SpringContextUtils; +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 lombok.RequiredArgsConstructor; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.ExecutionListener; +import org.jeecg.common.util.oConvertUtils; +import org.jeecg.modules.extbpm.process.common.WorkFlowGlobals; +import org.jeecg.modules.tasktask.entity.TaskTask; +import org.jeecg.modules.tasktask.service.ITaskTaskService; +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.*; +import java.util.stream.Collectors; + +@Slf4j +@Component("AfterImplWorkerTemStoreListener") +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class AfterImplWorkerTemStoreListener implements TaskListener { + + private static final long serialVersionUID = 1L; + private static final String VARIABLE_NAME = "tem_impl_worker"; + + private static RuntimeService runtimeService; + + static { + runtimeService = SpringContextUtils.getBean(RuntimeService.class); + } + + @Override + public void notify(DelegateTask delegateTask) { + if (delegateTask == null) return; + + String assignee = delegateTask.getAssignee(); + if (StringUtils.isBlank(assignee)) return; + + String currentExecutionId = delegateTask.getExecutionId(); + if (StringUtils.isBlank(currentExecutionId)) return; + + // 2. 先通过 runtimeService 查询当前的执行实例对象 + Execution currentExecution = runtimeService.createExecutionQuery() + .executionId(currentExecutionId) + .singleResult(); + + if (currentExecution == null) return; + + // 2. 向上寻找第一层父级 (比如:内层会签容器/子流程) + String parentId = currentExecution.getParentId(); + if (StringUtils.isBlank(parentId)) { + log.warn("无法找到父级执行实例,当前已是顶层"); + return; + } + + // 3. 核心:通过 runtimeService 获取第一层父级的实体对象,从而拿到第二层的 ID + // 只有拿到实体对象,才能调用 getParentId() 继续向上爬 + Execution parentExecution = runtimeService.createExecutionQuery() + .executionId(parentId) + .singleResult(); + + if (parentExecution != null) { + // 4. 获取第二层父级 ID (大会签容器/外层作用域) + String grandParentId = parentExecution.getParentId(); + + // 5. 确定最终存储目标 + // 如果有第二层父级,则存入第二层;如果没有,则退而求其次存入第一层 + String targetId = StringUtils.isNotBlank(grandParentId) ? grandParentId : parentId; + + try { + // 6. 执行存储 + runtimeService.setVariableLocal(targetId, VARIABLE_NAME, assignee); + + log.info("变量已存入向上两层级:TargetId={}, OriginalId={}, Assignee={}", + targetId, currentExecution.getId(), assignee); + } catch (Exception e) { + log.error("设置嵌套局部变量失败", e); + } + } + } +} \ No newline at end of file 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 da2b60c..1d137cd 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 @@ -36,7 +36,7 @@ public class XiSpeakFeedbackFlow { private final ITaskTaskService taskTaskService; - // 流程表达式内用法 ${XiSpeakFeedbackFlow.getImplDeptList()} + // 流程表达式内用法 ${XiSpeakFeedbackFlow.getImplDeptList(execution)} public List getImplDeptList(DelegateExecution execution) { String processInstId = execution.getProcessInstanceId(); String businessKey = execution.getProcessInstanceBusinessKey(); @@ -66,7 +66,7 @@ public class XiSpeakFeedbackFlow { return deptIdList; } - // 流程表达式内用法 ${XiSpeakFeedbackFlow.getImplDeptListLength()} + // 流程表达式内用法 ${XiSpeakFeedbackFlow.getImplDeptListLength(execution)} public int getImplDeptListLength(DelegateExecution execution) { List implDeptList = this.getImplDeptList(execution); if (implDeptList.isEmpty() || implDeptList == null) { @@ -76,6 +76,7 @@ public class XiSpeakFeedbackFlow { return implDeptList.size(); } + // 流程表达式内用法 ${XiSpeakFeedbackFlow.getImplDeptApprove(execution)} public String getImplDeptApprove(DelegateExecution execution) { String businessKey = execution.getProcessInstanceBusinessKey(); @@ -139,7 +140,7 @@ public class XiSpeakFeedbackFlow { * * @param execution 流程执行上下文 * @return 经办部门经办人 - * @example 表达式用法: ${XiSpeakFeedbackFlow.getImplDeptWorker()} + * @example 表达式用法: ${XiSpeakFeedbackFlow.getImplDeptWorker(execution)} */ public List getImplDeptWorker(DelegateExecution execution) { String businessKey = execution.getProcessInstanceBusinessKey(); @@ -192,7 +193,7 @@ public class XiSpeakFeedbackFlow { *

jj部门经办人

* * @return jj部门经办人 - * @example 表达式用法: ${XiSpeakFeedbackFlow.JJDeptWorkerList()} + * @example 表达式用法: ${XiSpeakFeedbackFlow.getJJDeptWorkerList()} */ public List getJJDeptWorkerList() { // 1. 安全获取配置对象 @@ -243,7 +244,7 @@ public class XiSpeakFeedbackFlow { *

获取jj部门审核人

* * @return jj部门审核人 - * @example 表达式用法: ${XiSpeakFeedbackFlow.getJjDeptLeaderList()} + * @example 表达式用法: ${XiSpeakFeedbackFlow.getJJDeptLeaderList()} */ public List getJJDeptLeaderList() { // 1. 安全获取配置对象 diff --git a/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeakfb/listener/AfterImplDeptLeaderApproveListener.java b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeakfb/listener/AfterImplDeptLeaderApproveListener.java new file mode 100644 index 0000000..4c95cd8 --- /dev/null +++ b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeakfb/listener/AfterImplDeptLeaderApproveListener.java @@ -0,0 +1,36 @@ +package org.jeecg.xispeakfb.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.common.api.vo.Result; +import org.jeecg.modules.bg.xispeak.entity.BgXiSpeakFeedback; +import org.jeecg.modules.bg.xispeak.service.IBgXiSpeakFeedbackService; +import org.jeecg.modules.tasktask.constant.FlowConstants; +import org.jeecg.modules.tasktask.entity.TaskTask; +import org.jeecg.modules.tasktask.service.ITaskTaskService; +import org.jeecg.xispeakfb.XiSpeakFeedbackConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.util.HashMap; +import java.util.Map; + +@Slf4j +@Component("AfterImplDeptLeaderApproveFeedbackListener") +@RequiredArgsConstructor(onConstructor_ = @Autowired) +public class AfterImplDeptLeaderApproveListener implements TaskListener { + + private static final long serialVersionUID = 1L; + + private final XiSpeakFeedbackConfig xiSpeakFeedbackConfig; + private final IBgXiSpeakFeedbackService bgXiSpeakFeedbackService; + private final ITaskTaskService taskTaskService; + + @Transactional(rollbackFor = Exception.class) + public void notify(DelegateTask delegateTask) { + + } +} 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 c4adcbf..8ef4c06 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 @@ -3,6 +3,7 @@ flow-biz: bg-dept-id: "2044677604785229826" ld-role-id: "2044680793306591234" need-sdw-approve-code: "needSdwApproval" + feedback-right-now-code: "feedback_right_now" json-data-key: "json_data" impl-dept-key: "implDept" impl-dept-key-underscore-key: "impl_dept" @@ -12,6 +13,9 @@ flow-biz: dept-worker-key: "subApproveUser" xi-speak-fb-flowcode: "process_1778315114392" form-url: "bg/xispeak/components/BgXiSpeakBPMForm" + tem-impl-worker-key: "tem_impl_worker" + tem-impl-leader-key: "tem_impl_leader" + tem-bg-leader-key: "tem_bg_leader" xi-speak-fb: dept-id: "x" business-id: "business_id" diff --git a/jeecg-module-supervision/pom.xml b/jeecg-module-supervision/pom.xml index d8a8670..e4a929f 100644 --- a/jeecg-module-supervision/pom.xml +++ b/jeecg-module-supervision/pom.xml @@ -25,6 +25,10 @@ org.jeecgframework.boot jeecg-system-local-api + + org.jeecgframework.boot + jeecg-system-biz + 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 522c319..06c578d 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 @@ -9,6 +9,14 @@ import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.jeecg.common.api.vo.Result; import org.jeecg.common.system.query.QueryGenerator; import org.jeecg.common.util.oConvertUtils; @@ -16,12 +24,10 @@ import org.jeecg.common.system.vo.SelectTreeModel; import org.jeecg.modules.bg.xispeak.entity.BgXiSpeak; import org.jeecg.modules.bg.xispeak.service.IBgXiSpeakService; -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.jeecg.modules.demo.bgpartymatter.entity.BgPartymatter; +import org.jeecg.modules.bg.xispeak.dto.BgXiSpeakBpmSaveDTO; +import org.flowable.engine.RuntimeService; +import com.alibaba.fastjson.JSONObject; +import org.jeecg.modules.tasktask.entity.TaskTask; import org.jeecgframework.poi.excel.ExcelImportUtil; import org.jeecgframework.poi.excel.def.NormalExcelConstants; import org.jeecgframework.poi.excel.entity.ExportParams; @@ -48,12 +54,12 @@ import org.apache.shiro.authz.annotation.RequiresPermissions; @Tag(name="习总书记重要讲话指示批示情况") @RestController @RequestMapping("/bg/xispeak/bgXiSpeak") +@RequiredArgsConstructor(onConstructor_ = @Autowired) @Slf4j public class BgXiSpeakController extends JeecgController{ - @Autowired - private IBgXiSpeakService bgXiSpeakService; - - /** + private final IBgXiSpeakService bgXiSpeakService; + private final RuntimeService runtimeService; + /** * 分页列表查询 * * @param bgXiSpeak @@ -66,9 +72,9 @@ public class BgXiSpeakController extends JeecgController> queryPageList(BgXiSpeak bgXiSpeak, - @RequestParam(name="pageNo", defaultValue="1") Integer pageNo, - @RequestParam(name="pageSize", defaultValue="10") Integer pageSize, - HttpServletRequest req) { + @RequestParam(name="pageNo", defaultValue="1") Integer pageNo, + @RequestParam(name="pageSize", defaultValue="10") Integer pageSize, + HttpServletRequest req) { String hasQuery = req.getParameter("hasQuery"); if(hasQuery != null && "true".equals(hasQuery)){ QueryWrapper queryWrapper = QueryGenerator.initQueryWrapper(bgXiSpeak, req.getParameterMap()); @@ -223,7 +229,59 @@ public class BgXiSpeakController extends JeecgController saveBpmForm(@RequestBody BgXiSpeakBpmSaveDTO dto) { + if (dto == null || dto.getFormData() == null || oConvertUtils.isEmpty(dto.getFormData().getId())) { + return Result.error("表单数据不存在"); + } + if (oConvertUtils.isEmpty(dto.getProcessInstanceId())) { + return Result.error("流程实例ID不能为空"); + } + String varField = oConvertUtils.isEmpty(dto.getVarField()) ? "json_data" : dto.getVarField(); + bgXiSpeakService.updateById(dto.getFormData()); + Object existingVar = runtimeService.getVariable(dto.getProcessInstanceId(), varField); + JSONObject mergedJson = new JSONObject(); + if (existingVar instanceof String) { + try { + mergedJson = JSONObject.parseObject((String) existingVar); + } catch (Exception e) { + log.warn("解析原有流程变量json_data失败,将使用空对象", e); + } + } + if (mergedJson == null) { + mergedJson = new JSONObject(); + } + JSONObject formDataJson = (JSONObject) JSON.toJSON(dto.getFormData()); + if (formDataJson != null) { + mergedJson.putAll(formDataJson); + } + runtimeService.setVariable(dto.getProcessInstanceId(), varField, mergedJson.toJSONString()); + return Result.OK("保存成功"); + } + + /** + * 保存业务数据同时更新流程变量jsonData + * @param id + * @return + */ + @AutoLog(value = "根据传入的业务表单id撤回所有在运行的流程") + @Operation(summary="根据传入的业务表单id撤回所有在运行的流程") + @PostMapping(value = "/withDrawXiSpeak") + public Result withDrawXiSpeak(@RequestParam("id") String id) { + if(bgXiSpeakService.withDrawXiSpeak(id)){ + return Result.OK("删除流程成功"); + } + return Result.error("id为" + id + "的xispeak表单删除相关流程失败"); + } + /** * 通过id删除 * diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/dto/BgXiSpeakBpmSaveDTO.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/dto/BgXiSpeakBpmSaveDTO.java new file mode 100644 index 0000000..2ed5d85 --- /dev/null +++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/dto/BgXiSpeakBpmSaveDTO.java @@ -0,0 +1,23 @@ +package org.jeecg.modules.bg.xispeak.dto; + +import java.io.Serializable; + +import org.jeecg.modules.bg.xispeak.entity.BgXiSpeak; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "BgXiSpeak BPM form save DTO") +public class BgXiSpeakBpmSaveDTO implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "Process instance id") + private String processInstanceId; + + @Schema(description = "Process variable name") + private String varField; + + @Schema(description = "BgXiSpeak form data") + private BgXiSpeak formData; +} \ No newline at end of file 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 db74797..b451289 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 @@ -131,6 +131,7 @@ public class BgXiSpeak implements Serializable { /**完成状态(0推进中,1已完成)*/ @Excel(name = "完成状态(0推进中,1已完成)", width = 15) @Schema(description = "完成状态(0推进中,1已完成)") + @Dict( dicCode = "db_status") private java.lang.Integer completionStatus; /**落实部门id*/ @Excel(name = "落实部门id", width = 15, dictTable = "sys_depart", dicText = "depart_name", dicCode = "id") 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 a12cff5..c946791 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 @@ -5,71 +5,80 @@ import org.jeecg.modules.bg.xispeak.entity.BgXiSpeak; import com.baomidou.mybatisplus.extension.service.IService; import org.jeecg.common.exception.JeecgBootException; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; + import java.util.List; /** * @Description: 习总书记重要讲话指示批示情况 * @Author: jeecg-boot - * @Date: 2026-04-16 + * @Date: 2026-04-16 * @Version: V1.0 */ public interface IBgXiSpeakService extends IService { - /**根节点父ID的值*/ - public static final String ROOT_PID_VALUE = "0"; - - /**树节点有子节点状态值*/ - public static final String HASCHILD = "1"; - - /**树节点无子节点状态值*/ - public static final String NOCHILD = "0"; + /** + * 根节点父ID的值 + */ + public static final String ROOT_PID_VALUE = "0"; - /** - * 新增节点 - * - * @param bgXiSpeak - */ - void addBgXiSpeak(BgXiSpeak bgXiSpeak); - - /** - * 修改节点 - * - * @param bgXiSpeak - * @throws JeecgBootException - */ - void updateBgXiSpeak(BgXiSpeak bgXiSpeak) throws JeecgBootException; - - /** - * 删除节点 - * - * @param id - * @throws JeecgBootException - */ - void deleteBgXiSpeak(String id) throws JeecgBootException; + /** + * 树节点有子节点状态值 + */ + public static final String HASCHILD = "1"; - /** - * 查询所有数据,无分页 - * - * @param queryWrapper - * @return List - */ + /** + * 树节点无子节点状态值 + */ + public static final String NOCHILD = "0"; + + /** + * 新增节点 + * + * @param bgXiSpeak + */ + void addBgXiSpeak(BgXiSpeak bgXiSpeak); + + /** + * 修改节点 + * + * @param bgXiSpeak + * @throws JeecgBootException + */ + void updateBgXiSpeak(BgXiSpeak bgXiSpeak) throws JeecgBootException; + + /** + * 删除节点 + * + * @param id + * @throws JeecgBootException + */ + void deleteBgXiSpeak(String id) throws JeecgBootException; + + /** + * 查询所有数据,无分页 + * + * @param queryWrapper + * @return List + */ List queryTreeListNoPage(QueryWrapper queryWrapper); - /** - * 【vue3专用】根据父级编码加载分类字典的数据 - * - * @param parentCode - * @return - */ - List queryListByCode(String parentCode); + /** + * 【vue3专用】根据父级编码加载分类字典的数据 + * + * @param parentCode + * @return + */ + List queryListByCode(String parentCode); - /** - * 【vue3专用】根据pid查询子节点集合 - * - * @param pid - * @return - */ - List queryListByPid(String pid); + /** + * 【vue3专用】根据pid查询子节点集合 + * + * @param pid + * @return + */ + List queryListByPid(String pid); - BgXiSpeak getByIdForUpdate(String id); + BgXiSpeak getByIdForUpdate(String id); + + boolean withDrawXiSpeak(String id); } 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 c8a6c60..93d9c94 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 @@ -1,18 +1,26 @@ 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.RuntimeService; 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.mapper.BgXiSpeakMapper; import org.jeecg.modules.bg.xispeak.service.IBgXiSpeakService; +import org.jeecg.modules.tasktask.entity.TaskTask; +import org.jeecg.modules.tasktask.service.impl.TaskTaskServiceImpl; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; @@ -23,8 +31,14 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; * @Version: V1.0 */ @Service +@Slf4j +@RequiredArgsConstructor(onConstructor_ = @Autowired) public class BgXiSpeakServiceImpl extends ServiceImpl implements IBgXiSpeakService { + + private final TaskTaskServiceImpl taskTaskServiceImpl; + private final RuntimeService runtimeService; + @Override public void addBgXiSpeak(BgXiSpeak bgXiSpeak) { //新增时设置hasChild为0 @@ -222,4 +236,48 @@ public class BgXiSpeakServiceImpl extends ServiceImpl queryWrapper = new LambdaQueryWrapper() + .eq(TaskTask::getBusinessId, id) + .last("for update"); // 只有查询才能加这个 + List taskTaskList = taskTaskServiceImpl.list(queryWrapper); + + if (taskTaskList == null || taskTaskList.isEmpty()) { + log.warn("XiSpeak表单删除流程时 {} 未找到关联的tasktask表单数据", id); + return false; + } + + // 2. 提取并去重流程实例 ID + List taskTaskProcIdList = taskTaskList.stream() + .map(TaskTask::getProcessInstId) + .filter(org.apache.commons.lang3.StringUtils::isNotBlank) + .distinct() + .toList(); + + // 3. 循环处理流程引擎中的数据 + for (String procId : taskTaskProcIdList) { + long count = runtimeService.createProcessInstanceQuery() + .processInstanceId(procId) + .count(); + + if (count > 0) { + String delReason = "删除xispeak表单关联的tasktask表单中流程id为" + procId + "的流程"; + runtimeService.deleteProcessInstance(procId, delReason); + log.info("成功中止运行中的流程实例: {}", procId); + } + } + + // 4. 【核心修改:删除用】重新创建一个干净的 Wrapper,绝不能带 for update! + LambdaQueryWrapper deleteWrapper = new LambdaQueryWrapper() + .eq(TaskTask::getBusinessId, id); // 不要加 .last("for update") + + taskTaskServiceImpl.remove(deleteWrapper); // 使用干净的 Wrapper 执行删除 + + return true; + } } diff --git a/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/tasktask/service/impl/TaskTaskServiceImpl.java b/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/tasktask/service/impl/TaskTaskServiceImpl.java index 3e41091..971bdaf 100644 --- a/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/tasktask/service/impl/TaskTaskServiceImpl.java +++ b/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/tasktask/service/impl/TaskTaskServiceImpl.java @@ -43,6 +43,14 @@ public class TaskTaskServiceImpl extends ServiceImpl i newTask.setStartTime(null); this.save(newTask); Result res = bpmBaseExtApiImpl.startMutilProcess(taskTask.getFlowCode(), newTask.getId(), taskTask.getFormUrl(), taskTask.getFormUrl(), userName, jsonString); + String procId = res.getResult(); + if(StringUtils.isBlank(procId)){ + log.warn("流程启动失败"); + return false; + }else{ + newTask.setProcessInstId(procId); + } + this.updateById(newTask); return res.isSuccess(); } @Transactional(rollbackFor = Exception.class)