This commit is contained in:
ruoyunbai 2025-11-17 10:55:25 +08:00
parent fdf4aa5ef4
commit d9b08c89ee
15 changed files with 2156 additions and 37 deletions

View File

@ -14,7 +14,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\common\\05-junction.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/common/05-junction.js"
}, },
"inject": { "inject": {
"name": "inject", "name": "inject",
@ -25,7 +25,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\common\\20-inject.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/common/20-inject.js"
}, },
"debug": { "debug": {
"name": "debug", "name": "debug",
@ -36,7 +36,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\common\\21-debug.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/common/21-debug.js"
}, },
"complete": { "complete": {
"name": "complete", "name": "complete",
@ -47,7 +47,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\common\\24-complete.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/common/24-complete.js"
}, },
"catch": { "catch": {
"name": "catch", "name": "catch",
@ -58,7 +58,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\common\\25-catch.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/common/25-catch.js"
}, },
"status": { "status": {
"name": "status", "name": "status",
@ -69,7 +69,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\common\\25-status.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/common/25-status.js"
}, },
"link": { "link": {
"name": "link", "name": "link",
@ -82,7 +82,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\common\\60-link.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/common/60-link.js"
}, },
"comment": { "comment": {
"name": "comment", "name": "comment",
@ -93,7 +93,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\common\\90-comment.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/common/90-comment.js"
}, },
"global-config": { "global-config": {
"name": "global-config", "name": "global-config",
@ -104,7 +104,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\common\\91-global-config.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/common/91-global-config.js"
}, },
"unknown": { "unknown": {
"name": "unknown", "name": "unknown",
@ -115,7 +115,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\common\\98-unknown.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/common/98-unknown.js"
}, },
"function": { "function": {
"name": "function", "name": "function",
@ -126,7 +126,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\function\\10-function.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/function/10-function.js"
}, },
"switch": { "switch": {
"name": "switch", "name": "switch",
@ -137,7 +137,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\function\\10-switch.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/function/10-switch.js"
}, },
"change": { "change": {
"name": "change", "name": "change",
@ -148,7 +148,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\function\\15-change.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/function/15-change.js"
}, },
"range": { "range": {
"name": "range", "name": "range",
@ -159,7 +159,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\function\\16-range.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/function/16-range.js"
}, },
"template": { "template": {
"name": "template", "name": "template",
@ -170,7 +170,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\function\\80-template.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/function/80-template.js"
}, },
"delay": { "delay": {
"name": "delay", "name": "delay",
@ -181,7 +181,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\function\\89-delay.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/function/89-delay.js"
}, },
"trigger": { "trigger": {
"name": "trigger", "name": "trigger",
@ -192,7 +192,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\function\\89-trigger.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/function/89-trigger.js"
}, },
"exec": { "exec": {
"name": "exec", "name": "exec",
@ -203,7 +203,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\function\\90-exec.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/function/90-exec.js"
}, },
"rbe": { "rbe": {
"name": "rbe", "name": "rbe",
@ -214,7 +214,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\function\\rbe.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/function/rbe.js"
}, },
"tls": { "tls": {
"name": "tls", "name": "tls",
@ -225,7 +225,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\network\\05-tls.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/network/05-tls.js"
}, },
"httpproxy": { "httpproxy": {
"name": "httpproxy", "name": "httpproxy",
@ -236,7 +236,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\network\\06-httpproxy.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/network/06-httpproxy.js"
}, },
"mqtt": { "mqtt": {
"name": "mqtt", "name": "mqtt",
@ -249,7 +249,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\network\\10-mqtt.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js"
}, },
"httpin": { "httpin": {
"name": "httpin", "name": "httpin",
@ -261,7 +261,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\network\\21-httpin.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/network/21-httpin.js"
}, },
"httprequest": { "httprequest": {
"name": "httprequest", "name": "httprequest",
@ -272,7 +272,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\network\\21-httprequest.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js"
}, },
"websocket": { "websocket": {
"name": "websocket", "name": "websocket",
@ -286,7 +286,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\network\\22-websocket.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/network/22-websocket.js"
}, },
"tcpin": { "tcpin": {
"name": "tcpin", "name": "tcpin",
@ -299,7 +299,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\network\\31-tcpin.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/network/31-tcpin.js"
}, },
"udp": { "udp": {
"name": "udp", "name": "udp",
@ -311,7 +311,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\network\\32-udp.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/network/32-udp.js"
}, },
"CSV": { "CSV": {
"name": "CSV", "name": "CSV",
@ -322,7 +322,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\parsers\\70-CSV.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js"
}, },
"HTML": { "HTML": {
"name": "HTML", "name": "HTML",
@ -333,7 +333,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\parsers\\70-HTML.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/parsers/70-HTML.js"
}, },
"JSON": { "JSON": {
"name": "JSON", "name": "JSON",
@ -344,7 +344,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\parsers\\70-JSON.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/parsers/70-JSON.js"
}, },
"XML": { "XML": {
"name": "XML", "name": "XML",
@ -355,7 +355,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\parsers\\70-XML.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/parsers/70-XML.js"
}, },
"YAML": { "YAML": {
"name": "YAML", "name": "YAML",
@ -366,7 +366,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\parsers\\70-YAML.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/parsers/70-YAML.js"
}, },
"split": { "split": {
"name": "split", "name": "split",
@ -378,7 +378,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\sequence\\17-split.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/sequence/17-split.js"
}, },
"sort": { "sort": {
"name": "sort", "name": "sort",
@ -389,7 +389,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\sequence\\18-sort.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/sequence/18-sort.js"
}, },
"batch": { "batch": {
"name": "batch", "name": "batch",
@ -400,7 +400,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\sequence\\19-batch.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/sequence/19-batch.js"
}, },
"file": { "file": {
"name": "file", "name": "file",
@ -412,7 +412,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\storage\\10-file.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/storage/10-file.js"
}, },
"watch": { "watch": {
"name": "watch", "name": "watch",
@ -423,7 +423,7 @@
"local": false, "local": false,
"user": false, "user": false,
"module": "node-red", "module": "node-red",
"file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\storage\\23-watch.js" "file": "/Users/zpc01/workspace/zzlh/node-red-master/packages/node_modules/@node-red/nodes/core/storage/23-watch.js"
} }
} }
} }

View File

@ -15,6 +15,12 @@
}, },
"tours": { "tours": {
"welcome": "4.1.0" "welcome": "4.1.0"
},
"dialog": {
"export": {
"pretty": true,
"json-view": false
}
} }
}, },
"menu-menu-item-palette": true, "menu-menu-item-palette": true,

696
compiliance-js/dms2oas.js Normal file
View File

