fix(flow): 解决非机器人节点携带机器人执行目标导致的后端误判问题

- 非机器人节点不再携带 executionTarget 属性,避免后端将空对象误判为无效角色引用
- 添加 FLOW_NODE_RESOURCE_OWNER 和 USER_RESOURCE_OWNER 常量用于资源归属管理
- 为流程节点创建的设备插槽添加 managedBy 字段标识资源归属
- 移除未被任何节点引用的自动注册角色定义和设备使用
- 为用户手动创建的角色和设备插槽标记为 USER_RESOURCE_OWNER 归属
- 在节点更新时自动清理未使用的节点资源,优化资源管理
This commit is contained in:
lixiaolong 2026-08-11 17:16:27 +08:00
parent bbc937ce94
commit e3a2761683
4 changed files with 80 additions and 6 deletions

View File

@ -2,6 +2,8 @@ import { getDeviceKindForAction } from '@/utils/robotDevice'
export const FLOW_SCHEMA_VERSION = 2
export const DEFAULT_ROLE_KEY = 'default_executor'
export const FLOW_NODE_RESOURCE_OWNER = 'FLOW_NODE'
export const USER_RESOURCE_OWNER = 'USER'
const DEVICE_KIND_META = Object.freeze({
1: { key: 'agv', name: '移动底盘' },
@ -75,6 +77,7 @@ const findOrCreateLegacySlot = (role, node, legacySlotByIdentity, legacyDeviceId
if (legacySlotByIdentity.has(identity)) return legacySlotByIdentity.get(identity)
const slot = createDeviceSlot(kind, role.deviceSlots)
slot.managedBy = FLOW_NODE_RESOURCE_OWNER
if (normalizedDeviceId) slot.legacyDeviceId = normalizedDeviceId
role.deviceSlots.push(slot)
legacySlotByIdentity.set(identity, slot)
@ -117,8 +120,11 @@ export const normalizeFlowResourceSchema = (sourceGraph) => {
}))
graph.nodes.forEach((node) => {
if (!isRobotNode(node)) return
const properties = node.properties || (node.properties = {})
if (!isRobotNode(node)) {
delete properties.executionTarget
return
}
const target = properties.executionTarget || {
roleKey: defaultRole.roleKey,
dispatchPolicy: 'ONE',
@ -133,6 +139,10 @@ export const normalizeFlowResourceSchema = (sourceGraph) => {
const slot = findOrCreateLegacySlot(targetRole, node, legacySlotByIdentity, deviceParam?.input)
if (slot) target.deviceSlotKey = slot.slotKey
}
const referencedSlot = targetRole.deviceSlots.find((slot) => slot.slotKey === target.deviceSlotKey)
if (referencedSlot && !referencedSlot.managedBy) {
referencedSlot.managedBy = FLOW_NODE_RESOURCE_OWNER
}
delete target.deviceBindings
properties.nodeParams = (properties.nodeParams || []).filter((param) => param.name !== 'deviceId')
properties.executionTarget = target
@ -147,6 +157,53 @@ export const ensureNodeExecutionResources = (sourceGraph, nodeId) => {
return { graph, node, start: getStartNode(graph) }
}
/**
* Removes role definitions and device usages that were registered automatically
* for flow nodes and are no longer referenced by any remaining node.
*/
export const pruneUnusedNodeResources = (sourceGraph) => {
const graph = clone(sourceGraph || { nodes: [], edges: [] })
const start = getStartNode(graph)
if (!start?.properties?.resourceRoles) {
return { graph, start, removedRoles: 0, removedSlots: 0 }
}
const usedRoleKeys = new Set()
const usedSlotsByRole = new Map()
graph.nodes.filter(isRobotNode).forEach((node) => {
const target = node.properties?.executionTarget
if (!target?.roleKey) return
usedRoleKeys.add(target.roleKey)
if (!target.deviceSlotKey) return
if (!usedSlotsByRole.has(target.roleKey)) usedSlotsByRole.set(target.roleKey, new Set())
usedSlotsByRole.get(target.roleKey).add(target.deviceSlotKey)
})
let removedSlots = 0
start.properties.resourceRoles.forEach((role) => {
const usedSlotKeys = usedSlotsByRole.get(role.roleKey) || new Set()
role.deviceSlots = (role.deviceSlots || []).filter((slot) => {
const shouldRemove = slot.managedBy === FLOW_NODE_RESOURCE_OWNER && !usedSlotKeys.has(slot.slotKey)
if (shouldRemove) removedSlots += 1
return !shouldRemove
})
})
const beforeRoleCount = start.properties.resourceRoles.length
start.properties.resourceRoles = start.properties.resourceRoles.filter((role, index) => (
index === 0
|| role.managedBy !== FLOW_NODE_RESOURCE_OWNER
|| usedRoleKeys.has(role.roleKey)
))
return {
graph,
start,
removedRoles: beforeRoleCount - start.properties.resourceRoles.length,
removedSlots,
}
}
export const validateFlowResources = (sourceGraph) => {
const graph = normalizeFlowResourceSchema(sourceGraph)
const roles = getResourceRoles(graph)
@ -212,8 +269,12 @@ export const mergeCopiedResourceRoles = (targetGraph, sourceRoles = [], copiedNo
if (!targetRole && sourceRole) {
targetRole = clone(sourceRole)
targetRole.managedBy = FLOW_NODE_RESOURCE_OWNER
delete targetRole.legacyRobotId
;(targetRole.deviceSlots || []).forEach((slot) => delete slot.legacyDeviceId)
;(targetRole.deviceSlots || []).forEach((slot) => {
slot.managedBy = FLOW_NODE_RESOURCE_OWNER
delete slot.legacyDeviceId
})
targetRoles.push(targetRole)
addedRoles += 1
addedSlots += targetRole.deviceSlots?.length || 0
@ -231,6 +292,7 @@ export const mergeCopiedResourceRoles = (targetGraph, sourceRoles = [], copiedNo
return
}
const newSlot = sourceSlot ? clone(sourceSlot) : createDeviceSlot(getDeviceKindForAction(node.properties?.action), targetRole.deviceSlots)
newSlot.managedBy = FLOW_NODE_RESOURCE_OWNER
delete newSlot.legacyDeviceId
targetRole.deviceSlots.push(newSlot)
target.deviceSlotKey = newSlot.slotKey

View File

@ -237,11 +237,13 @@ const validateAndSave = async () => {
ElMessage.warning('请选择该节点使用的设备用途')
}
// 保存到 lf
lf.setProperties(props.data.id, {
// 非机器人节点不应携带机器人执行目标,避免后端将空对象误判为无效角色引用。
const properties = {
...props.data.properties,
...formData
})
}
if (!isRobotNode.value) delete properties.executionTarget
lf.setProperties(props.data.id, properties)
if (inputValid && outputValid) {
emit('save-success')

View File

@ -69,6 +69,7 @@ import {
createDeviceSlot,
createResourceKey,
normalizeFlowResourceSchema,
USER_RESOURCE_OWNER,
} from '@/utils/flowResources'
const props = defineProps({ data: Object })
@ -93,11 +94,15 @@ const addRole = () => {
...createDefaultRobotRole(),
roleKey: createResourceKey('robot_role'),
displayName: `机器人角色${formData.resourceRoles.length + 1}`,
managedBy: USER_RESOURCE_OWNER,
})
}
const addDeviceSlot = (role) => {
role.deviceSlots.push(createDeviceSlot(null, role.deviceSlots))
role.deviceSlots.push({
...createDeviceSlot(null, role.deviceSlots),
managedBy: USER_RESOURCE_OWNER,
})
}
const referencedNodes = (predicate) => globalThis.lf.getGraphData().nodes.filter((node) => predicate(node.properties?.executionTarget))

View File

@ -275,6 +275,7 @@ import {
getResourceRoles,
mergeCopiedResourceRoles,
normalizeFlowResourceSchema,
pruneUnusedNodeResources,
validateFlowResources,
} from "@/utils/flowResources";
import {
@ -1702,6 +1703,10 @@ onMounted(() => {
if (index !== -1) {
nodeErrorList.value.splice(index, 1);
}
const cleanup = pruneUnusedNodeResources(lf.getGraphData());
if (cleanup.start && (cleanup.removedRoles || cleanup.removedSlots)) {
lf.setProperties(cleanup.start.id, cleanup.start.properties);
}
})
});