增加了动态生成跟节点验证。

This commit is contained in:
戚辰先生 2024-12-06 21:13:45 +08:00
parent 472c9862c4
commit 6e45e3b502
4 changed files with 393 additions and 189 deletions

View File

@ -23,6 +23,8 @@
"@logicflow/core": "^2.0.9",
"@logicflow/extension": "^2.0.13",
"@reduxjs/toolkit": "^2.0.1",
"ajv": "^8.17.1",
"ajv-formats": "^3.0.1",
"antd": "^5.22.2",
"axios": "^1.6.2",
"dagre": "^0.8.5",
@ -1149,6 +1151,22 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/@eslint/eslintrc/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -1174,6 +1192,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"node_modules/@eslint/eslintrc/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz",
@ -2343,27 +2367,34 @@
}
},
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"version": "8.17.1",
"resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"peer": true,
"node_modules/ajv-formats": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/ajv-formats/-/ajv-formats-3.0.1.tgz",
"integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
"dependencies": {
"ajv": "^8.0.0"
},
"peerDependencies": {
"ajv": "^6.9.1"
"ajv": "^8.0.0"
},
"peerDependenciesMeta": {
"ajv": {
"optional": true
}
}
},
"node_modules/ansi-regex": {
@ -3197,6 +3228,22 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/eslint/node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -3222,6 +3269,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/eslint/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"node_modules/eslint/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz",
@ -3344,6 +3397,11 @@
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true
},
"node_modules/fast-uri": {
"version": "3.0.3",
"resolved": "https://registry.npmmirror.com/fast-uri/-/fast-uri-3.0.3.tgz",
"integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw=="
},
"node_modules/fastq": {
"version": "1.17.1",
"resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.17.1.tgz",
@ -3956,9 +4014,9 @@
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
},
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
},
"node_modules/json-stable-stringify-without-jsonify": {
"version": "1.0.1",
@ -5452,6 +5510,14 @@
"resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
},
"node_modules/require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/reselect": {
"version": "5.1.1",
"resolved": "https://registry.npmmirror.com/reselect/-/reselect-5.1.1.tgz",
@ -5635,6 +5701,37 @@
"url": "https://opencollective.com/webpack"
}
},
"node_modules/schema-utils/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/schema-utils/node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"peer": true,
"peerDependencies": {
"ajv": "^6.9.1"
}
},
"node_modules/schema-utils/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"peer": true
},
"node_modules/screenfull": {
"version": "5.2.0",
"resolved": "https://registry.npmmirror.com/screenfull/-/screenfull-5.2.0.tgz",

View File

@ -25,6 +25,8 @@
"@logicflow/core": "^2.0.9",
"@logicflow/extension": "^2.0.13",
"@reduxjs/toolkit": "^2.0.1",
"ajv": "^8.17.1",
"ajv-formats": "^3.0.1",
"antd": "^5.22.2",
"axios": "^1.6.2",
"dagre": "^0.8.5",

View File

@ -1,7 +1,9 @@
import React, { useMemo, useEffect } from 'react';
import { Form, Input, Select, InputNumber, Switch, Divider } from 'antd';
import React, { useMemo } from 'react';
import { Form, Input, Select, InputNumber, Divider, Tooltip } from 'antd';
import { InfoCircleOutlined } from '@ant-design/icons';
import type { Rule } from 'antd/es/form';
import { NodeType } from '../../../../types';
import { validateNodeConfig } from './validate';
interface NodeConfigProps {
nodeType: NodeType;
@ -9,42 +11,80 @@ interface NodeConfigProps {
onValuesChange?: (changedValues: any, allValues: any) => void;
}
interface FormFields {
[key: string]: any;
interface JsonSchema {
type: string;
title?: string;
description?: string;
properties?: Record<string, JsonSchema>;
required?: string[];
minimum?: number;
maximum?: number;
minLength?: number;
maxLength?: number;
format?: string;
default?: any;
enum?: any[];
enumNames?: string[];
additionalProperties?: JsonSchema;
}
// 处理JSON字符串中的转义字符
const parseJsonSafely = (jsonString: string) => {
const parseJsonSafely = (jsonString: string): any => {
if (!jsonString) return {};
try {
// 处理Windows风格的换行符
const processed = jsonString.replace(/\r\n/g, '\n')
// 处理转义字符
.replace(/\\/g, '\\\\')
// 处理引号
.replace(/\\"/g, '\\"');
return JSON.parse(processed);
// 如果已经是对象,直接返回
if (typeof jsonString === 'object') {
return jsonString;
}
// 打印原始字符串内容,帮助调试
console.log('原始 JSON 字符串:', jsonString);
// 尝试直接解析
try {
return JSON.parse(jsonString);
} catch (e) {
console.log('直接解析失败,尝试预处理...');
}
// 预处理字符串
let processedString = jsonString;
// 1. 处理换行符
processedString = processedString.replace(/[\r\n]+/g, ' ');
// 2. 处理转义字符
processedString = processedString.replace(/\\/g, '\\\\')
.replace(/\\\\"/g, '\\"')
.replace(/\\\\n/g, '\\n')
.replace(/\\\\r/g, '\\r')
.replace(/\\\\t/g, '\\t');
// 3. 确保属性名称正确引用
processedString = processedString.replace(/(['"])?([a-zA-Z0-9_]+)(['"])?:/g, '"$2":');
console.log('处理后的 JSON 字符串:', processedString);
return JSON.parse(processedString);
} catch (error) {
console.error('JSON解析错误:', error);
console.debug('原始JSON字符串:', jsonString);
return null;
console.error('解析失败的字符串:', jsonString);
// 返回空对象而不是 null避免后续操作出错
return {};
}
};
const NodeConfig: React.FC<NodeConfigProps> = ({ nodeType, form, onValuesChange }) => {
// 解析节点配置模式
const nodeSchema = useMemo(() => {
if (!nodeType.configSchema) {
console.warn('节点配置模式为空');
return null;
}
if (!nodeType.configSchema) return null;
return parseJsonSafely(nodeType.configSchema);
}, [nodeType.configSchema]);
// 解析节点默认配置
const nodeDefaultConfig = useMemo(() => {
if (!nodeType.defaultConfig) return {};
const config = parseJsonSafely(nodeType.defaultConfig);
return config || {};
return parseJsonSafely(nodeType.defaultConfig) || {};
}, [nodeType.defaultConfig]);
// 当前选中的执行器
@ -54,160 +94,145 @@ const NodeConfig: React.FC<NodeConfigProps> = ({ nodeType, form, onValuesChange
const executorSchema = useMemo(() => {
if (!selectedExecutor || !nodeType.executors) return null;
const executor = nodeType.executors.find(e => e.code === selectedExecutor);
if (!executor || !executor.configSchema) return null;
if (!executor?.configSchema) return null;
return parseJsonSafely(executor.configSchema);
}, [selectedExecutor, nodeType.executors]);
// 获取当前执行器的默认配置
const executorDefaultConfig = useMemo(() => {
if (!selectedExecutor || !nodeType.executors) return {};
const executor = nodeType.executors.find(e => e.code === selectedExecutor);
if (!executor || !executor.defaultConfig) return {};
const config = parseJsonSafely(executor.defaultConfig);
return config || {};
}, [selectedExecutor, nodeType.executors]);
// 渲染基本配置表单项
const renderBasicFormItems = () => (
<>
<Form.Item
label="节点名称"
name="name"
rules={[{ required: true, message: '请输入节点名称' }]}
>
<Input placeholder="请输入节点名称" />
</Form.Item>
<Form.Item
label="节点描述"
name="description"
>
<Input.TextArea placeholder="请输入节点描述" />
</Form.Item>
</>
);
// 渲染网关节点配置
const renderGatewayConfig = () => {
if (!nodeType.code?.includes('GATEWAY')) return null;
return (
<Form.Item
label="网关类型"
name="type"
rules={[{ required: true, message: '请选择网关类型' }]}
>
<Select
placeholder="请选择网关类型"
options={[
{ label: '并行网关', value: 'PARALLEL' },
{ label: '排他网关', value: 'EXCLUSIVE' },
{ label: '包容网关', value: 'INCLUSIVE' }
]}
/>
</Form.Item>
);
};
// 渲染定时器节点配置
const renderTimerConfig = () => {
if (!nodeType.code?.includes('TIMER')) return null;
// 从schema中获取cron字段的配置
const cronField = nodeSchema?.properties?.cron;
if (!cronField) return null;
return (
<Form.Item
label={cronField.title || "CRON表达式"}
name="cron"
tooltip={cronField.description}
rules={[
{
required: nodeSchema?.required?.includes('cron'),
message: '请输入CRON表达式'
},
...(cronField.pattern ? [{
pattern: new RegExp(cronField.pattern),
message: '请输入有效的CRON表达式'
}] : [])
]}
>
<Input placeholder={`例如: ${cronField.examples?.[0] || ''}`} />
</Form.Item>
);
};
// 渲染Shell执行器特定配置
const renderShellExecutorConfig = () => {
if (!nodeType.code?.includes('SHELL')) return null;
// 验证配置
const validateField = async (_: any, value: any) => {
const allValues = form.getFieldsValue();
const validationResult = validateNodeConfig(nodeType, allValues);
return (
<>
<Form.Item
label="执行器"
name="executor"
rules={[{ required: true, message: '请选择执行器' }]}
>
<Select
placeholder="请选择执行器"
options={nodeType.executors?.map(executor => ({
label: executor.name,
value: executor.code
}))}
/>
</Form.Item>
if (!validationResult.valid) {
const fieldErrors = validationResult.errors.filter(error =>
error.toLowerCase().includes(_?.field?.toLowerCase() || '')
);
if (fieldErrors.length > 0) {
throw new Error(fieldErrors.join('; '));
}
}
return Promise.resolve();
};
{selectedExecutor === 'SHELL' && (
<>
<Form.Item
label="脚本内容"
name="script"
rules={[{ required: true, message: '请输入脚本内容' }]}
>
<Input.TextArea rows={6} placeholder="请输入脚本内容" />
// 根据schema生成表单验证规则
const generateRules = (schema: JsonSchema, fieldName: string): Rule[] => {
const rules: Rule[] = [];
if (schema.required?.includes(fieldName)) {
rules.push({ required: true, message: `请输入${schema.title || fieldName}` });
}
if (schema.type === 'string') {
if (schema.minLength !== undefined) {
rules.push({ min: schema.minLength, message: `${schema.title || fieldName}最少${schema.minLength}个字符` });
}
if (schema.maxLength !== undefined) {
rules.push({ max: schema.maxLength, message: `${schema.title || fieldName}最多${schema.maxLength}个字符` });
}
}
if (schema.type === 'number') {
if (schema.minimum !== undefined) {
rules.push({ type: 'number', min: schema.minimum, message: `${schema.title || fieldName}不能小于${schema.minimum}` });
}
if (schema.maximum !== undefined) {
rules.push({ type: 'number', max: schema.maximum, message: `${schema.title || fieldName}不能大于${schema.maximum}` });
}
}
rules.push({ validator: validateField });
return rules;
};
// 根据schema生成表单项
const renderFormItem = (fieldName: string, fieldSchema: JsonSchema) => {
const rules = generateRules(fieldSchema, fieldName);
const commonProps = {
label: (
<span>
{fieldSchema.title || fieldName}
{fieldSchema.description && (
<Tooltip title={fieldSchema.description}>
<InfoCircleOutlined style={{ marginLeft: 4 }} />
</Tooltip>
)}
</span>
),
name: fieldName,
rules
};
switch (fieldSchema.type) {
case 'string':
if (fieldSchema.enum) {
return (
<Form.Item {...commonProps} key={fieldName}>
<Select
placeholder={`请选择${fieldSchema.title || fieldName}`}
options={fieldSchema.enum.map((value, index) => ({
label: fieldSchema.enumNames?.[index] || value,
value
}))}
/>
</Form.Item>
<Form.Item
label="工作目录"
name="workingDirectory"
>
<Input placeholder="请输入工作目录" />
);
}
if (fieldSchema.format === 'shell') {
return (
<Form.Item {...commonProps} key={fieldName}>
<Input.TextArea rows={6} placeholder={`请输入${fieldSchema.title || fieldName}`} />
</Form.Item>
<Form.Item
label="超时时间"
name="timeout"
rules={[{ type: 'number', min: 0, message: '超时时间必须大于0' }]}
>
<InputNumber placeholder="请输入超时时间(秒)" style={{ width: '100%' }} />
);
}
return (
<Form.Item {...commonProps} key={fieldName}>
<Input placeholder={`请输入${fieldSchema.title || fieldName}`} />
</Form.Item>
);
case 'number':
return (
<Form.Item {...commonProps} key={fieldName}>
<InputNumber
style={{ width: '100%' }}
min={fieldSchema.minimum}
max={fieldSchema.maximum}
placeholder={`请输入${fieldSchema.title || fieldName}`}
/>
</Form.Item>
);
case 'object':
if (fieldSchema.additionalProperties) {
return (
<Form.Item {...commonProps} key={fieldName}>
<Input.TextArea
placeholder={`请输入${fieldSchema.title || fieldName},格式为 key=value`}
rows={4}
/>
</Form.Item>
<Form.Item
label="重试次数"
name="retryTimes"
rules={[{ type: 'number', min: 0, message: '重试次数不能为负数' }]}
>
<InputNumber placeholder="请输入重试次数" style={{ width: '100%' }} />
</Form.Item>
<Form.Item
label="重试间隔"
name="retryInterval"
rules={[{ type: 'number', min: 0, message: '重试间隔不能为负数' }]}
>
<InputNumber placeholder="请输入重试间隔(秒)" style={{ width: '100%' }} />
</Form.Item>
<Form.Item
label="环境变量"
name="environment"
>
<Input.TextArea placeholder="请输入环境变量" />
</Form.Item>
<Form.Item
label="成功退出码"
name="successExitCode"
>
<Input placeholder="请输入成功退出码" />
</Form.Item>
</>
)}
</>
);
}
return null;
default:
return null;
}
};
// 渲染节点基本配置
const renderNodeConfig = () => {
if (!nodeSchema?.properties) return null;
return Object.entries(nodeSchema.properties).map(([fieldName, fieldSchema]) =>
renderFormItem(fieldName, fieldSchema)
);
};
// 渲染执行器配置
const renderExecutorConfig = () => {
if (!executorSchema?.properties) return null;
return Object.entries(executorSchema.properties).map(([fieldName, fieldSchema]) =>
renderFormItem(fieldName, fieldSchema)
);
};
@ -216,13 +241,15 @@ const NodeConfig: React.FC<NodeConfigProps> = ({ nodeType, form, onValuesChange
form={form}
layout="vertical"
onValuesChange={onValuesChange}
initialValues={{ ...nodeDefaultConfig, ...executorDefaultConfig }}
initialValues={{ ...nodeDefaultConfig }}
>
{renderBasicFormItems()}
<Divider />
{renderGatewayConfig()}
{renderTimerConfig()}
{renderShellExecutorConfig()}
{renderNodeConfig()}
{selectedExecutor && (
<>
<Divider />
{renderExecutorConfig()}
</>
)}
</Form>
);
};

View File

@ -0,0 +1,78 @@
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
import { NodeType } from '../../../../types';
const ajv = new Ajv({
allErrors: true,
verbose: true,
$data: true,
strict: false
});
addFormats(ajv);
export interface ValidationResult {
valid: boolean;
errors: string[];
}
export const validateNodeConfig = (
nodeType: NodeType,
formData: any
): ValidationResult => {
console.log('Validating node config:', { nodeType, formData });
try {
// 1. 解析节点的 schema
const nodeSchema = JSON.parse(nodeType.configSchema);
console.log('Node schema:', nodeSchema);
let errors: string[] = [];
// 2. 验证基本配置
const validateNode = ajv.compile(nodeSchema);
const nodeValid = validateNode(formData);
console.log('Node validation result:', { valid: nodeValid, errors: validateNode.errors });
if (!nodeValid && validateNode.errors) {
errors = validateNode.errors.map(err => {
const field = err.instancePath.replace('/', '') || '配置';
return `${field}: ${err.message}`;
});
}
// 3. 如果有执行器配置,验证执行器配置
if (formData.executor && nodeType.executors?.length > 0) {
const executor = nodeType.executors.find(e => e.code === formData.executor);
if (executor?.configSchema) {
console.log('Validating executor config:', executor.configSchema);
const executorSchema = JSON.parse(executor.configSchema);
const validateExecutor = ajv.compile(executorSchema);
const executorValid = validateExecutor(formData);
console.log('Executor validation result:', { valid: executorValid, errors: validateExecutor.errors });
if (!executorValid && validateExecutor.errors) {
errors = errors.concat(
validateExecutor.errors.map(err => {
const field = err.instancePath.replace('/', '') || '执行器配置';
return `${field}: ${err.message}`;
})
);
}
}
}
const result = {
valid: errors.length === 0,
errors
};
console.log('Final validation result:', result);
return result;
} catch (error) {
console.error('Schema validation error:', error);
return {
valid: false,
errors: ['配置验证失败schema 解析错误']
};
}
};