@ -0,0 +1,696 @@
'use strict';
const DEFAULT_OPENAPI_VERSION = '3.0.1';
const DEFAULT_API_VERSION = '1.0.0';
const BASE_PREFIX = 'https://www.dev.ideas.cnpc/api/dms';
const DEFAULT_SERVICE_ID = 'well_kd_wellbore_ideas01';
const DEFAULT_API_SEGMENT = 'v1';
const FALLBACK_BASE_URL = `${BASE_PREFIX}/${DEFAULT_SERVICE_ID}/${DEFAULT_API_SEGMENT}`;
const FALLBACK_HEADERS = {
'Content-Type': 'application/json',
'Authorization': '1',
'Dataregion': 'ZZLH',
};
const SERVICE_DOMAIN_CATALOG = {
'well_kd_wellbore_ideas01': {
name: '井筒',
id: 'well_kd_wellbore_ideas01',
keywords: ['wb', 'wb_dr', 'wb_ml', 'wb_wl', 'wb_tp', 'wb_dh', 'wb_fr'],
},
'geo_kd_res_ideas01': {
name: '采油气',
id: 'geo_kd_res_ideas01',
keywords: ['pc', 'pc_op', 'pc_oe', 'pc_ge'],
},
'kd_cr_ideas01': {
name: '分析化验',
id: 'kd_cr_ideas01',
keywords: ['cr', 'cr_se'],
},
'kd_rs_ideas01': {
name: '油气藏',
id: 'kd_rs_ideas01',
keywords: ['rs', 'rs_rd', 'rs_rm', 'rs_gs', 'rs_in'],
},
};
let schema;
try {
// 优先使用 HTTP In 提供的 req.body.schema缺省时回退到 msg.payload。
const schemaInput = extractSchemaInput(msg);
schema = parseSchema(schemaInput);
} catch (error) {
node.error(`DMS -> Swagger 解析失败:${error.message}`, msg);
msg.error = error.message;
return msg;
}
const resourceTitle = typeof schema.title === 'string' && schema.title.trim()
? schema.title.trim()
: 'DMS Resource';
const resourceName = pascalCase(resourceTitle);
const collectionName = pluralize(kebabCase(resourceTitle));
const identityField = Array.isArray(schema.identityId) && schema.identityId.length > 0
? String(schema.identityId[0])
: 'id';
const schemaComponent = buildComponentSchema(resourceName, schema);
const identitySchema = schemaComponent.properties && schemaComponent.properties[identityField]
? clone(schemaComponent.properties[identityField])
: { type: 'string' };
const crudConfig = Object.assign({}, msg.crudConfig || {});
const dmsMeta = extractDmsMeta(msg);
msg.dms_meta = dmsMeta;
const serviceInfo = resolveServiceInfo(dmsMeta && dmsMeta.domain);
const apiVersionSegment = DEFAULT_API_SEGMENT;
const serviceId = serviceInfo.id;
const { resourceSegment, versionSegment } = deriveResourceSegments(dmsMeta, schema, collectionName);
const listPath = buildListPath(resourceSegment, versionSegment);
const singlePath = buildSinglePath(resourceSegment);
const detailPath = buildDetailPath(resourceSegment, versionSegment, identityField);
const createRequestSchema = buildCreateRequestSchema(resourceName);
const updateRequestSchema = buildCreateRequestSchema(resourceName);
const deleteRequestSchema = buildDeleteRequestSchema(identityField);
const listRequestSchema = buildListRequestSchema();
const listResponseSchema = buildListResponseSchema(resourceName);
const openApiDocument = {
openapi: DEFAULT_OPENAPI_VERSION,
info: {
title: `${resourceTitle} API`,
version: DEFAULT_API_VERSION,
description: schema.description || `${schema.$id || ''}`.trim(),
'x-dms-sourceId': schema.$id || undefined,
},
paths: buildCrudPaths({
resourceName,
identityField,
identitySchema,
listPath,
createPath: singlePath,
detailPath,
createRequestSchema,
updateRequestSchema,
deleteRequestSchema,
listRequestSchema,
listResponseSchema,
}),
components: {
schemas: {
[resourceName]: schemaComponent,
},
},
};
if (!crudConfig.baseUrl) {
crudConfig.baseUrl = `${BASE_PREFIX}/${serviceId}/${apiVersionSegment}`;
}
const headers = Object.assign({}, FALLBACK_HEADERS, crudConfig.headers || {});
const dataRegionValue = extractDataRegion(schema, headers.Dataregion);
if (dataRegionValue) {
headers.Dataregion = dataRegionValue;
crudConfig.dataRegion = dataRegionValue;
}
crudConfig.headers = headers;
crudConfig.identityField = crudConfig.identityField || identityField;
crudConfig.service = crudConfig.service || {
id: serviceId,
name: serviceInfo.name,
};
crudConfig.list = crudConfig.list || {};
if (!crudConfig.list.path) {
crudConfig.list.path = listPath;
}
crudConfig.list.method = crudConfig.list.method || 'POST';
crudConfig.create = crudConfig.create || {};
if (!crudConfig.create.path) {
crudConfig.create.path = singlePath;
}
crudConfig.create.method = crudConfig.create.method || 'POST';
crudConfig.delete = crudConfig.delete || {};
if (!crudConfig.delete.path) {
crudConfig.delete.path = singlePath;
}
crudConfig.delete.method = crudConfig.delete.method || 'DELETE';
crudConfig.detailPath = crudConfig.detailPath || detailPath;
crudConfig.version = crudConfig.version || versionSegment;
msg.crudConfig = crudConfig;
msg.headers = Object.assign({}, headers);
msg.oas_def = openApiDocument;
msg.payload = openApiDocument;
return msg;
function extractDataRegion(dmsSchema, fallback) {
if (!dmsSchema || typeof dmsSchema !== 'object') {
return fallback;
}
if (typeof dmsSchema.dataRegion === 'string' && dmsSchema.dataRegion.trim()) {
return dmsSchema.dataRegion.trim();
}
if (dmsSchema.defaultShow && Array.isArray(dmsSchema.defaultShow)) {
const candidate = dmsSchema.defaultShow.find(item => item && typeof item === 'string' && item.toLowerCase().includes('dataregion'));
if (candidate) {
return fallback;
}
}
const props = dmsSchema.properties;
if (props && typeof props === 'object' && props.dataRegion) {
if (typeof props.dataRegion.default === 'string' && props.dataRegion.default.trim()) {
return props.dataRegion.default.trim();
}
}
return fallback;
}
function extractSchemaInput(message) {
if (message && message.req && message.req.body && typeof message.req.body === 'object') {
if (message.req.body.schema !== undefined) {
return message.req.body.schema;
}
if (looksLikeDmsSchema(message.req.body)) {
return message.req.body;
}
}
if (message && message.payload !== undefined) {
if (message.payload && typeof message.payload === 'object') {
if (message.payload.schema !== undefined) {
return message.payload.schema;
}
if (looksLikeDmsSchema(message.payload)) {
return message.payload;
}
}
return message.payload;
}
throw new Error('未找到schema请在请求体的schema字段或msg.payload中提供');
}
function parseSchema(source) {
if (typeof source === 'string') {
try {
return JSON.parse(source);
} catch (error) {
throw new Error(`JSON 解析失败:${error.message}`);
}
}
if (!source || typeof source !== 'object') {
throw new Error('schema 必须是 DMS 定义对象或 JSON 字符串');
}
return source;
}
function buildComponentSchema(resourceName, dmsSchema) {
const { properties = {}, required = [], groupView, identityId, naturalKey, defaultShow } = dmsSchema;
const openApiProps = {};
for (const [propName, propSchema] of Object.entries(properties)) {
openApiProps[propName] = mapProperty(propSchema);
}
return {
type: 'object',
required: Array.isArray(required) ? required.slice() : [],
properties: openApiProps,
description: dmsSchema.description || dmsSchema.title || resourceName,
'x-dms-groupView': groupView || undefined,
'x-dms-identityId': identityId || undefined,
'x-dms-naturalKey': naturalKey || undefined,
'x-dms-defaultShow': defaultShow || undefined,
};
}
function mapProperty(propSchema) {
if (!propSchema || typeof propSchema !== 'object') {
return { type: 'string' };
}
const result = {};
const type = normalizeType(propSchema.type);
result.type = type.type;
if (type.format) {
result.format = type.format;
}
if (type.items) {
result.items = type.items;
}
if (propSchema.description) {
result.description = propSchema.description;
} else if (propSchema.title) {
result.description = propSchema.title;
}
if (propSchema.enum) {
result.enum = propSchema.enum.slice();
}
if (propSchema.default !== undefined) {
result.default = propSchema.default;
}
if (propSchema.mask) {
result['x-dms-mask'] = propSchema.mask;
}
if (propSchema.geom) {
result['x-dms-geom'] = propSchema.geom;
}
if (propSchema.title) {
result['x-dms-title'] = propSchema.title;
}
if (propSchema.type) {
result['x-dms-originalType'] = propSchema.type;
}
return result;
}
function normalizeType(typeValue) {
const normalized = typeof typeValue === 'string' ? typeValue.toLowerCase() : undefined;
switch (normalized) {
case 'number':
case 'integer':
case 'long':
case 'float':
case 'double':
return { type: 'number' };
case 'boolean':
return { type: 'boolean' };
case 'array':
return { type: 'array', items: { type: 'string' } };
case 'date':
return { type: 'string', format: 'date' };
case 'date-time':
return { type: 'string', format: 'date-time' };
case 'object':
return { type: 'object' };
case 'string':
default:
return { type: 'string' };
}
}
function buildCrudPaths({
resourceName,
identityField,
identitySchema,
listPath,
createPath,
detailPath,
createRequestSchema,
updateRequestSchema,
deleteRequestSchema,
listRequestSchema,
listResponseSchema,
}) {
const ref = `#/components/schemas/${resourceName}`;
const paths = {};
const normalisedListPath = normalisePath(listPath);
const normalisedCreatePath = normalisePath(createPath);
const normalisedDetailPath = detailPath ? normalisePath(detailPath) : null;
paths[normalisedListPath] = {
post: {
operationId: `list${resourceName}s`,
summary: `List ${resourceName} resources`,
requestBody: {
required: false,
content: {
'application/json': {
schema: listRequestSchema,
},
},
},
responses: {
200: {
description: 'Successful response',
content: {
'application/json': {
schema: listResponseSchema || {
type: 'array',
items: { $ref: ref },
},
},
},
},
},
},
};
paths[normalisedCreatePath] = {
post: {
operationId: `create${resourceName}`,
summary: `Create a ${resourceName}`,
requestBody: {
required: true,
content: {
'application/json': {
schema: createRequestSchema,
},
},
},
responses: {
201: {
description: 'Created',
content: {
'application/json': {
schema: { $ref: ref },
},
},
},
},
},
put: {
operationId: `update${resourceName}`,
summary: `Update a ${resourceName}`,
requestBody: {
required: true,
content: {
'application/json': {
schema: updateRequestSchema,
},
},
},
responses: {
200: {
description: 'Successful update',
content: {
'application/json': {
schema: { $ref: ref },
},
},
},
},
},
delete: {
operationId: `delete${resourceName}`,
summary: `Delete ${resourceName} resources`,
requestBody: {
required: true,
content: {
'application/json': {
schema: deleteRequestSchema,
},
},
},
responses: {
200: { description: 'Deleted' },
},
},
};
if (normalisedDetailPath) {
paths[normalisedDetailPath] = {
parameters: [
{
name: identityField,
in: 'path',
required: true,
schema: identitySchema,
},
],
get: {
operationId: `get${resourceName}`,
summary: `Get a single ${resourceName}`,
responses: {
200: {
description: 'Successful response',
content: {
'application/json': {
schema: { $ref: ref },
},
},
},
404: { description: `${resourceName} not found` },
},
},
};
}
return paths;
}
function normalisePath(path) {
if (!path) {
return '/';
}
return path.startsWith('/') ? path : `/${path}`;
}
function pascalCase(input) {
return input
.replace(/[^a-zA-Z0-9]+/g, ' ')
.split(' ')
.filter(Boolean)
.map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
.join('') || 'Resource';
}
function kebabCase(input) {
return input
.replace(/([a-z])([A-Z])/g, '$1-$2')
.replace(/[^a-zA-Z0-9]+/g, '-')
.replace(/^-+|-+$/g, '')
.toLowerCase() || 'resource';
}
function pluralize(word) {
if (word.endsWith('s')) {
return word;
}
if (word.endsWith('y')) {
return word.slice(0, -1) + 'ies';
}
return `${word}s`;
}
function clone(value) {
return JSON.parse(JSON.stringify(value));
}
function looksLikeDmsSchema(candidate) {
if (!candidate || typeof candidate !== 'object') {
return false;
}
if (candidate.properties && typeof candidate.properties === 'object' && candidate.title) {
return true;
}
if (candidate.$id && candidate.type && candidate.type.toLowerCase && candidate.type.toLowerCase() === 'object') {
return true;
}
return false;
}
function extractDmsMeta(message) {
const candidateReq = message && message.req && message.req.body && message.req.body.dms_meta;
const parsedReq = parseMetaCandidate(candidateReq);
if (parsedReq) {
return parsedReq;
}
const candidateMsg = message && message.dms_meta;
const parsedMsg = parseMetaCandidate(candidateMsg);
if (parsedMsg) {
return parsedMsg;
}
return null;
}
function parseMetaCandidate(candidate) {
if (!candidate) {
return null;
}
if (typeof candidate === 'string') {
try {
const value = JSON.parse(candidate);
return parseMetaCandidate(value);
} catch (err) {
return null;
}
}
if (typeof candidate === 'object') {
return clone(candidate);
}
return null;
}
function resolveServiceInfo(domain) {
if (!domain || typeof domain !== 'string') {
return SERVICE_DOMAIN_CATALOG[DEFAULT_SERVICE_ID];
}
const normalized = domain.toLowerCase();
let best = null;
for (const entry of Object.values(SERVICE_DOMAIN_CATALOG)) {
const matched = entry.keywords.some(keyword => {
if (typeof keyword !== 'string') return false;
const normKeyword = keyword.toLowerCase();
return normalized.includes(normKeyword) || normKeyword.includes(normalized);
});
if (matched) {
best = entry;
break;
}
}
return best || SERVICE_DOMAIN_CATALOG[DEFAULT_SERVICE_ID];
}
function deriveResourceSegments(meta, schema, collectionName) {
const defaultResource = (collectionName || '').replace(/^\//, '');
let resourceSegment = defaultResource;
let versionSegment = null;
if (meta && typeof meta === 'object') {
if (typeof meta.id === 'string' && meta.id.trim()) {
const parts = meta.id.trim().split('.');
if (parts.length > 0 && parts[0]) {
resourceSegment = parts[0];
}
if (parts.length > 1) {
versionSegment = parts.slice(1).join('.');
}
} else if (typeof meta.name === 'string' && meta.name.trim()) {
resourceSegment = meta.name.trim();
}
}
if (!resourceSegment && schema && schema.title) {
resourceSegment = kebabCase(schema.title);
}
return {
resourceSegment: resourceSegment || defaultResource || 'resource',
versionSegment: versionSegment,
};
}
function buildListPath(resourceSegment, versionSegment) {
if (versionSegment) {
return `/${resourceSegment}/${versionSegment}`;
}
return `/${resourceSegment}`;
}
function buildSinglePath(resourceSegment) {
return `/${resourceSegment}`;
}
function buildDetailPath(resourceSegment, versionSegment, identityField) {
if (versionSegment) {
return `/${resourceSegment}/${versionSegment}/{${identityField}}`;
}
return `/${resourceSegment}/{${identityField}}`;
}
function buildCreateRequestSchema(resourceName) {
const ref = `#/components/schemas/${resourceName}`;
return {
type: 'object',
required: ['version', 'act', 'data'],
properties: {
version: { type: 'string', default: '1.0.0' },
act: { type: 'integer', default: -1 },
data: {
type: 'array',
items: { $ref: ref },
},
},
};
}
function buildDeleteRequestSchema(identityField) {
return {
type: 'object',
required: ['version', 'data'],
properties: {
version: { type: 'string', default: '1.0.0' },
data: {
type: 'array',
items: {
type: 'string',
description: `Value of ${identityField}`,
},
},
},
};
}
function buildListRequestSchema() {
return {
type: 'object',
properties: {
version: { type: 'string', default: '1.0.0' },
data: {
type: 'object',
properties: {
pageNo: { type: 'integer', default: 1 },
pageSize: { type: 'integer', default: 20 },
isSearchCount: { type: 'boolean', default: true },
filters: {
type: 'array',
items: { type: 'object' },
},
},
},
},
};
}
function buildListResponseSchema(resourceName) {
const ref = `#/components/schemas/${resourceName}`;
return {
type: 'object',
properties: {
code: { type: 'integer' },
message: { type: 'string' },
data: {
type: 'object',
properties: {
list: {
type: 'array',
items: { $ref: ref },
},
total: { type: 'integer' },
},
},
},
};
}
function looksLikeDmsSchema(candidate) {
if (!candidate || typeof candidate !== 'object') {
return false;
}
if (candidate.properties && typeof candidate.properties === 'object' && candidate.title) {
return true;
}
if (candidate.$id && candidate.type && candidate.type.toLowerCase && candidate.type.toLowerCase() === 'object') {
return true;
}
return false;
}

View File

@ -0,0 +1,79 @@
'use strict';
/**
* 使用 msg.crudFlow.create 配置创建请求若未提供则保持旧有注入负载
*/
return configureCreateRequest(msg, node);
function configureCreateRequest(message, node) {
if (!message.crudFlow || !message.crudFlow.create) {
return message;
}
const create = message.crudFlow.create;
const baseUrl = message.crudFlow.baseUrl || '';
const payload = selectCreatePayload(create);
message.method = (create.method || 'POST').toUpperCase();
message.url = mergeUrl(baseUrl, create.path || '');
message.headers = Object.assign({}, message.crudFlow.headers || {}, create.headers || {});
if (payload !== undefined) {
message.payload = clone(payload);
} else if (message.payload !== undefined) {
delete message.payload;
}
const identityField = message.crudFlow.identityField || message.identityField || 'dsid';
const keyValue = payload && typeof payload === 'object'
? extractPrimaryKey(payload, identityField)
: undefined;
if (keyValue !== undefined) {
message.primaryKeyValue = keyValue;
message.crudFlow.delete = message.crudFlow.delete || {};
message.crudFlow.delete.actualKey = keyValue;
}
return message;
}
function selectCreatePayload(create) {
if (create.payload && Object.keys(create.payload).length > 0) {
return create.payload;
}
if (create.samplePayload) {
return create.samplePayload;
}
return undefined;
}
function extractPrimaryKey(payload, identityField) {
if (!payload) {
return undefined;
}
if (Object.prototype.hasOwnProperty.call(payload, identityField)) {
return payload[identityField];
}
if (Array.isArray(payload.data) && payload.data.length > 0 && payload.data[0][identityField] !== undefined) {
return payload.data[0][identityField];
}
return undefined;
}
function mergeUrl(base, path) {
const prefix = (base || '').replace(/\/+$/, '');
const suffix = (path || '').replace(/^\/+/, '');
if (!prefix) {
return `/${suffix}`;
}
if (!suffix) {
return prefix;
}
return `${prefix}/${suffix}`;
}
function clone(value) {
return value == null ? value : JSON.parse(JSON.stringify(value));
}

View File

@ -0,0 +1,78 @@
'use strict';
/**
* 依据 msg.crudFlow.delete 生成删除请求体支持占位符 {{primaryKey}}
*/
return configureDeleteRequest(msg, node);
function configureDeleteRequest(message, node) {
if (!message.crudFlow || !message.crudFlow.delete) {
return message;
}
const del = message.crudFlow.delete;
const baseUrl = message.crudFlow.baseUrl || '';
const primaryKeyValue = del.actualKey || message.primaryKeyValue || del.primaryKey || 'testid2';
const method = (del.method || 'POST').toUpperCase();
let path = del.path || '';
let payload = null;
if (method === 'DELETE' && path.includes('{')) {
path = path.replace(/\{[^}]+\}/g, encodeURIComponent(primaryKeyValue));
} else {
const template = del.payloadTemplate || { version: '1.0.0', data: ['{{primaryKey}}'] };
payload = materialiseTemplate(template, primaryKeyValue);
}
message.method = method;
message.url = mergeUrl(baseUrl, path);
message.headers = Object.assign({}, message.crudFlow.headers || {}, del.headers || {});
if (payload !== null) {
message.payload = payload;
} else if (message.payload !== undefined) {
delete message.payload;
}
return message;
}
function materialiseTemplate(template, primaryKeyValue) {
const cloned = clone(template);
return replacePlaceholder(cloned, primaryKeyValue);
}
function replacePlaceholder(value, primaryKeyValue) {
if (typeof value === 'string') {
return value.replace(/\{\{\s*primaryKey\s*\}\}/g, primaryKeyValue);
}
if (Array.isArray(value)) {
return value.map(item => replacePlaceholder(item, primaryKeyValue));
}
if (value && typeof value === 'object') {
const result = {};
for (const key of Object.keys(value)) {
result[key] = replacePlaceholder(value[key], primaryKeyValue);
}
return result;
}
return value;
}
function mergeUrl(base, path) {
const prefix = (base || '').replace(/\/+$/, '');
const suffix = (path || '').replace(/^\/+/, '');
if (!prefix) {
return `/${suffix}`;
}
if (!suffix) {
return prefix;
}
return `${prefix}/${suffix}`;
}
function clone(value) {
return value == null ? value : JSON.parse(JSON.stringify(value));
}

