refactor(supervision): 迁移 jeecg-module-flow 流程逻辑到 supervision 模块

This commit is contained in:
wsm
2026-08-06 14:52:47 +08:00
parent f849a2f049
commit 6f7e4c8587
35 changed files with 131 additions and 120 deletions
@@ -0,0 +1,31 @@
package org.jeecg.modules.supervision.common;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "flow-biz.org")
public class OrgConfig {
private Dept dept = new Dept();
private Role role = new Role();
@Data
public static class Dept {
private String jj;
private String sld;
private String dq;
private String bg;
}
@Data
public static class Role {
private String ld;
private String jjWorker;
private String sldJwsj;
private String sdwsj;
private String sdw;
}
}
@@ -0,0 +1,35 @@
package org.jeecg.modules.supervision.inspectimprove.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "flow-biz")
public class InspectCloseoutConfig {
private InspectCloseoutProperties inspectCloseout;
@Data
public static class InspectCloseoutProperties {
/** 业务主键 key */
private String businessKey;
/** JSON 数据 key */
private String jsonDataKey;
/** 表单 URL */
private String formUrl;
/** 流程编码 */
private String flowCode;
/** 销号申请人 key */
private String applicantKey;
/** 销号审批人 key */
private String approverKey;
/** 整改事项关联 key */
private String rectifyItemKey;
/** 销号说明 key */
private String closeoutDescKey;
/** 完成状态 key */
private String completionStatusKey;
}
}
@@ -0,0 +1,39 @@
package org.jeecg.modules.supervision.inspectimprove.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "flow-biz")
public class InspectImproveConfig {
private InspectImproveProperties inspectImprove;
@Data
public static class InspectImproveProperties {
/** 业务主键 key */
private String businessKey;
/** JSON 数据 key */
private String jsonDataKey;
/** 表单 URL */
private String formUrl;
/** 流程编码 */
private String flowCode;
/** 措施责任领导 key */
private String measureResLeaderKey;
/** 措施责任部门 key */
private String measureResDeptKey;
/** 问题责任领导 key */
private String questionResLeaderKey;
/** 问题责任部门 key */
private String questionResDeptKey;
/** 整改措施 key */
private String improveMeasureKey;
/** 工作进展 key */
private String workProgressKey;
/** 完成状态 key */
private String completionStatusKey;
}
}
@@ -0,0 +1,5 @@
package org.jeecg.modules.supervision.inspectimprove.constant;
public class InspectCloseoutConstant {
public static final int ZERO_INDEX = 0;
}
@@ -0,0 +1,15 @@
package org.jeecg.modules.supervision.inspectimprove.constant;
import org.jeecg.modules.supervision.dqinspecttask.entity.DqInspectTask;
public class InspectConstant {
public static final String NEED_APPROVE_STR = "1";
public static final String NOT_NEED_APPROVE_STR = "0";
public static final String IMPL_DEPT_LEADER_KEY = "impl_dept_leader";
public static final String DQ_DEPT_LEADER_KEY = "dq_dept_leader";
public static final String IS_END_KEY = "is_end";
public static final String SUB_APPROVE_USER_KEY = "sub_approve_user";
public static final String IS_NEED_LEADER_APPROVE_KEY = "is_need_leader_approve";
public static final String LEADER_APPROVE_USER_KEY = "leader_approve_user";
public static final int ZERO_INDEX = 0;
}
@@ -0,0 +1,229 @@
package org.jeecg.modules.supervision.inspectimprove.flow;
import com.alibaba.fastjson.JSONObject;
import com.jeecg.weibo.exception.BusinessException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.jeecg.modules.supervision.common.OrgConfig;
import org.jeecg.modules.supervision.inspectimprove.constant.InspectCloseoutConstant;
import org.jeecg.modules.supervision.inspectimprove.config.InspectCloseoutConfig;
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;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
@Component("inspectCloseoutFlow")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class InspectCloseoutFlow {
private final InspectCloseoutConfig inspectCloseoutConfig;
private final OrgConfig orgConfig;
private final FlowNodeExpression flowNodeExpression;
private final ISysBaseAPI iSysBaseAPI;
// ===================================================================
// 通用工具方法
// ===================================================================
private String getStringFromData(Object jsonData, String codeName, String logLabel) {
if (jsonData == null || StringUtils.isBlank(codeName)) {
return null;
}
try {
JSONObject data;
if (jsonData instanceof JSONObject) {
data = (JSONObject) jsonData;
} else if (jsonData instanceof String) {
String jsonStr = (String) jsonData;
if (StringUtils.isBlank(jsonStr)) return null;
data = JSONObject.parseObject(jsonStr);
} else {
data = JSONObject.parseObject(JSONObject.toJSONString(jsonData));
}
String result = data.getString(codeName);
log.info("{} 解析结果: {}", logLabel, result);
return result;
} catch (Exception e) {
log.warn("JSON解析失败: label={}, key={}", logLabel, codeName, e);
return null;
}
}
//流程表达式内用法 ${inspectImproveFlow.getJJDeptLdUserIdList()}
public List<String> getJJDeptLdUserIdList() {
String deptId = orgConfig.getDept().getJj();
String roleId = orgConfig.getRole().getLd();
log.info("【巡视整改销号-流程表达式】查询部门角色用户列表, deptId: {}, roleId: {}", deptId, roleId);
List<String> userList = iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi(deptId, roleId);
log.info("【巡视整改销号-流程表达式】查询到用户列表, userList: {}", userList);
return userList;
}
//流程表达式内用法 ${inspectCloseoutFlow.getJJDeptLdUserIdListLength()}
public int getJJDeptLdUserIdListLength() {
List<String> jjDeptLdUserIdList = this.getJJDeptLdUserIdList();
if (jjDeptLdUserIdList != null && !jjDeptLdUserIdList.isEmpty()) {
return jjDeptLdUserIdList.size();
}
log.error("【巡视整改销号-流程表达式】JJ部门LD角色没有对应用户");
return 0;
}
//流程表达式内用法 ${inspectCloseoutFlow.getJJDeptLdWorkerIdList()}
public List<String> getJJDeptLdWorkerIdList() {
String deptId = orgConfig.getDept().getJj();
String roleId = orgConfig.getRole().getJjWorker();
log.info("【巡视整改销号-流程表达式】查询部门角色用户列表, deptId: {}, roleId: {}", deptId, roleId);
List<String> userList = iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi(deptId, roleId);
log.info("【巡视整改销号-流程表达式】查询到用户列表, userList: {}", userList);
return userList;
}
//流程表达式内用法 ${inspectCloseoutFlow.getJWSJUser()}
public String getJWSJUser() {
String deptId = orgConfig.getDept().getSld();
String roleId = orgConfig.getRole().getSldJwsj();
log.info("【巡视整改销号-流程表达式】查询纪委书记用户, deptId: {}, roleId: {}", deptId, roleId);
List<String> userList = iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi(deptId, roleId);
if (CollectionUtils.isEmpty(userList)) {
log.error("【巡视整改销号-流程表达式】未查询到纪委书记用户, deptId: {}, roleId: {}", deptId, roleId);
throw new BusinessException("流程流转失败:未配置纪委书记用户,请联系管理员!");
}
if (userList.size() > 1) {
log.error("【巡视整改销号-流程表达式】查询到多个纪委书记用户, 数量: {}, 用户列表: {}", userList.size(), userList);
throw new BusinessException("流程流转失败:存在多个纪委书记用户,数据不唯一,请检查角色配置!");
}
String targetUser = userList.get(InspectCloseoutConstant.ZERO_INDEX);
log.info("【巡视整改销号-流程表达式】匹配到纪委书记用户: {}", targetUser);
return targetUser;
}
//流程表达式内用法 ${inspectCloseoutFlow.getDqDeptLdUserIdList()}
public List<String> getDqDeptLdUserIdList() {
String deptId = orgConfig.getDept().getDq();
String roleId = orgConfig.getRole().getJjWorker();
log.info("【巡视整改销号-流程表达式】查询部门角色用户列表, deptId: {}, roleId: {}", deptId, roleId);
List<String> userList = iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi(deptId, roleId);
log.info("【巡视整改销号-流程表达式】查询到用户列表, userList: {}", userList);
return userList;
}
//流程表达式内用法 ${inspectCloseoutFlow.getDqDeptLdUserIdListLength()}
public int getDqDeptLdUserIdListLength() {
List<String> dqDeptLdUserIdList = this.getDqDeptLdUserIdList();
if (dqDeptLdUserIdList != null && !dqDeptLdUserIdList.isEmpty()) {
return dqDeptLdUserIdList.size();
}
log.error("【巡视整改销号-流程表达式】DQ部门WORKER角色没有对应用户");
return 0;
}
//流程表达式内用法 ${inspectCloseoutFlow.getJJDeptLdWorkerIdListLength()}
public int getJJDeptLdWorkerIdListLength() {
List<String> jjDeptLdUserIdList = this.getJJDeptLdUserIdList();
if (jjDeptLdUserIdList != null && !jjDeptLdUserIdList.isEmpty()) {
return jjDeptLdUserIdList.size();
}
log.error("【巡视整改销号-流程表达式】JJ部门WORKER角色没有对应用户");
return 0;
}
private List<String> getListFromData(Object jsonData, String codeName, String logLabel) {
if (jsonData == null || StringUtils.isBlank(codeName)) {
log.warn("【巡视整改销号-流程表达式】{}解析参数为空, codeName: {}", logLabel, codeName);
return Collections.emptyList();
}
try {
JSONObject data;
if (jsonData instanceof JSONObject) {
data = (JSONObject) jsonData;
} else if (jsonData instanceof String) {
String jsonStr = (String) jsonData;
if (StringUtils.isBlank(jsonStr)) {
log.warn("【巡视整改销号-流程表达式】{}JSON字符串为空", logLabel);
return Collections.emptyList();
}
data = JSONObject.parseObject(jsonStr);
} else {
data = JSONObject.parseObject(JSONObject.toJSONString(jsonData));
}
String rawValue = data.getString(codeName);
if (StringUtils.isBlank(rawValue)) {
log.warn("【巡视整改销号-流程表达式】{}字段值为空, codeName: {}", logLabel, codeName);
return Collections.emptyList();
}
List<String> result = Arrays.stream(rawValue.split(","))
.map(String::trim)
.filter(StringUtils::isNotBlank)
.collect(Collectors.toList());
log.info("【巡视整改销号-流程表达式】{}解析结果: {}", logLabel, result);
return result;
} catch (Exception e) {
log.error("【巡视整改销号-流程表达式】{}JSON解析失败, codeName: {}", logLabel, codeName, e);
return Collections.emptyList();
}
}
// ===================================================================
// 流程表达式方法 — 用法示例: ${inspectCloseoutFlow.xxx}
// ===================================================================
/**
* ${inspectCloseoutFlow.getApproverList(json_data)}
* 获取销号审批人列表
*/
public List<String> getApproverList(Object jsonData) {
InspectCloseoutConfig.InspectCloseoutProperties props = inspectCloseoutConfig.getInspectCloseout();
return getListFromData(jsonData, props.getApproverKey(), "销号审批人");
}
/**
* ${inspectCloseoutFlow.getApproverListLength(json_data)}
*/
public int getApproverListLength(Object jsonData) {
return getApproverList(jsonData).size();
}
/**
* ${inspectCloseoutFlow.getApplicant(json_data)}
* 获取销号申请人
*/
public String getApplicant(Object jsonData) {
InspectCloseoutConfig.InspectCloseoutProperties props = inspectCloseoutConfig.getInspectCloseout();
return getStringFromData(jsonData, props.getApplicantKey(), "销号申请人");
}
/**
* ${inspectCloseoutFlow.getRectifyItemId(json_data)}
* 获取关联的整改事项ID
*/
public String getRectifyItemId(Object jsonData) {
InspectCloseoutConfig.InspectCloseoutProperties props = inspectCloseoutConfig.getInspectCloseout();
return getStringFromData(jsonData, props.getRectifyItemKey(), "整改事项ID");
}
/**
* ${inspectCloseoutFlow.getListSize(data)}
* 计算逗号分隔字符串的元素个数
*/
public int getListSize(String data) {
if (StringUtils.isEmpty(data)) {
return 0;
}
return (int) Arrays.stream(data.split(","))
.map(String::trim)
.filter(StringUtils::isNotEmpty)
.count();
}
}
@@ -0,0 +1,394 @@
package org.jeecg.modules.supervision.inspectimprove.flow;
import com.alibaba.fastjson.JSONObject;
import com.jeecg.weibo.exception.BusinessException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.delegate.DelegateExecution;
import org.jeecg.modules.supervision.common.OrgConfig;
import org.jeecg.modules.supervision.inspectimprove.constant.InspectConstant;
import org.jeecg.modules.supervision.inspectimprove.config.InspectImproveConfig;
import org.jeecg.common.system.api.ISysBaseAPI;
import org.jeecg.modules.supervision.dqinspecttask.entity.DqInspectTask;
import org.jeecg.modules.supervision.dqinspecttask.service.IDqInspectTaskService;
import org.jeecg.modules.supervision.inspectimprove.entity.DjInspectImprove;
import org.jeecg.modules.supervision.inspectimprove.service.IDjInspectImproveService;
import org.jeecg.modules.extbpm.process.common.expression.FlowNodeExpression;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
@Component("inspectImproveFlow")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class InspectImproveFlow {
private final InspectImproveConfig inspectImproveConfig;
private final OrgConfig orgConfig;
private final IDjInspectImproveService djInspectImproveService;
private final FlowNodeExpression flowNodeExpression;
private final ISysBaseAPI iSysBaseAPI;
private final IDqInspectTaskService dqInspectTaskService;
private final RuntimeService runtimeService;
//流程表达式内用法 ${inspectImproveFlow.getDqDeptLdUserIdList()}
public List<String> getDqDeptLdUserIdList() {
String deptId = orgConfig.getDept().getDq();
String roleId = orgConfig.getRole().getLd();
log.info("【巡视整改-流程表达式】查询部门角色用户列表, deptId: {}, roleId: {}", deptId, roleId);
List<String> userList = iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi(deptId, roleId);
log.info("【巡视整改-流程表达式】查询到用户列表, userList: {}", userList);
return userList;
}
// 流程表达式内用法 ${inspectImproveFlow.getSDWSJ()}
public String getSDWSJ() {
String deptId = orgConfig.getDept().getSld();
String roleId = orgConfig.getRole().getSdwsj();
log.info("【巡视整改-流程表达式】开始查询所党委书记用户, deptId: {}, roleId: {}", deptId, roleId);
List<String> userList = iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi(deptId, roleId);
// 1. 强校验:未查询到任何用户
if (CollectionUtils.isEmpty(userList)) {
log.error("【巡视整改-流程表达式getSDWSJ】异常:未查询到所党委书记用户!deptId: {}, roleId: {}", deptId, roleId);
throw new BusinessException("流程流转失败:未配置所党委书记用户,请联系管理员!");
}
// 2. 强校验:查询到多个用户(不唯一)
if (userList.size() > 1) {
log.error("【巡视整改-流程表达式getSDWSJ】严重异常:期望唯一用户,但查询到多个党委书记用户!数量: {}, 用户列表: {}", userList.size(), userList);
throw new BusinessException("流程流转失败:系统检测到存在多个党委书记用户,数据不唯一,请检查系统角色配置!");
}
// 3. 唯一性通过,校验提取出的值是否有效
String targetUser = userList.get(InspectConstant.ZERO_INDEX);
if (StringUtils.isEmpty(targetUser)) {
log.error("【巡视整改-流程表达式getSDWSJ】异常:查询到的党委书记用户名为空字符串!");
throw new BusinessException("流程流转失败:党委书记用户名数据非法!");
}
log.info("【巡视整改-流程表达式getSDWSJ】成功匹配到唯一的所党委书记用户: {}", targetUser);
return targetUser;
}
//流程表达式内用法 ${inspectImproveFlow.getDqDeptLdUserIdListLength()}
public int getDqDeptLdUserIdListLength() {
List<String> dqDeptLdUserIdList = this.getDqDeptLdUserIdList();
if (dqDeptLdUserIdList != null && !dqDeptLdUserIdList.isEmpty()) {
return dqDeptLdUserIdList.size();
}
log.warn("【巡视整改-流程表达式】dq部门ld角色没有对应用户");
return 0;
}
// 流程表达式内用法 ${inspectImproveFlow.getNeedDqLeaderApprove(execution)}
public String getNeedDqLeaderApprove(DelegateExecution execution) {
DqInspectTask temDqInspectTask = this.getDqInspectTask(execution);
// 如果上一步返回了 null(说明数据缺失),为了流程安全,这里直接抛出异常,阻止流程往错误的方向流转
if (temDqInspectTask == null) {
throw new IllegalStateException("无法判定是否需要审批:未找到对应的业务表单数据,流程暂停。");
}
// 字符串比对的优雅写法:把常量放在前面,天然防范 NPE
if (InspectConstant.NOT_NEED_APPROVE_STR.equals(temDqInspectTask.getIsNeedAppro())
|| StringUtils.isBlank(temDqInspectTask.getIsNeedAppro())) {
return InspectConstant.NOT_NEED_APPROVE_STR;
}
return InspectConstant.NEED_APPROVE_STR;
}
// 流程表达式内用法 ${inspectImproveFlow.getMeasureResLeader(execution)}
public String getMeasureResLeader(DelegateExecution execution) {
DqInspectTask temDqInspectTask = this.getDqInspectTask(execution);
if (temDqInspectTask == null) {
log.error("【巡视整改-流程表达式】获取措施责任领导时未找到业务表单数据, processInstanceId: {}", execution.getProcessInstanceId());
throw new IllegalStateException("无法获取措施责任领导:未找到对应的业务表单数据,流程暂停。");
}
String measureResLeader = temDqInspectTask.getMeasureResLeader();
if (StringUtils.isBlank(measureResLeader)) {
log.warn("【巡视整改-流程表达式】措施责任领导为空, businessKey: {}, processInstanceId: {}",
temDqInspectTask.getId(), execution.getProcessInstanceId());
return null;
}
log.info("【巡视整改-流程表达式】获取到措施责任领导: {}, businessKey: {}", measureResLeader, temDqInspectTask.getId());
return measureResLeader;
}
// 流程表达式内用法:${inspectImproveFlow.getImplDeptLeaderList(execution)}
public List<String> getImplDeptLeaderList(DelegateExecution execution) {
DqInspectTask temDqInspectTask = this.getDqInspectTask(execution);
if (temDqInspectTask == null) {
throw new IllegalStateException("无法获取审批人:未找到对应的业务表单数据,流程暂停。");
}
String measureDeptId = temDqInspectTask.getMeasureResDept();
if (StringUtils.isBlank(measureDeptId)) {
String errorMsg = String.format("流程流转失败: 业务表单[ID:%s]中的整改责任部门(measureResDept)为空,无法匹配审批人。", temDqInspectTask.getId());
log.error(errorMsg);
throw new IllegalStateException(errorMsg); // 保持严谨,核心数据缺失直接抛异常阻断
}
String ldRoleId = orgConfig.getRole().getLd();
log.info("正在查询整改责任部门: {} 下的角色: {} 的领导列表", measureDeptId, ldRoleId);
List<String> leaderList = iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi(
measureDeptId,
ldRoleId
);
if (leaderList == null || leaderList.isEmpty()) {
log.warn("【节点预警】整改责任部门: {} 下未配置角色: {} 的用户,审批人列表为空!", measureDeptId, ldRoleId);
return Collections.emptyList();
}
log.info("查询到的整改责任部门领导为:{}", leaderList);
return leaderList;
}
// 流程表达式内用法:${inspectImproveFlow.getImplDeptLeaderListLength(execution)}
public int getImplDeptLeaderListLength(DelegateExecution execution) {
return this.getImplDeptLeaderList(execution).size();
}
// 流程表达式内用法:${inspectImproveFlow.getIsEnd(execution)}
public String getIsEnd(DelegateExecution execution) {
Object value = runtimeService.getVariable(execution.getProcessInstanceId(), InspectConstant.IS_END_KEY);
return value != null ? value.toString() : null;
}
// 流程表达式内用法:${inspectImproveFlow.getIsNeedLeaderApprove(execution)}
public String getIsNeedLeaderApprove(DelegateExecution execution) {
Object value = runtimeService.getVariable(execution.getProcessInstanceId(), InspectConstant.IS_NEED_LEADER_APPROVE_KEY);
return value != null ? value.toString() : null;
}
// 流程表达式内用法:${inspectImproveFlow.getLeaderApproveUser(execution)}
public String getLeaderApproveUser(DelegateExecution execution) {
Object value = runtimeService.getVariable(execution.getProcessInstanceId(), InspectConstant.LEADER_APPROVE_USER_KEY);
return value != null ? value.toString() : null;
}
// 流程表达式内用法:${inspectImproveFlow.getSubApproveUser(execution)}
public String getSubApproveUser(DelegateExecution execution) {
Object value = runtimeService.getVariable(execution.getProcessInstanceId(), InspectConstant.SUB_APPROVE_USER_KEY);
return value != null ? value.toString() : null;
}
// 流程表达式内用法:${inspectImproveFlow.getDqDeptLeader(execution)}
public String getDqDeptLeader(DelegateExecution execution) {
Object value = runtimeService.getVariable(execution.getProcessInstanceId(), InspectConstant.DQ_DEPT_LEADER_KEY);
return value != null ? value.toString() : null;
}
// 流程表达式内用法:${inspectImproveFlow.getImplDeptLeader(execution)}
public String getImplDeptLeader(DelegateExecution execution) {
Object value = runtimeService.getVariable(execution.getProcessInstanceId(), InspectConstant.IMPL_DEPT_LEADER_KEY);
return value != null ? value.toString() : null;
}
// 流程表达式内用法:${inspectImproveFlow.getSLD(execution)}
public String getSLD(DelegateExecution execution) {
DqInspectTask tempDqInspectTask = this.getDqInspectTask(execution);
if (tempDqInspectTask == null) {
log.error("【巡视整改-流程表达式】未获取到巡视整改任务, businessKey: {}", execution.getProcessInstanceBusinessKey());
return null;
}
String sldUserStr = tempDqInspectTask.getChargeLeaderId();
if (StringUtils.isBlank(sldUserStr)) {
log.error("【巡视整改-流程表达式】未获取到所领导, businessKey: {}", execution.getProcessInstanceBusinessKey());
return null;
}
return sldUserStr;
}
// 流程表达式内用法:${inspectImproveFlow.getDqLeader(execution)}
public String getDqLeader(DelegateExecution execution) {
DqInspectTask tempDqInspectTask = this.getDqInspectTask(execution);
if (tempDqInspectTask == null) {
log.error("【巡视整改-流程表达式】未获取到巡视整改任务, businessKey: {}", execution.getProcessInstanceBusinessKey());
return null;
}
String supDeptLeader = tempDqInspectTask.getSupDeptleaderid();
if (StringUtils.isBlank(supDeptLeader)) {
log.error("【巡视整改-流程表达式】未获取到党群领导, businessKey: {}", execution.getProcessInstanceBusinessKey());
return null;
}
return supDeptLeader;
}
private DqInspectTask getDqInspectTask(DelegateExecution execution) {
Object inspectTaskId = execution.getVariable(inspectImproveConfig.getInspectImprove().getBusinessKey());
// 统一校验 null 和空字符串
if (inspectTaskId == null || StringUtils.isBlank(inspectTaskId.toString())) {
log.error("【巡视整改-流程表达式】未获取到有效的业务表单ID, processInstanceId: {}, businessKey: {}", execution.getProcessInstanceId(), inspectTaskId);
return null; // 明确返回 null,不给上游返回空壳对象
}
// 如果 getById 没查到,MyBatis-Plus 也会返回 null
DqInspectTask task = dqInspectTaskService.getById(inspectTaskId.toString());
if (task == null) {
log.error("【巡视整改-流程表达式】未查询到业务表单记录, businessKey: {}, processInstanceId: {}", inspectTaskId, execution.getProcessInstanceId());
} else {
log.info("【巡视整改-流程表达式】已查询到业务表单, businessKey: {}", inspectTaskId);
}
return task;
}
// ===================================================================
// 通用工具方法
// ===================================================================
/**
* 从 JSON 数据中解析字符串字段
*/
private String getStringFromData(Object jsonData, String codeName, String logLabel) {
if (jsonData == null || StringUtils.isBlank(codeName)) {
return null;
}
try {
JSONObject data;
if (jsonData instanceof JSONObject) {
data = (JSONObject) jsonData;
} else if (jsonData instanceof String) {
String jsonStr = (String) jsonData;
if (StringUtils.isBlank(jsonStr)) return null;
data = JSONObject.parseObject(jsonStr);
} else {
data = JSONObject.parseObject(JSONObject.toJSONString(jsonData));
}
String result = data.getString(codeName);
log.info("{} 解析结果: {}", logLabel, result);
return result;
} catch (Exception e) {
log.warn("JSON解析失败: label={}, key={}", logLabel, codeName, e);
return null;
}
}
/**
* 从 JSON 数据中解析逗号分隔的列表字段
*/
private List<String> getListFromData(Object jsonData, String codeName, String logLabel) {
if (jsonData == null || StringUtils.isBlank(codeName)) {
return Collections.emptyList();
}
try {
JSONObject data;
if (jsonData instanceof JSONObject) {
data = (JSONObject) jsonData;
} else if (jsonData instanceof String) {
String jsonStr = (String) jsonData;
if (StringUtils.isBlank(jsonStr)) return Collections.emptyList();
data = JSONObject.parseObject(jsonStr);
} else {
data = JSONObject.parseObject(JSONObject.toJSONString(jsonData));
}
String rawValue = data.getString(codeName);
if (StringUtils.isBlank(rawValue)) {
return Collections.emptyList();
}
List<String> result = Arrays.stream(rawValue.split(","))
.map(String::trim)
.filter(StringUtils::isNotBlank)
.collect(Collectors.toList());
log.info("{} 解析结果: {}", logLabel, result);
return result;
} catch (Exception e) {
log.warn("JSON解析失败: label={}, key={}", logLabel, codeName, e);
return Collections.emptyList();
}
}
// ===================================================================
// 流程表达式方法 — 用法示例: ${inspectImproveFlow.xxx}
// ===================================================================
/**
* ${inspectImproveFlow.getMeasureResLeaderList(json_data)}
* 获取措施责任领导列表
*/
public List<String> getMeasureResLeaderList(Object jsonData) {
InspectImproveConfig.InspectImproveProperties props = inspectImproveConfig.getInspectImprove();
return getListFromData(jsonData, props.getMeasureResLeaderKey(), "措施责任领导");
}
/**
* ${inspectImproveFlow.getMeasureResLeaderListLength(json_data)}
*/
public int getMeasureResLeaderListLength(Object jsonData) {
return getMeasureResLeaderList(jsonData).size();
}
/**
* ${inspectImproveFlow.getQuestionResLeaderList(json_data)}
* 获取问题责任领导列表
*/
public List<String> getQuestionResLeaderList(Object jsonData) {
InspectImproveConfig.InspectImproveProperties props = inspectImproveConfig.getInspectImprove();
return getListFromData(jsonData, props.getQuestionResLeaderKey(), "问题责任领导");
}
/**
* ${inspectImproveFlow.getQuestionResLeaderListLength(json_data)}
*/
public int getQuestionResLeaderListLength(Object jsonData) {
return getQuestionResLeaderList(jsonData).size();
}
/**
* ${inspectImproveFlow.getMeasureResDeptList(json_data)}
* 获取措施责任部门列表
*/
public List<String> getMeasureResDeptList(Object jsonData) {
InspectImproveConfig.InspectImproveProperties props = inspectImproveConfig.getInspectImprove();
return getListFromData(jsonData, props.getMeasureResDeptKey(), "措施责任部门");
}
/**
* ${inspectImproveFlow.getMeasureResDeptListLength(json_data)}
*/
public int getMeasureResDeptListLength(Object jsonData) {
return getMeasureResDeptList(jsonData).size();
}
/**
* ${inspectImproveFlow.getMeasureResLeader(json_data)}
* 获取措施责任领导(单值)
*/
public String getMeasureResLeader(Object jsonData) {
InspectImproveConfig.InspectImproveProperties props = inspectImproveConfig.getInspectImprove();
return getStringFromData(jsonData, props.getMeasureResLeaderKey(), "措施责任领导");
}
/**
* ${inspectImproveFlow.getListSize(json_data)}
* 计算逗号分隔字符串的元素个数
*/
public int getListSize(String data) {
if (StringUtils.isEmpty(data)) {
return 0;
}
return (int) Arrays.stream(data.split(","))
.map(String::trim)
.filter(StringUtils::isNotEmpty)
.count();
}
}
@@ -0,0 +1,38 @@
package org.jeecg.modules.supervision.inspectimprove.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.modules.supervision.inspectimprove.config.InspectCloseoutConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Objects;
/**
* 巡视整改销号审批通过后,更新关联整改事项状态
*/
@Slf4j
@Component("AfterCloseoutApprovedListener")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class AfterCloseoutApprovedListener implements TaskListener {
private static final long serialVersionUID = 1L;
private final InspectCloseoutConfig inspectCloseoutConfig;
@Override
public void notify(DelegateTask delegateTask) {
Object businessKeyObj = delegateTask.getVariable(
inspectCloseoutConfig.getInspectCloseout().getBusinessKey());
String businessKey = Objects.toString(businessKeyObj, "").trim();
if (businessKey.isEmpty()) {
log.warn("【销号审批监听器】未查询到业务表单ID,跳过。TaskId: {}", delegateTask.getId());
return;
}
// TODO: 根据实际业务实体,更新关联的整改事项状态为"已销号"
log.info("【销号审批监听器】业务表单 [id={}] 销号审批通过", businessKey);
}
}
@@ -0,0 +1,30 @@
package org.jeecg.modules.supervision.inspectimprove.listener;
import lombok.RequiredArgsConstructor;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.delegate.TaskListener;
import org.flowable.task.service.delegate.DelegateTask;
import org.jeecg.modules.supervision.inspectimprove.constant.InspectConstant;
import org.jeecg.modules.supervision.inspectimprove.service.IDjInspectImproveService;
import org.jeecg.modules.supervision.inspectimprove.config.InspectImproveConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 巡视整改流程党群领导审批通过后,将党群领导写入流程变量
*/
@Component("InspectAfterDqLeaderApproveListener")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class AfterDqLeaderApproveListener implements TaskListener {
private static final long serialVersionUID = 1L;
private final InspectImproveConfig inspectImproveConfig;
private final IDjInspectImproveService djInspectImproveService;
private final RuntimeService runtimeService;
@Override
public void notify(DelegateTask delegateTask) {
LeaderApproveHelper.handle(delegateTask, runtimeService,
InspectConstant.DQ_DEPT_LEADER_KEY, "【巡视整改-党群领导审批监听器】");
}
}
@@ -0,0 +1,30 @@
package org.jeecg.modules.supervision.inspectimprove.listener;
import lombok.RequiredArgsConstructor;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.delegate.TaskListener;
import org.flowable.task.service.delegate.DelegateTask;
import org.jeecg.modules.supervision.inspectimprove.constant.InspectConstant;
import org.jeecg.modules.supervision.inspectimprove.service.IDjInspectImproveService;
import org.jeecg.modules.supervision.inspectimprove.config.InspectImproveConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 巡视整改流程部门领导审批通过后,将部门领导写入流程变量
*/
@Component("InspectAfterImplDeptLeaderApproveListener")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class AfterImplDeptLeaderApproveListener implements TaskListener {
private static final long serialVersionUID = 1L;
private final InspectImproveConfig inspectImproveConfig;
private final IDjInspectImproveService djInspectImproveService;
private final RuntimeService runtimeService;
@Override
public void notify(DelegateTask delegateTask) {
LeaderApproveHelper.handle(delegateTask, runtimeService,
InspectConstant.IMPL_DEPT_LEADER_KEY, "【巡视整改-部门领导审批监听器】");
}
}
@@ -0,0 +1,50 @@
package org.jeecg.modules.supervision.inspectimprove.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.modules.supervision.inspectimprove.entity.DjInspectImprove;
import org.jeecg.modules.supervision.inspectimprove.service.IDjInspectImproveService;
import org.jeecg.modules.supervision.inspectimprove.config.InspectImproveConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Objects;
/**
* 巡视整改流程最终审批通过后,将业务表单状态标记为已完成
*/
@Slf4j
@Component("AfterInspectImproveCompleteListener")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class AfterInspectImproveCompleteListener implements TaskListener {
private static final long serialVersionUID = 1L;
private final InspectImproveConfig inspectImproveConfig;
private final IDjInspectImproveService djInspectImproveService;
@Override
public void notify(DelegateTask delegateTask) {
Object businessKeyObj = delegateTask.getVariable(
inspectImproveConfig.getInspectImprove().getBusinessKey());
String businessKey = Objects.toString(businessKeyObj, "").trim();
if (businessKey.isEmpty()) {
log.warn("【巡视整改完成监听器】未查询到业务表单ID,跳过。TaskId: {}", delegateTask.getId());
return;
}
try {
DjInspectImprove entity = djInspectImproveService.getById(businessKey);
if (entity == null) {
log.warn("【巡视整改完成监听器】业务数据不存在,id: {}", businessKey);
return;
}
// TODO: 根据实际业务补充完成状态字段的更新逻辑
log.info("【巡视整改完成监听器】业务表单 [id={}] 流程已完成", businessKey);
} catch (Exception e) {
log.error("【巡视整改完成监听器】更新业务表单异常, businessKey: " + businessKey, e);
}
}
}
@@ -0,0 +1,56 @@
package org.jeecg.modules.supervision.inspectimprove.listener;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.flowable.engine.delegate.TaskListener;
import org.flowable.task.service.delegate.DelegateTask;
import org.jeecg.modules.supervision.inspectimprove.config.InspectImproveConfig;
import org.jeecg.modules.supervision.dqinspecttask.entity.DqInspectTask;
import org.jeecg.modules.supervision.dqinspecttask.service.IDqInspectTaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 巡视整改-措施责任领导监听器:任务完成后将当前审批人写入 DqInspectTask.measureResLeader
*/
@Slf4j
@Component("AfterMeasureResLeaderStoreListener")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class AfterMeasureResLeaderStoreListener implements TaskListener {
private static final long serialVersionUID = 1L;
private final InspectImproveConfig inspectImproveConfig;
private final IDqInspectTaskService dqInspectTaskService;
@Override
public void notify(DelegateTask delegateTask) {
String assignee = delegateTask.getAssignee();
String taskId = delegateTask.getId();
if (StringUtils.isEmpty(assignee)) {
log.error("【巡视整改-措施责任领导存储监听器】未获取到审批人, taskId: {}", taskId);
return;
}
Object businessKeyObj = delegateTask.getVariable(inspectImproveConfig.getInspectImprove().getBusinessKey());
if (businessKeyObj == null || StringUtils.isBlank(businessKeyObj.toString())) {
log.error("【巡视整改-措施责任领导存储监听器】未获取到业务表单ID, taskId: {}", taskId);
return;
}
String businessKey = businessKeyObj.toString();
DqInspectTask task = dqInspectTaskService.getById(businessKey);
if (task == null) {
log.error("【巡视整改-措施责任领导存储监听器】未查询到业务表单记录, businessKey: {}", businessKey);
return;
}
task.setMeasureResLeader(assignee);
dqInspectTaskService.updateById(task);
log.info("【巡视整改-措施责任领导存储监听器】已将审批人 {} 写入 DqInspectTask.measureResLeader, businessKey: {}, taskId: {}",
assignee, businessKey, taskId);
}
}
@@ -0,0 +1,52 @@
package org.jeecg.modules.supervision.inspectimprove.listener;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.ExecutionListener;
import org.jeecg.modules.supervision.inspectimprove.config.InspectImproveConfig;
import org.jeecg.modules.supervision.dqinspecttask.entity.DqInspectTask;
import org.jeecg.modules.supervision.dqinspecttask.service.IDqInspectTaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 流程结束时将 DqInspectTask.completedStage 原子递增 1
*
* <p>配置方式:在 BPMN 流程定义的 end 事件上添加 ExecutionListener
* event="end"delegateExpression="${DqInspectTaskStageIncrementListener}"</p>
*/
@Slf4j
@Component("DqInspectTaskStageIncrementListener")
@RequiredArgsConstructor
public class DqInspectTaskStageIncrementListener implements ExecutionListener {
private static final long serialVersionUID = 1L;
private final InspectImproveConfig inspectImproveConfig;
private final IDqInspectTaskService dqInspectTaskService;
@Override
public void notify(DelegateExecution execution) {
Object businessKeyObj = execution.getVariable(inspectImproveConfig.getInspectImprove().getBusinessKey());
if (businessKeyObj == null || StringUtils.isBlank(businessKeyObj.toString())) {
log.warn("【巡视整改-阶段递增监听器】未获取到业务表单ID,跳过, processInstanceId: {}",
execution.getProcessInstanceId());
return;
}
String businessKey = businessKeyObj.toString();
boolean updated = dqInspectTaskService.lambdaUpdate()
.setSql("completed_stage = completed_stage + 1")
.eq(DqInspectTask::getId, businessKey)
.update();
if (updated) {
log.info("【巡视整改-阶段递增监听器】completedStage 已递增, businessKey: {}, processInstanceId: {}",
businessKey, execution.getProcessInstanceId());
} else {
log.warn("【巡视整改-阶段递增监听器】递增未影响任何行,可能记录已删除, businessKey: {}", businessKey);
}
}
}
@@ -0,0 +1,24 @@
package org.jeecg.modules.supervision.inspectimprove.listener;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.flowable.engine.RuntimeService;
import org.flowable.task.service.delegate.DelegateTask;
@Slf4j
public final class LeaderApproveHelper {
private LeaderApproveHelper() {}
public static void handle(DelegateTask delegateTask, RuntimeService runtimeService,
String variableKey, String logPrefix) {
String assignee = delegateTask.getAssignee();
if (StringUtils.isEmpty(assignee)) {
log.error("{}未获取到审批人, taskId: {}", logPrefix, delegateTask.getId());
return;
}
runtimeService.setVariable(delegateTask.getProcessInstanceId(), variableKey, assignee);
log.info("{}将审批人 {} 写入流程变量, processInstanceId: {}, 变量名: {}",
logPrefix, assignee, delegateTask.getProcessInstanceId(), variableKey);
}
}
@@ -0,0 +1,41 @@
package org.jeecg.modules.supervision.xispeak.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "flow-biz")
public class XiSpeakConfig {
/**
* 对应 yml 中的 xi-speak 节点
* Spring 会自动将中划线命名映射为驼峰命名
*/
private XiSpeakProperties xiSpeak;
@Data
public static class XiSpeakProperties {
/** 对应 need-sdw-approve-code */
private String needSdwApproveCode;
/** 对应 json-data-key */
private String jsonDataKey;
/** 对应 impl-dept-key */
private String implDeptKey;
/** 对应impl-dept-collection-used*/
private String implDeptCollectionUsedKey;
/** 对应business-key*/
private String businessKey;
/** 对应impl-dept-key-underscore-key*/
private String implDeptKeyUnderscoreKey;
private String deptWorkerKey;
private String xiSpeakFbFlowCode;
private String formUrl;
private String feedbackRightNowCode;
private String temImplWorkerKey;
private String temImplLeaderKey;
private String temBgLeaderKey;
private String sdwLeaderListKey;
private String isEndKey;
}
}
@@ -0,0 +1,21 @@
package org.jeecg.modules.supervision.xispeak.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "flow-biz")
public class XiSpeakFeedbackConfig {
/**
* 复用 xi-speak 配置节点
* 两个模块共用同一份配置
*/
private XiSpeakFeedbackConfig.XiSpeakPropertiesFb xiSpeakFb;
@Data
public static class XiSpeakPropertiesFb {
String businessId;
}
}
@@ -0,0 +1,298 @@
package org.jeecg.modules.supervision.xispeak.flow;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.delegate.DelegateExecution;
import org.jeecg.modules.supervision.common.OrgConfig;
import org.jeecg.modules.supervision.xispeak.config.XiSpeakFeedbackConfig;
import org.jeecg.common.constant.SymbolConstant;
import org.jeecg.common.system.api.ISysBaseAPI;
import org.jeecg.modules.supervision.xispeak.entity.BgXiSpeak;
import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetail;
import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetailMap;
import org.jeecg.modules.supervision.xispeak.service.IBgXiSpeakService;
import org.jeecg.modules.tasktask.entity.TaskTask;
import org.jeecg.modules.tasktask.service.ITaskTaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
@Component("XiSpeakFeedbackFlow")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class XiSpeakFeedbackFlow {
private final XiSpeakFeedbackConfig xiSpeakFeedbackConfig;
private final OrgConfig orgConfig;
private final ISysBaseAPI iSysBaseAPI;
private final RuntimeService runtimeService;
private final IBgXiSpeakService bgXiSpeakService;
private final ITaskTaskService taskTaskService;
// 流程表达式内用法 ${XiSpeakFeedbackFlow.getImplDeptList(execution)}
public List<String> getImplDeptList(DelegateExecution execution) {
String processInstId = execution.getProcessInstanceId();
String businessKey = execution.getProcessInstanceBusinessKey();
if (StringUtils.isAnyBlank(processInstId, businessKey)) {
log.warn("流程上下文中业务关键信息缺失![ProcessInstanceId: {}, BusinessKey: {}, ExecutionId: {}, ActivityId: {}]",
processInstId, businessKey, execution.getId(), execution.getCurrentActivityId());
return Collections.emptyList();
}
BgXiSpeak xiSpeak = bgXiSpeakService.getById(businessKey);
if (xiSpeak == null) {
log.warn("根据查询到的业务表单id无法获取业务数据行![BusinessKey: {}]", businessKey);
return Collections.emptyList();
}
DeptApproveDetailMap approveInfoMap = xiSpeak.getApproveInfo();
if (approveInfoMap == null || approveInfoMap.isEmpty()) {
log.warn("根据查询到的业务表单数据无法获取对应的审批信息![BgXiSpeak: {}]", xiSpeak);
return Collections.emptyList();
}
List<String> deptIdList = approveInfoMap.keySet().stream()
.filter(StringUtils::isNotBlank) // 可选:过滤掉空的Key
.collect(Collectors.toList());
return deptIdList;
}
// 流程表达式内用法 ${XiSpeakFeedbackFlow.getImplDeptListLength(execution)}
public int getImplDeptListLength(DelegateExecution execution) {
List<String> implDeptList = this.getImplDeptList(execution);
if (implDeptList.isEmpty() || implDeptList == null) {
log.warn("当前节点无经办部门{}", execution);
return 0;
}
return implDeptList.size();
}
// 流程表达式内用法 ${XiSpeakFeedbackFlow.getImplDeptApprove(execution)}
public String getImplDeptApprove(DelegateExecution execution) {
String businessKey = execution.getProcessInstanceBusinessKey();
// 1. 基础校验
if (StringUtils.isBlank(businessKey)) {
log.warn("流程 BusinessKey 缺失,ExecutionId: {}", execution.getId());
return StringUtils.EMPTY;
}
// 2. 获取任务主表数据
TaskTask taskTask = taskTaskService.getById(businessKey);
if (taskTask == null || StringUtils.isBlank(taskTask.getBusinessId())) {
log.warn("无法获取任务数据或业务关联ID[taskTaskId: {}]", businessKey);
return StringUtils.EMPTY;
}
// 3. 获取业务表单数据
BgXiSpeak xiSpeak = bgXiSpeakService.getById(taskTask.getBusinessId());
if (xiSpeak == null || xiSpeak.getApproveInfo() == null) {
log.warn("业务表单数据或审批配置缺失![xiBusinessId: {}]", taskTask.getBusinessId());
return StringUtils.EMPTY;
}
// 4. 获取匹配参数
String handlerName = taskTask.getDeptHandlerName();
if (StringUtils.isBlank(handlerName)) {
return StringUtils.EMPTY;
}
DeptApproveDetailMap approveInfoMap = xiSpeak.getApproveInfo();
return approveInfoMap.values().stream()
.filter(detail -> detail.getImplUserNameList() != null && detail.getImplUserNameList().contains(handlerName))
.map(DeptApproveDetail::getApproverName)
.findFirst()
.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>
*
* @param execution 流程执行上下文
* @return 经办部门经办人
* @example 表达式用法: ${XiSpeakFeedbackFlow.getImplDeptWorker(execution)}
*/
public List<String> getImplDeptWorker(DelegateExecution execution) {
String businessKey = execution.getProcessInstanceBusinessKey();
// 1. 基础校验
if (StringUtils.isBlank(businessKey)) {
log.warn("流程 BusinessKey 缺失,ExecutionId: {}", execution.getId());
return Collections.emptyList();
}
// 2. 获取任务主表数据并判空
TaskTask taskTask = taskTaskService.getById(businessKey);
if (taskTask == null || StringUtils.isBlank(taskTask.getBusinessId())) {
log.warn("无法获取任务数据或业务关联ID[taskTaskId: {}]", businessKey);
return Collections.emptyList();
}
String implDeptWorker = taskTask.getDeptHandlerName();
if (StringUtils.isBlank(implDeptWorker)) {
log.warn("中间表taskTask[id={}]没有经办人数据", businessKey);
return Collections.emptyList();
}
// 将 "张三, 李四 " 转换为 ["张三", "李四"]
return Arrays.stream(implDeptWorker.split(","))
.map(String::trim) // 去掉前后空格
.filter(StringUtils::isNotBlank) // 过滤掉空字符串(防止出现 "张三,,李四"
.collect(Collectors.toList());
}
/**
* 经办部门经办人数量
* <p>经办部门经办人数量</p>
*
* @param execution 流程执行上下文
* @return 经办部门经办人数量
* @example 表达式用法: ${XiSpeakFeedbackFlow.getImplDeptWorkerLength()}
*/
public int getImplDeptWorkerLength(DelegateExecution execution) {
List<String> implDeptWorkerList = this.getImplDeptWorker(execution);
if (implDeptWorkerList == null || implDeptWorkerList.isEmpty()) {
log.warn("{}流程经办部门经办人数量为0",execution.getProcessInstanceId());
return 0;
}
return implDeptWorkerList.size();
}
/**
* 获取jj部门经办人
* <p>jj部门经办人</p>
*
* @return jj部门经办人
* @example 表达式用法: ${XiSpeakFeedbackFlow.getJJDeptWorkerList()}
*/
public List<String> getJJDeptWorkerList() {
String deptId = orgConfig.getDept().getJj();
String roleId = orgConfig.getRole().getJjWorker();
// 2. 调用接口并处理结果
List<String> workerList = iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi(deptId, roleId);
if (CollectionUtils.isEmpty(workerList)) {
log.warn("未查找到匹配的部门经办人 [deptId: {}, roleId: {}]", deptId, roleId);
return Collections.emptyList();
}
return workerList;
}
/**
* 获取jj部门经办人列表长度
* <p>获取jj部门经办人列表长度</p>
*
* @return jj部门经办人列表长度
* @example 表达式用法: ${XiSpeakFeedbackFlow.getJJDeptWorkerListLength()}
*/
public int getJJDeptWorkerListLength() {
List<String> jjDeptWorkerList = this.getJJDeptWorkerList();
if (jjDeptWorkerList == null || jjDeptWorkerList.isEmpty()) {
log.warn("获取jj部经办人数量为0");
return 0;
}
return jjDeptWorkerList.size();
}
/**
* 获取jj部门审核人
* <p>获取jj部门审核人</p>
*
* @return jj部门审核人
* @example 表达式用法: ${XiSpeakFeedbackFlow.getJJDeptLeaderList()}
*/
public List<String> getJJDeptLeaderList() {
String deptId = orgConfig.getDept().getJj();
String roleId = orgConfig.getRole().getLd();
// 2. 调用接口并处理结果
List<String> workerList = iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi(deptId, roleId);
if (CollectionUtils.isEmpty(workerList)) {
log.warn("未查找到匹配的部门审核人 [deptId: {}, roleId: {}]", deptId, roleId);
return Collections.emptyList();
}
return workerList;
}
/**
* 获取jj部门审核人列表长度
* <p>获取jj部门审核人列表长度</p>
*
* @return jj部门审核人列表长度
* @example 表达式用法: ${XiSpeakFeedbackFlow.getJJDeptLeaderListLength()}
*/
public int getJJDeptLeaderListLength() {
List<String> jjDeptWorkerList = this.getJJDeptLeaderList();
if (jjDeptWorkerList == null || jjDeptWorkerList.isEmpty()) {
log.warn("[获取JJ部审批人数量为0]");
return 0;
}
return jjDeptWorkerList.size();
}
}
@@ -0,0 +1,419 @@
package org.jeecg.modules.supervision.xispeak.flow;
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.modules.supervision.common.OrgConfig;
import org.jeecg.modules.supervision.xispeak.config.XiSpeakConfig;
import org.jeecg.common.constant.SymbolConstant;
import org.jeecg.common.system.api.ISysBaseAPI;
import org.jeecg.modules.supervision.xispeak.entity.BgXiSpeak;
import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetail;
import org.jeecg.modules.supervision.xispeak.service.IBgXiSpeakService;
import org.jeecg.modules.extbpm.process.common.expression.FlowNodeExpression;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
@Component("xiSpeakFlow")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class XiSpeakFlow {
private final XiSpeakConfig xiSpeakConfig;
private final OrgConfig orgConfig;
private final ISysBaseAPI iSysBaseAPI;
private final RuntimeService runtimeService;
private final FlowNodeExpression flowNodeExpression;
private final IBgXiSpeakService bgXiSpeakService;
//流程表达式内用法 ${xiSpeakFlow.getBgDeptLdUserIdList()}
public List<String> getBgDeptLdUserIdList() {
String deptId = orgConfig.getDept().getBg();
String roleId = orgConfig.getRole().getLd();
log.info("正在查询部门: {} 下的角色: {} 的用户列表", deptId, roleId);
List<String> userList = iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi(deptId, roleId);
log.info("查询到的用户为: {}", userList);
return userList;
}
public List<String> getSDWLeader() {
String roleId = orgConfig.getRole().getSdw();
log.info("正在查询sdw:{}角色下的用户列表", roleId);
List<String> userList = iSysBaseAPI.getUserByRoleIdLocalApi(roleId);
log.info("查询到的用户为: {}", userList);
return userList;
}
public int getSDWLeaderLength(Object jsonData) {
return this.getSdwLeaderList(jsonData).size();
}
//流程表达式内用法 ${xiSpeakFlow.getBgDeptLdUserIdListLength()}
public int getBgDeptLdUserIdListLength() {
return getBgDeptLdUserIdList().size();
}
/**
* 提取后的公共解析方法
* @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;
}
try {
JSONObject data;
// 2. 统一转换为 JSONObject (此处逻辑复用)
if (jsonData instanceof JSONObject) {
data = (JSONObject) jsonData;
} else if (jsonData instanceof String) {
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));
}
// 3. 提取结果
Integer result = data.getInteger(codeName);
int finalResult = (result == null) ? 0 : result;
log.info("{} 业务解析结果: {}", logLabel, finalResult);
return finalResult;
} catch (Exception e) {
log.warn("JSON解析失败: 业务={}, codeKey={}, 数据={}", logLabel, codeName, jsonData, e);
return 0;
}
}
private List<String> getListCodeValueFromData(Object jsonData, String codeName, String logLabel) {
// 1. 基础校验:如果入参为空或配置的 Key 为空,直接返回空列表
if (jsonData == null || org.apache.commons.lang.StringUtils.isBlank(codeName)) {
return Collections.emptyList();
}
try {
JSONObject data;
// 2. 统一转换为 JSONObject
if (jsonData instanceof JSONObject) {
data = (JSONObject) jsonData;
} else if (jsonData instanceof String) {
String jsonStr = (String) jsonData;
if (org.apache.commons.lang.StringUtils.isBlank(jsonStr)) {
return Collections.emptyList();
}
data = JSONObject.parseObject(jsonStr);
} else {
// 处理普通 POJO 对象
data = JSONObject.parseObject(JSONObject.toJSONString(jsonData));
}
// 3. 提取目标字段的字符串值
String rawValue = data.getString(codeName);
// 4. 转换逗号分隔的字符串为 List
List<String> resultList;
if (org.apache.commons.lang.StringUtils.isBlank(rawValue)) {
resultList = Collections.emptyList();
} else {
// 使用逗号分割,并过滤掉每个元素前后的空格
resultList = Arrays.stream(rawValue.split(","))
.map(String::trim)
.filter(org.apache.commons.lang.StringUtils::isNotBlank) // 过滤掉因连续逗号产生的空元素
.collect(Collectors.toList());
}
log.info("{} 业务解析结果: {}", logLabel, resultList);
return resultList;
} catch (Exception e) {
log.warn("JSON解析失败: 业务={}, codeKey={}, 数据={}", logLabel, codeName, jsonData, e);
// 异常情况下返回空列表,确保调用方不崩溃
return Collections.emptyList();
}
}
// --- 调用处 ---
//流程表达式内用法${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.getSdwLeaderList(json_data)}
public List<String> getSdwLeaderList(Object jsonData) {
String codeName = xiSpeakConfig.getXiSpeak().getSdwLeaderListKey();
return getListCodeValueFromData(jsonData, codeName, "立即反馈");
}
//流程表达式内用法${xiSpeakFlow.getSdwLeaderListLength(json_data)}
public int getSdwLeaderListLength(Object jsonData) {
return this.getSdwLeaderList(jsonData).size();
}
/**
* 流程表达式内用法: ${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.getImplDeptLeaderIsEnd(execution)=='1'}
* 优先从业务表单 JSON 读取,没有则回退到流程变量(兼容旧流程实例)
*/
public String getImplDeptLeaderIsEnd(DelegateExecution execution) {
DeptApproveDetail detail = getDeptApproveDetail(execution);
if (detail != null && detail.getIsEnd() != null) {
return String.valueOf(detail.getIsEnd());
}
// 兜底:从流程变量读取(兼容旧流程)
Object value = flowNodeExpression.getSonProcessVariable(execution, xiSpeakConfig.getXiSpeak().getIsEndKey());
return value == null ? "0" : value.toString();
}
/**
* 流程表达式内用法: ${xiSpeakFlow.getImplDeptLeader(execution)}
*/
public String getImplDeptLeader(DelegateExecution execution) {
DeptApproveDetail detail = getDeptApproveDetail(execution);
if (detail != null && detail.getIsEnd() != null) {
return String.valueOf(detail.getImplWorker());
}
return detail == null ? StringUtils.EMPTY_STRING : detail.getImplWorker();
}
/**
* 流程表达式内用法: ${xiSpeakFlow.getImplDeptWorkerListFromJson(execution)}
* 优先从业务表单 JSON 读取,没有则回退到流程变量(兼容旧流程实例)
*/
public List<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();
}
/**
* 流程表达式内用法: ${xiSpeakFlow.getTemBgLeader(execution)}
*/
public String getTemBgLeader(DelegateExecution execution) {
return getLocalVarAsString(execution, xiSpeakConfig.getXiSpeak().getTemBgLeaderKey());
}
/**
* 从执行实例中获取当前部门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();
}
/**
* 抽取出的公共逻辑:从当前执行实例获取局部变量并转为字符串
*/
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, orgConfig.getRole().getLd());
}
public int getImplDeptLdsListLength(String deptId) {
return getImplDeptLdsList(deptId).size();
}
public int getListSize(String jsonData){
if (org.apache.commons.lang3.StringUtils.isEmpty(jsonData)) {
return 0;
}
return Math.toIntExact(Arrays.stream(jsonData.split(","))
.map(String::trim)
.filter(org.apache.commons.lang3.StringUtils::isNotEmpty)
.count());
}
// List<String> getImplDeptWorker(String JG_LOCAL_PROCESS_ID,String deptId){
// runtimeService.get
// }
public Integer getImplDeptNums(String jsonData) {
String codeName = xiSpeakConfig.getXiSpeak().getImplDeptKey();
if (jsonData == null || org.apache.commons.lang.StringUtils.isEmpty(codeName)) {
return 0;
}
// 1. 统一转成 JSONObject
JSONObject json;
json = JSON.parseObject((String) jsonData);
if (json == null || !json.containsKey(codeName)) {
return 0;
}
// 2. 获取原始对象进行兼容性处理
Object value = json.get(codeName);
if (value == null) {
return 0;
}
List<String> resultList = new ArrayList<>();
if (value instanceof Collection) {
// 情况 A: 本身就是集合/JSON数组
resultList.addAll(json.getJSONArray(codeName).toJavaList(String.class));
} else if (value instanceof String) {
// 情况 B: 是逗号分隔的字符串 "ID1,ID2,ID3"
String str = (String) value;
if (org.apache.commons.lang.StringUtils.isNotEmpty(str)) {
// 使用 split 拆分,并过滤掉空格和空字符串
String[] split = str.split(",");
for (String s : split) {
if (org.apache.commons.lang.StringUtils.isNotEmpty(s.trim())) {
resultList.add(s.trim());
}
}
}
}
return resultList.size();
}
/**
* 优先从业务表单 JSON 读取 implUserNameList / implWorker,没有则回退到流程变量(兼容旧流程实例)
*/
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();
if (execution == null || org.apache.commons.lang.StringUtils.isBlank(variableName)) {
return Collections.emptyList();
}
try {
Object value = execution.getVariableLocal(variableName);
if (value == null) {
value = execution.getVariable(variableName);
}
if (value != null) {
return Arrays.stream(String.valueOf(value).split(SymbolConstant.COMMA))
.map(String::trim)
.filter(org.apache.commons.lang.StringUtils::isNotBlank)
.collect(Collectors.toList());
}
} catch (Exception e) {
log.warn("getDeptWorkerList from process variable failed", e);
}
return Collections.emptyList();
}
}
@@ -0,0 +1,93 @@
package org.jeecg.modules.supervision.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.supervision.xispeak.entity.BgXiSpeak;
import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetail;
import org.jeecg.modules.supervision.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.modules.supervision.xispeak.config.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);
}
}
}
}
@@ -0,0 +1,59 @@
package org.jeecg.modules.supervision.xispeak.listener;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.flowable.engine.delegate.TaskListener;
import org.flowable.task.service.delegate.DelegateTask;
import org.jeecg.modules.supervision.xispeak.constant.XiSpeakConstant;
import org.jeecg.modules.supervision.xispeak.entity.BgXiSpeak;
import org.jeecg.modules.supervision.xispeak.service.IBgXiSpeakService;
import lombok.RequiredArgsConstructor;
import org.jeecg.modules.supervision.xispeak.config.XiSpeakConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
@Slf4j
@Component("AfterBgWorkerFinalApproveListener")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class AfterBgWorkerFinalApproveListener implements TaskListener {
private static final long serialVersionUID = 1L;
private final XiSpeakConfig xiSpeakConfig;
private final IBgXiSpeakService bgXiSpeakService;
@Override
public void notify(DelegateTask delegateTask) {
// 💡 优化 2: 安全获取变量,使用 Objects.toString 防止流程变量缺失导致 NPE 崩溃
Object businessKeyObj = delegateTask.getVariable(xiSpeakConfig.getXiSpeak().getBusinessKey());
String rawBusinessKey = Objects.toString(businessKeyObj, "").trim();
if (rawBusinessKey.isEmpty()) {
log.warn("【xispeak最终审批监听器】未查询到对应业务表单 id,跳过更新。TaskId: {}", delegateTask.getId());
return;
}
try {
// 💡 优化 3: 极致性能!告别先查后改。直接 new 一个只带 ID 的干净实体进行局部更新
BgXiSpeak updateEntity = new BgXiSpeak();
updateEntity.setId(rawBusinessKey); // 假设你的主键是 String 类型
updateEntity.setCompletionStatus(XiSpeakConstant.CompletionStatus.FINISHED);
// 执行更新
boolean success = bgXiSpeakService.updateById(updateEntity);
if (success) {
log.info("【xispeak最终审批监听器】成功将业务表单 [id={}] 的状态更新为已完成", rawBusinessKey);
} else {
// 如果数据库里根本没这条记录,updateById 会返回 false
log.warn("【xispeak最终审批监听器】更新失败,数据库中不存在 id 为 {} 的业务数据", rawBusinessKey);
}
} catch (Exception e) {
// 💡 优化 4: 增加异常捕获,避免因为业务表更新失败导致整个工作流引擎在推进时抛出未捕获异常
log.error("【xispeak最终审批监听器】更新业务表单异常, businessKey: " + rawBusinessKey, e);
throw e; // 如果希望流程为此阻断、回滚,再将其抛出
}
}
}
@@ -0,0 +1,124 @@
package org.jeecg.modules.supervision.xispeak.listener;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.flowable.engine.delegate.TaskListener;
import org.flowable.task.service.delegate.DelegateTask;
import org.jeecg.common.api.vo.Result;
import org.jeecg.modules.supervision.xispeak.entity.BgXiSpeak;
import org.jeecg.modules.supervision.xispeak.entity.BgXiSpeakFeedback;
import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetail;
import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetailMap;
import org.jeecg.modules.supervision.xispeak.service.IBgXiSpeakFeedbackService;
import org.jeecg.modules.supervision.xispeak.service.IBgXiSpeakService;
import org.jeecg.modules.tasktask.constant.FlowConstants;
import org.jeecg.modules.tasktask.entity.TaskTask;
import org.jeecg.modules.tasktask.service.ITaskTaskService;
import org.jeecg.modules.supervision.xispeak.config.XiSpeakConfig;
import org.jeecg.modules.supervision.xispeak.config.XiSpeakFeedbackConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
@Slf4j
@Component("AfterImplDeptLeaderApproveFeedbackListener")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class AfterImplDeptLeaderApproveFeedbackListener implements TaskListener {
private static final long serialVersionUID = 1L;
private final XiSpeakFeedbackConfig xiSpeakFeedbackConfig;
private final IBgXiSpeakFeedbackService bgXiSpeakFeedbackService;
private final ITaskTaskService taskTaskService;
private final XiSpeakConfig xiSpeakConfig;
private final IBgXiSpeakService bgXiSpeakService;
@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. 🎯 架构优化:移除 FOR UPDATE 悲观锁,改用普通查询,预防工作流交叉死锁
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);
// 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);
bgXiSpeakService.updateById(xiSpeak);
log.info("【任务通知】业务单据 [{}] 部门 [{}] 经办人审批记录成功更新为: {}",
businessKeyStr, cleanDeptId, currentWorkerName);
} else {
log.info("【任务通知】业务单据 [{}] 部门 [{}] 经办人未发生变化 [{}], 跳过数据库更新",
businessKeyStr, cleanDeptId, currentWorkerName);
}
} catch (Exception e) {
// 6. 异常全上下文捕获,方便线上无缝排查
log.error(String.format("【任务通知】处理任务结束监听器异常。TaskId: %s, BusinessKey: %s", taskId, businessKeyStr), e);
throw e; // 抛出异常以触发 Spring 事务和 Flowable 流程回滚
}
}
/**
* 辅助方法:安全解析部门ID,支持 Collection 和 String 表现形式
*/
private String parseDeptId(Object rawDeptVar) {
if (rawDeptVar instanceof Collection) {
Collection<?> collection = (Collection<?>) rawDeptVar;
if (!collection.isEmpty()) {
return Objects.toString(collection.iterator().next(), "").trim();
}
}
return rawDeptVar.toString().replace("[", "").replace("]", "").trim();
}
}
@@ -0,0 +1,93 @@
package org.jeecg.modules.supervision.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.supervision.xispeak.entity.BgXiSpeak;
import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetail;
import org.jeecg.modules.supervision.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.modules.supervision.xispeak.config.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("AfterImplDeptLeaderApproveListener")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class AfterImplDeptLeaderApproveListener implements TaskListener {
private static final long serialVersionUID = 1L;
private static final String VARIABLE_NAME = "tem_impl_leader";
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);
}
}
}
}
@@ -0,0 +1,138 @@
package org.jeecg.modules.supervision.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.supervision.xispeak.entity.BgXiSpeak;
import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetail;
import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetailMap;
import org.jeecg.modules.supervision.xispeak.service.IBgXiSpeakService;
import org.jeecg.modules.supervision.xispeak.config.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();
}
}
@@ -0,0 +1,109 @@
package org.jeecg.modules.supervision.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.supervision.xispeak.entity.BgXiSpeak;
import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetail;
import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetailMap;
import org.jeecg.modules.supervision.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.modules.supervision.xispeak.config.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("AfterImplWorkerApproveListener")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class AfterImplWorkerApproveListener implements TaskListener {
private static final long serialVersionUID = 1L;
private final XiSpeakConfig xiSpeakConfig;
private final IBgXiSpeakService bgXiSpeakService;
private final RuntimeService runtimeService;
@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;
}
BgXiSpeak xiSpeak = bgXiSpeakService.getByIdForUpdate(rawBusinessKey.toString());
if (xiSpeak == null) {
log.error("业务数据不存在: {}", rawBusinessKey);
return;
}
String implDeptKey = xiSpeakConfig.getXiSpeak().getImplDeptCollectionUsedKey();
Object rawDeptVar = delegateTask.getVariableLocal(implDeptKey);
if (rawDeptVar == null) {
rawDeptVar = delegateTask.getVariable(implDeptKey);
}
if (rawDeptVar == null) {
log.error("流程数据异常:未找到实施部门ID变量: {}", implDeptKey);
return;
}
String cleanDeptId = rawDeptVar.toString().replace("[", "").replace("]", "").trim();
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);
}
// 更新落实人列表
List<String> workerList = detail.getImplUserNameList();
if (workerList == null) {
workerList = new ArrayList<>();
}
if (!workerList.contains(currentWorkerName)) {
workerList.add(currentWorkerName);
detail.setImplUserNameList(workerList);
xiSpeak.setApproveInfo(approveMap);
bgXiSpeakService.updateById(xiSpeak);
log.info("部门 {} 经办人审批记录更新成功: {}", cleanDeptId, currentWorkerName);
} else {
log.info("部门 {} 经办人 {} 已在列表中,跳过更新", cleanDeptId, currentWorkerName);
}
}
}
@@ -0,0 +1,93 @@
package org.jeecg.modules.supervision.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.supervision.xispeak.entity.BgXiSpeak;
import org.jeecg.modules.supervision.xispeak.entity.DeptApproveDetail;
import org.jeecg.modules.supervision.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.modules.supervision.xispeak.config.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);
}
}
}
}
@@ -0,0 +1,118 @@
package org.jeecg.modules.supervision.xispeak.listener;
import lombok.extern.slf4j.Slf4j;
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.supervision.xispeak.config.XiSpeakConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
@Component("AfterSDWApproveHqListener")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class AfterSDWApproveHqListener implements ExecutionListener {
private static final long serialVersionUID = 1L;
private final FlowNodeExpression flowNodeExpression;
@Autowired
private RuntimeService runtimeService;
private final XiSpeakConfig xiSpeakConfig;
@Override
public void notify(DelegateExecution execution) {
XiSpeakConfig.XiSpeakProperties props = xiSpeakConfig.getXiSpeak();
//RuntimeService runtimeService = SpringContextUtils.getBean(RuntimeService.class);
String mainProcessId = (String)execution.getProcessInstanceId();
String url = (String)execution.getVariable(WorkFlowGlobals.BPM_FORM_CONTENT_URL);
// 打印主流程里所有的变量名,看看有没有 json_data
Map<String, Object> variables = runtimeService.getVariables(execution.getRootProcessInstanceId());
log.info("主流程中的所有变量名: " + variables.keySet());
// 打印当前执行流的所有变量名
log.info("当前执行流的所有变量名: " + execution.getVariables().keySet());
String bizTitle = (String)runtimeService.getVariable(mainProcessId,WorkFlowGlobals.BPM_BIZ_TITLE);
if(org.apache.commons.lang.StringUtil.isEmpty(url)) {
url = (String)runtimeService.getVariable(mainProcessId,WorkFlowGlobals.BPM_FORM_CONTENT_URL);
String mobileUrl = (String)runtimeService.getVariable(mainProcessId,WorkFlowGlobals.BPM_FORM_CONTENT_URL_MOBILE);
execution.setVariable(WorkFlowGlobals.BPM_FORM_CONTENT_URL, url);
execution.setVariable(WorkFlowGlobals.BPM_FORM_CONTENT_URL_MOBILE, mobileUrl);
}
// 1. 获取主流程中的 json_data 对象(可能是 String 或 JSONObject
Object jsonDataObj = (String)runtimeService.getVariable(mainProcessId,props.getJsonDataKey());
if (oConvertUtils.isNotEmpty(jsonDataObj)) {
String json_data = jsonDataObj.toString();
try {
// 2. 调用表达式解析工具获取特定字段的字符串内容
String jsonStr = flowNodeExpression.getJsondatastring(json_data, props.getImplDeptKey());
// 3. 只有当字段存在且内容不为空时,才进行解析
if (oConvertUtils.isNotEmpty(jsonStr)) {
List<String> deptIds = Arrays.stream(jsonStr.split(","))
.map(String::trim) // 去掉可能存在的空格
.filter(s -> !s.isEmpty()) // 过滤掉空字符串
.collect(Collectors.toList());
if (deptIds != null && !deptIds.isEmpty()) {
// 4. 将提取出的 List 存入子流程变量(供多实例 Collection 使用)
execution.setVariable( props.getImplDeptKey(), deptIds);
log.info("子流程变量 deptIdList 注入成功: " + deptIds);
}
}
} catch (Exception e) {
log.error("解析 JSON 字段 pending_depts_id 失败: ", e);
}
}
//获取主表数据id
String businessKey = (String)runtimeService.getVariable(mainProcessId, WorkFlowGlobals.BPM_DATA_ID);
//获取主表的设计表单数据ID
String BPM_DES_DATA_ID = oConvertUtils.getString(runtimeService.getVariable(mainProcessId, WorkFlowGlobals.BPM_DES_DATA_ID));
//获取主表表名
String tableName = (String)runtimeService.getVariable(mainProcessId,WorkFlowGlobals.BPM_FORM_KEY);
execution.setVariable(WorkFlowGlobals.BPM_FORM_KEY, tableName);
//--update--begin------author:scott-----date:20210512-----for:出差借款子流程,加载表单数据为空问题---------
//log.info("----------传入子流程的数据ID--------------: "+execution.getVariable(WorkFlowGlobals.DATA_ID));
if(oConvertUtils.isNotEmpty(execution.getVariable(WorkFlowGlobals.DATA_ID))){
execution.setVariable(WorkFlowGlobals.BPM_DATA_ID, execution.getVariable(WorkFlowGlobals.DATA_ID));
//如果是设计器表单,则还需要设置设计表单数据ID
if(oConvertUtils.isNotEmpty(BPM_DES_DATA_ID)){
execution.setVariable(WorkFlowGlobals.BPM_DES_DATA_ID, execution.getVariable(WorkFlowGlobals.DATA_ID));
}
}else{
//未传入id值,则获取主表数据id给子流程【online表单需要】
execution.setVariable(WorkFlowGlobals.BPM_DATA_ID, businessKey);
//如果是设计器表单,则还需要设置设计表单数据ID
if(oConvertUtils.isNotEmpty(BPM_DES_DATA_ID)){
execution.setVariable(WorkFlowGlobals.BPM_DES_DATA_ID, BPM_DES_DATA_ID);
}
}
//--update--end------author:scott-----date:20210512-----for:出差借款子流程,加载表单数据为空问题---------
//子流程实例set业务号和主流程保持一致
runtimeService.updateBusinessKey(execution.getProcessInstanceId(), businessKey);
}
}
@@ -0,0 +1,56 @@
flow-biz:
org:
dept:
jj: "2054466800692432898"
sld: "2044677562460508161"
dq: "2044677628281720834"
bg: "2044677604785229826"
role:
ld: "2044680793306591234"
jj-worker: "2047511967494213633"
sld-jwsj: "2063902766943363073"
sdwsj: "2062796937607499777"
sdw: "2044676455280570370"
xi-speak:
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"
impl-dept-collection-used-key: "impl_dept_id"
business-key: "business_id"
dept-worker-key: "subApproveUser"
xi-speak-fb-flowcode: "process_1778315114392"
form-url: "bg/xispeak/components/BgXiSpeakBPMForm?showFeedbackInfo=1&showJiJianFields=1"
tem-impl-worker-key: "tem_impl_worker"
tem-impl-leader-key: "tem_impl_leader"
tem-bg-leader-key: "tem_bg_leader"
sdw-leader-list-key: "sdwLeaderList"
is-end-key: "isEnd"
inspect-improve:
business-key: "business_id"
json-data-key: "json_data"
form-url: "dj/inspectimprove/components/DjInspectImproveBPMForm"
flow-code: ""
measure-res-leader-key: "measureResLeader"
measure-res-dept-key: "measureResDept"
question-res-leader-key: "questionResLeader"
question-res-dept-key: "questionResDept"
improve-measure-key: "improveMeasure"
work-progress-key: "workProgress"
completion-status-key: "completionStatus"
inspect-closeout:
business-key: "business_id"
json-data-key: "json_data"
form-url: "dj/inspectcloseout/components/InspectCloseoutBPMForm"
flow-code: ""
applicant-key: "applicant"
approver-key: "approver"
rectify-item-key: "rectifyItemId"
closeout-desc-key: "closeoutDesc"
completion-status-key: "completionStatus"
xi-speak-fb:
business-id: "business_id"
@@ -0,0 +1,326 @@
package org.jeecg.modules.supervision.inspectimprove;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import com.alibaba.fastjson.JSONObject;
import com.jeecg.weibo.exception.BusinessException;
import org.jeecg.modules.supervision.common.OrgConfig;
import org.jeecg.common.system.api.ISysBaseAPI;
import org.jeecg.modules.supervision.inspectimprove.config.InspectCloseoutConfig;
import org.jeecg.modules.supervision.inspectimprove.config.InspectCloseoutConfig.InspectCloseoutProperties;
import org.jeecg.modules.supervision.inspectimprove.flow.InspectCloseoutFlow;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Collections;
import java.util.List;
@ExtendWith(MockitoExtension.class)
class InspectCloseoutFlowTest {
@Mock
private InspectCloseoutConfig inspectCloseoutConfig;
@Mock
private OrgConfig orgConfig;
@Mock
private ISysBaseAPI iSysBaseAPI;
private InspectCloseoutFlow flow;
private InspectCloseoutProperties props;
private OrgConfig.Dept dept;
private OrgConfig.Role role;
@BeforeEach
void setUp() {
props = new InspectCloseoutProperties();
props.setBusinessKey("businessKey");
props.setJsonDataKey("jsonDataKey");
props.setFormUrl("formUrl");
props.setFlowCode("flowCode");
props.setApplicantKey("applicantKey");
props.setApproverKey("approverKey");
props.setRectifyItemKey("rectifyItemKey");
props.setCloseoutDescKey("closeoutDescKey");
props.setCompletionStatusKey("completionStatusKey");
lenient().when(inspectCloseoutConfig.getInspectCloseout()).thenReturn(props);
dept = new OrgConfig.Dept();
dept.setJj("dept-jj");
dept.setSld("dept-sld");
dept.setDq("dept-dq");
role = new OrgConfig.Role();
role.setLd("role-ld");
role.setJjWorker("role-jj-worker");
role.setSldJwsj("role-sld-jwsj");
lenient().when(orgConfig.getDept()).thenReturn(dept);
lenient().when(orgConfig.getRole()).thenReturn(role);
// FlowNodeExpression 未使用,传 null
flow = new InspectCloseoutFlow(inspectCloseoutConfig, orgConfig, null, iSysBaseAPI);
}
// ==================== 部门角色用户查询 ====================
@Nested
class DeptRoleUserQuery {
@Test
void getJJDeptLdUserIdList_shouldReturnUserList() {
List<String> users = List.of("user1", "user2");
when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-jj", "role-ld"))
.thenReturn(users);
assertEquals(users, flow.getJJDeptLdUserIdList());
}
@Test
void getJJDeptLdUserIdList_shouldReturnEmptyList() {
when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-jj", "role-ld"))
.thenReturn(Collections.emptyList());
assertEquals(Collections.emptyList(), flow.getJJDeptLdUserIdList());
}
@Test
void getJJDeptLdUserIdListLength_shouldReturnCount() {
when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-jj", "role-ld"))
.thenReturn(List.of("user1", "user2", "user3"));
assertEquals(3, flow.getJJDeptLdUserIdListLength());
}
@Test
void getJJDeptLdUserIdListLength_shouldReturnZeroWhenEmpty() {
when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-jj", "role-ld"))
.thenReturn(Collections.emptyList());
assertEquals(0, flow.getJJDeptLdUserIdListLength());
}
@Test
void getJJDeptLdWorkerIdList_shouldReturnUserList() {
List<String> users = List.of("worker1");
when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-jj", "role-jj-worker"))
.thenReturn(users);
assertEquals(users, flow.getJJDeptLdWorkerIdList());
}
@Test
void getJJDeptLdWorkerIdListLength_shouldReturnCount() {
when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-jj", "role-ld"))
.thenReturn(List.of("user1", "user2"));
assertEquals(2, flow.getJJDeptLdWorkerIdListLength());
}
@Test
void getDqDeptLdUserIdList_shouldReturnUserList() {
List<String> users = List.of("dq_user1");
when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-dq", "role-jj-worker"))
.thenReturn(users);
assertEquals(users, flow.getDqDeptLdUserIdList());
}
@Test
void getDqDeptLdUserIdListLength_shouldReturnCount() {
when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-dq", "role-jj-worker"))
.thenReturn(List.of("user1", "user2"));
assertEquals(2, flow.getDqDeptLdUserIdListLength());
}
@Test
void getDqDeptLdUserIdListLength_shouldReturnZeroWhenEmpty() {
when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-dq", "role-jj-worker"))
.thenReturn(Collections.emptyList());
assertEquals(0, flow.getDqDeptLdUserIdListLength());
}
}
// ==================== JWSJ用户查询 ====================
@Nested
class JWSJUserQuery {
@Test
void getJWSJUser_shouldReturnUniqueUser() {
when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-sld", "role-sld-jwsj"))
.thenReturn(List.of("jwsj_user"));
assertEquals("jwsj_user", flow.getJWSJUser());
}
@Test
void getJWSJUser_shouldThrowWhenEmpty() {
when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-sld", "role-sld-jwsj"))
.thenReturn(Collections.emptyList());
assertThrows(BusinessException.class, () -> flow.getJWSJUser());
}
@Test
void getJWSJUser_shouldThrowWhenMultipleUsers() {
when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-sld", "role-sld-jwsj"))
.thenReturn(List.of("user1", "user2"));
assertThrows(BusinessException.class, () -> flow.getJWSJUser());
}
}
// ==================== JSON 数据解析 ====================
@Nested
class JsonDataParsing {
@Test
void getApproverList_shouldParseCommaSeparated() {
JSONObject json = new JSONObject();
json.put("approverKey", "user1,user2,user3");
List<String> result = flow.getApproverList(json);
assertEquals(3, result.size());
assertTrue(result.contains("user1"));
assertTrue(result.contains("user2"));
assertTrue(result.contains("user3"));
}
@Test
void getApproverList_shouldHandleJsonString() {
String jsonStr = "{\"approverKey\":\"user1,user2\"}";
List<String> result = flow.getApproverList(jsonStr);
assertEquals(2, result.size());
}
@Test
void getApproverList_shouldReturnEmptyWhenFieldMissing() {
JSONObject json = new JSONObject();
List<String> result = flow.getApproverList(json);
assertTrue(result.isEmpty());
}
@Test
void getApproverList_shouldReturnEmptyWhenValueBlank() {
JSONObject json = new JSONObject();
json.put("approverKey", "");
List<String> result = flow.getApproverList(json);
assertTrue(result.isEmpty());
}
@Test
void getApproverList_shouldReturnEmptyWhenJsonDataNull() {
List<String> result = flow.getApproverList(null);
assertTrue(result.isEmpty());
}
@Test
void getApproverListLength_shouldReturnCount() {
JSONObject json = new JSONObject();
json.put("approverKey", "user1,user2,user3");
assertEquals(3, flow.getApproverListLength(json));
}
@Test
void getApproverListLength_shouldReturnZeroWhenEmpty() {
JSONObject json = new JSONObject();
assertEquals(0, flow.getApproverListLength(json));
}
@Test
void getApplicant_shouldReturnApplicantValue() {
JSONObject json = new JSONObject();
json.put("applicantKey", "zhangsan");
assertEquals("zhangsan", flow.getApplicant(json));
}
@Test
void getApplicant_shouldHandleJsonString() {
String jsonStr = "{\"applicantKey\":\"lisi\"}";
assertEquals("lisi", flow.getApplicant(jsonStr));
}
@Test
void getApplicant_shouldReturnNullWhenMissing() {
JSONObject json = new JSONObject();
assertNull(flow.getApplicant(json));
}
@Test
void getApplicant_shouldReturnNullWhenJsonDataNull() {
assertNull(flow.getApplicant(null));
}
@Test
void getRectifyItemId_shouldReturnRectifyItemId() {
JSONObject json = new JSONObject();
json.put("rectifyItemKey", "item-001");
assertEquals("item-001", flow.getRectifyItemId(json));
}
@Test
void getRectifyItemId_shouldHandleJsonString() {
String jsonStr = "{\"rectifyItemKey\":\"item-002\"}";
assertEquals("item-002", flow.getRectifyItemId(jsonStr));
}
@Test
void getRectifyItemId_shouldReturnNullWhenMissing() {
JSONObject json = new JSONObject();
assertNull(flow.getRectifyItemId(json));
}
@Test
void getRectifyItemId_shouldReturnNullWhenJsonDataNull() {
assertNull(flow.getRectifyItemId(null));
}
}
// ==================== 工具方法 ====================
@Nested
class UtilityMethods {
@Test
void getListSize_shouldCountCommaSeparated() {
assertEquals(3, flow.getListSize("a,b,c"));
assertEquals(1, flow.getListSize("single"));
assertEquals(0, flow.getListSize(""));
assertEquals(0, flow.getListSize(null));
}
@Test
void getListSize_shouldHandleSpaces() {
assertEquals(2, flow.getListSize(" a , b "));
}
@Test
void getListSize_shouldFilterEmptyElements() {
assertEquals(2, flow.getListSize("a,,b"));
}
}
}
@@ -0,0 +1,396 @@
package org.jeecg.modules.supervision.inspectimprove;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import com.alibaba.fastjson.JSONObject;
import com.jeecg.weibo.exception.BusinessException;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.delegate.DelegateExecution;
import org.jeecg.modules.supervision.common.OrgConfig;
import org.jeecg.common.system.api.ISysBaseAPI;
import org.jeecg.modules.supervision.inspectimprove.config.InspectImproveConfig;
import org.jeecg.modules.supervision.inspectimprove.config.InspectImproveConfig.InspectImproveProperties;
import org.jeecg.modules.supervision.inspectimprove.constant.InspectConstant;
import org.jeecg.modules.supervision.inspectimprove.flow.InspectImproveFlow;
import org.jeecg.modules.supervision.dqinspecttask.entity.DqInspectTask;
import org.jeecg.modules.supervision.dqinspecttask.service.IDqInspectTaskService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Collections;
import java.util.List;
@ExtendWith(MockitoExtension.class)
class InspectImproveFlowTest {
@Mock
private InspectImproveConfig inspectImproveConfig;
@Mock
private OrgConfig orgConfig;
@Mock
private ISysBaseAPI iSysBaseAPI;
@Mock
private IDqInspectTaskService dqInspectTaskService;
@Mock
private RuntimeService runtimeService;
@Mock
private DelegateExecution execution;
private InspectImproveFlow flow;
private InspectImproveProperties props;
private OrgConfig.Dept dept;
private OrgConfig.Role role;
@BeforeEach
void setUp() {
props = new InspectImproveProperties();
props.setBusinessKey("businessKey");
props.setMeasureResLeaderKey("measureResLeader");
props.setMeasureResDeptKey("measureResDept");
props.setQuestionResLeaderKey("questionResLeader");
props.setQuestionResDeptKey("questionResDept");
lenient().when(inspectImproveConfig.getInspectImprove()).thenReturn(props);
dept = new OrgConfig.Dept();
dept.setDq("dept-dq");
dept.setSld("dept-sld");
role = new OrgConfig.Role();
role.setLd("role-ld");
role.setSdwsj("role-sdwsj");
lenient().when(orgConfig.getDept()).thenReturn(dept);
lenient().when(orgConfig.getRole()).thenReturn(role);
// IDjInspectImproveService, FlowNodeExpression 未使用,传 null
flow = new InspectImproveFlow(inspectImproveConfig, orgConfig, null, null,
iSysBaseAPI, dqInspectTaskService, runtimeService);
}
/** 为需要 DqInspectTask 的方法设置通用 mock */
private void mockExecutionBusinessKey(String businessKey) {
when(execution.getVariable("businessKey")).thenReturn(businessKey);
}
private DqInspectTask mockTaskFound(String id) {
DqInspectTask task = new DqInspectTask();
task.setId(id);
when(dqInspectTaskService.getById(id)).thenReturn(task);
return task;
}
// ==================== 读取流程变量 ====================
@Nested
class ProcessVariableReaders {
@Test
void getIsEnd_shouldReturnValue() {
when(execution.getProcessInstanceId()).thenReturn("pi-001");
when(runtimeService.getVariable("pi-001", InspectConstant.IS_END_KEY)).thenReturn("true");
assertEquals("true", flow.getIsEnd(execution));
}
@Test
void getIsEnd_shouldReturnNullWhenNotSet() {
when(execution.getProcessInstanceId()).thenReturn("pi-001");
when(runtimeService.getVariable("pi-001", InspectConstant.IS_END_KEY)).thenReturn(null);
assertNull(flow.getIsEnd(execution));
}
@Test
void getDqDeptLeader_shouldReturnStoredValue() {
when(execution.getProcessInstanceId()).thenReturn("pi-001");
when(runtimeService.getVariable("pi-001", InspectConstant.DQ_DEPT_LEADER_KEY)).thenReturn("user1");
assertEquals("user1", flow.getDqDeptLeader(execution));
}
@Test
void getImplDeptLeader_shouldReturnStoredValue() {
when(execution.getProcessInstanceId()).thenReturn("pi-001");
when(runtimeService.getVariable("pi-001", InspectConstant.IMPL_DEPT_LEADER_KEY)).thenReturn("user2");
assertEquals("user2", flow.getImplDeptLeader(execution));
}
@Test
void getSubApproveUser_shouldReturnStoredValue() {
when(execution.getProcessInstanceId()).thenReturn("pi-001");
when(runtimeService.getVariable("pi-001", InspectConstant.SUB_APPROVE_USER_KEY)).thenReturn("user3");
assertEquals("user3", flow.getSubApproveUser(execution));
}
}
// ==================== DqInspectTask 字段读取 ====================
@Nested
class TaskFieldReaders {
@Test
void getNeedDqLeaderApprove_shouldReturnNeedApprove() {
mockExecutionBusinessKey("biz-001");
DqInspectTask task = mockTaskFound("biz-001");
task.setIsNeedAppro(InspectConstant.NEED_APPROVE_STR);
assertEquals(InspectConstant.NEED_APPROVE_STR, flow.getNeedDqLeaderApprove(execution));
}
@Test
void getNeedDqLeaderApprove_shouldReturnNotNeedWhenBlank() {
mockExecutionBusinessKey("biz-001");
DqInspectTask task = mockTaskFound("biz-001");
task.setIsNeedAppro("");
assertEquals(InspectConstant.NOT_NEED_APPROVE_STR, flow.getNeedDqLeaderApprove(execution));
}
@Test
void getNeedDqLeaderApprove_shouldReturnNotNeedWhenNull() {
mockExecutionBusinessKey("biz-001");
DqInspectTask task = mockTaskFound("biz-001");
task.setIsNeedAppro(null);
assertEquals(InspectConstant.NOT_NEED_APPROVE_STR, flow.getNeedDqLeaderApprove(execution));
}
@Test
void getNeedDqLeaderApprove_shouldThrowWhenTaskNotFound() {
mockExecutionBusinessKey("biz-001");
when(dqInspectTaskService.getById("biz-001")).thenReturn(null);
assertThrows(IllegalStateException.class, () -> flow.getNeedDqLeaderApprove(execution));
}
@Test
void getMeasureResLeader_shouldReturnLeaderName() {
mockExecutionBusinessKey("biz-001");
DqInspectTask task = mockTaskFound("biz-001");
task.setMeasureResLeader("leader1");
assertEquals("leader1", flow.getMeasureResLeader(execution));
}
@Test
void getMeasureResLeader_shouldReturnNullWhenBlank() {
mockExecutionBusinessKey("biz-001");
DqInspectTask task = mockTaskFound("biz-001");
task.setMeasureResLeader("");
assertNull(flow.getMeasureResLeader(execution));
}
@Test
void getSLD_shouldReturnChargeLeaderId() {
mockExecutionBusinessKey("biz-001");
DqInspectTask task = mockTaskFound("biz-001");
task.setChargeLeaderId("sld_user");
assertEquals("sld_user", flow.getSLD(execution));
}
@Test
void getSLD_shouldReturnNullWhenBlank() {
mockExecutionBusinessKey("biz-001");
DqInspectTask task = mockTaskFound("biz-001");
task.setChargeLeaderId("");
assertNull(flow.getSLD(execution));
}
@Test
void getDqLeader_shouldReturnSupDeptLeader() {
mockExecutionBusinessKey("biz-001");
DqInspectTask task = mockTaskFound("biz-001");
task.setSupDeptleaderid("dq_leader");
assertEquals("dq_leader", flow.getDqLeader(execution));
}
}
// ==================== ISysBaseAPI 用户查询 ====================
@Nested
class UserQueryMethods {
@Test
void getDqDeptLdUserIdList_shouldReturnUserList() {
List<String> users = List.of("user1", "user2");
when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-dq", "role-ld"))
.thenReturn(users);
assertEquals(users, flow.getDqDeptLdUserIdList());
}
@Test
void getDqDeptLdUserIdListLength_shouldReturnCount() {
when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-dq", "role-ld"))
.thenReturn(List.of("user1", "user2"));
assertEquals(2, flow.getDqDeptLdUserIdListLength());
}
@Test
void getSDWSJ_shouldReturnUniqueUser() {
when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-sld", "role-sdwsj"))
.thenReturn(List.of("party_secretary"));
assertEquals("party_secretary", flow.getSDWSJ());
}
@Test
void getSDWSJ_shouldThrowWhenEmpty() {
when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-sld", "role-sdwsj"))
.thenReturn(Collections.emptyList());
assertThrows(BusinessException.class, () -> flow.getSDWSJ());
}
@Test
void getSDWSJ_shouldThrowWhenMultipleUsers() {
when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-sld", "role-sdwsj"))
.thenReturn(List.of("user1", "user2"));
assertThrows(BusinessException.class, () -> flow.getSDWSJ());
}
}
// ==================== 部门领导列表查询(需要 DqInspectTask) ====================
@Nested
class ImplDeptLeaderMethods {
@Test
void getImplDeptLeaderList_shouldReturnLeaders() {
mockExecutionBusinessKey("biz-001");
DqInspectTask task = mockTaskFound("biz-001");
task.setMeasureResDept("dept-100");
List<String> leaders = List.of("leader1");
when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-100", "role-ld"))
.thenReturn(leaders);
assertEquals(leaders, flow.getImplDeptLeaderList(execution));
}
@Test
void getImplDeptLeaderList_shouldThrowWhenMeasureResDeptBlank() {
mockExecutionBusinessKey("biz-001");
DqInspectTask task = mockTaskFound("biz-001");
task.setMeasureResDept("");
assertThrows(IllegalStateException.class, () -> flow.getImplDeptLeaderList(execution));
}
@Test
void getImplDeptLeaderListLength_shouldReturnCount() {
mockExecutionBusinessKey("biz-001");
DqInspectTask task = mockTaskFound("biz-001");
task.setMeasureResDept("dept-100");
when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-100", "role-ld"))
.thenReturn(List.of("leader1", "leader2"));
assertEquals(2, flow.getImplDeptLeaderListLength(execution));
}
}
// ==================== JSON 数据解析 ====================
@Nested
class JsonParsingMethods {
@Test
void getMeasureResLeaderList_shouldParseCommaSeparated() {
JSONObject json = new JSONObject();
json.put("measureResLeader", "user1,user2,user3");
List<String> result = flow.getMeasureResLeaderList(json);
assertEquals(3, result.size());
assertTrue(result.contains("user1"));
assertTrue(result.contains("user2"));
assertTrue(result.contains("user3"));
}
@Test
void getMeasureResLeaderList_shouldHandleJsonString() {
String jsonStr = "{\"measureResLeader\":\"user1,user2\"}";
List<String> result = flow.getMeasureResLeaderList(jsonStr);
assertEquals(2, result.size());
}
@Test
void getMeasureResLeaderList_shouldReturnEmptyWhenFieldMissing() {
JSONObject json = new JSONObject();
List<String> result = flow.getMeasureResLeaderList(json);
assertTrue(result.isEmpty());
}
@Test
void getMeasureResLeaderListLength_shouldReturnCount() {
JSONObject json = new JSONObject();
json.put("measureResLeader", "user1,user2,user3");
assertEquals(3, flow.getMeasureResLeaderListLength(json));
}
@Test
void getQuestionResLeaderList_shouldParse() {
JSONObject json = new JSONObject();
json.put("questionResLeader", "leader1,leader2");
List<String> result = flow.getQuestionResLeaderList(json);
assertEquals(2, result.size());
}
@Test
void getMeasureResDeptList_shouldParse() {
JSONObject json = new JSONObject();
json.put("measureResDept", "dept1,dept2");
List<String> result = flow.getMeasureResDeptList(json);
assertEquals(2, result.size());
}
@Test
void getMeasureResLeader_singleValue_shouldReturnString() {
JSONObject json = new JSONObject();
json.put("measureResLeader", "singleLeader");
assertEquals("singleLeader", flow.getMeasureResLeader(json));
}
@Test
void getMeasureResLeader_singleValue_shouldReturnNullWhenMissing() {
JSONObject json = new JSONObject();
assertNull(flow.getMeasureResLeader(json));
}
@Test
void getListSize_shouldCountCommaSeparated() {
assertEquals(3, flow.getListSize("a,b,c"));
assertEquals(1, flow.getListSize("single"));
assertEquals(0, flow.getListSize(""));
assertEquals(0, flow.getListSize(null));
}
}
}
@@ -0,0 +1,73 @@
package org.jeecg.modules.supervision.inspectimprove.listener;
import static org.mockito.Mockito.*;
import org.flowable.task.service.delegate.DelegateTask;
import org.jeecg.modules.supervision.inspectimprove.config.InspectCloseoutConfig;
import org.jeecg.modules.supervision.inspectimprove.config.InspectCloseoutConfig.InspectCloseoutProperties;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class AfterCloseoutApprovedListenerTest {
@Mock
private InspectCloseoutConfig inspectCloseoutConfig;
@Mock
private DelegateTask delegateTask;
private AfterCloseoutApprovedListener listener;
private InspectCloseoutProperties props;
@BeforeEach
void setUp() {
props = new InspectCloseoutProperties();
props.setBusinessKey("businessKey");
when(inspectCloseoutConfig.getInspectCloseout()).thenReturn(props);
listener = new AfterCloseoutApprovedListener(inspectCloseoutConfig);
}
@Test
void shouldProcessWhenBusinessKeyExists() {
when(delegateTask.getVariable("businessKey")).thenReturn("biz-001");
listener.notify(delegateTask);
// 无异常即成功 — 当前实现仅记录日志
}
@Test
void shouldSkipWhenBusinessKeyIsNull() {
when(delegateTask.getVariable("businessKey")).thenReturn(null);
when(delegateTask.getId()).thenReturn("task-001");
listener.notify(delegateTask);
// 无异常即成功
}
@Test
void shouldSkipWhenBusinessKeyIsEmpty() {
when(delegateTask.getVariable("businessKey")).thenReturn("");
when(delegateTask.getId()).thenReturn("task-001");
listener.notify(delegateTask);
// 无异常即成功
}
@Test
void shouldSkipWhenBusinessKeyIsBlank() {
when(delegateTask.getVariable("businessKey")).thenReturn(" ");
when(delegateTask.getId()).thenReturn("task-001");
listener.notify(delegateTask);
// 无异常即成功
}
}
@@ -0,0 +1,41 @@
package org.jeecg.modules.supervision.inspectimprove.listener;
import static org.mockito.Mockito.*;
import org.flowable.engine.RuntimeService;
import org.flowable.task.service.delegate.DelegateTask;
import org.jeecg.modules.supervision.inspectimprove.constant.InspectConstant;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class AfterDqLeaderApproveListenerTest {
@Mock
private RuntimeService runtimeService;
@Mock
private DelegateTask delegateTask;
@Test
void shouldWriteDqDeptLeaderToProcessVariable() {
when(delegateTask.getAssignee()).thenReturn("zhangsan");
when(delegateTask.getProcessInstanceId()).thenReturn("proc-001");
// inspectImproveConfig and djInspectImproveService 在此监听器中未使用,传 null
new AfterDqLeaderApproveListener(null, null, runtimeService).notify(delegateTask);
verify(runtimeService).setVariable("proc-001", InspectConstant.DQ_DEPT_LEADER_KEY, "zhangsan");
}
@Test
void shouldSkipWhenAssigneeIsNull() {
when(delegateTask.getAssignee()).thenReturn(null);
new AfterDqLeaderApproveListener(null, null, runtimeService).notify(delegateTask);
verify(runtimeService, never()).setVariable(any(), any(), any());
}
}
@@ -0,0 +1,40 @@
package org.jeecg.modules.supervision.inspectimprove.listener;
import static org.mockito.Mockito.*;
import org.flowable.engine.RuntimeService;
import org.flowable.task.service.delegate.DelegateTask;
import org.jeecg.modules.supervision.inspectimprove.constant.InspectConstant;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class AfterImplDeptLeaderApproveListenerTest {
@Mock
private RuntimeService runtimeService;
@Mock
private DelegateTask delegateTask;
@Test
void shouldWriteImplDeptLeaderToProcessVariable() {
when(delegateTask.getAssignee()).thenReturn("lisi");
when(delegateTask.getProcessInstanceId()).thenReturn("proc-002");
new AfterImplDeptLeaderApproveListener(null, null, runtimeService).notify(delegateTask);
verify(runtimeService).setVariable("proc-002", InspectConstant.IMPL_DEPT_LEADER_KEY, "lisi");
}
@Test
void shouldSkipWhenAssigneeIsNull() {
when(delegateTask.getAssignee()).thenReturn(null);
new AfterImplDeptLeaderApproveListener(null, null, runtimeService).notify(delegateTask);
verify(runtimeService, never()).setVariable(any(), any(), any());
}
}
@@ -0,0 +1,77 @@
package org.jeecg.modules.supervision.inspectimprove.listener;
import static org.mockito.Mockito.*;
import org.flowable.task.service.delegate.DelegateTask;
import org.jeecg.modules.supervision.inspectimprove.config.InspectImproveConfig;
import org.jeecg.modules.supervision.inspectimprove.config.InspectImproveConfig.InspectImproveProperties;
import org.jeecg.modules.supervision.inspectimprove.entity.DjInspectImprove;
import org.jeecg.modules.supervision.inspectimprove.service.IDjInspectImproveService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class AfterInspectImproveCompleteListenerTest {
@Mock
private InspectImproveConfig inspectImproveConfig;
@Mock
private IDjInspectImproveService djInspectImproveService;
@Mock
private DelegateTask delegateTask;
private AfterInspectImproveCompleteListener listener;
@BeforeEach
void setUp() {
InspectImproveProperties props = new InspectImproveProperties();
props.setBusinessKey("businessKey");
when(inspectImproveConfig.getInspectImprove()).thenReturn(props);
listener = new AfterInspectImproveCompleteListener(inspectImproveConfig, djInspectImproveService);
}
@Test
void shouldSkipWhenBusinessKeyIsNull() {
when(delegateTask.getVariable("businessKey")).thenReturn(null);
listener.notify(delegateTask);
verify(djInspectImproveService, never()).getById(any());
}
@Test
void shouldSkipWhenBusinessKeyIsEmpty() {
when(delegateTask.getVariable("businessKey")).thenReturn("");
listener.notify(delegateTask);
verify(djInspectImproveService, never()).getById(any());
}
@Test
void shouldSkipWhenEntityNotFound() {
when(delegateTask.getVariable("businessKey")).thenReturn("biz-001");
when(djInspectImproveService.getById("biz-001")).thenReturn(null);
listener.notify(delegateTask);
verify(djInspectImproveService).getById("biz-001");
}
@Test
void shouldNotThrowWhenEntityExists() {
when(delegateTask.getVariable("businessKey")).thenReturn("biz-001");
when(djInspectImproveService.getById("biz-001")).thenReturn(new DjInspectImprove());
// 当前实现仅有 TODO,不应抛异常
listener.notify(delegateTask);
verify(djInspectImproveService).getById("biz-001");
}
}
@@ -0,0 +1,110 @@
package org.jeecg.modules.supervision.inspectimprove.listener;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import org.flowable.task.service.delegate.DelegateTask;
import org.jeecg.modules.supervision.inspectimprove.config.InspectImproveConfig;
import org.jeecg.modules.supervision.inspectimprove.config.InspectImproveConfig.InspectImproveProperties;
import org.jeecg.modules.supervision.dqinspecttask.entity.DqInspectTask;
import org.jeecg.modules.supervision.dqinspecttask.service.IDqInspectTaskService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.lenient;
@ExtendWith(MockitoExtension.class)
class AfterMeasureResLeaderStoreListenerTest {
@Mock
private InspectImproveConfig inspectImproveConfig;
@Mock
private IDqInspectTaskService dqInspectTaskService;
@Mock
private DelegateTask delegateTask;
private AfterMeasureResLeaderStoreListener listener;
@BeforeEach
void setUp() {
InspectImproveProperties props = new InspectImproveProperties();
props.setBusinessKey("businessKey");
lenient().when(inspectImproveConfig.getInspectImprove()).thenReturn(props);
listener = new AfterMeasureResLeaderStoreListener(inspectImproveConfig, dqInspectTaskService);
}
@Test
void shouldSkipWhenAssigneeIsNull() {
when(delegateTask.getAssignee()).thenReturn(null);
listener.notify(delegateTask);
verify(dqInspectTaskService, never()).getById(any());
verify(dqInspectTaskService, never()).updateById(any());
}
@Test
void shouldSkipWhenAssigneeIsEmpty() {
when(delegateTask.getAssignee()).thenReturn("");
listener.notify(delegateTask);
verify(dqInspectTaskService, never()).getById(any());
}
@Test
void shouldSkipWhenBusinessKeyIsNull() {
when(delegateTask.getAssignee()).thenReturn("zhangsan");
when(delegateTask.getVariable("businessKey")).thenReturn(null);
listener.notify(delegateTask);
verify(dqInspectTaskService, never()).getById(any());
}
@Test
void shouldSkipWhenBusinessKeyIsBlank() {
when(delegateTask.getAssignee()).thenReturn("zhangsan");
when(delegateTask.getVariable("businessKey")).thenReturn(" ");
listener.notify(delegateTask);
verify(dqInspectTaskService, never()).getById(any());
}
@Test
void shouldSkipWhenEntityNotFound() {
when(delegateTask.getAssignee()).thenReturn("zhangsan");
when(delegateTask.getVariable("businessKey")).thenReturn("biz-001");
when(dqInspectTaskService.getById("biz-001")).thenReturn(null);
listener.notify(delegateTask);
verify(dqInspectTaskService).getById("biz-001");
verify(dqInspectTaskService, never()).updateById(any());
}
@Test
void shouldUpdateEntityWithAssignee() {
when(delegateTask.getAssignee()).thenReturn("zhangsan");
when(delegateTask.getId()).thenReturn("task-001");
when(delegateTask.getVariable("businessKey")).thenReturn("biz-001");
DqInspectTask task = new DqInspectTask();
task.setId("biz-001");
when(dqInspectTaskService.getById("biz-001")).thenReturn(task);
listener.notify(delegateTask);
ArgumentCaptor<DqInspectTask> captor = ArgumentCaptor.forClass(DqInspectTask.class);
verify(dqInspectTaskService).updateById(captor.capture());
assertEquals("zhangsan", captor.getValue().getMeasureResLeader());
}
}
@@ -0,0 +1,62 @@
package org.jeecg.modules.supervision.inspectimprove.listener;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import org.flowable.engine.RuntimeService;
import org.flowable.task.service.delegate.DelegateTask;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class LeaderApproveHelperTest {
@Mock
private DelegateTask delegateTask;
@Mock
private RuntimeService runtimeService;
private static final String VAR_KEY = "test_var";
private static final String LOG_PREFIX = "【测试】";
@Test
void shouldSetVariableWhenAssigneeExists() {
when(delegateTask.getAssignee()).thenReturn("zhangsan");
when(delegateTask.getProcessInstanceId()).thenReturn("proc-001");
LeaderApproveHelper.handle(delegateTask, runtimeService, VAR_KEY, LOG_PREFIX);
verify(runtimeService).setVariable("proc-001", VAR_KEY, "zhangsan");
}
@Test
void shouldNotSetVariableWhenAssigneeIsNull() {
when(delegateTask.getAssignee()).thenReturn(null);
LeaderApproveHelper.handle(delegateTask, runtimeService, VAR_KEY, LOG_PREFIX);
verify(runtimeService, never()).setVariable(any(), any(), any());
}
@Test
void shouldNotSetVariableWhenAssigneeIsEmpty() {
when(delegateTask.getAssignee()).thenReturn("");
LeaderApproveHelper.handle(delegateTask, runtimeService, VAR_KEY, LOG_PREFIX);
verify(runtimeService, never()).setVariable(any(), any(), any());
}
@Test
void shouldUseCorrectProcessInstanceId() {
when(delegateTask.getAssignee()).thenReturn("lisi");
when(delegateTask.getProcessInstanceId()).thenReturn("proc-999");
LeaderApproveHelper.handle(delegateTask, runtimeService, VAR_KEY, LOG_PREFIX);
verify(runtimeService).setVariable("proc-999", VAR_KEY, "lisi");
}
}