feat: 优化试运行结果轮询逻辑,确保实例归属有效性并处理异常情况
This commit is contained in:
parent
be3c8165aa
commit
fb207a4ff1
@ -977,7 +977,10 @@ let flowPollTimer = null;
|
||||
let flowDisposed = false;
|
||||
|
||||
/**
|
||||
* 清理 clearFlowPollTimer 对应的数据或交互;作用范围仅限当前组件或模块。
|
||||
* 清理当前试运行结果轮询定时器。
|
||||
*
|
||||
* 实现思路:每次重新调度、终止流程或销毁页面前都先清除旧定时器,确保同一时刻
|
||||
* 只有一条轮询链继续执行,避免多个请求竞争修改流程状态。
|
||||
*/
|
||||
const clearFlowPollTimer = () => {
|
||||
if (flowPollTimer !== null) {
|
||||
@ -987,16 +990,36 @@ const clearFlowPollTimer = () => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 调度 scheduleFlowView 对应的数据或交互;作用范围仅限当前组件或模块。
|
||||
* @param {*} currentInstId 当前流程实例 ID
|
||||
* @param {*} taskId 任务 ID
|
||||
* @param {*} delay 延迟毫秒数
|
||||
* 判断轮询结果是否仍属于当前页面正在关注的流程实例。
|
||||
*
|
||||
* 实现思路:普通试运行只接受当前 instId 的响应,防止上一次请求较晚返回后覆盖
|
||||
* 新一次试运行状态;日志模式是指定任务的一次性查询,不受当前试运行实例限制。
|
||||
*
|
||||
* @param {string | number | null} currentInstId 本次轮询对应的流程实例 ID
|
||||
* @returns {boolean} 页面仍有效且允许处理该实例结果时返回 true
|
||||
*/
|
||||
const isCurrentFlowPoll = (currentInstId) => {
|
||||
if (flowDisposed) return false;
|
||||
if (flowInfoData.value.isLog) return true;
|
||||
return String(currentInstId ?? "") === String(instId.value ?? "");
|
||||
};
|
||||
|
||||
/**
|
||||
* 调度下一次试运行结果查询。
|
||||
*
|
||||
* 实现思路:调度前再次校验实例归属,并替换已有定时器。这样接口临时异常时可以
|
||||
* 继续等待最终状态,同时旧实例或已销毁页面不会重新启动轮询。
|
||||
*
|
||||
* @param {string | number | null} currentInstId 当前流程实例 ID
|
||||
* @param {string | number | null} taskId 指定的任务 ID
|
||||
* @param {number} delay 轮询间隔,单位毫秒
|
||||
*/
|
||||
const scheduleFlowView = (currentInstId, taskId = null, delay = 2000) => {
|
||||
if (!isCurrentFlowPoll(currentInstId)) return;
|
||||
clearFlowPollTimer();
|
||||
flowPollTimer = window.setTimeout(() => {
|
||||
flowPollTimer = null;
|
||||
if (!flowDisposed) loopFlowView(currentInstId, taskId);
|
||||
if (isCurrentFlowPoll(currentInstId)) loopFlowView(currentInstId, taskId);
|
||||
}, delay);
|
||||
};
|
||||
|
||||
@ -1448,22 +1471,84 @@ const emitLoopContainerOutputs = (logs) => {
|
||||
});
|
||||
};
|
||||
|
||||
const loopFlowView = async (instId, taskId = null) => {
|
||||
// 轮询思路:优先使用任务 ID 查询指定执行,否则查询实例最新状态,再统一映射节点状态。
|
||||
// 仅在非日志模式下根据终态停止或续排定时器,日志回放保持为一次性快照。
|
||||
/**
|
||||
* 判断执行日志是否属于主流程层级。
|
||||
*
|
||||
* 实现思路:兼容后端可能返回的 null、空数组、空对象及其 JSON 字符串形式;非空
|
||||
* iteration 表示循环或子流程内部执行,不能用来判定整个试运行已经结束。
|
||||
*
|
||||
* @param {*} iteration 执行日志携带的迭代层级
|
||||
* @returns {boolean} 日志属于主流程层级时返回 true
|
||||
*/
|
||||
const isMainFlowIteration = (iteration) => {
|
||||
if (iteration == null) return true;
|
||||
if (Array.isArray(iteration)) return iteration.length === 0;
|
||||
if (typeof iteration === "object") return Object.keys(iteration).length === 0;
|
||||
if (typeof iteration !== "string") return false;
|
||||
return ["", "[]", "{}", "null"].includes(iteration.trim().toLowerCase());
|
||||
};
|
||||
|
||||
/**
|
||||
* 从执行日志中查找主流程结束节点的最新记录。
|
||||
*
|
||||
* 实现思路:优先兼容运行日志的 nodeType/action 标识,同时使用当前画布结束节点 ID
|
||||
* 兜底,避免后端字段缺失或格式差异导致成功状态无法同步到页面。
|
||||
*
|
||||
* @param {Array} logs 当前流程实例的执行日志
|
||||
* @returns {Object | undefined} 主流程结束节点的最新日志,不存在时返回 undefined
|
||||
*/
|
||||
const findMainFlowEndLog = (logs) => {
|
||||
const endNodeIds = new Set(
|
||||
(lf.getGraphData().nodes || [])
|
||||
.filter((node) => String(node?.type || "").toLowerCase() === "end")
|
||||
.map((node) => String(node.id)),
|
||||
);
|
||||
return [...logs].reverse().find((item) => {
|
||||
const nodeType = String(item?.nodeType || "").toUpperCase();
|
||||
const action = String(item?.action || "").toUpperCase();
|
||||
const isEndNode = nodeType === "END" || action === "END" || endNodeIds.has(String(item?.nodeId || ""));
|
||||
return isEndNode && isMainFlowIteration(item?.iteration);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询并同步试运行结果,直到当前实例进入成功、失败、暂停或终止状态。
|
||||
*
|
||||
* 实现思路:每次响应先校验实例归属,再更新节点运行态;只有主流程结束节点成功时
|
||||
* 才开放发布。请求异常或业务响应暂不可用时继续轮询,避免一次临时失败让页面永久
|
||||
* 停留在“试运行中”;日志回放仍保持一次性查询。
|
||||
*
|
||||
* @param {string | number | null} currentInstId 当前流程实例 ID
|
||||
* @param {string | number | null} taskId 指定的任务 ID
|
||||
* @returns {Promise<void>} 状态同步完成后结束
|
||||
*/
|
||||
const loopFlowView = async (currentInstId, taskId = null) => {
|
||||
const params = {
|
||||
itemId: flowInfoData.value.itemId,
|
||||
instId,
|
||||
instId: currentInstId,
|
||||
};
|
||||
if (taskId) {
|
||||
params.taskId = taskId;
|
||||
}
|
||||
const res = await flowView(params);
|
||||
if (flowDisposed) return;
|
||||
if (res.code === 200) {
|
||||
let res;
|
||||
try {
|
||||
res = await flowView(params);
|
||||
} catch {
|
||||
// 查询异常不代表试运行失败;保留当前节点结果并继续查询,直到拿到后端明确终态。
|
||||
if (!flowInfoData.value.isLog && isCurrentFlowPoll(currentInstId)) {
|
||||
scheduleFlowView(currentInstId, taskId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!isCurrentFlowPoll(currentInstId)) return;
|
||||
if (res.code !== 200) {
|
||||
if (!flowInfoData.value.isLog) scheduleFlowView(currentInstId, taskId);
|
||||
return;
|
||||
}
|
||||
|
||||
const arr = res.data || [];
|
||||
if (arr.length === 0) {
|
||||
if (!flowInfoData.value.isLog) scheduleFlowView(instId, taskId);
|
||||
if (!flowInfoData.value.isLog) scheduleFlowView(currentInstId, taskId);
|
||||
return;
|
||||
}
|
||||
if (arr.length > 0) hasRunResult.value = true;
|
||||
@ -1485,13 +1570,7 @@ const loopFlowView = async (instId, taskId = null) => {
|
||||
oldFlowData = JSON.parse(JSON.stringify(lf.getGraphData()));
|
||||
const first = arr[0];
|
||||
const reversed = [...arr].reverse();
|
||||
const mainEnd = reversed.find(
|
||||
(item) =>
|
||||
String(item?.nodeType || "").toUpperCase() === "END" &&
|
||||
(item?.iteration == null ||
|
||||
item.iteration === "" ||
|
||||
(Array.isArray(item.iteration) && item.iteration.length === 0)),
|
||||
);
|
||||
const mainEnd = findMainFlowEndLog(arr);
|
||||
const failed = reversed.find(
|
||||
(item) => String(item?.status || "").toUpperCase() === "FAILED",
|
||||
);
|
||||
@ -1516,8 +1595,7 @@ const loopFlowView = async (instId, taskId = null) => {
|
||||
}
|
||||
} else if (!flowInfoData.value.isLog) {
|
||||
// END is first persisted as RUNNING and updated to SUCCESS after the handler returns.
|
||||
scheduleFlowView(instId, taskId);
|
||||
}
|
||||
scheduleFlowView(currentInstId, taskId);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user