View File

@ -0,0 +1,45 @@
'use strict';
/**
* 根据 msg.crudFlow.list 设置查询请求兼容旧的手工注入流程
*/
return configureListRequest(msg, node);
function configureListRequest(message, node) {
if (!message.crudFlow || !message.crudFlow.list) {
return message;
}
const list = message.crudFlow.list;
const baseUrl = message.crudFlow.baseUrl || '';
message.method = (list.method || 'GET').toUpperCase();
message.url = mergeUrl(baseUrl, list.path || '');
message.headers = Object.assign({}, message.crudFlow.headers || {}, list.headers || {});
if (list.payload !== undefined) {
message.payload = clone(list.payload);
} else if (message.payload !== undefined) {
delete message.payload;
}
message.listRequestConfigured = true;
return message;
}
function mergeUrl(base, path) {
const prefix = (base || '').replace(/\/+$/, '');
const suffix = (path || '').replace(/^\/+/, '');
if (!prefix) {
return `/${suffix}`;
}
if (!suffix) {
return prefix;
}
return `${prefix}/${suffix}`;
}
function clone(value) {
return value == null ? value : JSON.parse(JSON.stringify(value));
}

View File

@ -0,0 +1,84 @@
'use strict';
/**
* 解析创建接口返回值记录是否成功以及实际主键
*/
return handleCreateResponse(msg, node);
function handleCreateResponse(message, node) {
let parsed;
try {
parsed = typeof message.payload === 'string' ? JSON.parse(message.payload) : message.payload;
} catch (error) {
node.error(`创建接口响应解析失败:${error.message}`, message);
message.isCreated = false;
return message;
}
const success = parsed && typeof parsed === 'object' && parsed.code === 0;
message.isCreated = success;
if (!success) {
return message;
}
const identityField = (message.crudFlow && message.crudFlow.identityField) ||
message.identityField || 'dsid';
const candidate = extractPrimaryKey(parsed, identityField);
if (candidate) {
message.primaryKeyValue = candidate;
if (message.crudFlow && message.crudFlow.delete) {
message.crudFlow.delete.actualKey = candidate;
}
}
return message;
}
function extractPrimaryKey(response, identityField) {
if (!response || typeof response !== 'object') {
return null;
}
const data = response.data;
if (!data) {
return null;
}
if (typeof data === 'string' || typeof data === 'number') {
return String(data);
}
if (Array.isArray(data)) {
for (const item of data) {
const value = extractPrimaryKey(item, identityField);
if (value) {
return value;
}
}
} else if (typeof data === 'object') {
if (data[identityField] !== undefined && data[identityField] !== null) {
return String(data[identityField]);
}
if (Array.isArray(data.list)) {
for (const item of data.list) {
const value = extractPrimaryKey(item, identityField);
if (value) {
return value;
}
}
}
if (Array.isArray(data.items)) {
for (const item of data.items) {
const value = extractPrimaryKey(item, identityField);
if (value) {
return value;
}
}
}
}
return null;
}

