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