fix:流程执行完成,更新算列状态

This commit is contained in:
2026-01-23 17:54:22 +08:00
parent 5185abcf97
commit c01c2bfbc5
11 changed files with 439 additions and 22 deletions

6
.idea/MarsCodeWorkspaceAppSettings.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="com.codeverse.userSettings.MarscodeWorkspaceAppSettingsState">
<option name="progress" value="1.0" />
</component>
</project>

74
.qoder/rules/rule.md Normal file
View File

@@ -0,0 +1,74 @@
---
trigger: manual
# Java 开发规范与重构指南
作为我的 AI 编程伙伴,请在生成、修改或评审代码时严格遵循以下 10 条核心原则:
### 1. 单一职责原则 (SRP)
* **规则**:一个方法只做一件事。
* **实践**:如果方法逻辑过长,请通过**拆分职责明确的私有方法**Private Methods来解耦。
* **目标**:提升代码的可读性和方法复用率。
### 2. 卫语句优先 (Guard Clauses)
* **规则**:减少 `if-else` 嵌套深度。
* **实践**:使用**卫语句提前返回**Early Return。先处理异常分支和边界条件核心逻辑放在方法最后。
* **示例**
```java
if (user == null) return;
// 执行核心逻辑...
```
### 3. 重复逻辑抽取
* **规则**:不要重复你自己 (DRY)。
* **实践**:将频繁出现的**参数校验、类型转换、对象构建**等模式封装为公共工具类或私有通用方法。
### 4. 数据封装 (Encapsulation)
* **规则**:避免多个相关变量散落在方法参数中。
* **实践**:如果多个变量在逻辑上高度相关(如 `startTime`, `endTime`),应**封装为内部类或 DTO 对象**。
### 5. 统一异常处理
* **规则**:禁止散乱的 try-catch。
* **实践**:使用**通用的响应校验方法**或全局异常拦截器。确保错误码和错误信息在整个系统中保持一致。
### 6. 参数对象化
* **规则**:控制方法入参数量。
* **实践**:当方法参数**超过 3 个**时,必须封装为 Request 对象或 Context 对象。
### 7. 语义化命名
* **规则**:变量名应具备自解释性。
* **实践****变量即文档**。使用准确的动词+名词组合,避免使用 `a`, `b`, `list1` 等无意义命名。
### 8. 消灭魔术值 (Magic Values)
* **规则**:禁止在逻辑判断中直接使用硬编码的数字或字符串。
* **实践**:全面**拥抱枚举 (Enum) 或常量 (Static Final)**。
### 9. 声明式编程
* **规则**:提升集合处理的可读性。
* **实践**:优先使用 **Java Stream API** 或函数式编程替代复杂的 `for/while` 循环。
### 10. 智能注释
* **规则**:避免“无效注释”。
* **实践**不要解释代码在做什么What而是解释**为什么要这么做Why**。如果代码本身足够清晰,则无需注释。
---
**当你为我生成代码时,请先自检是否违反了上述规则。如果我提交的代码不符合这些原则,请主动提示并给出重构建议。**
---
---

View File

@@ -51,4 +51,14 @@ public class SimulationRunFeignClientImpl implements ISimulationRunFeignClient {
return SdmResponse.failed("内部调用生成自动化报告失败");
}
}
@Override
public SdmResponse updateStatusByProcessInstanceId(String processInstanceId, Integer statusCode) {
try {
return simulationRunFeignClient.updateStatusByProcessInstanceId(processInstanceId, statusCode);
} catch (Exception e) {
log.error("根据流程实例ID更新算例状态失败: processInstanceId={}, statusCode={}", processInstanceId, statusCode, e);
return SdmResponse.failed("更新算例状态失败");
}
}
}

View File