View File

@ -0,0 +1,46 @@
'use strict';
/**
* 汇总 CRUD 执行结果生成统一响应结构
*/
return summariseCrudResult(msg, node);
function summariseCrudResult(message, node) {
const identityField = (message.crudFlow && message.crudFlow.identityField) ||
message.identityField || 'dsid';
const primaryKey = (message.crudFlow && message.crudFlow.delete && message.crudFlow.delete.actualKey) ||
message.primaryKeyValue || null;
const listOk = message.listError ? false : true;
const createOk = message.isCreated === undefined ? true : !!message.isCreated;
const deleteOk = message.isDeleted === undefined ? true : !!message.isDeleted;
const success = listOk && createOk && deleteOk;
message.payload = {
code: success ? 0 : 1,
message: success ? '井创建删除流程测试成功' : '井创建删除流程部分失败',
details: {
listOk,
createOk,
deleteOk,
identityField,
primaryKey,
},
};
delete message.crudFlow;
delete message.identityField;
delete message.primaryKeyValue;
delete message.listRequestConfigured;
delete message.listError;
delete message.isCreated;
delete message.isDeleted;
delete message.method;
delete message.url;
delete message.headers;
delete message.statusCode;
return message;
}

View File

@ -0,0 +1,334 @@
'use strict';
/**
* 构建 CRUD 执行动作所需的指令集合保存于 msg.crudFlow
* 约定 operationId 采用 dmsoas 转换后默认的命名
* list<ResourceName>s / create<ResourceName> / delete<ResourceName>
* 允许使用 msg.crudConfig.* 进行覆盖
*/
const FALLBACK_BASE_URL = 'https://www.dev.ideas.cnpc/api/dms/well_kd_wellbore_ideas01/v1';
const DEFAULT_LIST_PAYLOAD = {
version: '1.0.0',
data: [],
pageNo: 1,
pageSize: 20,
isSearchCount: true,
};
const DEFAULT_CREATE_SAMPLE = {
version: '1.0.0',
act: -1,
data: [
{
dsid: 'testid2',
wellId: 'WELL-zzlhTEST-002',
wellCommonName: 'zzlh测试用井名',
wellLegalName: 'zzlh-test-01',
wellPurpose: '开发井',
wellType: '直井',
dataRegion: 'ZZLH',
projectId: 'PROJ-ZZLH-001',
projectName: 'zzlh测试地质单元',
orgId: 'ORG-ZZLH-01',
orgName: 'zzlh采油厂',
bsflag: 1,
wellState: '生产中',
spudDate: '2024-01-15',
completionDate: '2024-05-20',
prodDate: '2024-06-01',
egl: 145.5,
kbd: 5.2,
kb: 150.7,
actualXAxis: 550123.45,
actualYAxis: 4998765.32,
coordinateSystemName: 'zzlh测试坐标系',
geoDescription: '位于zzlh测试区域',
remarks: '这是一口用于系统测试的生产井。',
createUserId: 'testuser001',
createDate: '2025-09-12T10:00:00Z',
updateUserId: 'testuser001',
updateDate: '2025-09-12T10:00:00Z',
},
],
};
const DEFAULT_DELETE_TEMPLATE = {
version: '1.0.0',
data: ['{{primaryKey}}'],
};
return buildCrudPlan(msg, node);
function buildCrudPlan(message, node) {
const requestBody = getRequestBody(message);
const crudConfig = mergeDeep({}, requestBody.crudConfig || {}, message.crudConfig || {});
const apiDoc = normaliseOpenApi(message, crudConfig, requestBody, node);
const operations = collectOperations(apiDoc);
const listOp = pickOperation('list', operations, crudConfig);
const createOp = pickOperation('create', operations, crudConfig);
const deleteOp = pickOperation('delete', operations, crudConfig);
if (!listOp || !createOp || !deleteOp) {
const missing = [
!listOp ? 'list' : null,
!createOp ? 'create' : null,
!deleteOp ? 'delete' : null,
].filter(Boolean).join(', ');
const errMsg = `未能在 OpenAPI 文档中找到必要的 CRUD 操作:${missing}`;
node.error(errMsg, message);
message.error = errMsg;
return null;
}
const resourceName = determineResourceName([createOp, listOp, deleteOp]) || 'Resource';
const identityField = crudConfig.identityField ||
requestBody.identityField ||
selectIdentityField(apiDoc, crudConfig) ||
'dsid';
const baseUrl = trimTrailingSlash(
crudConfig.baseUrl ||
requestBody.baseUrl ||
message.baseUrl ||
FALLBACK_BASE_URL
);
const headers = mergeDeep({}, requestBody.headers || {}, crudConfig.headers || {});
const listConfig = mergeDeep(
{},
{ payload: clone(DEFAULT_LIST_PAYLOAD) },
requestBody.list || {},
crudConfig.list || {}
);
const createConfig = mergeDeep(
{},
{ payload: clone(DEFAULT_CREATE_SAMPLE) },
requestBody.create || {},
crudConfig.create || {}
);
const deleteConfig = mergeDeep(
{},
{ payloadTemplate: clone(DEFAULT_DELETE_TEMPLATE) },
requestBody.delete || {},
crudConfig.delete || {}
);
if (requestBody.dataRegion) {
headers.Dataregion = requestBody.dataRegion;
}
message.crudFlow = {
resourceName,
identityField,
baseUrl,
headers,
list: Object.assign({
operationId: listOp.operationId,
method: listOp.method,
path: listOp.path,
}, listConfig),
create: Object.assign({
operationId: createOp.operationId,
method: createOp.method,
path: createOp.path,
samplePayload: clone(DEFAULT_CREATE_SAMPLE),
}, createConfig),
delete: Object.assign({
operationId: deleteOp.operationId,
method: deleteOp.method,
path: deleteOp.path,
}, deleteConfig),
openapi: apiDoc,
};
delete message.crudConfig;
if (message.baseUrl) {
delete message.baseUrl;
}
if (message.headers && !Object.keys(message.headers).length) {
delete message.headers;
}
message.oas_def = apiDoc;
delete message.error;
return message;
}
function normaliseOpenApi(message, crudConfig, requestBody, node) {
let candidate =
crudConfig.openapi ||
requestBody.openapi ||
message.oas_def ||
message.swagger ||
message.payload;
if (typeof candidate === 'string') {
try {
candidate = JSON.parse(candidate);
} catch (err) {
throw new Error(`OpenAPI JSON 解析失败:${err.message}`);
}
}
if (!candidate || typeof candidate !== 'object') {
throw new Error('未提供合法的 OpenAPI 文档');
}
if (!candidate.paths || typeof candidate.paths !== 'object' || Object.keys(candidate.paths).length === 0) {
throw new Error('OpenAPI 文档缺少 paths 定义');
}
return candidate;
}
function collectOperations(apiDoc) {
const operations = [];
const allowed = ['get', 'post', 'put', 'delete', 'patch', 'options', 'head', 'trace'];
for (const path of Object.keys(apiDoc.paths)) {
const pathItem = apiDoc.paths[path];
if (!pathItem || typeof pathItem !== 'object') {
continue;
}
for (const method of Object.keys(pathItem)) {
if (!allowed.includes(method.toLowerCase())) {
continue;
}
const operation = pathItem[method];
if (!operation || typeof operation !== 'object') {
continue;
}
operations.push({
operationId: operation.operationId || '',
summary: operation.summary || '',
description: operation.description || '',
method: method.toUpperCase(),
path,
operation,
});
}
}
return operations;
}
function pickOperation(kind, operations, crudConfig) {
const overrideId =
(crudConfig[kind] && crudConfig[kind].operationId) ||
crudConfig[`${kind}OperationId`];
if (overrideId) {
return operations.find(op => op.operationId === overrideId);
}
const matcher = getDefaultMatcher(kind);
let matched = operations.find(op => matcher.test(op.operationId));
if (matched) {
return matched;
}
// 兜底策略
switch (kind) {
case 'list':
return operations.find(op => op.method === 'GET') || null;
case 'create':
return operations.find(op => op.method === 'POST') || null;
case 'delete':
return operations.find(op => op.method === 'DELETE') ||
operations.find(op => op.method === 'POST' && /delete/i.test(op.operationId)) ||
null;
default:
return null;
}
}
function getDefaultMatcher(kind) {
switch (kind) {
case 'list':
return /^list[A-Z].*s$/;
case 'create':
return /^create[A-Z].*/;
case 'delete':
return /^delete[A-Z].*/;
default:
return /^$/;
}
}
function determineResourceName(candidates) {
for (const item of candidates) {
const opId = item && item.operationId ? item.operationId : '';
let match;
if ((match = opId.match(/^list([A-Z].*)s$/))) {
return match[1];
}
if ((match = opId.match(/^create([A-Z].*)$/))) {
return match[1];
}
if ((match = opId.match(/^delete([A-Z].*)$/))) {
return match[1];
}
}
return null;
}
function selectIdentityField(apiDoc, crudConfig) {
if (crudConfig.identityField) {
return crudConfig.identityField;
}
if (apiDoc.components && apiDoc.components.schemas) {
for (const schemaName of Object.keys(apiDoc.components.schemas)) {
const schema = apiDoc.components.schemas[schemaName];
if (!schema || typeof schema !== 'object') {
continue;
}
const identity = Array.isArray(schema['x-dms-identityId']) ? schema['x-dms-identityId'][0] : null;
if (identity) {
return identity;
}
}
}
if (apiDoc.components && apiDoc.components.schemas) {
for (const schemaName of Object.keys(apiDoc.components.schemas)) {
const schema = apiDoc.components.schemas[schemaName];
if (schema && schema.properties && Object.prototype.hasOwnProperty.call(schema.properties, 'dsid')) {
return 'dsid';
}
}
}
return 'dsid';
}
function trimTrailingSlash(url) {
if (!url) {
return '';
}
return url.replace(/\/+$/, '');
}
function getRequestBody(message) {
if (message && message.req && message.req.body && typeof message.req.body === 'object') {
return message.req.body;
}
return {};
}
function clone(value) {
return value == null ? value : JSON.parse(JSON.stringify(value));
}
function mergeDeep(target, ...sources) {
for (const source of sources) {
if (!isPlainObject(source)) {
continue;
}
for (const key of Object.keys(source)) {
const value = source[key];
if (value === undefined) {
continue;
}
if (isPlainObject(value)) {
const base = isPlainObject(target[key]) ? target[key] : {};
target[key] = mergeDeep({}, base, value);
} else {
target[key] = clone(value);
}
}
}
return target;
}
function isPlainObject(value) {
return Object.prototype.toString.call(value) === '[object Object]';
}

