diff --git a/.node-red-data/.config.nodes.json b/.node-red-data/.config.nodes.json index d695208..ed7ca8c 100644 --- a/.node-red-data/.config.nodes.json +++ b/.node-red-data/.config.nodes.json @@ -14,7 +14,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\common\\05-junction.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\common\\05-junction.js" }, "inject": { "name": "inject", @@ -25,7 +25,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\common\\20-inject.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\common\\20-inject.js" }, "debug": { "name": "debug", @@ -36,7 +36,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\common\\21-debug.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\common\\21-debug.js" }, "complete": { "name": "complete", @@ -47,7 +47,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\common\\24-complete.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\common\\24-complete.js" }, "catch": { "name": "catch", @@ -58,7 +58,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\common\\25-catch.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\common\\25-catch.js" }, "status": { "name": "status", @@ -69,7 +69,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\common\\25-status.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\common\\25-status.js" }, "link": { "name": "link", @@ -82,7 +82,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\common\\60-link.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\common\\60-link.js" }, "comment": { "name": "comment", @@ -93,7 +93,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\common\\90-comment.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\common\\90-comment.js" }, "global-config": { "name": "global-config", @@ -104,7 +104,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\common\\91-global-config.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\common\\91-global-config.js" }, "unknown": { "name": "unknown", @@ -115,7 +115,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\common\\98-unknown.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\common\\98-unknown.js" }, "function": { "name": "function", @@ -126,7 +126,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\function\\10-function.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\function\\10-function.js" }, "switch": { "name": "switch", @@ -137,7 +137,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\function\\10-switch.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\function\\10-switch.js" }, "change": { "name": "change", @@ -148,7 +148,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\function\\15-change.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\function\\15-change.js" }, "range": { "name": "range", @@ -159,7 +159,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\function\\16-range.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\function\\16-range.js" }, "template": { "name": "template", @@ -170,7 +170,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\function\\80-template.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\function\\80-template.js" }, "delay": { "name": "delay", @@ -181,7 +181,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\function\\89-delay.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\function\\89-delay.js" }, "trigger": { "name": "trigger", @@ -192,7 +192,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\function\\89-trigger.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\function\\89-trigger.js" }, "exec": { "name": "exec", @@ -203,7 +203,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\function\\90-exec.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\function\\90-exec.js" }, "rbe": { "name": "rbe", @@ -214,7 +214,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\function\\rbe.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\function\\rbe.js" }, "tls": { "name": "tls", @@ -225,7 +225,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\network\\05-tls.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\network\\05-tls.js" }, "httpproxy": { "name": "httpproxy", @@ -236,7 +236,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\network\\06-httpproxy.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\network\\06-httpproxy.js" }, "mqtt": { "name": "mqtt", @@ -249,7 +249,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\network\\10-mqtt.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\network\\10-mqtt.js" }, "httpin": { "name": "httpin", @@ -261,7 +261,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\network\\21-httpin.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\network\\21-httpin.js" }, "httprequest": { "name": "httprequest", @@ -272,7 +272,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\network\\21-httprequest.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\network\\21-httprequest.js" }, "websocket": { "name": "websocket", @@ -286,7 +286,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\network\\22-websocket.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\network\\22-websocket.js" }, "tcpin": { "name": "tcpin", @@ -299,7 +299,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\network\\31-tcpin.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\network\\31-tcpin.js" }, "udp": { "name": "udp", @@ -311,7 +311,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\network\\32-udp.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\network\\32-udp.js" }, "CSV": { "name": "CSV", @@ -322,7 +322,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\parsers\\70-CSV.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\parsers\\70-CSV.js" }, "HTML": { "name": "HTML", @@ -333,7 +333,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\parsers\\70-HTML.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\parsers\\70-HTML.js" }, "JSON": { "name": "JSON", @@ -344,7 +344,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\parsers\\70-JSON.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\parsers\\70-JSON.js" }, "XML": { "name": "XML", @@ -355,7 +355,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\parsers\\70-XML.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\parsers\\70-XML.js" }, "YAML": { "name": "YAML", @@ -366,7 +366,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\parsers\\70-YAML.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\parsers\\70-YAML.js" }, "split": { "name": "split", @@ -378,7 +378,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\sequence\\17-split.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\sequence\\17-split.js" }, "sort": { "name": "sort", @@ -389,7 +389,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\sequence\\18-sort.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\sequence\\18-sort.js" }, "batch": { "name": "batch", @@ -400,7 +400,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\sequence\\19-batch.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\sequence\\19-batch.js" }, "file": { "name": "file", @@ -412,7 +412,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\storage\\10-file.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\storage\\10-file.js" }, "watch": { "name": "watch", @@ -423,7 +423,7 @@ "local": false, "user": false, "module": "node-red", - "file": "C:\\workspace\\动态合规\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\storage\\23-watch.js" + "file": "D:\\workspace\\zzlh\\node-red\\packages\\node_modules\\@node-red\\nodes\\core\\storage\\23-watch.js" } } } diff --git a/.node-red-data/projects/zsy/flows.json b/.node-red-data/projects/zsy/flows.json index 208861e..53ce29a 100644 --- a/.node-red-data/projects/zsy/flows.json +++ b/.node-red-data/projects/zsy/flows.json @@ -2154,8 +2154,8 @@ "name": "", "statusCode": "msg.testcase.results", "headers": {}, - "x": 900, - "y": 260, + "x": 1120, + "y": 280, "wires": [] }, { @@ -2170,7 +2170,7 @@ "initialize": "", "finalize": "", "libs": [], - "x": 340, + "x": 300, "y": 40, "wires": [ [ @@ -2206,8 +2206,8 @@ "once": false, "onceDelay": 0.1, "topic": "", - "x": 110, - "y": 300, + "x": 70, + "y": 280, "wires": [ [ "211c041ab58c88f2" @@ -2218,8 +2218,8 @@ "id": "dfe6ed572461c4a5", "type": "debug", "z": "78d15f59dee4b6d8", - "name": "debug 10", - "active": false, + "name": "转化结果", + "active": true, "tosidebar": true, "console": false, "tostatus": false, @@ -2238,7 +2238,7 @@ "name": "", "scope": null, "uncaught": false, - "x": 160, + "x": 80, "y": 460, "wires": [ [ @@ -2259,8 +2259,8 @@ "targetType": "full", "statusVal": "", "statusType": "auto", - "x": 440, - "y": 460, + "x": 260, + "y": 400, "wires": [] }, { @@ -2268,14 +2268,14 @@ "type": "file", "z": "78d15f59dee4b6d8", "name": "", - "filename": "C:\\workspace\\test2.txt", + "filename": "D:\\workspace\\zzlh\\node-red\\compiliance-js\\dms2oas.txt", "filenameType": "str", "appendNewline": true, "createDir": false, "overwriteFile": "false", "encoding": "none", - "x": 760, - "y": 200, + "x": 850, + "y": 220, "wires": [ [] ] @@ -2329,8 +2329,8 @@ "initialize": "", "finalize": "", "libs": [], - "x": 350, - "y": 640, + "x": 410, + "y": 560, "wires": [ [ "540a5771113ca5dd", @@ -2432,8 +2432,8 @@ "initialize": "", "finalize": "", "libs": [], - "x": 360, - "y": 340, + "x": 480, + "y": 380, "wires": [ [ "76358bc84ac6e3e1" @@ -2452,11 +2452,11 @@ "initialize": "", "finalize": "", "libs": [], - "x": 270, - "y": 560, + "x": 170, + "y": 580, "wires": [ [ - "60ce224bd2ed8e69" + "4b89069c3dcb949e" ] ] }, @@ -2472,8 +2472,8 @@ "initialize": "", "finalize": "", "libs": [], - "x": 940, - "y": 520, + "x": 1160, + "y": 700, "wires": [ [ "cfa3c9b06af4840e", @@ -2507,8 +2507,8 @@ "links": [ "6c06fbe60248c14c" ], - "x": 1155, - "y": 540, + "x": 1215, + "y": 520, "wires": [] }, { @@ -2574,11 +2574,10 @@ "initialize": "", "finalize": "", "libs": [], - "x": 560, + "x": 720, "y": 160, "wires": [ [ - "dfe6ed572461c4a5", "d666851b16cf6482" ] ] @@ -2604,8 +2603,8 @@ "once": false, "onceDelay": 0.1, "topic": "", - "x": 70, - "y": 180, + "x": 90, + "y": 160, "wires": [ [ "211c041ab58c88f2" @@ -2616,19 +2615,20 @@ "id": "41818dbfe05be246", "type": "function", "z": "78d15f59dee4b6d8", - "name": "function 6", - "func": "'use strict';\n\ntry {\n const rawOas = extractOasInput(msg);\n const oasDef = typeof rawOas === 'string' ? JSON.parse(rawOas) : rawOas;\n\n validateOas(oasDef);\n\n const incomingCrudConfig = msg.req && msg.req.body && msg.req.body.crudConfig;\n if (isPlainObject(incomingCrudConfig)) {\n msg.crudConfig = mergeDeep({}, msg.crudConfig || {}, incomingCrudConfig);\n }\n\n if ((!msg.crudConfig || !msg.crudConfig.baseUrl) && Array.isArray(oasDef.servers) && oasDef.servers.length) {\n const validServer = oasDef.servers.find(server => server && typeof server.url === 'string' && server.url.trim());\n if (validServer) {\n msg.crudConfig = msg.crudConfig || {};\n msg.crudConfig.baseUrl = trimTrailingSlash(validServer.url.trim());\n }\n }\n\n msg.oas_def = oasDef;\n msg.payload = oasDef;\n delete msg.error;\n return msg;\n} catch (err) {\n node.error(`读取 OAS 定义失败: ${err.message}`, msg);\n msg.error = err.message;\n return msg;\n}\n\nfunction extractOasInput(message) {\n if (message && message.req && message.req.body && message.req.body.schema !== undefined) {\n return message.req.body.schema;\n }\n\n if (message && message.payload && message.payload.schema !== undefined) {\n return message.payload.schema;\n }\n\n throw new Error('未在 msg.req.body.schema 找到 OAS 定义');\n}\n\nfunction validateOas(oas) {\n if (!oas || typeof oas !== 'object') {\n throw new Error('OAS 文档必须是对象或 JSON 字符串');\n }\n\n if (!oas.openapi && !oas.swagger) {\n throw new Error('OpenAPI 文档缺少 openapi/swagger 字段');\n }\n\n if (!oas.paths || typeof oas.paths !== 'object' || Object.keys(oas.paths).length === 0) {\n throw new Error('OpenAPI 文档缺少 paths 定义');\n }\n}\n\nfunction mergeDeep(target, ...sources) {\n for (const source of sources) {\n if (!isPlainObject(source)) {\n continue;\n }\n\n for (const key of Object.keys(source)) {\n const value = source[key];\n if (value === undefined) {\n continue;\n }\n\n if (isPlainObject(value)) {\n const base = isPlainObject(target[key]) ? target[key] : {};\n target[key] = mergeDeep({}, base, value);\n } else {\n target[key] = clone(value);\n }\n }\n }\n\n return target;\n}\n\nfunction isPlainObject(candidate) {\n return Object.prototype.toString.call(candidate) === '[object Object]';\n}\n\nfunction clone(value) {\n return value == null ? value : JSON.parse(JSON.stringify(value));\n}\n\nfunction trimTrailingSlash(url) {\n return !url ? '' : url.replace(/\\/+$/, '');\n}\n", + "name": "直接处理oas", + "func": "'use strict';\n\ntry {\n const rawOas = extractOasInput(msg);\n const oasDef = typeof rawOas === 'string' ? JSON.parse(rawOas) : rawOas;\n\n validateOas(oasDef);\n\n const incomingCrudConfig = msg.req && msg.req.body && msg.req.body.crudConfig;\n if (isPlainObject(incomingCrudConfig)) {\n msg.crudConfig = mergeDeep({}, msg.crudConfig || {}, incomingCrudConfig);\n }\n\n if ((!msg.crudConfig || !msg.crudConfig.baseUrl) && Array.isArray(oasDef.servers) && oasDef.servers.length) {\n const validServer = oasDef.servers.find(server => server && typeof server.url === 'string' && server.url.trim());\n if (validServer) {\n msg.crudConfig = msg.crudConfig || {};\n msg.crudConfig.baseUrl = trimTrailingSlash(validServer.url.trim());\n }\n }\n\n msg.oas_def = oasDef;\n msg.payload = oasDef;\n delete msg.error;\n return msg;\n} catch (err) {\n node.error(`读取 OAS 定义失败: ${err.message}`, msg);\n if (node.warn) {\n node.warn(`Debug - msg keys: ${Object.keys(msg).join(',')}`);\n if (msg.req) node.warn(`Debug - msg.req.body type: ${typeof msg.req.body}`);\n node.warn(`Debug - msg.payload type: ${typeof msg.payload}`);\n }\n msg.error = err.message;\n return msg;\n}\n\nfunction extractOasInput(message) {\n // Helper to check if an object looks like OAS\n const isOas = (obj) => obj && (obj.openapi || obj.swagger);\n\n // 1. Check msg.req.body\n let reqBody = message.req && message.req.body;\n // If body is string, try to parse it (common in some HTTP inputs)\n if (typeof reqBody === 'string') {\n try { reqBody = JSON.parse(reqBody); } catch(e) {}\n }\n \n if (reqBody && typeof reqBody === 'object') {\n if (reqBody.schema !== undefined) return reqBody.schema;\n if (isOas(reqBody)) return reqBody;\n }\n\n // 2. Check msg.payload\n let payload = message.payload;\n // If payload is string, try to parse it\n if (typeof payload === 'string') {\n try { payload = JSON.parse(payload); } catch(e) {}\n }\n\n if (payload && typeof payload === 'object') {\n if (payload.schema !== undefined) return payload.schema;\n if (isOas(payload)) return payload;\n }\n\n // 3. Check msg.schema (direct injection fallback)\n if (message.schema !== undefined) return message.schema;\n\n // 4. Fallback: if original payload was a string and we haven't found anything yet, return it \n // (The main logic will try to parse it as OAS)\n if (typeof message.payload === 'string') return message.payload;\n\n throw new Error('未找到 OAS 定义。已检查: msg.req.body.schema, msg.req.body, msg.payload.schema, msg.payload, msg.schema');\n}\n\nfunction validateOas(oas) {\n if (!oas || typeof oas !== 'object') {\n throw new Error('OAS 文档必须是对象或 JSON 字符串');\n }\n\n if (!oas.openapi && !oas.swagger) {\n throw new Error('OpenAPI 文档缺少 openapi/swagger 字段');\n }\n\n if (!oas.paths || typeof oas.paths !== 'object' || Object.keys(oas.paths).length === 0) {\n throw new Error('OpenAPI 文档缺少 paths 定义');\n }\n}\n\nfunction mergeDeep(target, ...sources) {\n for (const source of sources) {\n if (!isPlainObject(source)) {\n continue;\n }\n\n for (const key of Object.keys(source)) {\n const value = source[key];\n if (value === undefined) {\n continue;\n }\n\n if (isPlainObject(value)) {\n const base = isPlainObject(target[key]) ? target[key] : {};\n target[key] = mergeDeep({}, base, value);\n } else {\n target[key] = clone(value);\n }\n }\n }\n\n return target;\n}\n\nfunction isPlainObject(candidate) {\n return Object.prototype.toString.call(candidate) === '[object Object]';\n}\n\nfunction clone(value) {\n return value == null ? value : JSON.parse(JSON.stringify(value));\n}\n\nfunction trimTrailingSlash(url) {\n return !url ? '' : url.replace(/\\/+$/, '');\n}\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 260, - "y": 160, + "x": 270, + "y": 140, "wires": [ [ - "9c84414e55654e6a" + "9c84414e55654e6a", + "b8560bc29323d193" ] ] }, @@ -2637,7 +2637,7 @@ "type": "switch", "z": "78d15f59dee4b6d8", "name": "", - "property": "req.bod", + "property": "req.body", "propertyType": "msg", "rules": [ { @@ -2662,5 +2662,90 @@ "f414ed448fcf544b" ] ] + }, + { + "id": "b8560bc29323d193", + "type": "debug", + "z": "78d15f59dee4b6d8", + "name": "直接输出结果", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "msg", + "targetType": "jsonata", + "statusVal": "", + "statusType": "auto", + "x": 280, + "y": 300, + "wires": [] + }, + { + "id": "1d3dbb308f165b24", + "type": "file", + "z": "78d15f59dee4b6d8", + "name": "", + "filename": "D:\\workspace\\zzlh\\node-red\\compiliance-js\\directoas.txt", + "filenameType": "str", + "appendNewline": true, + "createDir": false, + "overwriteFile": "false", + "encoding": "none", + "x": 750, + "y": 260, + "wires": [ + [] + ] + }, + { + "id": "83f73f7e3191d853", + "type": "function", + "z": "78d15f59dee4b6d8", + "name": "根据 OpenAPI Schema 生成默认请求参数", + "func": "'use strict';\n\n/**\n * Node-RED Function 节点脚本:根据 OpenAPI Schema 生成默认请求参数。\n * 作为 LLM 生成的兜底或并行方案,不调用 LLM,直接根据 Schema 定义生成示例值。\n * \n * 输入:\n * - msg.oas_def / msg.payload / msg.swagger: OpenAPI 3.x 文档对象或 JSON 字符串\n * - msg.operationId: 目标 operationId (通常由上游 \"设置create上下文\" 节点设置)\n * \n * 输出:\n * - msg.mock: { \n * operationId, method, path, mediaType, \n * pathParams, query, headers, cookies, body \n * }\n * - msg.payload: 同 msg.mock\n * - msg.mockSource: 'rule-based'\n */\n\nlet openApi;\ntry {\n openApi = extractOpenApiDocument(msg);\n} catch (error) {\n node.error(`Swagger 自动填充初始化失败:${error.message}`, msg);\n msg.error = error.message;\n return msg;\n}\n\nconst operationCtx = resolveOperationContext(msg, openApi);\nif (!operationCtx.operation) {\n const message = `未找到匹配的接口定义(operationId=${operationCtx.requestedOperationId || '未指定'}, path=${operationCtx.requestedPath || '未指定'}, method=${operationCtx.requestedMethod || '未指定'})`;\n node.error(message, msg);\n msg.error = message;\n return msg;\n}\n\nconst parameterSamples = buildParameterSamples(operationCtx, openApi);\nconst bodySample = buildRequestBodySample(operationCtx.operation, openApi);\n\nmsg.mock = Object.assign({}, msg.mock, {\n operationId: operationCtx.operation.operationId || operationCtx.requestedOperationId || '',\n method: (operationCtx.method || '').toUpperCase(),\n path: operationCtx.path || '',\n mediaType: bodySample.mediaType,\n pathParams: parameterSamples.path,\n query: parameterSamples.query,\n headers: parameterSamples.header,\n cookies: parameterSamples.cookie,\n body: bodySample.payload,\n});\n\nmsg.payload = msg.mock;\nmsg.mockCandidates = operationCtx.candidates;\nif (operationCtx.autoSelected) {\n msg.mockAutoSelected = true;\n}\n\n// 标记来源为规则生成,以便后续区分\nmsg.mockSource = 'rule-based';\n\nreturn msg;\n\n// --------------------------------------------------------------------------------\n// Helper Functions\n// --------------------------------------------------------------------------------\n\nfunction extractOpenApiDocument(message) {\n const candidate =\n message && message.oas_def ? message.oas_def :\n message && message.swagger ? message.swagger :\n message && message.payload ? message.payload :\n null;\n\n if (!candidate) {\n throw new Error('未提供 OpenAPI 文档(需要 msg.oas_def / msg.swagger / msg.payload)');\n }\n\n if (typeof candidate === 'string') {\n try {\n return JSON.parse(candidate);\n } catch (error) {\n throw new Error(`OpenAPI JSON 解析失败:${error.message}`);\n }\n }\n\n if (typeof candidate !== 'object') {\n throw new Error('OpenAPI 文档必须是对象或 JSON 字符串');\n }\n\n if (!candidate.paths || typeof candidate.paths !== 'object') {\n throw new Error('OpenAPI 文档缺少 paths 字段');\n }\n\n return candidate;\n}\n\nfunction resolveOperationContext(message, apiDoc) {\n const requestedOperationId =\n (message && message.operationId) ||\n (message && message.req && message.req.body && message.req.body.operationId) ||\n null;\n\n const requestedPath =\n (message && message.path) ||\n (message && message.req && message.req.body && message.req.body.path) ||\n null;\n\n const requestedMethodRaw =\n (message && message.method) ||\n (message && message.req && message.req.body && message.req.body.method) ||\n null;\n const requestedMethod = requestedMethodRaw ? String(requestedMethodRaw).toLowerCase() : null;\n\n const operations = enumerateOperations(apiDoc);\n\n let matched = null;\n if (requestedOperationId) {\n matched = operations.find(op => op.operation.operationId === requestedOperationId);\n }\n\n if (!matched && requestedPath && requestedMethod) {\n matched = operations.find(op => op.path === requestedPath && op.method === requestedMethod);\n }\n\n let autoSelected = false;\n if (!matched && operations.length > 0) {\n matched = operations[0];\n autoSelected = true;\n }\n\n return {\n operation: matched ? matched.operation : undefined,\n method: matched ? matched.method : undefined,\n pathItem: matched ? matched.pathItem : undefined,\n path: matched ? matched.path : undefined,\n autoSelected,\n candidates: operations.map(op => ({\n operationId: op.operation && op.operation.operationId ? op.operation.operationId : '',\n method: op.method ? op.method.toUpperCase() : '',\n path: op.path,\n summary: op.operation && op.operation.summary ? op.operation.summary : '',\n })),\n requestedOperationId,\n requestedPath,\n requestedMethod,\n };\n}\n\nfunction enumerateOperations(apiDoc) {\n const results = [];\n const validMethods = ['get', 'put', 'post', 'delete', 'patch', 'options', 'head', 'trace'];\n for (const path of Object.keys(apiDoc.paths || {})) {\n const pathItem = apiDoc.paths[path];\n if (!pathItem || typeof pathItem !== 'object') {\n continue;\n }\n for (const method of validMethods) {\n if (pathItem[method] && typeof pathItem[method] === 'object') {\n results.push({\n path,\n method,\n operation: pathItem[method],\n pathItem,\n });\n }\n }\n }\n return results;\n}\n\nfunction buildParameterSamples(operationContext, apiDoc) {\n const aggregated = {\n path: {},\n query: {},\n header: {},\n cookie: {},\n };\n\n const seen = new Set();\n const paramSources = [];\n if (Array.isArray(operationContext.pathItem && operationContext.pathItem.parameters)) {\n paramSources.push(operationContext.pathItem.parameters);\n }\n if (Array.isArray(operationContext.operation.parameters)) {\n paramSources.push(operationContext.operation.parameters);\n }\n\n for (const source of paramSources) {\n for (const param of source) {\n if (!param || typeof param !== 'object') {\n continue;\n }\n const name = param.name || '';\n const location = (param.in || 'query').toLowerCase();\n const key = `${location}:${name}`;\n if (!name || seen.has(key)) {\n continue;\n }\n seen.add(key);\n\n const resolvedParam = resolveMaybeRef(param, apiDoc);\n const targetBucket = aggregated[location] || aggregated.query;\n targetBucket[name] = generateParameterSample(resolvedParam, apiDoc, location);\n }\n }\n\n return aggregated;\n}\n\nfunction generateParameterSample(param, apiDoc, location) {\n if (!param || typeof param !== 'object') {\n return 'sample';\n }\n\n const example = pickExample(param);\n if (example !== undefined) {\n return example;\n }\n\n const schema = resolveMaybeRef(param.schema, apiDoc);\n const sample = generateSampleValue(schema, apiDoc, 0, new Set(), param.name);\n if (sample !== undefined) {\n return sample;\n }\n\n // fallback\n switch (location) {\n case 'path':\n return 'sample-id';\n case 'header':\n return 'sample-header';\n case 'cookie':\n return 'sample-cookie';\n default:\n return 'sample';\n }\n}\n\nfunction buildRequestBodySample(operation, apiDoc) {\n const resolvedBody = resolveMaybeRef(operation.requestBody, apiDoc);\n if (!resolvedBody || typeof resolvedBody !== 'object') {\n return { payload: undefined, mediaType: undefined };\n }\n\n const content = resolvedBody.content;\n if (!content || typeof content !== 'object') {\n return { payload: undefined, mediaType: undefined };\n }\n\n const preferredMediaTypes = [\n 'application/json',\n 'application/*+json',\n 'application/x-www-form-urlencoded',\n 'multipart/form-data',\n 'text/plain',\n ];\n\n let chosenMediaType = null;\n for (const mediaType of preferredMediaTypes) {\n if (content[mediaType]) {\n chosenMediaType = mediaType;\n break;\n }\n }\n if (!chosenMediaType) {\n const mediaKeys = Object.keys(content);\n chosenMediaType = mediaKeys.length > 0 ? mediaKeys[0] : null;\n }\n if (!chosenMediaType) {\n return { payload: undefined, mediaType: undefined };\n }\n\n const mediaObject = content[chosenMediaType];\n if (!mediaObject || typeof mediaObject !== 'object') {\n return { payload: undefined, mediaType: chosenMediaType };\n }\n\n if (mediaObject.example !== undefined) {\n return { payload: clone(mediaObject.example), mediaType: chosenMediaType };\n }\n if (mediaObject.examples && typeof mediaObject.examples === 'object') {\n const firstExample = Object.values(mediaObject.examples)[0];\n if (firstExample && typeof firstExample === 'object' && firstExample.value !== undefined) {\n return { payload: clone(firstExample.value), mediaType: chosenMediaType };\n }\n }\n\n const schema = resolveMaybeRef(mediaObject.schema, apiDoc);\n const payload = generateSampleValue(schema, apiDoc);\n return { payload, mediaType: chosenMediaType };\n}\n\nfunction pickExample(node) {\n if (!node || typeof node !== 'object') {\n return undefined;\n }\n if (node.example !== undefined) {\n return clone(node.example);\n }\n if (node.examples && typeof node.examples === 'object') {\n const first = Object.values(node.examples)[0];\n if (first && typeof first === 'object' && first.value !== undefined) {\n return clone(first.value);\n }\n }\n if (node.default !== undefined) {\n return clone(node.default);\n }\n return undefined;\n}\n\nfunction generateSampleValue(schema, apiDoc, depth = 0, seenRefs = new Set(), fieldName = null) {\n if (!schema || typeof schema !== 'object') {\n return undefined;\n }\n\n // Special handling for identity field\n if (fieldName) {\n const identityField = (msg.crudFlow && msg.crudFlow.identityField) || 'dsid';\n if (fieldName === identityField) {\n return `zzlh_testkey${Date.now()}`;\n }\n }\n\n if (depth > 8) {\n return undefined;\n }\n\n if (schema.example !== undefined) {\n return clone(schema.example);\n }\n if (schema.default !== undefined) {\n return clone(schema.default);\n }\n if (schema.const !== undefined) {\n return clone(schema.const);\n }\n if (Array.isArray(schema.enum) && schema.enum.length > 0) {\n return clone(schema.enum[0]);\n }\n\n if (schema.$ref) {\n if (seenRefs.has(schema.$ref)) {\n return undefined;\n }\n seenRefs.add(schema.$ref);\n const resolved = resolveMaybeRef(schema, apiDoc, seenRefs);\n return generateSampleValue(resolved, apiDoc, depth + 1, seenRefs, fieldName);\n }\n\n if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0) {\n return generateSampleValue(schema.oneOf[0], apiDoc, depth + 1, seenRefs, fieldName);\n }\n if (Array.isArray(schema.anyOf) && schema.anyOf.length > 0) {\n return generateSampleValue(schema.anyOf[0], apiDoc, depth + 1, seenRefs, fieldName);\n }\n if (Array.isArray(schema.allOf) && schema.allOf.length > 0) {\n const merged = schema.allOf\n .map(part => resolveMaybeRef(part, apiDoc, seenRefs))\n .filter(part => part && typeof part === 'object')\n .reduce((acc, part) => Object.assign(acc, part), {});\n return generateSampleValue(merged, apiDoc, depth + 1, seenRefs, fieldName);\n }\n\n const type = inferSchemaType(schema);\n switch (type) {\n case 'object': {\n const result = {};\n if (schema.properties && typeof schema.properties === 'object') {\n for (const [key, value] of Object.entries(schema.properties)) {\n const resolvedChild = resolveMaybeRef(value, apiDoc, seenRefs);\n const childSample = generateSampleValue(resolvedChild, apiDoc, depth + 1, seenRefs, key);\n if (childSample !== undefined) {\n result[key] = childSample;\n }\n }\n }\n const required = Array.isArray(schema.required) ? schema.required : [];\n for (const propertyName of required) {\n if (!Object.prototype.hasOwnProperty.call(result, propertyName)) {\n result[propertyName] = pickFallbackByFormat({ type: 'string' });\n }\n }\n return Object.keys(result).length > 0 ? result : {};\n }\n case 'array': {\n const itemSchema = resolveMaybeRef(schema.items, apiDoc, seenRefs) || { type: 'string' };\n const itemSample = generateSampleValue(itemSchema, apiDoc, depth + 1, seenRefs);\n return itemSample !== undefined ? [itemSample] : [];\n }\n case 'integer':\n return Number.isInteger(schema.minimum) ? schema.minimum : 1;\n case 'number':\n if (schema.minimum !== undefined) {\n return typeof schema.minimum === 'number' ? schema.minimum : 0;\n }\n return 1;\n case 'boolean':\n return true;\n case 'string':\n default:\n return pickFallbackByFormat(schema);\n }\n}\n\nfunction inferSchemaType(schema) {\n if (!schema || typeof schema !== 'object') {\n return 'string';\n }\n if (schema.type) {\n return schema.type;\n }\n if (schema.properties) {\n return 'object';\n }\n if (schema.items) {\n return 'array';\n }\n return 'string';\n}\n\nfunction pickFallbackByFormat(schema) {\n const format = schema && schema.format ? schema.format.toLowerCase() : null;\n switch (format) {\n case 'date':\n return '2025-01-01';\n case 'date-time':\n return '2025-01-01T00:00:00Z';\n case 'email':\n return 'user@example.com';\n case 'uuid':\n return '00000000-0000-4000-8000-000000000000';\n case 'uri':\n case 'url':\n return 'https://example.com/resource';\n case 'byte':\n return Buffer.from('sample').toString('base64');\n case 'binary':\n return '';\n default:\n break;\n }\n\n const pattern = schema && schema.pattern;\n if (pattern && /[0-9]{4}-[0-9]{2}-[0-9]{2}/.test(pattern)) {\n return '2025-01-01';\n }\n\n return 'sample';\n}\n\nfunction resolveMaybeRef(node, apiDoc, seenRefs = new Set()) {\n if (!node || typeof node !== 'object') {\n return node;\n }\n if (!node.$ref) {\n return node;\n }\n\n const ref = node.$ref;\n if (seenRefs.has(ref)) {\n return {};\n }\n seenRefs.add(ref);\n\n const resolved = resolveRef(ref, apiDoc);\n if (!resolved || typeof resolved !== 'object') {\n return node;\n }\n\n const remainder = Object.assign({}, node);\n delete remainder.$ref;\n\n return Object.assign({}, clone(resolved), remainder);\n}\n\nfunction resolveRef(ref, apiDoc) {\n if (typeof ref !== 'string' || !ref.startsWith('#/')) {\n return null;\n }\n\n const tokens = ref.slice(2).split('/').map(unescapeRefToken);\n let current = apiDoc;\n for (const token of tokens) {\n if (current && typeof current === 'object' && Object.prototype.hasOwnProperty.call(current, token)) {\n current = current[token];\n } else {\n return null;\n }\n }\n return current;\n}\n\nfunction unescapeRefToken(token) {\n return token.replace(/~1/g, '/').replace(/~0/g, '~');\n}\n\nfunction clone(value) {\n return value == null ? value : JSON.parse(JSON.stringify(value));\n}\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 540, + "y": 820, + "wires": [ + [ + "31d5f84e4c6f69f6" + ] + ] + }, + { + "id": "4b89069c3dcb949e", + "type": "switch", + "z": "78d15f59dee4b6d8", + "name": "", + "property": "req.body", + "propertyType": "msg", + "rules": [ + { + "t": "hask", + "v": "useLLM", + "vt": "str" + }, + { + "t": "else" + } + ], + "checkall": "true", + "repair": false, + "outputs": 2, + "x": 230, + "y": 720, + "wires": [ + [ + "60ce224bd2ed8e69" + ], + [ + "83f73f7e3191d853" + ] + ] } ] \ No newline at end of file