@@ -26,4 +26,14 @@ public interface ISimulationRunFeignClient {
@PostMapping("/run/generateReportInternal")
SdmResponse<Void> generateReportInternal(@RequestBody SpdmReportReq req);
/**
* 根据流程实例ID更新算例状态
*
* @param processInstanceId 流程实例ID
* @param statusCode 状态值(RUNNING/SUSPENDED/COMPLETED/CANCELLED/ERROR)
* @return SdmResponse
*/
@PostMapping("/run/updateStatusByProcessInstanceId")
SdmResponse updateStatusByProcessInstanceId(@RequestParam String processInstanceId, @RequestParam Integer statusCode);
}

View File

@@ -0,0 +1,48 @@
package com.sdm.flowable.config;
import com.sdm.flowable.listener.GlobalStatusEventListener;
import lombok.extern.slf4j.Slf4j;
import org.flowable.common.engine.api.delegate.event.FlowableEventListener;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.spring.boot.EngineConfigurationConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
/**
* Flowable流程引擎配置类
* 用于注册全局事件监听器等配置
*
* @author SDM
* @date 2026-01-23
*/
@Slf4j
@Configuration
public class FlowableEngineConfig {
/**
* 注册全局事件监听器
* 通过EngineConfigurationConfigurer可以在流程引擎初始化时添加监听器
*/
@Bean
public EngineConfigurationConfigurer<SpringProcessEngineConfiguration> globalEventListenerConfigurer(
GlobalStatusEventListener globalStatusEventListener) {
return engineConfiguration -> {
// 获取现有的事件监听器列表
List<FlowableEventListener> eventListeners = engineConfiguration.getEventListeners();
if (eventListeners == null) {
eventListeners = new ArrayList<>();
}
// 添加全局状态同步监听器
eventListeners.add(globalStatusEventListener);
engineConfiguration.setEventListeners(eventListeners);
log.info("✅ 已注册Flowable全局事件监听器: GlobalStatusEventListener");
log.info(" 监听事件类型: PROCESS_COMPLETED, PROCESS_CANCELLED, ENTITY_SUSPENDED, ENTITY_ACTIVATED, JOB_MOVED_TO_DEADLETTER");
log.info(" 状态映射: COMPLETED, CANCELLED, SUSPENDED, RUNNING, ERROR");
};
}
}

View File