View File

@ -0,0 +1,32 @@
'use strict';
/**
* 为参数生成器 / LLM 设定目标的 operationId/path并清理 msg.mock
*/
return setCreateContext(msg, node);
function setCreateContext(message, node) {
if (!message.crudFlow || !message.crudFlow.create) {
const err = '缺少 crudFlow.create 配置,无法准备创建操作';
node.error(err, message);
message.error = err;
return null;
}
const create = message.crudFlow.create;
message.operationId = create.operationId;
message.method = create.method || 'POST';
message.path = create.path;
message.mock = {};
if (create.prompt) {
message.prompt = create.prompt;
}
if (!message.oas_def && message.crudFlow.openapi) {
message.oas_def = message.crudFlow.openapi;
}
delete message.error;
return message;
}

View File

@ -0,0 +1,62 @@
'use strict';
/**
* LLM 生成的创建请求体写回 crudFlow提取主键供删除步骤使用
*/
return storeCreateResult(msg, node);
function storeCreateResult(message, node) {
if (!message.crudFlow || !message.crudFlow.create) {
return message;
}
const identityField = message.crudFlow.identityField || message.identityField || 'dsid';
const mockBody = extractMockBody(message);
if (mockBody) {
message.crudFlow.create.payload = clone(mockBody);
} else if (!message.crudFlow.create.payload) {
node.warn('未从模型生成创建参数,继续使用默认样例', message);
message.crudFlow.create.payload = clone(message.crudFlow.create.samplePayload || {});
}
const payload = message.crudFlow.create.payload || {};
const primaryKeyValue = payload[identityField];
if (primaryKeyValue !== undefined) {
message.primaryKeyValue = primaryKeyValue;
message.crudFlow.delete = message.crudFlow.delete || {};
message.crudFlow.delete.actualKey = primaryKeyValue;
}
delete message.mock;
delete message.mockCandidates;
delete message.mockPrompt;
delete message.mockSource;
delete message.mockAutoSelected;
delete message.llmContext;
delete message.llmRaw;
delete message.prompt;
delete message.method;
delete message.path;
delete message.url;
delete message.headers;
delete message.statusCode;
delete message.payload;
return message;
}
function extractMockBody(message) {
if (message && message.mock && typeof message.mock === 'object' && message.mock.body && typeof message.mock.body === 'object') {
return message.mock.body;
}
if (message && message.payload && typeof message.payload === 'object' && !Array.isArray(message.payload)) {
return message.payload;
}
return null;
}
function clone(value) {
return value == null ? value : JSON.parse(JSON.stringify(value));
}

