!22 wsm-xispeak-收集后反馈流程

Merge pull request !22 from new_new_new/feature/20260515
This commit is contained in:
new_new_new
2026-05-15 08:42:51 +00:00
committed by Gitee
16 changed files with 634 additions and 117 deletions
@@ -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;
}
}
@@ -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<String> 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<String> 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<String> 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<String> 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<String> 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());
}
}
@@ -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);
}
}
}
}
@@ -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<String, DeptApproveDetail> 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);
}
}
}
}
@@ -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;
@@ -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);
}
}
}
}
@@ -36,7 +36,7 @@ public class XiSpeakFeedbackFlow {
private final ITaskTaskService taskTaskService;
// 流程表达式内用法 ${XiSpeakFeedbackFlow.getImplDeptList()}
// 流程表达式内用法 ${XiSpeakFeedbackFlow.getImplDeptList(execution)}
public List<String> 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<String> 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<String> getImplDeptWorker(DelegateExecution execution) {
String businessKey = execution.getProcessInstanceBusinessKey();
@@ -192,7 +193,7 @@ public class XiSpeakFeedbackFlow {
* <p>jj部门经办人</p>
*
* @return jj部门经办人
* @example 表达式用法: ${XiSpeakFeedbackFlow.JJDeptWorkerList()}
* @example 表达式用法: ${XiSpeakFeedbackFlow.getJJDeptWorkerList()}
*/
public List<String> getJJDeptWorkerList() {
// 1. 安全获取配置对象
@@ -243,7 +244,7 @@ public class XiSpeakFeedbackFlow {
* <p>获取jj部门审核人</p>
*
* @return jj部门审核人
* @example 表达式用法: ${XiSpeakFeedbackFlow.getJjDeptLeaderList()}
* @example 表达式用法: ${XiSpeakFeedbackFlow.getJJDeptLeaderList()}
*/
public List<String> getJJDeptLeaderList() {
// 1. 安全获取配置对象
@@ -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) {
}
}
@@ -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"
+4
View File
@@ -25,6 +25,10 @@
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-system-local-api</artifactId>
</dependency>
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-system-biz</artifactId>
</dependency>
</dependencies>
</project>
@@ -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<BgXiSpeak, IBgXiSpeakService>{
@Autowired
private IBgXiSpeakService bgXiSpeakService;
/**
private final IBgXiSpeakService bgXiSpeakService;
private final RuntimeService runtimeService;
/**
* 分页列表查询
*
* @param bgXiSpeak
@@ -66,9 +72,9 @@ public class BgXiSpeakController extends JeecgController<BgXiSpeak, IBgXiSpeakSe
@Operation(summary="习总书记重要讲话指示批示情况-分页列表查询")
@GetMapping(value = "/rootList")
public Result<IPage<BgXiSpeak>> 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<BgXiSpeak> queryWrapper = QueryGenerator.initQueryWrapper(bgXiSpeak, req.getParameterMap());
@@ -223,7 +229,59 @@ public class BgXiSpeakController extends JeecgController<BgXiSpeak, IBgXiSpeakSe
bgXiSpeakService.updateBgXiSpeak(bgXiSpeak);
return Result.OK("编辑成功!");
}
/**
* 保存业务数据同时更新流程变量jsonData
* @param dto
* @return
*/
@AutoLog(value = "保存业务数据同时更新流程变量jsonData")
@Operation(summary="保存业务数据同时更新流程变量jsonData")
@PostMapping(value = "/saveBpmForm")
public Result<String> 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<String> withDrawXiSpeak(@RequestParam("id") String id) {
if(bgXiSpeakService.withDrawXiSpeak(id)){
return Result.OK("删除流程成功");
}
return Result.error("id为" + id + "的xispeak表单删除相关流程失败");
}
/**
* 通过id删除
*
@@ -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;
}
@@ -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")
@@ -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<BgXiSpeak> {
/**根节点父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<BgXiSpeak>
*/
/**
* 树节点无子节点状态值
*/
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<BgXiSpeak>
*/
List<BgXiSpeak> queryTreeListNoPage(QueryWrapper<BgXiSpeak> queryWrapper);
/**
* 【vue3专用】根据父级编码加载分类字典的数据
*
* @param parentCode
* @return
*/
List<SelectTreeModel> queryListByCode(String parentCode);
/**
* 【vue3专用】根据父级编码加载分类字典的数据
*
* @param parentCode
* @return
*/
List<SelectTreeModel> queryListByCode(String parentCode);
/**
* 【vue3专用】根据pid查询子节点集合
*
* @param pid
* @return
*/
List<SelectTreeModel> queryListByPid(String pid);
/**
* 【vue3专用】根据pid查询子节点集合
*
* @param pid
* @return
*/
List<SelectTreeModel> queryListByPid(String pid);
BgXiSpeak getByIdForUpdate(String id);
BgXiSpeak getByIdForUpdate(String id);
boolean withDrawXiSpeak(String id);
}
@@ -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<BgXiSpeakMapper, BgXiSpeak> 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<BgXiSpeakMapper, BgXiSpeak
.eq(BgXiSpeak::getId, id)
.last("for update"));
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean withDrawXiSpeak(String id) {
// 1. 【查询用】锁住并查询业务数据,带 for update
LambdaQueryWrapper<TaskTask> queryWrapper = new LambdaQueryWrapper<TaskTask>()
.eq(TaskTask::getBusinessId, id)
.last("for update"); // 只有查询才能加这个
List<TaskTask> taskTaskList = taskTaskServiceImpl.list(queryWrapper);
if (taskTaskList == null || taskTaskList.isEmpty()) {
log.warn("XiSpeak表单删除流程时 {} 未找到关联的tasktask表单数据", id);
return false;
}
// 2. 提取并去重流程实例 ID
List<String> 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<TaskTask> deleteWrapper = new LambdaQueryWrapper<TaskTask>()
.eq(TaskTask::getBusinessId, id); // 不要加 .last("for update")
taskTaskServiceImpl.remove(deleteWrapper); // 使用干净的 Wrapper 执行删除
return true;
}
}
@@ -43,6 +43,14 @@ public class TaskTaskServiceImpl extends ServiceImpl<TaskTaskMapper, TaskTask> i
newTask.setStartTime(null);
this.save(newTask);
Result<String> 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)