js
This commit is contained in:
parent
fdf4aa5ef4
commit
d9b08c89ee
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
696
compiliance-js/dms2oas.js
Normal 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;
|
||||||
|
}
|
||||||
79
compiliance-js/flow1-compose-create-request.js
Normal file
79
compiliance-js/flow1-compose-create-request.js
Normal 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));
|
||||||
|
}
|
||||||
78
compiliance-js/flow1-compose-delete-request.js
Normal file
78
compiliance-js/flow1-compose-delete-request.js
Normal 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));
|
||||||
|
}
|
||||||
45
compiliance-js/flow1-compose-list-request.js
Normal file
45
compiliance-js/flow1-compose-list-request.js
Normal 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));
|
||||||
|
}
|
||||||
84
compiliance-js/flow1-handle-create-response.js
Normal file
84
compiliance-js/flow1-handle-create-response.js
Normal 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;
|
||||||
|
}
|
||||||
46
compiliance-js/flow1-summarize-crud.js
Normal file
46
compiliance-js/flow1-summarize-crud.js
Normal 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;
|
||||||
|
}
|
||||||
334
compiliance-js/flow2-build-crud-plan.js
Normal file
334
compiliance-js/flow2-build-crud-plan.js
Normal file
@ -0,0 +1,334 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建 CRUD 执行动作所需的指令集合,保存于 msg.crudFlow。
|
||||||
|
* 约定 operationId 采用 dms→oas 转换后默认的命名:
|
||||||
|
* 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]';
|
||||||
|
}
|
||||||
32
compiliance-js/flow2-set-create-context.js
Normal file
32
compiliance-js/flow2-set-create-context.js
Normal 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;
|
||||||
|
}
|
||||||
62
compiliance-js/flow2-store-create-result.js
Normal file
62
compiliance-js/flow2-store-create-result.js
Normal 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
1
compiliance-js/oas.txt
Normal file
File diff suppressed because one or more lines are too long
489
compiliance-js/prepare-llm-request.js
Normal file
489
compiliance-js/prepare-llm-request.js
Normal file
@ -0,0 +1,489 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Node-RED Function 节点脚本:准备大模型 HTTP 请求。
|
||||||
|
* 将生成 prompt、HTTP 请求参数,并把上下文写入 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);
|
||||||
48
compiliance-js/preview-prompt.js
Normal file
48
compiliance-js/preview-prompt.js
Normal 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);
|
||||||
119
compiliance-js/process-llm-response.js
Normal file
119
compiliance-js/process-llm-response.js
Normal 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);
|
||||||
Loading…
x
Reference in New Issue
Block a user