feat(flow): 添加节点数据校验功能

- 新增节点校验器,支持多种节点类型数据校验
- 在保存流程数据前增加节点完整性校验
- 实现 REST、代码、条件、循环、事件、等待、周期等节点的专属校验规则
- 添加校验错误信息展示功能- 导出校验相关工具函数供其他模块使用
master
钟良源 4 months ago
parent 3627621454
commit e8ff97ecdc

@ -41,6 +41,8 @@ export const getNodeEditorByType = (nodeType: string, localNodeType?: string) =>
return nodeEditors[nodeType] || nodeEditors['basic'];
};
export * from './validators';
export default {
nodeEditors,
registerNodeEditor,

@ -0,0 +1,383 @@
import { Message } from '@arco-design/web-react';
export interface ValidationResult {
isValid: boolean;
errors: string[];
}
/**
*
* @param nodeData
* @param nodeType
* @returns
*/
export const validateNodeData = (nodeData: any, nodeType: string): ValidationResult => {
const errors: string[] = [];
// 检查基本字段
if (!nodeData.title) {
errors.push('节点标题不能为空');
}
// 根据不同节点类型进行特定校验
switch (nodeType) {
case 'REST':
errors.push(...validateRestNode(nodeData));
break;
case 'CODE':
errors.push(...validateCodeNode(nodeData));
break;
case 'SWITCH':
errors.push(...validateSwitchNode(nodeData));
break;
case 'LOOP_START':
case 'LOOP_END':
errors.push(...validateLoopNode(nodeData, nodeType));
break;
case 'EVENTSEND':
case 'EVENTLISTENE':
errors.push(...validateEventNode(nodeData));
break;
case 'WAIT':
errors.push(...validateWaitNode(nodeData));
break;
case 'CYCLE':
errors.push(...validateCycleNode(nodeData));
break;
default:
// 对于其他节点类型,检查基本参数
errors.push(...validateBasicParams(nodeData));
break;
}
return {
isValid: errors.length === 0,
errors
};
};
/**
* REST
* @param nodeData
* @returns
*/
const validateRestNode = (nodeData: any): string[] => {
const errors: string[] = [];
if (!nodeData.component || !nodeData.component.customDef) {
errors.push('REST节点配置信息不完整');
return errors;
}
let customDef;
try {
customDef = typeof nodeData.component.customDef === 'string'
? JSON.parse(nodeData.component.customDef)
: nodeData.component.customDef;
} catch (e) {
errors.push('REST节点配置信息格式错误');
return errors;
}
if (!customDef.method) {
errors.push('请求方法不能为空');
}
if (!customDef.url) {
errors.push('URL地址不能为空');
}
else {
// 基本的 URL 格式校验
const urlPattern = /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;
if (!urlPattern.test(customDef.url)) {
errors.push('URL地址格式不正确');
}
}
return errors;
};
/**
*
* @param nodeData
* @returns
*/
const validateCodeNode = (nodeData: any): string[] => {
const errors: string[] = [];
if (!nodeData.component || !nodeData.component.customDef) {
errors.push('代码节点配置信息不完整');
return errors;
}
let customDef;
try {
customDef = typeof nodeData.component.customDef === 'string'
? JSON.parse(nodeData.component.customDef)
: nodeData.component.customDef;
} catch (e) {
errors.push('代码节点配置信息格式错误');
return errors;
}
if (!customDef.sourceCode) {
errors.push('代码内容不能为空');
}
return errors;
};
/**
*
* @param nodeData
* @returns
*/
const validateSwitchNode = (nodeData: any): string[] => {
const errors: string[] = [];
// 检查条件表达式
if (!nodeData.component || !nodeData.component.customDef) {
errors.push('条件节点配置信息不完整');
return errors;
}
let customDef;
try {
customDef = typeof nodeData.component.customDef === 'string'
? JSON.parse(nodeData.component.customDef)
: nodeData.component.customDef;
} catch (e) {
errors.push('条件节点配置信息格式错误');
return errors;
}
// 检查是否有条件表达式
if (!customDef.conditions || customDef.conditions.length === 0) {
errors.push('请至少添加一个条件表达式');
}
// 检查每个条件表达式的完整性
if (customDef.conditions && customDef.conditions.length > 0) {
customDef.conditions.forEach((condition: any, index: number) => {
if (!condition.apiOutId) {
errors.push(`${index + 1}个条件表达式的逻辑出口不能为空`);
}
if (!condition.expression) {
errors.push(`${index + 1}个条件表达式的表达式不能为空`);
}
});
}
return errors;
};
/**
*
* @param nodeData
* @param nodeType
* @returns
*/
const validateLoopNode = (nodeData: any, nodeType: string): string[] => {
// LOOP_START 类型节点不需要校验
if (nodeType === 'LOOP_START') {
return [];
}
const errors: string[] = [];
// 检查循环条件(仅针对 LOOP_END 节点)
if (!nodeData.component || !nodeData.component.customDef) {
errors.push('循环节点配置信息不完整');
return errors;
}
let customDef;
try {
customDef = typeof nodeData.component.customDef === 'string'
? JSON.parse(nodeData.component.customDef)
: nodeData.component.customDef;
} catch (e) {
errors.push('循环节点配置信息格式错误');
return errors;
}
// 检查循环条件
if (!customDef.conditions || customDef.conditions.length === 0) {
errors.push('请至少添加一个循环条件');
}
return errors;
};
/**
*
* @param nodeData
* @returns
*/
const validateEventNode = (nodeData: any): string[] => {
const errors: string[] = [];
if (!nodeData.component || !nodeData.component.customDef) {
errors.push('事件节点配置信息不完整');
return errors;
}
let customDef;
try {
customDef = typeof nodeData.component.customDef === 'string'
? JSON.parse(nodeData.component.customDef)
: nodeData.component.customDef;
} catch (e) {
errors.push('事件节点配置信息格式错误');
return errors;
}
if (!customDef.eventId) {
errors.push('请选择事件');
}
return errors;
};
/**
*
* @param nodeData
* @returns
*/
const validateWaitNode = (nodeData: any): string[] => {
const errors: string[] = [];
if (!nodeData.component || !nodeData.component.customDef) {
errors.push('等待节点配置信息不完整');
return errors;
}
let customDef;
try {
customDef = typeof nodeData.component.customDef === 'string'
? JSON.parse(nodeData.component.customDef)
: nodeData.component.customDef;
} catch (e) {
errors.push('等待节点配置信息格式错误');
return errors;
}
// 检查等待时间是否设置
if (customDef.duration === undefined || customDef.duration === null) {
errors.push('请设置等待时间');
}
else if (typeof customDef.duration !== 'number' || customDef.duration < 0) {
errors.push('等待时间必须是非负数');
}
return errors;
};
/**
*
* @param nodeData
* @returns
*/
const validateCycleNode = (nodeData: any): string[] => {
const errors: string[] = [];
if (!nodeData.component || !nodeData.component.customDef) {
errors.push('周期节点配置信息不完整');
return errors;
}
let customDef;
try {
customDef = typeof nodeData.component.customDef === 'string'
? JSON.parse(nodeData.component.customDef)
: nodeData.component.customDef;
} catch (e) {
errors.push('周期节点配置信息格式错误');
return errors;
}
// 检查 Cron 表达式是否设置
if (!customDef.intervalSeconds) {
errors.push('请设置 Cron 表达式');
}
return errors;
};
/**
*
* @param nodeData
* @returns
*/
const validateBasicParams = (nodeData: any): string[] => {
const errors: string[] = [];
// 检查输入参数的完整性
if (nodeData.parameters?.dataIns) {
nodeData.parameters.dataIns.forEach((param: any, index: number) => {
if (!param.id) {
errors.push(`${index + 1}个输入参数的标识不能为空`);
}
if (!param.dataType) {
errors.push(`${index + 1}个输入参数的数据类型不能为空`);
}
});
}
// 检查输出参数的完整性
if (nodeData.parameters?.dataOuts) {
nodeData.parameters.dataOuts.forEach((param: any, index: number) => {
if (!param.id) {
errors.push(`${index + 1}个输出参数的标识不能为空`);
}
if (!param.dataType) {
errors.push(`${index + 1}个输出参数的数据类型不能为空`);
}
});
}
return errors;
};
/**
*
* @param nodes
* @returns
*/
export const validateAllNodes = (nodes: any[]): ValidationResult => {
const allErrors: string[] = [];
nodes.forEach((node, index) => {
const nodeType = node.data?.type || node.type;
const nodeName = node.data?.title || `节点${index + 1}`;
const result = validateNodeData(node.data, nodeType);
if (!result.isValid) {
result.errors.forEach(error => {
allErrors.push(`[${nodeName}] ${error}`);
});
}
});
return {
isValid: allErrors.length === 0,
errors: allErrors
};
};
/**
*
* @param errors
*/
export const showValidationErrors = (errors: string[]) => {
if (errors.length > 0) {
// 创建错误信息内容
let content = '存在以下问题需要修正:\n';
content += errors.map((error, index) => `${index + 1}. ${error}`).join('\n');
Message.error({
content: content,
duration: 5000
});
}
};

@ -24,6 +24,7 @@ import ImageNode from '@/components/FlowEditor/node/imageNode/ImageNode';
import CodeNode from '@/components/FlowEditor/node/codeNode/CodeNode';
import RestNode from '@/components/FlowEditor/node/restNode/RestNode';
import { updateCanvasDataMap } from '@/store/ideContainer';
import { validateAllNodes, showValidationErrors } from '@/components/FlowEditor/nodeEditors/validators/nodeValidators';
import { Dispatch } from 'redux';
@ -214,8 +215,6 @@ export const useFlowCallbacks = (
const sourceParams = sourceNode.data?.parameters || {};
const targetParams = targetNode.data?.parameters || {};
console.log('params:', params, sourceParams, targetParams);
// 获取源handle和目标handle的类型 (api或data)
const sourceHandleType = getHandleType(params.sourceHandle, sourceParams);
const targetHandleType = getHandleType(params.targetHandle, targetParams);
@ -782,6 +781,13 @@ export const useFlowCallbacks = (
// 保存所有节点和边数据到服务器
const saveFlowDataToServer = useCallback(async () => {
try {
// 首先校验所有节点数据是否完整
const validation = validateAllNodes(nodes);
if (!validation.isValid) {
showValidationErrors(validation.errors);
return;
}
// 转换会原始数据类型
const revertedData = revertFlowData(nodes, edges);
console.log('revertedData(中断):', revertedData);

Loading…
Cancel
Save