1
compiliance-js/oas.txt Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,489 @@
'use strict';
/**
* Node-RED Function 节点脚本准备大模型 HTTP 请求
* 将生成 promptHTTP 请求参数并把上下文写入 msg.llmContext
*/
const DEFAULT_API_KEY = 'sk-lbGrsUPL1iby86h554FaE536C343435dAa9bA65967A840B2';
const DEFAULT_BASE_URL = 'https://aiproxy.petrotech.cnpc/v1';
const DEFAULT_ENDPOINT_PATH = '/chat/completions';
const DEFAULT_MODEL = 'deepseek-v3';
function prepareLlmRequest(msg, node) {
if (!msg.operationId) {
msg.operationId = 'createResource';
}
try {
const apiDoc = extractOpenApiDocument(msg);
const operationCtx = resolveOperationContext(msg, apiDoc);
if (!operationCtx.operation) {
const err = `未找到匹配的接口定义operationId=${operationCtx.requestedOperationId || '未指定'}, path=${operationCtx.requestedPath || '未指定'}, method=${operationCtx.requestedMethod || '未指定'})`;
node.error(err, msg);
msg.error = err;
return null;
}
const prompt = buildPrompt(operationCtx, apiDoc, msg);
const requestPayload = buildRequestPayload(prompt, msg);
const apiKey = (msg.llm && msg.llm.apiKey) || DEFAULT_API_KEY;
const baseUrl = (msg.llm && msg.llm.baseUrl) || DEFAULT_BASE_URL;
const endpointPath = (msg.llm && msg.llm.endpoint) || DEFAULT_ENDPOINT_PATH;
const url = buildUrl(baseUrl, endpointPath);
msg.method = 'POST';
msg.url = url;
msg.headers = Object.assign({}, msg.headers, {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`,
});
msg.payload = requestPayload;
msg.rejectUnauthorized = false;
msg.llmContext = {
prompt,
operationCtx,
provider: 'dashscope',
requestConfig: {
baseUrl,
endpointPath,
model: requestPayload.model,
temperature: requestPayload.temperature,
},
};
delete msg.error;
return msg;
} catch (error) {
node.error(`LLM 请求准备失败:${error.message}`, msg);
msg.error = error.message;
return null;
}
}
function extractOpenApiDocument(message) {
const candidate =
message && message.oas_def ? message.oas_def :
message && message.swagger ? message.swagger :
message && message.payload ? message.payload :
null;
if (!candidate) {
throw new Error('未提供 OpenAPI 文档(需要 msg.oas_def / msg.swagger / msg.payload');
}
if (typeof candidate === 'string') {
try {
return JSON.parse(candidate);
} catch (error) {
throw new Error(`OpenAPI JSON 解析失败:${error.message}`);
}
}
if (typeof candidate !== 'object') {
throw new Error('OpenAPI 文档必须是对象或 JSON 字符串');
}
if (!candidate.paths || typeof candidate.paths !== 'object') {
throw new Error('OpenAPI 文档缺少 paths 字段');
}
return candidate;
}
function resolveOperationContext(message, apiDoc) {
const requestedOperationId =
(message && message.operationId) ||
(message && message.req && message.req.body && message.req.body.operationId) ||
null;
const requestedPath =
(message && message.path) ||
(message && message.req && message.req.body && message.req.body.path) ||
null;
const requestedMethodRaw =
(message && message.method) ||
(message && message.req && message.req.body && message.req.body.method) ||
null;
const requestedMethod = requestedMethodRaw ? String(requestedMethodRaw).toLowerCase() : null;
const operations = enumerateOperations(apiDoc);
let matched = null;
if (requestedOperationId) {
matched = operations.find(op => op.operation.operationId === requestedOperationId);
}
if (!matched && requestedPath && requestedMethod) {
matched = operations.find(op => op.path === requestedPath && op.method === requestedMethod);
}
let autoSelected = false;
if (!matched && operations.length > 0) {
matched = operations[0];
autoSelected = true;
}
return {
operation: matched ? matched.operation : undefined,
method: matched ? matched.method : undefined,
pathItem: matched ? matched.pathItem : undefined,
path: matched ? matched.path : undefined,
autoSelected,
candidates: operations.map(op => ({
operationId: op.operation && op.operation.operationId ? op.operation.operationId : '',
method: op.method ? op.method.toUpperCase() : '',
path: op.path,
summary: op.operation && op.operation.summary ? op.operation.summary : '',
})),
requestedOperationId,
requestedPath,
requestedMethod,
};
}
function enumerateOperations(apiDoc) {
const results = [];
const validMethods = ['get', 'put', 'post', 'delete', 'patch', 'options', 'head', 'trace'];
for (const path of Object.keys(apiDoc.paths || {})) {
const pathItem = apiDoc.paths[path];
if (!pathItem || typeof pathItem !== 'object') {
continue;
}
for (const method of validMethods) {
if (pathItem[method] && typeof pathItem[method] === 'object') {
results.push({
path,
method,
operation: pathItem[method],
pathItem,
});
}
}
}
return results;
}
function buildPrompt(operationCtx, apiDoc, message) {
const operation = operationCtx.operation;
const method = (operationCtx.method || '').toUpperCase();
const path = operationCtx.path || '';
const summary = operation.summary || '';
const description = operation.description || '';
const parameters = gatherParameters(operationCtx, apiDoc);
const requestBodySchema = gatherRequestBodySchema(operation, apiDoc);
const userNotes = message && message.prompt ? String(message.prompt) : '';
const requiredFields = extractRequiredFields(requestBodySchema, apiDoc);
const requiredFieldsText = requiredFields.length > 0
? requiredFields.map(field => `- ${field}`).join('\n')
: '';
const lines = [];
lines.push('你是一名负责生成 HTTP 接口测试参数的助手,请严格输出以下结构的 JSON');
lines.push('{');
lines.push(' "pathParams": { ... },');
lines.push(' "query": { ... },');
lines.push(' "headers": { ... },');
lines.push(' "cookies": { ... },');
lines.push(' "body": { ... },');
lines.push(' "notes": "..."');
lines.push('}');
lines.push('未用到的分区请返回空对象,不要在 JSON 外输出任何文字。');
lines.push('');
lines.push('body.data[0] 必须遵守:');
lines.push('- 覆盖 schema 中声明的全部必填字段,并给出符合字段类型/格式的真实感样例值。');
lines.push('- 非必填字段也尽可能全覆盖');
lines.push('- 不得遗漏必填字段;若 schema 内有嵌套对象/数组的必填字段,同样要补齐。');
lines.push('- 保持数值、日期、字符串等格式,只在 schema 无提示时使用 "sample"、"2025-01-01" 等占位值。');
if (requiredFieldsText) {
lines.push('必填字段清单(必须全部出现在 body.data[0] 中):');
lines.push(requiredFieldsText);
}
lines.push('');
lines.push('若 schema 中存在数组元素或复合结构的必填字段,也要为这些子字段提供值。');
lines.push('');
lines.push(`Target operation: ${method} ${path}`);
if (summary) {
lines.push(`Summary: ${summary}`);
}
if (description) {
lines.push(`Description: ${description}`);
}
if (parameters.length > 0) {
lines.push('Parameters:');
for (const param of parameters) {
lines.push(`- [${param.in}] ${param.name}: ${param.type || 'any'}${param.required ? ' (required)' : ''}${param.description ? ` - ${param.description}` : ''}`);
}
} else {
lines.push('Parameters: none defined.');
}
if (requestBodySchema) {
lines.push('Request body schema (JSON Schema excerpt):');
lines.push(indentSnippet(JSON.stringify(requestBodySchema, null, 2), 2));
} else {
lines.push('Request body: not defined.');
}
if (userNotes) {
lines.push('');
lines.push('User notes:');
lines.push(userNotes);
}
return lines.join('\n');
}
function gatherParameters(operationCtx, apiDoc) {
const aggregated = [];
const seen = new Set();
const sources = [];
if (Array.isArray(operationCtx.pathItem && operationCtx.pathItem.parameters)) {
sources.push(operationCtx.pathItem.parameters);
}
if (Array.isArray(operationCtx.operation.parameters)) {
sources.push(operationCtx.operation.parameters);
}
for (const list of sources) {
for (const param of list) {
if (!param || typeof param !== 'object') {
continue;
}
const resolved = resolveMaybeRef(param, apiDoc);
const key = `${resolved.in}:${resolved.name}`;
if (!resolved.name || seen.has(key)) {
continue;
}
seen.add(key);
aggregated.push({
name: resolved.name,
in: resolved.in || 'query',
required: !!resolved.required,
description: resolved.description || '',
type: resolved.schema ? inferFriendlyType(resolved.schema, apiDoc) : '',
});
}
}
return aggregated;
}
function gatherRequestBodySchema(operation, apiDoc) {
const requestBody = resolveMaybeRef(operation.requestBody, apiDoc);
if (!requestBody || typeof requestBody !== 'object' || !requestBody.content) {
return null;
}
const content = requestBody.content;
const mediaType = Object.keys(content).find(key => key.includes('json')) || Object.keys(content)[0];
if (!mediaType) {
return null;
}
const mediaObject = resolveMaybeRef(content[mediaType], apiDoc);
if (!mediaObject || typeof mediaObject !== 'object' || !mediaObject.schema) {
return null;
}
const schema = resolveMaybeRef(mediaObject.schema, apiDoc);
return resolveSchemaDeep(schema, apiDoc);
}
function inferFriendlyType(schema, apiDoc) {
if (!schema) {
return '';
}
const resolved = resolveMaybeRef(schema, apiDoc);
if (!resolved || typeof resolved !== 'object') {
return '';
}
if (resolved.type) {
if (resolved.type === 'array' && resolved.items) {
const itemType = inferFriendlyType(resolved.items, apiDoc) || 'any';
return `[${itemType}]`;
}
return resolved.type;
}
if (resolved.enum && Array.isArray(resolved.enum)) {
return `enum(${resolved.enum.slice(0, 3).join(', ')}${resolved.enum.length > 3 ? ', …' : ''})`;
}
if (resolved.properties) {
return 'object';
}
return '';
}
function buildRequestPayload(prompt, message) {
const systemPrompt = (message.llm && message.llm.systemPrompt) ||
'You write JSON only. Focus on realistic values for testing HTTP APIs.';
const model = (message.llm && message.llm.model) || DEFAULT_MODEL;
const temperature = (message.llm && typeof message.llm.temperature === 'number')
? message.llm.temperature : 0.2;
return {
model,
temperature,
response_format: { type: 'json_object' },
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: prompt },
],
};
}
function buildUrl(baseUrl, endpointPath) {
const normalizedBase = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
const path = endpointPath.startsWith('/') ? endpointPath.slice(1) : endpointPath;
return `${normalizedBase}${path}`;
}
function resolveMaybeRef(node, apiDoc) {
if (!node || typeof node !== 'object') {
return node;
}
if (!node.$ref) {
return node;
}
const resolved = resolveRef(node.$ref, apiDoc);
if (!resolved || typeof resolved !== 'object') {
return node;
}
const remainder = Object.assign({}, node);
delete remainder.$ref;
return Object.assign({}, clone(resolved), remainder);
}
function resolveRef(ref, apiDoc) {
if (typeof ref !== 'string' || !ref.startsWith('#/')) {
return null;
}
const tokens = ref.slice(2).split('/').map(unescapeRefToken);
let current = apiDoc;
for (const token of tokens) {
if (current && typeof current === 'object' && Object.prototype.hasOwnProperty.call(current, token)) {
current = current[token];
} else {
return null;
}
}
return current;
}
function unescapeRefToken(token) {
return token.replace(/~1/g, '/').replace(/~0/g, '~');
}
function indentSnippet(text, indentLevel, maxLength) {
const trimmed = maxLength && text.length > maxLength ? `${text.slice(0, maxLength)}` : text;
const indent = ' '.repeat(indentLevel * 2);
return trimmed.split('\n').map(line => `${indent}${line}`).join('\n');
}
function clone(value) {
return value == null ? value : JSON.parse(JSON.stringify(value));
}
function extractRequiredFields(schema, apiDoc) {
const result = new Set();
collectRequiredFields(schema, apiDoc, result, '');
return Array.from(result);
}
function collectRequiredFields(schema, apiDoc, result, pathPrefix) {
if (!schema || typeof schema !== 'object') {
return;
}
if (schema.$ref) {
const resolved = resolveRef(schema.$ref, apiDoc);
if (!resolved) {
return;
}
collectRequiredFields(resolveSchemaDeep(resolved, apiDoc), apiDoc, result, pathPrefix);
return;
}
if (Array.isArray(schema.required) && schema.properties && typeof schema.properties === 'object') {
for (const key of schema.required) {
const nextPath = pathPrefix ? `${pathPrefix}.${key}` : key;
result.add(nextPath);
collectRequiredFields(schema.properties[key], apiDoc, result, nextPath);
}
}
if (schema.type === 'array' && schema.items) {
const nextPrefix = pathPrefix ? `${pathPrefix}[]` : '[]';
collectRequiredFields(schema.items, apiDoc, result, nextPrefix);
}
if (schema.allOf && Array.isArray(schema.allOf)) {
for (const part of schema.allOf) {
collectRequiredFields(part, apiDoc, result, pathPrefix);
}
}
}
function resolveSchemaDeep(schema, apiDoc, seen = new Set()) {
if (!schema || typeof schema !== 'object') {
return schema;
}
if (schema.$ref) {
const ref = schema.$ref;
if (seen.has(ref)) {
return {};
}
seen.add(ref);
const resolved = resolveRef(ref, apiDoc);
if (!resolved) {
return schema;
}
const merged = Object.assign({}, clone(resolved), clone(schema));
delete merged.$ref;
return resolveSchemaDeep(merged, apiDoc, seen);
}
const cloned = clone(schema);
if (cloned.properties && typeof cloned.properties === 'object') {
for (const key of Object.keys(cloned.properties)) {
cloned.properties[key] = resolveSchemaDeep(cloned.properties[key], apiDoc, new Set(seen));
}
}
if (cloned.items) {
cloned.items = resolveSchemaDeep(cloned.items, apiDoc, new Set(seen));
}
if (cloned.allOf && Array.isArray(cloned.allOf)) {
cloned.allOf = cloned.allOf.map(item => resolveSchemaDeep(item, apiDoc, new Set(seen)));
}
if (cloned.oneOf && Array.isArray(cloned.oneOf)) {
cloned.oneOf = cloned.oneOf.map(item => resolveSchemaDeep(item, apiDoc, new Set(seen)));
}
if (cloned.anyOf && Array.isArray(cloned.anyOf)) {
cloned.anyOf = cloned.anyOf.map(item => resolveSchemaDeep(item, apiDoc, new Set(seen)));
}
return cloned;
}
return prepareLlmRequest(msg, node);

View File

@ -0,0 +1,48 @@
'use strict';
/**
* Usage: node compiliance-js/preview-prompt.js path/to/oas.json [operationId]
* Reads the OAS file, loads prepare-llm-request.js in a sandbox, and prints the prompt.
*/
const fs = require('fs');
const path = require('path');
const vm = require('vm');
const [, , oasPath, operationIdArg] = process.argv;
if (!oasPath) {
console.error('Usage: node compiliance-js/preview-prompt.js <oas.json> [operationId]');
process.exit(1);
}
const oas = JSON.parse(fs.readFileSync(oasPath, 'utf8'));
let code = fs.readFileSync(path.join(__dirname, 'prepare-llm-request.js'), 'utf8');
code = code.replace(/\nreturn\s+prepareLlmRequest\(msg,\s*node\);?\s*$/m, '\n');
const context = {
msg: { oas_def: oas, operationId: operationIdArg || 'createResource' },
node: {
error: (err) => { throw new Error(err); },
warn: (...args) => console.warn('[WARN]', ...args),
log: (...args) => console.log('[LOG]', ...args),
},
console,
Buffer,
process,
require,
setTimeout,
env: { get: () => undefined },
};
vm.createContext(context);
vm.runInContext(code, context);
if (!context.buildPrompt || !context.resolveOperationContext || !context.extractOpenApiDocument) {
throw new Error('Failed to load helper functions from prepare-llm-request.js');
}
const apiDoc = context.extractOpenApiDocument(context.msg);
const operationCtx = context.resolveOperationContext(context.msg, apiDoc);
const prompt = context.buildPrompt(operationCtx, apiDoc, context.msg);
console.log(prompt);

View File

@ -0,0 +1,119 @@
'use strict';
/**
* Node-RED Function 节点脚本处理大模型 HTTP 响应
* 将响应解析为 mock 数据并写回 msg
*/
function processLlmResponse(msg, node) {
const context = msg.llmContext || {};
if (!context.prompt || !context.operationCtx) {
const err = 'LLM 上下文缺失,无法解析响应';
node.error(err, msg);
msg.error = err;
return msg;
}
if (msg.statusCode && (msg.statusCode < 200 || msg.statusCode >= 300)) {
const err = `大模型接口返回状态码 ${msg.statusCode}`;
node.error(err, msg);
msg.error = err;
return msg;
}
let raw = msg.payload;
if (Buffer.isBuffer(raw)) {
raw = raw.toString('utf8');
}
if (typeof raw === 'string') {
try {
raw = JSON.parse(raw);
} catch (error) {
node.error(`无法解析大模型响应:${error.message}`, msg);
msg.error = error.message;
return msg;
}
}
try {
const parsed = parseModelResponse(raw);
msg.llmRaw = raw;
msg.mock = Object.assign({}, msg.mock, parsed.mock);
msg.mockSource = context.provider || 'dashscope';
msg.mockPrompt = context.prompt;
msg.mockCandidates = context.operationCtx.candidates;
msg.mockAutoSelected = !!context.operationCtx.autoSelected;
msg.payload = msg.mock;
delete msg.llmContext;
if (msg.headers && msg.headers.Authorization) {
delete msg.headers.Authorization;
}
delete msg.error;
return msg;
} catch (error) {
node.error(`大模型参数生成失败:${error.message}`, msg);
msg.error = error.message;
return msg;
}
}
function parseModelResponse(response) {
if (!response || typeof response !== 'object') {
throw new Error('大模型响应为空或不是对象');
}
if (response.mock && typeof response.mock === 'object') {
return { mock: response.mock };
}
const choices = Array.isArray(response.choices) ? response.choices : [];
const firstChoice = choices[0];
const message = firstChoice && firstChoice.message ? firstChoice.message : null;
const content = message && typeof message === 'object' ? message.content : null;
if (!content) {
throw new Error('响应中缺少 choices[0].message.content');
}
let mockObject;
if (typeof content === 'string') {
try {
mockObject = JSON.parse(content);
} catch (err) {
throw new Error(`无法解析模型返回的 JSON${err.message},原始文本:${content}`);
}
} else if (Array.isArray(content)) {
const jsonPart = content.find(part => part.type === 'output_text' || part.type === 'text' || part.type === 'json');
const text = jsonPart && jsonPart.text ? jsonPart.text : null;
if (!text) {
throw new Error('响应内容不是字符串,且未找到可解析的文本段');
}
try {
mockObject = JSON.parse(text);
} catch (err) {
throw new Error(`无法解析模型返回的 JSON${err.message},原始文本:${text}`);
}
} else {
throw new Error('模型返回的 message.content 既不是字符串也不是文本片段数组');
}
if (!mockObject || typeof mockObject !== 'object') {
throw new Error('模型返回的 JSON 不是对象');
}
const normalisedMock = {
pathParams: mockObject.pathParams || mockObject.path_parameters || {},
query: mockObject.query || mockObject.query_params || {},
headers: mockObject.headers || {},
cookies: mockObject.cookies || {},
body: mockObject.body || {},
notes: mockObject.notes || '',
};
return { mock: normalisedMock };
}
return processLlmResponse(msg, node);