@@ -7,35 +7,43 @@ public enum ProcessInstanceStateEnum {
/**
* 运行中
*/
RUNNING("running"),
/**
* 挂起
*/
SUSPENDED("suspended"),
/**
* 错误
*/
ERROR("error"),
RUNNING(1,"running"),
/**
* 已完成
*/
COMPLETED("completed"),
COMPLETED(2,"completed"),
/**
* 错误
*/
ERROR(3,"error"),
/**
* 挂起
*/
SUSPENDED(4,"suspended"),
/**
* 已取消
*/
CANCELLED("cancelled");
CANCELLED(5,"cancelled");
private final String code;
private final Integer code;
ProcessInstanceStateEnum(String code) {
private final String value;
ProcessInstanceStateEnum(Integer code, String value) {
this.code = code;
this.value = value;
}
public String getCode() {
public String getValue() {
return value;
}
public Integer getCode() {
return code;
}
}

View File

@@ -0,0 +1,187 @@
package com.sdm.flowable.listener;
import com.sdm.common.feign.inter.project.ISimulationRunFeignClient;
import com.sdm.flowable.enums.ProcessInstanceStateEnum;
import lombok.extern.slf4j.Slf4j;
import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent;
import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType;
import org.flowable.common.engine.api.delegate.event.FlowableEvent;
import org.flowable.common.engine.api.delegate.event.FlowableEventListener;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import org.flowable.job.api.Job;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* 全局算例状态同步监听器
* 作用:替代定时任务,实时推送状态
* <p>
* 监听Flowable流程引擎的全生命周期事件实现算例状态的实时同步
* - PROCESS_COMPLETED: 流程正常完成
* - PROCESS_CANCELLED: 流程被取消/终止
* - ENTITY_SUSPENDED: 实体挂起(需过滤流程实例)
* - ENTITY_ACTIVATED: 实体激活(需过滤流程实例)
* - JOB_MOVED_TO_DEADLETTER: 作业移入死信队列ERROR状态
* </p>
*
* @author SDM
* @date 2026-01-23
*/
@Slf4j
@Component
public class GlobalStatusEventListener implements FlowableEventListener {
@Autowired
private ISimulationRunFeignClient simulationRunFeignClient;
@Override
public Set<FlowableEngineEventType> getTypes() {
return new HashSet<>(Arrays.asList(
FlowableEngineEventType.PROCESS_COMPLETED, // 流程完成
FlowableEngineEventType.PROCESS_CANCELLED, // 流程取消精确匹配不需要判断deleteReason
FlowableEngineEventType.ENTITY_SUSPENDED, // 实体挂起(需过滤流程实例)
FlowableEngineEventType.ENTITY_ACTIVATED, // 实体激活(需过滤流程实例)
FlowableEngineEventType.JOB_MOVED_TO_DEADLETTER // 作业进入死信ERROR状态
));
}
@Override
public void onEvent(FlowableEvent event) {
try {
FlowableEngineEventType eventType = (FlowableEngineEventType) event.getType();
// 1. 流程正常完成
if (eventType == FlowableEngineEventType.PROCESS_COMPLETED) {
handleProcessCompleted((FlowableEngineEntityEvent) event);
}
// 2. 流程被取消Flowable 7.x有独立的CANCELLED事件
else if (eventType == FlowableEngineEventType.PROCESS_CANCELLED) {
handleProcessCancelled((FlowableEngineEntityEvent) event);
}
// 3. 实体挂起/激活(需要过滤,只处理流程实例级别)
else if (eventType == FlowableEngineEventType.ENTITY_SUSPENDED ||
eventType == FlowableEngineEventType.ENTITY_ACTIVATED) {
handleSuspendOrActivate((FlowableEngineEntityEvent) event, eventType);
}
// 4. 作业进入死信队列ERROR状态的金标准
else if (eventType == FlowableEngineEventType.JOB_MOVED_TO_DEADLETTER) {
handleDeadLetter((FlowableEngineEntityEvent) event);
}
} catch (Exception e) {
log.error("处理Flowable事件异常: eventType={}", event.getType(), e);
}
}
// --- 内部逻辑方法 ---
/**
* 处理流程正常完成事件
* PROCESS_COMPLETED只表示流程走到EndEvent不包括取消场景
*/
private void handleProcessCompleted(FlowableEngineEntityEvent event) {
String processInstanceId = event.getProcessInstanceId();
log.info("流程正常完成: processInstanceId={}", processInstanceId);
doUpdate(processInstanceId, ProcessInstanceStateEnum.COMPLETED.getCode());
}
/**
* 处理流程取消事件
* Flowable 7.x提供了独立的PROCESS_CANCELLED事件更精确
*/
private void handleProcessCancelled(FlowableEngineEntityEvent event) {
String processInstanceId = event.getProcessInstanceId();
ExecutionEntity execution = (ExecutionEntity) event.getEntity();
log.info("流程被取消: processInstanceId={}, deleteReason={}",
processInstanceId, execution.getDeleteReason());
doUpdate(processInstanceId, ProcessInstanceStateEnum.CANCELLED.getCode());
}
/**
* 处理实体挂起/激活事件
* 关键:必须过滤,只处理流程实例级别的挂起/激活,忽略子流程或其他实体
*/
private void handleSuspendOrActivate(FlowableEngineEntityEvent event, FlowableEngineEventType eventType) {
Object entity = event.getEntity();
// 只处理ExecutionEntity流程执行实体
if (entity instanceof ExecutionEntity) {
ExecutionEntity execution = (ExecutionEntity) entity;
// 关键判断isProcessInstanceType()确保是流程实例本身,而非子分支
if (execution.isProcessInstanceType()) {
String processInstanceId = execution.getProcessInstanceId();
Integer status = (eventType == FlowableEngineEventType.ENTITY_SUSPENDED) ? ProcessInstanceStateEnum.SUSPENDED.getCode() : ProcessInstanceStateEnum.RUNNING.getCode();
log.info("流程实例{}状态变更: processInstanceId={}",
status.equals("SUSPENDED") ? "挂起" : "激活", processInstanceId);
doUpdate(processInstanceId, status);
} else {
log.debug("忽略非流程实例级别的挂起/激活事件: executionId={}", execution.getId());
}
}
}
/**
* 处理死信事件ERROR状态的标准方式
* JOB_MOVED_TO_DEADLETTER是最精确的ERROR信号
* - 比JOB_EXECUTION_FAILURE准确FAILURE只是重试中的失败
* - 表示引擎已放弃重试,必须人工干预
* <p>
* 前提条件ServiceTask必须配置async="true"和R0/PT0S重试策略
*/
private void handleDeadLetter(FlowableEngineEntityEvent event) {
Object entity = event.getEntity();
if (entity instanceof Job) {
Job job = (Job) entity;
String processInstanceId = job.getProcessInstanceId();
String exceptionMessage = job.getExceptionMessage();
log.error("❌ 作业进入死信队列流程ERROR: processInstanceId={}, jobId={}, exception={}",
processInstanceId, job.getId(), exceptionMessage);
doUpdate(processInstanceId, ProcessInstanceStateEnum.ERROR.getCode());
}
}
/**
* 真正的更新数据库逻辑
* 通过Feign调用project服务更新算例状态
*/
private void doUpdate(String processInstanceId, Integer statusCode) {
if (processInstanceId == null) {
log.warn("流程实例ID为空跳过状态更新");
return;
}
log.info(">>> 更新算例状态 [{}] -> {}", processInstanceId, statusCode);
try {
simulationRunFeignClient.updateStatusByProcessInstanceId(processInstanceId, statusCode);
} catch (Exception e) {
log.error("更新算例状态失败: processInstanceId={}, status={}", processInstanceId, statusCode, e);
}
}
@Override
public boolean isFailOnException() {
// 返回false即使监听器抛异常也不影响流程继续执行
// 这是容错设计,保证业务流程的稳定性
return false;
}
@Override
public boolean isFireOnTransactionLifecycleEvent() {
// 返回false在事务提交前触发确保状态及时更新
// 如果返回true监听器会在事务提交后触发可能导致状态更新滞后
return false;
}
@Override
public String getOnTransaction() {
return null;
}
}

View File

@@ -472,11 +472,11 @@ public class ProcessService implements Iprocess{
if (isRunning) {
// --- 运行中 ---
if (hasError) {
status = ProcessInstanceStateEnum.ERROR.getCode(); // 有死信作业,视为异常
status = ProcessInstanceStateEnum.ERROR.getValue(); // 有死信作业,视为异常
} else if (isSuspended) {
status = ProcessInstanceStateEnum.SUSPENDED.getCode(); // 被挂起
status = ProcessInstanceStateEnum.SUSPENDED.getValue(); // 被挂起
} else {
status = ProcessInstanceStateEnum.RUNNING.getCode(); // 正常运行
status = ProcessInstanceStateEnum.RUNNING.getValue(); // 正常运行
}
} else {
// --- 已结束 (运行时查不到,历史表里有) ---
@@ -484,11 +484,11 @@ public class ProcessService implements Iprocess{
if (deleteReason == null) {
// 1. 正常走完结束节点deleteReason 为空
status = ProcessInstanceStateEnum.COMPLETED.getCode();
status = ProcessInstanceStateEnum.COMPLETED.getValue();
} else {
// 2. 有删除原因,说明是被取消或强制终止的
// 你可以根据 reason 的内容做更细的区分,或者统称为 cancelled
status = ProcessInstanceStateEnum.CANCELLED.getCode();
status = ProcessInstanceStateEnum.CANCELLED.getValue();
}
}
info.setStatus(status);

View File

@@ -395,4 +395,13 @@ public class SimulationRunController implements ISimulationRunFeignClient {
return runService.syncKeyResultToTask(req);
}
/**
* 根据流程实例ID更新算例状态 (内部调用)
* 由Flowable全局事件监听器调用
*/
@PostMapping("/updateStatusByProcessInstanceId")
public SdmResponse updateStatusByProcessInstanceId(@RequestParam String processInstanceId, @RequestParam Integer statusCode) {
return runService.updateStatusByProcessInstanceId(processInstanceId, statusCode);
}
}

View File

@@ -110,4 +110,12 @@ public interface ISimulationRunService extends IService<SimulationRun> {
SdmResponse syncKeyResultToTask(KeyResultReq req);
/**
* 根据流程实例ID更新算例状态
* @param processInstanceId 流程实例ID
* @param statusCode 状态值(RUNNING/SUSPENDED/COMPLETED/CANCELLED/ERROR)
* @return SdmResponse
*/
SdmResponse updateStatusByProcessInstanceId(String processInstanceId, Integer statusCode);
}

View File

@@ -623,7 +623,7 @@ public class SimulationRunServiceImpl extends ServiceImpl<SimulationRunMapper, S
runPerformance.setId(null);
runPerformance.setRunId(simulationRun.getUuid());
runPerformance.setUuid(RandomUtil.generateString(32));
runPerformance.setCompleteStatus(PerformanceStatusEnum.UNCOMPLETED.getCode());
runPerformance.setCompleteStatus(RunPerformanceStatusEnum.UNCOMPLETED.getCode());
runPerformance.setCreator(userId);
runPerformance.setCreateTime(null);
runPerformanceList.add(runPerformance);
@@ -2303,4 +2303,61 @@ public class SimulationRunServiceImpl extends ServiceImpl<SimulationRunMapper, S
}
return SdmResponse.success();
}
/**
* 根据流程实例ID更新算例状态
* 由Flowable全局事件监听器调用,实现流程状态与算例状态的实时同步
*
* @param processInstanceId 流程实例ID
* @param statusCode 状态值(RUNNING/SUSPENDED/COMPLETED/CANCELLED/ERROR)
* @return SdmResponse
*/
@Override
public SdmResponse updateStatusByProcessInstanceId(String processInstanceId, Integer statusCode) {
log.info("根据流程实例ID更新算例状态: processInstanceId={}, statusCode={}", processInstanceId, statusCode);
if (StringUtils.isBlank(processInstanceId)) {
log.error("流程实例ID为空");
return SdmResponse.failed("流程实例ID为空");
}
if (ObjectUtils.isEmpty(statusCode)) {
log.error("状态值为空");
return SdmResponse.failed("状态值为空");
}
try {
// 根据flowInstanceId查找算例
SimulationRun run = this.lambdaQuery()
.eq(SimulationRun::getFlowInstanceId, processInstanceId)
.one();
if (run == null) {
log.warn("未找到对应的算例: processInstanceId={}", processInstanceId);
return SdmResponse.failed("未找到对应的算例");
}
// 更新算例状态
boolean updated = this.lambdaUpdate()
.set(SimulationRun::getStatus, statusCode)
.set(SimulationRun::getUpdateTime, LocalDateTime.now())
.set(statusCode.equals(RunStatusEnum.COMPLETED.getCode()) || statusCode.equals(RunStatusEnum.FAILED.getCode()),
SimulationRun::getFinishTime, LocalDateTime.now())
.eq(SimulationRun::getFlowInstanceId, processInstanceId)
.update();
if (updated) {
log.info("算例状态更新成功: runId={}, flowInstanceId={}, statusCode={}",
run.getUuid(), processInstanceId, statusCode);
return SdmResponse.success("状态更新成功");
} else {
log.error("算例状态更新失败: runId={}, flowInstanceId={}", run.getUuid(), processInstanceId);
return SdmResponse.failed("状态更新失败");
}
} catch (Exception e) {
log.error("更新算例状态异常: processInstanceId={}, status={}", processInstanceId, statusCode, e);
return SdmResponse.failed("更新状态异常: " + e.getMessage());
}
}
}