feat(xispeak): 部门审批信息改为持久化到业务表单JSON,不再依赖流程引擎变量

- 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 <noreply@anthropic.com>
This commit is contained in:
wsm
2026-05-27 20:25:43 +08:00
co-authored by Claude Opus 4.7
parent 1b49fdbbe0
commit 68eec9f2a2
8 changed files with 212 additions and 44 deletions
@@ -42,5 +42,6 @@ public class XiSpeakConfig {
private String temImplLeaderKey; private String temImplLeaderKey;
private String temBgLeaderKey; private String temBgLeaderKey;
private String sdwLeaderListKey; private String sdwLeaderListKey;
private String isEndKey;
} }
} }
@@ -10,6 +10,9 @@ import org.flowable.engine.RuntimeService;
import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.delegate.DelegateExecution;
import org.jeecg.common.constant.SymbolConstant; import org.jeecg.common.constant.SymbolConstant;
import org.jeecg.common.system.api.ISysBaseAPI; 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.jeecg.modules.extbpm.process.common.expression.FlowNodeExpression;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -26,6 +29,7 @@ public class XiSpeakFlow {
private final ISysBaseAPI iSysBaseAPI; private final ISysBaseAPI iSysBaseAPI;
private final RuntimeService runtimeService; private final RuntimeService runtimeService;
private final FlowNodeExpression flowNodeExpression; private final FlowNodeExpression flowNodeExpression;
private final IBgXiSpeakService bgXiSpeakService;
//流程表达式内用法 ${xiSpeakFlow.getBgDeptLdUserIdList()} //流程表达式内用法 ${xiSpeakFlow.getBgDeptLdUserIdList()}
public List<String> getBgDeptLdUserIdList() { public List<String> getBgDeptLdUserIdList() {
@@ -194,25 +198,6 @@ public class XiSpeakFlow {
return flowNodeExpression.getSonProcessHqVariableList(execution,xiSpeakConfig.getXiSpeak().getTemImplWorkerKey()).size(); 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)} * 流程表达式内用法: ${xiSpeakFlow.getTemImplLeaderList(execution)}
*/ */
@@ -242,10 +227,48 @@ public class XiSpeakFlow {
} }
/** /**
* 流程表达式内用法: ${xiSpeakFlow.getTemBgLeaderListLength(execution)} * 流程表达式内用法: ${xiSpeakFlow.getImplDeptLeaderIsEnd(execution)=='1'}
* 优先从业务表单 JSON 读取,没有则回退到流程变量(兼容旧流程实例)
*/ */
public int getImplDeptLeaderIsEnd(DelegateExecution execution) { public String getImplDeptLeaderIsEnd(DelegateExecution execution) {
return flowNodeExpression.getSonProcessHqVariableList(execution,xiSpeakConfig.getXiSpeak().getTemBgLeaderKey()); 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<String> 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(); return resultList.size();
} }
/**
* 优先从业务表单 JSON 读取 implUserNameList / implWorker,没有则回退到流程变量(兼容旧流程实例)
*/
public List<String> getDeptWorkerList(DelegateExecution execution) { public List<String> 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(); String variableName = xiSpeakConfig.getXiSpeak().getDeptWorkerKey();
if (execution == null || org.apache.commons.lang.StringUtils.isBlank(variableName)) { if (execution == null || org.apache.commons.lang.StringUtils.isBlank(variableName)) {
return Collections.emptyList(); return Collections.emptyList();
} }
try { try {
// 1. 优先尝试从当前 Execution 的局部作用域获取
// 在多实例(会签)中,每个分支特有的变量(如 item 变量)通常存储在这里
Object value = execution.getVariableLocal(variableName); Object value = execution.getVariableLocal(variableName);
// 2. 如果局部没有,直接使用 getVariable 获取
// 该方法会自动向上追溯:当前 Execution -> 父 Execution -> 流程实例(Global)
if (value == null) { if (value == null) {
value = execution.getVariable(variableName); value = execution.getVariable(variableName);
} }
// 3. 结果处理
if (value != null) { 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) { } catch (Exception e) {
log.warn("getSonProcessVariable 失败, executionId={}, variableName={}", log.warn("getDeptWorkerList from process variable failed", e);
execution.getId(), variableName, e);
} }
return Collections.emptyList(); return Collections.emptyList();
} }
private List<String> 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());
}
} }
@@ -115,6 +115,56 @@ public class XiSpeakFeedbackFlow {
.orElse(""); .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);
}
/** /**
* 获取当前流程的经办部门的经办人。 * 获取当前流程的经办部门的经办人。
* <p>用于流程表达式判断,通过 BusinessKey 关联业务主键,获取当前经办人。</p> * <p>用于流程表达式判断,通过 BusinessKey 关联业务主键,获取当前经办人。</p>
@@ -17,6 +17,7 @@ flow-biz:
tem-impl-leader-key: "tem_impl_leader" tem-impl-leader-key: "tem_impl_leader"
tem-bg-leader-key: "tem_bg_leader" tem-bg-leader-key: "tem_bg_leader"
sdw-leader-list-key: "sdwLeaderList" sdw-leader-list-key: "sdwLeaderList"
is-end-key: "isEnd"
xi-speak-fb: xi-speak-fb:
dept-id: "x" dept-id: "x"
business-id: "business_id" business-id: "business_id"
@@ -266,6 +266,26 @@ public class BgXiSpeakController extends JeecgController<BgXiSpeak, IBgXiSpeakSe
return Result.OK("保存成功"); return Result.OK("保存成功");
} }
/**
* 保存部门审批信息(isEnd、部门经办人)到业务表单 approveInfo JSON
*/
@AutoLog(value = "保存部门审批信息")
@Operation(summary="保存部门审批信息到业务表单")
@PostMapping(value = "/saveSubApprove")
public Result<String> saveSubApprove(@RequestBody Map<String, Object> 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<String> implUserNameList = (List<String>) 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 * 保存业务数据同时更新流程变量jsonData
* @param id * @param id
@@ -14,6 +14,8 @@ public class DeptApproveDetail {
private String deptId; // 部门ID private String deptId; // 部门ID
private String approverId; // 审批人ID(从监听器获取的) private String approverId; // 审批人ID(从监听器获取的)
private String approverName; // 审批人姓名(可选) private String approverName; // 审批人姓名(可选)
private Integer isEnd; // 是否办结(0=分配部门经办人,1=办结)
private String implWorker; // 被分配的部门经办人用户名
@JsonAlias("implUserId") @JsonAlias("implUserId")
private List<String> implUserIdList; // 落实人Id private List<String> implUserIdList; // 落实人Id
@JsonAlias("implUserName") @JsonAlias("implUserName")
@@ -85,4 +85,10 @@ public interface IBgXiSpeakService extends IService<BgXiSpeak> {
List<BgXiSpeak> getRootListWithDept(String pid); List<BgXiSpeak> getRootListWithDept(String pid);
void checkAndMarkCompletedByProcessInstId(String processInstanceId); void checkAndMarkCompletedByProcessInstId(String processInstanceId);
/**
* 保存部门审批信息(isEnd、部门经办人/经办人列表)到业务表单 approveInfo JSON 中
* isEnd=1 时 implWorker 生效,isEnd=0 时 implUserNameList 生效
*/
void saveSubApprove(String mainId, String deptId, Integer isEnd, String implWorker, List<String> implUserNameList);
} }
@@ -8,6 +8,8 @@ import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.util.oConvertUtils; import org.jeecg.common.util.oConvertUtils;
import org.jeecg.common.system.vo.SelectTreeModel; import org.jeecg.common.system.vo.SelectTreeModel;
import org.jeecg.modules.bg.xispeak.entity.BgXiSpeak; 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.mapper.BgXiSpeakMapper;
import org.jeecg.modules.bg.xispeak.service.IBgXiSpeakService; import org.jeecg.modules.bg.xispeak.service.IBgXiSpeakService;
import org.jeecg.modules.taskapprovalopinion.entity.TaskApprovalOpinion; import org.jeecg.modules.taskapprovalopinion.entity.TaskApprovalOpinion;
@@ -351,4 +353,40 @@ public class BgXiSpeakServiceImpl extends ServiceImpl<BgXiSpeakMapper, BgXiSpeak
log.info("业务表单 {} 的所有流程已结束,完成状态已更新为已完成", businessId); log.info("业务表单 {} 的所有流程已结束,完成状态已更新为已完成", businessId);
} }
} }
@Override
@Transactional(rollbackFor = Exception.class)
public void saveSubApprove(String mainId, String deptId, Integer isEnd, String implWorker, List<String> 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);
}
} }