feat(flow): 添加节点数据校验功能
- 新增节点校验器,支持多种节点类型数据校验 - 在保存流程数据前增加节点完整性校验 - 实现 REST、代码、条件、循环、事件、等待、周期等节点的专属校验规则 - 添加校验错误信息展示功能- 导出校验相关工具函数供其他模块使用master
parent
3627621454
commit
e8ff97ecdc
@ -0,0 +1 @@
|
||||
export * from './nodeValidators';
|
||||
@ -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
|
||||
});
|
||||
}
|
||||
};
|
||||
Loading…
Reference in New Issue