!123 fix(flow): 修复流程运行阻断与数据一致性问题

* fix(flow): 修复流程运行阻断与数据一致性问题
This commit is contained in:
wsm
2026-08-11 09:45:38 +00:00
parent f5469b2b74
commit bd68ec888d
19 changed files with 139 additions and 31 deletions
@@ -1,7 +1,6 @@
package org.jeecg.modules.supervision.bgpartymatter.controller;
import com.alibaba.fastjson.JSONObject;
import org.flowable.engine.RuntimeService;
import org.jeecg.common.system.query.QueryGenerator;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.jeecg.common.system.query.QueryRuleEnum;
@@ -76,9 +75,6 @@ public class BgPartymatterController extends JeecgController<BgPartymatter, IBgP
@Autowired
private IBgPartymatterFeedbackService bgPartymatterFeedbackService;
@Autowired
protected RuntimeService runtimeService;
/*---------------------------------主表处理-begin-------------------------------------*/
/**
@@ -200,8 +196,8 @@ public class BgPartymatterController extends JeecgController<BgPartymatter, IBgP
String varField = oConvertUtils.isEmpty(dto.getVarField()) ? "json_data" : dto.getVarField();
fillMeetingTypeForUpdate(dto.getFormData());
fillGeneratedNumber(dto.getFormData());
bgPartymatterService.updateById(dto.getFormData());
runtimeService.setVariable(dto.getProcessInstanceId(), varField, JSONObject.toJSONString(dto.getFormData()));
bgPartymatterService.saveBpmFormAndSyncVariable(dto.getFormData(), dto.getProcessInstanceId(), varField,
JSONObject.toJSONString(dto.getFormData()));
return Result.OK("保存成功");
}
@@ -30,5 +30,15 @@ public interface IBgPartymatterService extends IService<BgPartymatter> {
*/
public void delBatchMain (Collection<? extends Serializable> idList);
/**
* 保存BPM办理页主表,并同步流程变量;二者同事务,setVariable 失败时回滚主表更新,保证一致
*
* @param formData 主表数据
* @param processInstanceId 流程实例ID
* @param varField 流程变量名
* @param jsonData 写入流程变量的JSON字符串
*/
void saveBpmFormAndSyncVariable(BgPartymatter formData, String processInstanceId, String varField, String jsonData);
}
@@ -4,6 +4,7 @@ import org.jeecg.modules.supervision.bgpartymatter.entity.BgPartymatter;
import org.jeecg.modules.supervision.bgpartymatter.mapper.BgPartymatterFeedbackMapper;
import org.jeecg.modules.supervision.bgpartymatter.mapper.BgPartymatterMapper;
import org.jeecg.modules.supervision.bgpartymatter.service.IBgPartymatterService;
import org.flowable.engine.RuntimeService;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
@@ -25,7 +26,9 @@ public class BgPartymatterServiceImpl extends ServiceImpl<BgPartymatterMapper, B
private BgPartymatterMapper bgPartymatterMapper;
@Autowired
private BgPartymatterFeedbackMapper bgPartymatterFeedbackMapper;
@Autowired
private RuntimeService runtimeService;
@Override
@Transactional(rollbackFor = Exception.class)
public void delMain(String id) {
@@ -41,5 +44,12 @@ public class BgPartymatterServiceImpl extends ServiceImpl<BgPartymatterMapper, B
bgPartymatterMapper.deleteById(id);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void saveBpmFormAndSyncVariable(BgPartymatter formData, String processInstanceId, String varField, String jsonData) {
this.updateById(formData);
runtimeService.setVariable(processInstanceId, varField, jsonData);
}
}
@@ -12,7 +12,6 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.alibaba.fastjson.JSONObject;
import org.flowable.engine.RuntimeService;
import org.jeecg.modules.supervision.common.BpmStatus;
import org.jeecg.modules.supervision.common.SupervisionConstant;
import org.jeecg.modules.supervision.xispeak.entity.BgXiSpeak;
@@ -67,8 +66,6 @@ public class BgTakepulseController extends JeecgController<BgTakepulse, IBgTakep
private IBgTakepulseService bgTakepulseService;
@Autowired
private IBgTakepulseFeedbackService bgTakepulseFeedbackService;
@Autowired
protected RuntimeService runtimeService;
/**
* 分页列表查询
@@ -213,8 +210,8 @@ public class BgTakepulseController extends JeecgController<BgTakepulse, IBgTakep
return Result.error("流程实例ID不能为空");
}
String varField = oConvertUtils.isEmpty(dto.getVarField()) ? "json_data" : dto.getVarField();
bgTakepulseService.updateById(dto.getFormData());
runtimeService.setVariable(dto.getProcessInstanceId(), varField, JSONObject.toJSONString(dto.getFormData()));
bgTakepulseService.saveBpmFormAndSyncVariable(dto.getFormData(), dto.getProcessInstanceId(), varField,
JSONObject.toJSONString(dto.getFormData()));
return Result.OK("保存成功");
}
@@ -44,5 +44,15 @@ public interface IBgTakepulseService extends IService<BgTakepulse> {
* @param idList
*/
public void delBatchMain (Collection<? extends Serializable> idList);
/**
* 保存BPM办理页主表,并同步流程变量;二者同事务,setVariable 失败时回滚主表更新,保证一致
*
* @param formData 主表数据
* @param processInstanceId 流程实例ID
* @param varField 流程变量名
* @param jsonData 写入流程变量的JSON字符串
*/
void saveBpmFormAndSyncVariable(BgTakepulse formData, String processInstanceId, String varField, String jsonData);
}
@@ -5,6 +5,7 @@ import org.jeecg.modules.supervision.bqtakepulse.entity.BgTakepulseFeedback;
import org.jeecg.modules.supervision.bqtakepulse.mapper.BgTakepulseFeedbackMapper;
import org.jeecg.modules.supervision.bqtakepulse.mapper.BgTakepulseMapper;
import org.jeecg.modules.supervision.bqtakepulse.service.IBgTakepulseService;
import org.flowable.engine.RuntimeService;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
@@ -26,6 +27,8 @@ public class BgTakepulseServiceImpl extends ServiceImpl<BgTakepulseMapper, BgTak
private BgTakepulseMapper bgTakepulseMapper;
@Autowired
private BgTakepulseFeedbackMapper bgTakepulseFeedbackMapper;
@Autowired
private RuntimeService runtimeService;
@Override
@Transactional(rollbackFor = Exception.class)
@@ -76,6 +79,13 @@ public class BgTakepulseServiceImpl extends ServiceImpl<BgTakepulseMapper, BgTak
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void saveBpmFormAndSyncVariable(BgTakepulse formData, String processInstanceId, String varField, String jsonData) {
this.updateById(formData);
runtimeService.setVariable(processInstanceId, varField, jsonData);
}
/**
* 新增入口统一补齐逻辑删除正常值,避免 @TableLogic 将 del_flag 为空的数据过滤掉。
*/
@@ -131,9 +131,9 @@ public class InspectCloseoutFlow {
//流程表达式内用法 ${inspectCloseoutFlow.getJJDeptLdWorkerIdListLength()}
public int getJJDeptLdWorkerIdListLength() {
List<String> jjDeptLdUserIdList = this.getJJDeptLdUserIdList();
if (jjDeptLdUserIdList != null && !jjDeptLdUserIdList.isEmpty()) {
return jjDeptLdUserIdList.size();
List<String> jjDeptWorkerIdList = this.getJJDeptLdWorkerIdList();
if (jjDeptWorkerIdList != null && !jjDeptWorkerIdList.isEmpty()) {
return jjDeptWorkerIdList.size();
}
log.error("【巡视整改销号-流程表达式】JJ部门WORKER角色没有对应用户");
return 0;
@@ -3,6 +3,7 @@ package org.jeecg.modules.supervision.dqinspecttask.listener;
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.flowable.engine.delegate.ExecutionListener;
import org.jeecg.modules.supervision.dqinspecttask.config.InspectImproveConfig;
@@ -24,8 +25,12 @@ public class DqInspectTaskStageIncrementListener implements ExecutionListener {
private static final long serialVersionUID = 1L;
/** 幂等标记变量名:同一流程实例结束仅递增一次 */
private static final String INCREMENT_FLAG_KEY = "stage_increment_done";
private final InspectImproveConfig inspectImproveConfig;
private final IDqInspectTaskService dqInspectTaskService;
private final RuntimeService runtimeService;
@Override
public void notify(DelegateExecution execution) {
@@ -37,6 +42,16 @@ public class DqInspectTaskStageIncrementListener implements ExecutionListener {
}
String businessKey = businessKeyObj.toString();
String processInstanceId = execution.getProcessInstanceId();
// 幂等保护:同一流程实例结束仅递增一次,避免多结束事件/命令重试导致 completedStage 重复累加
if (Boolean.TRUE.equals(runtimeService.getVariable(processInstanceId, INCREMENT_FLAG_KEY))) {
log.info("【巡视整改-阶段递增监听器】本次流程已完成阶段递增,跳过, businessKey: {}, processInstanceId: {}",
businessKey, processInstanceId);
return;
}
runtimeService.setVariable(processInstanceId, INCREMENT_FLAG_KEY, Boolean.TRUE);
boolean updated = dqInspectTaskService.lambdaUpdate()
.setSql("completed_stage = completed_stage + 1")
.eq(DqInspectTask::getId, businessKey)
@@ -44,7 +59,7 @@ public class DqInspectTaskStageIncrementListener implements ExecutionListener {
if (updated) {
log.info("【巡视整改-阶段递增监听器】completedStage 已递增, businessKey: {}, processInstanceId: {}",
businessKey, execution.getProcessInstanceId());
businessKey, processInstanceId);
} else {
log.warn("【巡视整改-阶段递增监听器】递增未影响任何行,可能记录已删除, businessKey: {}", businessKey);
}
@@ -235,8 +235,8 @@ public class FixedContact20260730Controller extends JeecgController<FixedContact
return Result.error("流程实例ID不能为空");
}
String varField = oConvertUtils.isEmpty(dto.getVarField()) ? "json_data" : dto.getVarField();
fixedContact20260730Service.updateById(dto.getFormData());
runtimeService.setVariable(dto.getProcessInstanceId(), varField, JSONObject.toJSONString(dto.getFormData()));
fixedContact20260730Service.saveBpmFormAndSyncVariable(dto.getFormData(), dto.getProcessInstanceId(), varField,
JSONObject.toJSONString(dto.getFormData()));
return Result.OK("保存成功");
}
@@ -23,4 +23,14 @@ public interface IFixedContact20260730Service extends IService<FixedContact20260
* 悲观锁查询(for update),需在事务中使用
*/
FixedContact20260730 getByIdForUpdate(String id);
/**
* 保存BPM办理页主表,并同步流程变量;二者同事务,setVariable 失败时回滚主表更新,保证一致
*
* @param formData 主表数据
* @param processInstanceId 流程实例ID
* @param varField 流程变量名
* @param jsonData 写入流程变量的JSON字符串
*/
void saveBpmFormAndSyncVariable(FixedContact20260730 formData, String processInstanceId, String varField, String jsonData);
}
@@ -5,6 +5,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.RuntimeService;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.modules.supervision.fixcontact.entity.FixedContact20260730;
import org.jeecg.modules.supervision.fixcontact.entity.FixedContactFeedback20260730;
@@ -26,6 +27,9 @@ public class FixedContact20260730ServiceImpl extends ServiceImpl<FixedContact202
@Autowired
private FixedContactFeedback20260730Mapper fixedContactFeedback20260730Mapper;
@Autowired
private RuntimeService runtimeService;
@Override
@Transactional(rollbackFor = Exception.class)
public void saveMain(FixedContact20260730 entity, List<FixedContactFeedback20260730> subList) {
@@ -70,7 +74,8 @@ public class FixedContact20260730ServiceImpl extends ServiceImpl<FixedContact202
@Override
@Transactional(rollbackFor = Exception.class)
public void saveSubApprove(String mainId, String deptId, Integer isEnd, String implWorker, List<String> implUserNameList) {
FixedContact20260730 fixedContact = this.getById(mainId);
// 行锁(for update)防止多部门并发保存时读-改-写丢失更新
FixedContact20260730 fixedContact = this.getByIdForUpdate(mainId);
if (fixedContact == null) {
throw new JeecgBootException("业务表单不存在");
}
@@ -109,4 +114,11 @@ public class FixedContact20260730ServiceImpl extends ServiceImpl<FixedContact202
.eq(FixedContact20260730::getId, id)
.last("for update"));
}
@Override
@Transactional(rollbackFor = Exception.class)
public void saveBpmFormAndSyncVariable(FixedContact20260730 formData, String processInstanceId, String varField, String jsonData) {
this.updateById(formData);
runtimeService.setVariable(processInstanceId, varField, jsonData);
}
}
@@ -302,7 +302,6 @@ public class BgXiSpeakController extends JeecgController<BgXiSpeak, IBgXiSpeakSe
return Result.error("流程实例ID不能为空");
}
String varField = oConvertUtils.isEmpty(dto.getVarField()) ? "json_data" : dto.getVarField();
bgXiSpeakService.updateById(dto.getFormData());
Object existingVar = runtimeService.getVariable(dto.getProcessInstanceId(), varField);
JSONObject mergedJson = new JSONObject();
if (existingVar instanceof String) {
@@ -319,7 +318,8 @@ public class BgXiSpeakController extends JeecgController<BgXiSpeak, IBgXiSpeakSe
if (formDataJson != null) {
mergedJson.putAll(formDataJson);
}
runtimeService.setVariable(dto.getProcessInstanceId(), varField, mergedJson.toJSONString());
bgXiSpeakService.saveBpmFormAndSyncVariable(dto.getFormData(), dto.getProcessInstanceId(), varField,
mergedJson.toJSONString());
return Result.OK("保存成功");
}
@@ -232,10 +232,10 @@ public class XiSpeakFlow {
*/
public String getImplDeptLeader(DelegateExecution execution) {
DeptApproveDetail detail = getDeptApproveDetail(execution);
if (detail != null && detail.getIsEnd() != null) {
return String.valueOf(detail.getImplWorker());
if (detail != null && detail.getImplWorker() != null) {
return detail.getImplWorker();
}
return detail == null ? StringUtils.EMPTY_STRING : detail.getImplWorker();
return StringUtils.EMPTY_STRING;
}
/**
@@ -294,7 +294,22 @@ public class XiSpeakFlow {
}
// 流程表达式用法${xiSpeakFlow.getImplDeptIsEnd(execution)==1}
public int getImplDeptIsEnd(DelegateExecution execution){
return getDeptApproveDetail(execution).getIsEnd();
DeptApproveDetail detail = getDeptApproveDetail(execution);
if (detail != null && detail.getIsEnd() != null) {
return detail.getIsEnd();
}
// 兜底:从流程变量读取(兼容旧流程),避免 NPE 导致流程卡死
Object value = flowNodeExpression.getSonProcessVariable(execution, xiSpeakConfig.getXiSpeak().getIsEndKey());
if (value == null) {
return 0;
}
try {
return Integer.parseInt(value.toString());
} catch (NumberFormatException e) {
log.warn("【常委会事项-流程表达式】is_end 流程变量非数字, processInstanceId: {}, value: {}",
execution.getProcessInstanceId(), value);
return 0;
}
}
/**
@@ -86,7 +86,9 @@ public class AfterBgLeaderApproveListener implements TaskListener {
log.info("变量已存入向上两层级:TargetId={}, OriginalId={}, Assignee={}",
targetId, currentExecution.getId(), assignee);
} catch (Exception e) {
log.error("设置嵌套局部变量失败", e);
log.error("【县委常委会事项-审批监听器】向上两层写入变量失败, targetId: {}, variableKey: {}, assignee: {}, error: {}",
targetId, props.getTemBgLeaderKey(), assignee, e.getMessage(), e);
throw new RuntimeException("【县委常委会事项-审批监听器】向上两层写入流程变量失败", e);
}
}
}
@@ -86,7 +86,9 @@ public class AfterImplDeptLeaderApproveListener implements TaskListener {
log.info("变量已存入向上两层级:TargetId={}, OriginalId={}, Assignee={}",
targetId, currentExecution.getId(), assignee);
} catch (Exception e) {
log.error("设置嵌套局部变量失败", e);
log.error("【县委常委会事项-审批监听器】向上两层写入变量失败, targetId: {}, variableKey: {}, assignee: {}, error: {}",
targetId, VARIABLE_NAME, assignee, e.getMessage(), e);
throw new RuntimeException("【县委常委会事项-审批监听器】向上两层写入流程变量失败", e);
}
}
}
@@ -86,7 +86,9 @@ public class AfterImplWorkerTemStoreListener implements TaskListener {
log.info("变量已存入向上两层级:TargetId={}, OriginalId={}, Assignee={}",
targetId, currentExecution.getId(), assignee);
} catch (Exception e) {
log.error("设置嵌套局部变量失败", e);
log.error("【县委常委会事项-审批监听器】向上两层写入变量失败, targetId: {}, variableKey: {}, assignee: {}, error: {}",
targetId, VARIABLE_NAME, assignee, e.getMessage(), e);
throw new RuntimeException("【县委常委会事项-审批监听器】向上两层写入流程变量失败", e);
}
}
}
@@ -99,4 +99,14 @@ public interface IBgXiSpeakService extends IService<BgXiSpeak> {
* isEnd=1 时 implWorker 生效,isEnd=0 时 implUserNameList 生效
*/
void saveSubApprove(String mainId, String deptId, Integer isEnd, String implWorker, List<String> implUserNameList);
/**
* 保存BPM办理页主表,并同步流程变量;二者同事务,setVariable 失败时回滚主表更新,保证一致
*
* @param formData 主表数据
* @param processInstanceId 流程实例ID
* @param varField 流程变量名
* @param jsonData 写入流程变量的JSON字符串
*/
void saveBpmFormAndSyncVariable(BgXiSpeak formData, String processInstanceId, String varField, String jsonData);
}
@@ -468,4 +468,11 @@ public class BgXiSpeakServiceImpl extends ServiceImpl<BgXiSpeakMapper, BgXiSpeak
log.info("部门审批信息保存成功 mainId={}, deptId={}, isEnd={}, implWorker={}, implUserNameList={}",
mainId, deptId, isEnd, implWorker, implUserNameList);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void saveBpmFormAndSyncVariable(BgXiSpeak formData, String processInstanceId, String varField, String jsonData) {
this.updateById(formData);
runtimeService.setVariable(processInstanceId, varField, jsonData);
}
}
@@ -115,7 +115,7 @@ class InspectCloseoutFlowTest {
@Test
void getJJDeptLdWorkerIdListLength_shouldReturnCount() {
when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-jj", "role-ld"))
when(iSysBaseAPI.getUsersListByDeptIdAndRoleIdLocalApi("dept-jj", "role-jj-worker"))
.thenReturn(List.of("user1", "user2"));
assertEquals(2, flow.getJJDeptLdWorkerIdListLength());