ruoyunbai 3b0c405698 flow
2025-11-04 17:37:52 +08:00

2545 lines
161 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[
{
"id": "0ac020c7380665e7",
"type": "tab",
"label": "测试流程",
"disabled": false,
"info": "",
"env": []
},
{
"id": "2bbc1a949095492c",
"type": "tab",
"label": "CRUD子流程",
"disabled": false,
"info": "",
"env": []
},
{
"id": "78d15f59dee4b6d8",
"type": "tab",
"label": "接口",
"disabled": false,
"info": "",
"env": []
},
{
"id": "8aaa24cf39b5742a",
"type": "http proxy",
"name": "https",
"url": "https://10.22.98.21:8080",
"noproxy": []
},
{
"id": "97353ed49f690c0b",
"type": "http proxy",
"name": "http",
"url": "http://10.22.98.21:8080",
"noproxy": []
},
{
"id": "be9c605d4cdaa297",
"type": "http request",
"z": "0ac020c7380665e7",
"name": "创建井",
"method": "POST",
"ret": "txt",
"paytoqs": "ignore",
"url": "https://www.dev.ideas.cnpc/api/dms/well_kd_wellbore_ideas01/v1/cd_well",
"tls": "",
"persist": true,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 1030,
"y": 560,
"wires": [
[
"6f07c9e60472a814",
"2220f90bcc99b3a4",
"df389955c2ae3156"
]
]
},
{
"id": "96fbb1fa63a1cb8b",
"type": "inject",
"z": "0ac020c7380665e7",
"name": "创建",
"props": [
{
"p": "payload"
},
{
"p": "headers",
"v": "{\"Content-Type\":\"application/json\",\"Authorization\":\"1\",\"Dataregion\":\"ZZLH\"}",
"vt": "json"
},
{
"p": "rejectUnauthorized",
"v": "false",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "{\"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\"}]}",
"payloadType": "json",
"x": 850,
"y": 400,
"wires": [
[
"013f3a0718a44e18"
]
]
},
{
"id": "6f07c9e60472a814",
"type": "debug",
"z": "0ac020c7380665e7",
"name": "创建结果",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 1180,
"y": 440,
"wires": []
},
{
"id": "1b18d7419c63f2b5",
"type": "inject",
"z": "0ac020c7380665e7",
"name": "查询详情",
"props": [
{
"p": "headers",
"v": "{\"Content-Type\":\"application/json\",\"Authorization\":\"1\",\"Dataregion\":\"ZZLH\"}",
"vt": "json"
},
{
"p": "rejectUnauthorized",
"v": "false",
"vt": "str"
},
{
"p": "url",
"v": "https://www.dev.ideas.cnpc/api/dms/well_kd_wellbore_ideas01/v1/cd_well/1.0.0/testid2",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"x": 160,
"y": 220,
"wires": [
[
"70a49687f823bf4e"
]
]
},
{
"id": "88cf35d496381288",
"type": "debug",
"z": "0ac020c7380665e7",
"name": "查询",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 470,
"y": 220,
"wires": []
},
{
"id": "70a49687f823bf4e",
"type": "http request",
"z": "0ac020c7380665e7",
"name": "查询井详情",
"method": "GET",
"ret": "txt",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": true,
"proxy": "",
"insecureHTTPParser": false,
"authType": "basic",
"senderr": false,
"headers": [],
"x": 330,
"y": 240,
"wires": [
[
"88cf35d496381288"
]
]
},
{
"id": "ae5c8d27d69b77ed",
"type": "http request",
"z": "0ac020c7380665e7",
"name": "更新井",
"method": "PUT",
"ret": "txt",
"paytoqs": "ignore",
"url": "https://www.dev.ideas.cnpc/api/dms/well_kd_wellbore_ideas01/v1/cd_well",
"tls": "",
"persist": true,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 270,
"y": 340,
"wires": [
[
"2b37a50f557b48ff"
]
]
},
{
"id": "7f1ff1fda219baaa",
"type": "inject",
"z": "0ac020c7380665e7",
"name": "更新",
"props": [
{
"p": "payload"
},
{
"p": "headers",
"v": "{\"Content-Type\":\"application/json\",\"Authorization\":\"1\",\"Dataregion\":\"ZZLH\"}",
"vt": "json"
},
{
"p": "rejectUnauthorized",
"v": "false",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "{\"version\":\"1.0.0\",\"act\":-1,\"data\":[{\"dsid\":\"testid2\",\"wellId\":\"WELL-zzlhTEST-002\",\"wellCommonName\":\"zzlh测试用井名\",\"wellLegalName\":\"zzlh-test-01\",\"wellPurpose\":\"开发井---更新16\",\"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\"}]}",
"payloadType": "json",
"x": 110,
"y": 280,
"wires": [
[
"ae5c8d27d69b77ed"
]
]
},
{
"id": "2b37a50f557b48ff",
"type": "debug",
"z": "0ac020c7380665e7",
"name": "更新结果",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 460,
"y": 340,
"wires": []
},
{
"id": "c3514fbf44147aed",
"type": "inject",
"z": "0ac020c7380665e7",
"name": "删除",
"props": [
{
"p": "headers",
"v": "{\"Content-Type\":\"application/json\",\"Authorization\":\"1\",\"Dataregion\":\"ZZLH\"}",
"vt": "json"
},
{
"p": "rejectUnauthorized",
"v": "false",
"vt": "str"
},
{
"p": "url",
"v": "https://www.dev.ideas.cnpc/api/dms/well_kd_wellbore_ideas01/v1/cd_well",
"vt": "str"
},
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "{\"version\":\"1.0.0\",\"data\":[\"testid2\"]}",
"payloadType": "json",
"x": 90,
"y": 380,
"wires": [
[
"0d437a2a77570c57"
]
]
},
{
"id": "647fd9a7f8fc214c",
"type": "debug",
"z": "0ac020c7380665e7",
"name": "删除结果",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 500,
"y": 420,
"wires": []
},
{
"id": "0d437a2a77570c57",
"type": "http request",
"z": "0ac020c7380665e7",
"name": "删除井",
"method": "DELETE",
"ret": "txt",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": true,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 230,
"y": 420,
"wires": [
[
"647fd9a7f8fc214c"
]
]
},
{
"id": "044945e983586ce5",
"type": "inject",
"z": "0ac020c7380665e7",
"name": "查询列表",
"props": [
{
"p": "headers",
"v": "{\"Content-Type\":\"application/json\",\"Authorization\":\"1\",\"Dataregion\":\"ZZLH\"}",
"vt": "json"
},
{
"p": "rejectUnauthorized",
"v": "false",
"vt": "str"
},
{
"p": "url",
"v": "https://www.dev.ideas.cnpc/api/dms/well_kd_wellbore_ideas01/v1/cd_well/1.0.0",
"vt": "str"
},
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "{}",
"payloadType": "json",
"x": 120,
"y": 480,
"wires": [
[
"5921574867140eb3"
]
]
},
{
"id": "286d3b129ca1b756",
"type": "debug",
"z": "0ac020c7380665e7",
"name": "查询",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 490,
"y": 480,
"wires": []
},
{
"id": "5921574867140eb3",
"type": "http request",
"z": "0ac020c7380665e7",
"name": "查询井列表",
"method": "POST",
"ret": "txt",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": true,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 290,
"y": 500,
"wires": [
[
"286d3b129ca1b756",
"819906f13bb83e88"
]
]
},
{
"id": "819906f13bb83e88",
"type": "function",
"z": "0ac020c7380665e7",
"name": "function 1",
"func": "try {\n \n const payload = JSON.parse(msg.payload);\n if (payload && payload.data && Array.isArray(payload.data.list)) {\n msg.isEmpty = payload.data.list.length === 0;\n } else {\n msg.isEmpty = false; \n }\n} catch (e) {\n \n \n msg.isEmpty = false\n}\n\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 300,
"y": 600,
"wires": [
[
"7b828761c4ae4f35"
]
]
},
{
"id": "7b828761c4ae4f35",
"type": "switch",
"z": "0ac020c7380665e7",
"name": "",
"property": "isEmpty",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "true",
"vt": "jsonata"
},
{
"t": "neq",
"v": "true",
"vt": "jsonata"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 470,
"y": 580,
"wires": [
[
"e308201b5e361b2e",
"013f3a0718a44e18"
],
[
"5712828285fd7a38"
]
]
},
{
"id": "5712828285fd7a38",
"type": "debug",
"z": "0ac020c7380665e7",
"name": "debug 1",
"active": true,
"tosidebar": true,
"console": true,
"tostatus": false,
"complete": "\"初始不为空,错误\"",
"targetType": "jsonata",
"statusVal": "",
"statusType": "auto",
"x": 490,
"y": 680,
"wires": []
},
{
"id": "e308201b5e361b2e",
"type": "debug",
"z": "0ac020c7380665e7",
"name": "debug 2",
"active": true,
"tosidebar": true,
"console": true,
"tostatus": false,
"complete": "\"初始为空,正确\"",
"targetType": "jsonata",
"statusVal": "",
"statusType": "auto",
"x": 690,
"y": 640,
"wires": []
},
{
"id": "013f3a0718a44e18",
"type": "change",
"z": "0ac020c7380665e7",
"name": "",
"rules": [
{
"t": "set",
"p": "payload",
"pt": "msg",
"to": "{\"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\"}]}",
"tot": "json"
},
{
"t": "set",
"p": "headers",
"pt": "msg",
"to": "{\"Content-Type\":\"application/json\",\"Authorization\":\"1\",\"Dataregion\":\"ZZLH\"}",
"tot": "json"
},
{
"t": "set",
"p": "rejectUnauthorized",
"pt": "msg",
"to": "false",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 880,
"y": 560,
"wires": [
[
"be9c605d4cdaa297"
]
]
},
{
"id": "8f574d489345a331",
"type": "switch",
"z": "0ac020c7380665e7",
"name": "",
"property": "isCreated",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "true",
"vt": "jsonata"
},
{
"t": "eq",
"v": "false",
"vt": "jsonata"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 1330,
"y": 620,
"wires": [
[
"23f668f265c1db5c",
"8f280fa3aac33505"
],
[
"4fd140a8f2df7e53"
]
]
},
{
"id": "2220f90bcc99b3a4",
"type": "function",
"z": "0ac020c7380665e7",
"name": "function 2",
"func": "try {\n const payloadObject = JSON.parse(msg.payload);\n msg.isCreated = (payloadObject.code === 0);\n\n} catch (e) {\n msg.isCreated = false;\n}\n\n// 返回更新后的 msg 对象\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1160,
"y": 620,
"wires": [
[
"8f574d489345a331"
]
]
},
{
"id": "23f668f265c1db5c",
"type": "debug",
"z": "0ac020c7380665e7",
"name": "debug 3",
"active": true,
"tosidebar": true,
"console": true,
"tostatus": false,
"complete": "\"创建成功\"",
"targetType": "jsonata",
"statusVal": "",
"statusType": "auto",
"x": 1390,
"y": 460,
"wires": []
},
{
"id": "4fd140a8f2df7e53",
"type": "debug",
"z": "0ac020c7380665e7",
"name": "debug 4",
"active": true,
"tosidebar": true,
"console": true,
"tostatus": false,
"complete": "创建失败",
"targetType": "jsonata",
"statusVal": "",
"statusType": "auto",
"x": 1410,
"y": 740,
"wires": []
},
{
"id": "df389955c2ae3156",
"type": "debug",
"z": "0ac020c7380665e7",
"name": "debug 6",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "msg",
"targetType": "jsonata",
"statusVal": "",
"statusType": "auto",
"x": 1000,
"y": 680,
"wires": []
},
{
"id": "39c28a10430cce71",
"type": "debug",
"z": "0ac020c7380665e7",
"name": "debug 5",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 800,
"y": 740,
"wires": []
},
{
"id": "551a78c76ae2bccf",
"type": "http request",
"z": "0ac020c7380665e7",
"name": "删除井",
"method": "DELETE",
"ret": "txt",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": true,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 1770,
"y": 660,
"wires": [
[
"99f3d98419231d12",
"e3cc68ad6021510f"
]
]
},
{
"id": "99f3d98419231d12",
"type": "debug",
"z": "0ac020c7380665e7",
"name": "删除结果",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 1960,
"y": 540,
"wires": []
},
{
"id": "7d94b96bcc85d684",
"type": "inject",
"z": "0ac020c7380665e7",
"name": "删除",
"props": [
{
"p": "headers",
"v": "{\"Content-Type\":\"application/json\",\"Authorization\":\"1\",\"Dataregion\":\"ZZLH\"}",
"vt": "json"
},
{
"p": "rejectUnauthorized",
"v": "false",
"vt": "str"
},
{
"p": "url",
"v": "https://www.dev.ideas.cnpc/api/dms/well_kd_wellbore_ideas01/v1/cd_well",
"vt": "str"
},
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "{\"version\":\"1.0.0\",\"data\":[\"testid2\"]}",
"payloadType": "json",
"x": 1710,
"y": 480,
"wires": [
[
"551a78c76ae2bccf"
]
]
},
{
"id": "8f280fa3aac33505",
"type": "change",
"z": "0ac020c7380665e7",
"name": "",
"rules": [
{
"t": "set",
"p": "payload",
"pt": "msg",
"to": "{\"version\":\"1.0.0\",\"data\":[\"testid2\"]}",
"tot": "json"
},
{
"t": "set",
"p": "headers",
"pt": "msg",
"to": "{\"Content-Type\":\"application/json\",\"Authorization\":\"1\",\"Dataregion\":\"ZZLH\"}",
"tot": "json"
},
{
"t": "set",
"p": "rejectUnauthorized",
"pt": "msg",
"to": "false",
"tot": "str"
},
{
"t": "set",
"p": "url",
"pt": "msg",
"to": "https://www.dev.ideas.cnpc/api/dms/well_kd_wellbore_ideas01/v1/cd_well",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 1580,
"y": 620,
"wires": [
[
"551a78c76ae2bccf"
]
]
},
{
"id": "6585c529e8732137",
"type": "debug",
"z": "0ac020c7380665e7",
"name": "debug 7",
"active": true,
"tosidebar": true,
"console": true,
"tostatus": false,
"complete": "\"删除成功\"",
"targetType": "jsonata",
"statusVal": "",
"statusType": "auto",
"x": 2170,
"y": 540,
"wires": []
},
{
"id": "3b9a9af3fb68645f",
"type": "switch",
"z": "0ac020c7380665e7",
"name": "",
"property": "isDeleted",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "true",
"vt": "jsonata"
},
{
"t": "eq",
"v": "false",
"vt": "jsonata"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 2110,
"y": 700,
"wires": [
[
"6585c529e8732137",
"cfbf5ff4275a974e"
],
[
"f80987279214f312"
]
]
},
{
"id": "f80987279214f312",
"type": "debug",
"z": "0ac020c7380665e7",
"name": "debug 8",
"active": true,
"tosidebar": true,
"console": true,
"tostatus": false,
"complete": "删除失败",
"targetType": "jsonata",
"statusVal": "",
"statusType": "auto",
"x": 2190,
"y": 820,
"wires": []
},
{
"id": "e3cc68ad6021510f",
"type": "function",
"z": "0ac020c7380665e7",
"name": "function 3",
"func": "try {\n const payloadObject = JSON.parse(msg.payload);\n msg.isDeleted = (payloadObject.code === 0);\n\n} catch (e) {\n msg.isDeleted = false;\n}\n\n// 返回更新后的 msg 对象\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1920,
"y": 700,
"wires": [
[
"3b9a9af3fb68645f",
"f92150ebdd4ca3ff"
]
]
},
{
"id": "f92150ebdd4ca3ff",
"type": "debug",
"z": "0ac020c7380665e7",
"name": "debug 9",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "msg",
"targetType": "jsonata",
"statusVal": "",
"statusType": "auto",
"x": 2000,
"y": 760,
"wires": []
},
{
"id": "c281f9583e262827",
"type": "function",
"z": "0ac020c7380665e7",
"name": "function 4",
"func": "msg.headers = { \"Content-Type\": \"application/json\", \"Authorization\": \"1\", \"Dataregion\": \"ZZLH\" }\nmsg.body={}\nmsg.rejectUnauthorized=false\nmsg.url = \"https://www.dev.ideas.cnpc/api/dms/well_kd_wellbore_ideas01/v1/cd_well/1.0.0\"\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 100,
"y": 580,
"wires": [
[
"5921574867140eb3"
]
]
},
{
"id": "ea8a01974cede86a",
"type": "http in",
"z": "0ac020c7380665e7",
"name": "",
"url": "/test",
"method": "get",
"upload": false,
"skipBodyParsing": false,
"swaggerDoc": "",
"x": 80,
"y": 660,
"wires": [
[
"c281f9583e262827"
]
]
},
{
"id": "fdf4a7a6166cf05c",
"type": "http response",
"z": "0ac020c7380665e7",
"name": "",
"statusCode": "",
"headers": {},
"x": 2370,
"y": 700,
"wires": []
},
{
"id": "cfbf5ff4275a974e",
"type": "function",
"z": "0ac020c7380665e7",
"name": "function 5",
"func": "msg.payload = { \"code\": 0, \"message\": \"井创建删除流程测试成功\",}\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2260,
"y": 640,
"wires": [
[
"fdf4a7a6166cf05c"
]
]
},
{
"id": "ac556871dae3b9ea",
"type": "inject",
"z": "0ac020c7380665e7",
"name": "查询列表",
"props": [
{
"p": "headers",
"v": "{\"Content-Type\":\"application/json\",\"Dataregion\":\"ZZLH\"}",
"vt": "json"
},
{
"p": "rejectUnauthorized",
"v": "false",
"vt": "str"
},
{
"p": "url",
"v": "https://www.dev.ideas.cnpc/api/dms/well_kd_wellbore_ideas01/v1/cd_well/1.0.0",
"vt": "str"
},
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "{}",
"payloadType": "json",
"x": 280,
"y": 100,
"wires": [
[
"ee3b0307d3ddb476"
]
]
},
{
"id": "c69cfc2d862f1fe4",
"type": "debug",
"z": "0ac020c7380665e7",
"name": "查询",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 650,
"y": 100,
"wires": []
},
{
"id": "ee3b0307d3ddb476",
"type": "http request",
"z": "0ac020c7380665e7",
"name": "查询井列表",
"method": "POST",
"ret": "txt",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": true,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 450,
"y": 120,
"wires": [
[
"c69cfc2d862f1fe4"
]
]
},
{
"id": "d50ee4d6f62682dd",
"type": "inject",
"z": "0ac020c7380665e7",
"name": "创建",
"props": [
{
"p": "payload"
},
{
"p": "headers",
"v": "{\"Content-Type\":\"application/json\",\"Dataregion\":\"ZZLH\"}",
"vt": "json"
},
{
"p": "rejectUnauthorized",
"v": "false",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "{\"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\"}]}",
"payloadType": "json",
"x": 850,
"y": 160,
"wires": [
[
"61b9d6e67e3f48f6"
]
]
},
{
"id": "61b9d6e67e3f48f6",
"type": "http request",
"z": "0ac020c7380665e7",
"name": "创建井",
"method": "POST",
"ret": "txt",
"paytoqs": "ignore",
"url": "https://www.dev.ideas.cnpc/api/dms/well_kd_wellbore_ideas01/v1/cd_well",
"tls": "",
"persist": true,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 990,
"y": 100,
"wires": [
[
"9821ae3eea5804dc"
]
]
},
{
"id": "9821ae3eea5804dc",
"type": "debug",
"z": "0ac020c7380665e7",
"name": "创建结果",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 1080,
"y": 220,
"wires": []
},
{
"id": "6b82573f9ffd406c",
"type": "http request",
"z": "2bbc1a949095492c",
"name": "创建",
"method": "use",
"ret": "txt",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": true,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 950,
"y": 480,
"wires": [
[
"4de04763f97579e6",
"dff6a9719efe6bb8",
"b18ba558db58cd2c"
]
]
},
{
"id": "fd19275e55b06039",
"type": "inject",
"z": "2bbc1a949095492c",
"name": "创建",
"props": [
{
"p": "payload"
},
{
"p": "headers",
"v": "{\"Content-Type\":\"application/json\",\"Authorization\":\"1\",\"Dataregion\":\"ZZLH\"}",
"vt": "json"
},
{
"p": "rejectUnauthorized",
"v": "false",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "{\"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\"}]}",
"payloadType": "json",
"x": 850,
"y": 320,
"wires": [
[
"6b82573f9ffd406c"
]
]
},
{
"id": "4de04763f97579e6",
"type": "debug",
"z": "2bbc1a949095492c",
"name": "创建结果",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 1180,
"y": 360,
"wires": []
},
{
"id": "9d308d75e615a2b1",
"type": "http request",
"z": "2bbc1a949095492c",
"name": "更新井",
"method": "PUT",
"ret": "txt",
"paytoqs": "ignore",
"url": "https://www.dev.ideas.cnpc/api/dms/well_kd_wellbore_ideas01/v1/cd_well",
"tls": "",
"persist": true,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 270,
"y": 280,
"wires": [
[
"53b9ebee8b3ad17f"
]
]
},
{
"id": "7b342ce3a2bf48c1",
"type": "inject",
"z": "2bbc1a949095492c",
"name": "更新",
"props": [
{
"p": "payload"
},
{
"p": "headers",
"v": "{\"Content-Type\":\"application/json\",\"Authorization\":\"1\",\"Dataregion\":\"ZZLH\"}",
"vt": "json"
},
{
"p": "rejectUnauthorized",
"v": "false",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "{\"version\":\"1.0.0\",\"act\":-1,\"data\":[{\"dsid\":\"testid2\",\"wellId\":\"WELL-zzlhTEST-002\",\"wellCommonName\":\"zzlh测试用井名\",\"wellLegalName\":\"zzlh-test-01\",\"wellPurpose\":\"开发井---更新16\",\"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\"}]}",
"payloadType": "json",
"x": 110,
"y": 260,
"wires": [
[
"9d308d75e615a2b1"
]
]
},
{
"id": "53b9ebee8b3ad17f",
"type": "debug",
"z": "2bbc1a949095492c",
"name": "更新结果",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 460,
"y": 260,
"wires": []
},
{
"id": "cfd7ae558fdd3340",
"type": "inject",
"z": "2bbc1a949095492c",
"name": "查询列表",
"props": [
{
"p": "headers",
"v": "{\"Content-Type\":\"application/json\",\"Authorization\":\"1\",\"Dataregion\":\"ZZLH\"}",
"vt": "json"
},
{
"p": "rejectUnauthorized",
"v": "false",
"vt": "str"
},
{
"p": "url",
"v": "https://www.dev.ideas.cnpc/api/dms/well_kd_wellbore_ideas01/v1/cd_well/1.0.0",
"vt": "str"
},
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "{}",
"payloadType": "json",
"x": 120,
"y": 400,
"wires": [
[
"1c0decb86a5b16bc"
]
]
},
{
"id": "54b7e6f656002a74",
"type": "debug",
"z": "2bbc1a949095492c",
"name": "查询",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 490,
"y": 400,
"wires": []
},
{
"id": "1c0decb86a5b16bc",
"type": "http request",
"z": "2bbc1a949095492c",
"name": "查询列表",
"method": "use",
"ret": "txt",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": true,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 280,
"y": 420,
"wires": [
[
"54b7e6f656002a74",
"c226f4cb78c4eeda"
]
]
},
{
"id": "c226f4cb78c4eeda",
"type": "function",
"z": "2bbc1a949095492c",
"name": "判断是否为空",
"func": "try {\n \n const payload = JSON.parse(msg.payload);\n if (payload && payload.data && Array.isArray(payload.data.list)) {\n msg.isEmpty = payload.data.list.length === 0;\n } else {\n msg.isEmpty = false; \n }\n} catch (e) {\n \n \n msg.isEmpty = false\n}\n\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 320,
"y": 520,
"wires": [
[
"8c029cbbaf7a4083"
]
]
},
{
"id": "8c029cbbaf7a4083",
"type": "switch",
"z": "2bbc1a949095492c",
"name": "",
"property": "isEmpty",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "true",
"vt": "jsonata"
},
{
"t": "neq",
"v": "true",
"vt": "jsonata"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 470,
"y": 500,
"wires": [
[
"58738307f84d7063",
"e9fc4a2ffe2cd375"
],
[
"0d33ba7f9e75ba14"
]
]
},
{
"id": "0d33ba7f9e75ba14",
"type": "debug",
"z": "2bbc1a949095492c",
"name": "debug 12",
"active": true,
"tosidebar": true,
"console": true,
"tostatus": false,
"complete": "\"初始不为空,错误\"",
"targetType": "jsonata",
"statusVal": "",
"statusType": "auto",
"x": 490,
"y": 600,
"wires": []
},
{
"id": "58738307f84d7063",
"type": "debug",
"z": "2bbc1a949095492c",
"name": "debug 13",
"active": true,
"tosidebar": true,
"console": true,
"tostatus": false,
"complete": "\"初始为空,正确\"",
"targetType": "jsonata",
"statusVal": "",
"statusType": "auto",
"x": 690,
"y": 560,
"wires": []
},
{
"id": "eae40b8b1740da9d",
"type": "switch",
"z": "2bbc1a949095492c",
"name": "",
"property": "isCreated",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "true",
"vt": "jsonata"
},
{
"t": "eq",
"v": "false",
"vt": "jsonata"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 1330,
"y": 540,
"wires": [
[
"7f25bc1c56632834",
"9130d5e40d2e8b53"
],
[
"b32a8f9b0f2cd01b"
]
]
},
{
"id": "dff6a9719efe6bb8",
"type": "function",
"z": "2bbc1a949095492c",
"name": "判断创建结果",
"func": "try {\n const payloadObject = JSON.parse(msg.payload);\n msg.isCreated = (payloadObject.code === 0);\n\n} catch (e) {\n msg.isCreated = false;\n}\n\n// 返回更新后的 msg 对象\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1120,
"y": 540,
"wires": [
[
"eae40b8b1740da9d"
]
]
},
{
"id": "7f25bc1c56632834",
"type": "debug",
"z": "2bbc1a949095492c",
"name": "debug 14",
"active": true,
"tosidebar": true,
"console": true,
"tostatus": false,
"complete": "\"创建成功\"",
"targetType": "jsonata",
"statusVal": "",
"statusType": "auto",
"x": 1390,
"y": 380,
"wires": []
},
{
"id": "b32a8f9b0f2cd01b",
"type": "debug",
"z": "2bbc1a949095492c",
"name": "debug 15",
"active": true,
"tosidebar": true,
"console": true,
"tostatus": false,
"complete": "创建失败",
"targetType": "jsonata",
"statusVal": "",
"statusType": "auto",
"x": 1410,
"y": 660,
"wires": []
},
{
"id": "b18ba558db58cd2c",
"type": "debug",
"z": "2bbc1a949095492c",
"name": "debug 16",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "msg",
"targetType": "jsonata",
"statusVal": "",
"statusType": "auto",
"x": 1000,
"y": 600,
"wires": []
},
{
"id": "1c609e3fa4938dc3",
"type": "debug",
"z": "2bbc1a949095492c",
"name": "debug 17",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 800,
"y": 660,
"wires": []
},
{
"id": "6b971983d782d570",
"type": "http request",
"z": "2bbc1a949095492c",
"name": "删除",
"method": "use",
"ret": "txt",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": true,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 1770,
"y": 580,
"wires": [
[
"449b2760f19c65ef",
"588b71ddf9f6ad2a"
]
]
},
{
"id": "449b2760f19c65ef",
"type": "debug",
"z": "2bbc1a949095492c",
"name": "删除结果",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 1960,
"y": 460,
"wires": []
},
{
"id": "8534db3f11cc613a",
"type": "inject",
"z": "2bbc1a949095492c",
"name": "删除",
"props": [
{
"p": "headers",
"v": "{\"Content-Type\":\"application/json\",\"Authorization\":\"1\",\"Dataregion\":\"ZZLH\"}",
"vt": "json"
},
{
"p": "rejectUnauthorized",
"v": "false",
"vt": "str"
},
{
"p": "url",
"v": "https://www.dev.ideas.cnpc/api/dms/well_kd_wellbore_ideas01/v1/cd_well",
"vt": "str"
},
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "{\"version\":\"1.0.0\",\"data\":[\"testid2\"]}",
"payloadType": "json",
"x": 1710,
"y": 400,
"wires": [
[
"6b971983d782d570"
]
]
},
{
"id": "87da5039b77ae777",
"type": "debug",
"z": "2bbc1a949095492c",
"name": "debug 18",
"active": true,
"tosidebar": true,
"console": true,
"tostatus": false,
"complete": "\"删除成功\"",
"targetType": "jsonata",
"statusVal": "",
"statusType": "auto",
"x": 2170,
"y": 460,
"wires": []
},
{
"id": "466bd32fcc7164c2",
"type": "switch",
"z": "2bbc1a949095492c",
"name": "",
"property": "isDeleted",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "true",
"vt": "jsonata"
},
{
"t": "eq",
"v": "false",
"vt": "jsonata"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 2110,
"y": 620,
"wires": [
[
"87da5039b77ae777",
"6ce191ad8e5a407e"
],
[
"b41e21ee453d29d7"
]
]
},
{
"id": "b41e21ee453d29d7",
"type": "debug",
"z": "2bbc1a949095492c",
"name": "debug 19",
"active": true,
"tosidebar": true,
"console": true,
"tostatus": false,
"complete": "删除失败",
"targetType": "jsonata",
"statusVal": "",
"statusType": "auto",
"x": 2190,
"y": 740,
"wires": []
},
{
"id": "588b71ddf9f6ad2a",
"type": "function",
"z": "2bbc1a949095492c",
"name": "function 8",
"func": "try {\n const payloadObject = JSON.parse(msg.payload);\n msg.isDeleted = (payloadObject.code === 0);\n\n} catch (e) {\n msg.isDeleted = false;\n}\n\n// 返回更新后的 msg 对象\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1920,
"y": 620,
"wires": [
[
"466bd32fcc7164c2",
"5b1fd3727096c31d"
]
]
},
{
"id": "5b1fd3727096c31d",
"type": "debug",
"z": "2bbc1a949095492c",
"name": "debug 20",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "msg",
"targetType": "jsonata",
"statusVal": "",
"statusType": "auto",
"x": 2000,
"y": 680,
"wires": []
},
{
"id": "cada8d4a6105ea2d",
"type": "function",
"z": "2bbc1a949095492c",
"name": "function 9",
"func": "msg.headers = { \"Content-Type\": \"application/json\", \"Authorization\": \"1\", \"Dataregion\": \"ZZLH\" }\nmsg.body={}\nmsg.rejectUnauthorized=false\nmsg.url = \"https://www.dev.ideas.cnpc/api/dms/well_kd_wellbore_ideas01/v1/cd_well/1.0.0\"\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 100,
"y": 500,
"wires": [
[
"1c0decb86a5b16bc"
]
]
},
{
"id": "21d47918f0e5a611",
"type": "http in",
"z": "2bbc1a949095492c",
"name": "",
"url": "/test",
"method": "get",
"upload": false,
"skipBodyParsing": false,
"swaggerDoc": "",
"x": 80,
"y": 580,
"wires": [
[
"cada8d4a6105ea2d"
]
]
},
{
"id": "608d38ac3750e9a9",
"type": "http response",
"z": "2bbc1a949095492c",
"name": "",
"statusCode": "",
"headers": {},
"x": 2410,
"y": 700,
"wires": []
},
{
"id": "6ce191ad8e5a407e",
"type": "function",
"z": "2bbc1a949095492c",
"name": "function 10",
"func": "msg.payload = { \"code\": 0, \"message\": \"井创建删除流程测试成功\",}\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2260,
"y": 560,
"wires": [
[
"ab1f2a98d3200f0d"
]
]
},
{
"id": "954bc3b915b9ad93",
"type": "inject",
"z": "2bbc1a949095492c",
"name": "创建",
"props": [
{
"p": "payload"
},
{
"p": "headers",
"v": "{\"Content-Type\":\"application/json\",\"Dataregion\":\"ZZLH\"}",
"vt": "json"
},
{
"p": "rejectUnauthorized",
"v": "false",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "{\"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\"}]}",
"payloadType": "json",
"x": 850,
"y": 80,
"wires": [
[
"05975bc9c72aea39"
]
]
},
{
"id": "05975bc9c72aea39",
"type": "http request",
"z": "2bbc1a949095492c",
"name": "创建井",
"method": "POST",
"ret": "txt",
"paytoqs": "ignore",
"url": "https://www.dev.ideas.cnpc/api/dms/well_kd_wellbore_ideas01/v1/cd_well",
"tls": "",
"persist": true,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 990,
"y": 20,
"wires": [
[
"0144f7e3d85b8e7e"
]
]
},
{
"id": "0144f7e3d85b8e7e",
"type": "debug",
"z": "2bbc1a949095492c",
"name": "创建结果",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 1080,
"y": 140,
"wires": []
},
{
"id": "6c06fbe60248c14c",
"type": "link in",
"z": "2bbc1a949095492c",
"name": "crud子流程linkin",
"links": [
"80da1e9b993f73dc"
],
"x": 65,
"y": 340,
"wires": [
[
"b23f184ea63d57f8"
]
]
},
{
"id": "b23f184ea63d57f8",
"type": "function",
"z": "2bbc1a949095492c",
"name": "设置查询请求",
"func": "'use strict';\n\n/**\n * 根据 msg.crudFlow.list 设置查询请求,兼容旧的手工注入流程。\n */\n\nreturn configureListRequest(msg, node);\n\nfunction configureListRequest(message, node) {\n if (!message.crudFlow || !message.crudFlow.list) {\n return message;\n }\n\n const list = message.crudFlow.list;\n const baseUrl = message.crudFlow.baseUrl || '';\n\n message.method = (list.method || 'GET').toUpperCase();\n message.url = mergeUrl(baseUrl, list.path || '');\n message.headers = Object.assign({}, message.crudFlow.headers || {}, list.headers || {});\n\n if (list.payload !== undefined) {\n message.payload = clone(list.payload);\n }\n\n message.listRequestConfigured = true;\n return message;\n}\n\nfunction mergeUrl(base, path) {\n const prefix = (base || '').replace(/\\/+$/, '');\n const suffix = (path || '').replace(/^\\/+/, '');\n if (!prefix) {\n return `/${suffix}`;\n }\n if (!suffix) {\n return prefix;\n }\n return `${prefix}/${suffix}`;\n}\n\nfunction clone(value) {\n return value == null ? value : JSON.parse(JSON.stringify(value));\n}\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 280,
"y": 660,
"wires": [
[
"bd042d824b60b5ac",
"091820a71299ef8e"
]
]
},
{
"id": "bd042d824b60b5ac",
"type": "http request",
"z": "2bbc1a949095492c",
"name": "",
"method": "use",
"ret": "txt",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 280,
"y": 720,
"wires": [
[
"c226f4cb78c4eeda",
"c2abaaef33a4e83a"
]
]
},
{
"id": "e9fc4a2ffe2cd375",
"type": "function",
"z": "2bbc1a949095492c",
"name": "准备create",
"func": "'use strict';\n\n/**\n * 使用 msg.crudFlow.create 配置创建请求;若未提供则保持旧有注入负载。\n */\n\nreturn configureCreateRequest(msg, node);\n\nfunction configureCreateRequest(message, node) {\n if (!message.crudFlow || !message.crudFlow.create) {\n return message;\n }\n\n const create = message.crudFlow.create;\n const baseUrl = message.crudFlow.baseUrl || '';\n const payload = selectCreatePayload(create);\n\n message.method = (create.method || 'POST').toUpperCase();\n message.url = mergeUrl(baseUrl, create.path || '');\n message.headers = Object.assign({}, message.crudFlow.headers || {}, create.headers || {});\n\n if (payload !== undefined) {\n message.payload = clone(payload);\n }\n\n const identityField = message.crudFlow.identityField || message.identityField || 'dsid';\n const keyValue = payload && typeof payload === 'object'\n ? extractPrimaryKey(payload, identityField)\n : undefined;\n\n if (keyValue !== undefined) {\n message.primaryKeyValue = keyValue;\n message.crudFlow.delete = message.crudFlow.delete || {};\n message.crudFlow.delete.actualKey = keyValue;\n }\n\n return message;\n}\n\nfunction selectCreatePayload(create) {\n if (create.payload && Object.keys(create.payload).length > 0) {\n return create.payload;\n }\n if (create.samplePayload) {\n return create.samplePayload;\n }\n return undefined;\n}\n\nfunction extractPrimaryKey(payload, identityField) {\n if (!payload) {\n return undefined;\n }\n if (Object.prototype.hasOwnProperty.call(payload, identityField)) {\n return payload[identityField];\n }\n if (Array.isArray(payload.data) && payload.data.length > 0 && payload.data[0][identityField] !== undefined) {\n return payload.data[0][identityField];\n }\n return undefined;\n}\n\nfunction mergeUrl(base, path) {\n const prefix = (base || '').replace(/\\/+$/, '');\n const suffix = (path || '').replace(/^\\/+/, '');\n if (!prefix) {\n return `/${suffix}`;\n }\n if (!suffix) {\n return prefix;\n }\n return `${prefix}/${suffix}`;\n}\n\nfunction clone(value) {\n return value == null ? value : JSON.parse(JSON.stringify(value));\n}\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 750,
"y": 480,
"wires": [
[
"6b82573f9ffd406c"
]
]
},
{
"id": "9130d5e40d2e8b53",
"type": "function",
"z": "2bbc1a949095492c",
"name": "准备delete",
"func": "'use strict';\n\n/**\n * 依据 msg.crudFlow.delete 生成删除请求体,支持占位符 {{primaryKey}}。\n */\n\nreturn configureDeleteRequest(msg, node);\n\nfunction configureDeleteRequest(message, node) {\n if (!message.crudFlow || !message.crudFlow.delete) {\n return message;\n }\n\n const del = message.crudFlow.delete;\n const baseUrl = message.crudFlow.baseUrl || '';\n const primaryKeyValue = del.actualKey || message.primaryKeyValue || del.primaryKey || 'testid2';\n const method = (del.method || 'POST').toUpperCase();\n\n let path = del.path || '';\n let payload = null;\n\n if (method === 'DELETE' && path.includes('{')) {\n path = path.replace(/\\{[^}]+\\}/g, encodeURIComponent(primaryKeyValue));\n } else {\n const template = del.payloadTemplate || { version: '1.0.0', data: ['{{primaryKey}}'] };\n payload = materialiseTemplate(template, primaryKeyValue);\n }\n\n message.method = method;\n message.url = mergeUrl(baseUrl, path);\n message.headers = Object.assign({}, message.crudFlow.headers || {}, del.headers || {});\n\n if (payload !== null) {\n message.payload = payload;\n } else if (message.payload !== undefined) {\n delete message.payload;\n }\n\n return message;\n}\n\nfunction materialiseTemplate(template, primaryKeyValue) {\n const cloned = clone(template);\n return replacePlaceholder(cloned, primaryKeyValue);\n}\n\nfunction replacePlaceholder(value, primaryKeyValue) {\n if (typeof value === 'string') {\n return value.replace(/\\{\\{\\s*primaryKey\\s*\\}\\}/g, primaryKeyValue);\n }\n if (Array.isArray(value)) {\n return value.map(item => replacePlaceholder(item, primaryKeyValue));\n }\n if (value && typeof value === 'object') {\n const result = {};\n for (const key of Object.keys(value)) {\n result[key] = replacePlaceholder(value[key], primaryKeyValue);\n }\n return result;\n }\n return value;\n}\n\nfunction mergeUrl(base, path) {\n const prefix = (base || '').replace(/\\/+$/, '');\n const suffix = (path || '').replace(/^\\/+/, '');\n if (!prefix) {\n return `/${suffix}`;\n }\n if (!suffix) {\n return prefix;\n }\n return `${prefix}/${suffix}`;\n}\n\nfunction clone(value) {\n return value == null ? value : JSON.parse(JSON.stringify(value));\n}\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1570,
"y": 560,
"wires": [
[
"6b971983d782d570"
]
]
},
{
"id": "ab1f2a98d3200f0d",
"type": "function",
"z": "2bbc1a949095492c",
"name": "汇总",
"func": "'use strict';\n\n/**\n * 汇总 CRUD 执行结果,生成统一响应结构。\n */\n\nreturn summariseCrudResult(msg, node);\n\nfunction summariseCrudResult(message, node) {\n const identityField = (message.crudFlow && message.crudFlow.identityField) ||\n message.identityField || 'dsid';\n const primaryKey = (message.crudFlow && message.crudFlow.delete && message.crudFlow.delete.actualKey) ||\n message.primaryKeyValue || null;\n\n const listOk = message.listError ? false : true;\n const createOk = message.isCreated === undefined ? true : !!message.isCreated;\n const deleteOk = message.isDeleted === undefined ? true : !!message.isDeleted;\n\n const success = listOk && createOk && deleteOk;\n\n message.payload = {\n code: success ? 0 : 1,\n message: success ? '井创建删除流程测试成功' : '井创建删除流程部分失败',\n details: {\n listOk,\n createOk,\n deleteOk,\n identityField,\n primaryKey,\n },\n };\n\n return message;\n}\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 2310,
"y": 640,
"wires": [
[
"608d38ac3750e9a9",
"35caa921acd6676e"
]
]
},
{
"id": "35caa921acd6676e",
"type": "debug",
"z": "2bbc1a949095492c",
"name": "汇总结果",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 2500,
"y": 580,
"wires": []
},
{
"id": "091820a71299ef8e",
"type": "debug",
"z": "2bbc1a949095492c",
"name": "请求内容",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 480,
"y": 760,
"wires": []
},
{
"id": "c2abaaef33a4e83a",
"type": "debug",
"z": "2bbc1a949095492c",
"name": "请求结果",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 460,
"y": 820,
"wires": []
},
{
"id": "fd3672b8ebb314ee",
"type": "http in",
"z": "78d15f59dee4b6d8",
"name": "单接口测试",
"url": "/single_test",
"method": "post",
"upload": false,
"skipBodyParsing": false,
"swaggerDoc": "",
"x": 120,
"y": 140,
"wires": [
[
"f414ed448fcf544b"
]
]
},
{
"id": "d666851b16cf6482",
"type": "http response",
"z": "78d15f59dee4b6d8",
"name": "",
"statusCode": "msg.testcase.results",
"headers": {},
"x": 700,
"y": 260,
"wires": []
},
{
"id": "f414ed448fcf544b",
"type": "function",
"z": "78d15f59dee4b6d8",
"name": "dms转化为oas",
"func": "'use strict';\n\nconst DEFAULT_OPENAPI_VERSION = '3.0.1';\nconst DEFAULT_API_VERSION = '1.0.0';\nconst BASE_PREFIX = 'https://www.dev.ideas.cnpc/api/dms';\nconst DEFAULT_SERVICE_ID = 'well_kd_wellbore_ideas01';\nconst DEFAULT_API_SEGMENT = 'v1';\nconst FALLBACK_BASE_URL = `${BASE_PREFIX}/${DEFAULT_SERVICE_ID}/${DEFAULT_API_SEGMENT}`;\nconst FALLBACK_HEADERS = {\n 'Content-Type': 'application/json',\n 'Authorization': '1',\n 'Dataregion': 'ZZLH',\n};\nconst SERVICE_DOMAIN_CATALOG = {\n 'well_kd_wellbore_ideas01': {\n name: '井筒',\n id: 'well_kd_wellbore_ideas01',\n keywords: ['wb', 'wb_dr', 'wb_ml', 'wb_wl', 'wb_tp', 'wb_dh', 'wb_fr'],\n },\n 'geo_kd_res_ideas01': {\n name: '采油气',\n id: 'geo_kd_res_ideas01',\n keywords: ['pc', 'pc_op', 'pc_oe', 'pc_ge'],\n },\n 'kd_cr_ideas01': {\n name: '分析化验',\n id: 'kd_cr_ideas01',\n keywords: ['cr', 'cr_se'],\n },\n 'kd_rs_ideas01': {\n name: '油气藏',\n id: 'kd_rs_ideas01',\n keywords: ['rs', 'rs_rd', 'rs_rm', 'rs_gs', 'rs_in'],\n },\n};\n\nlet schema;\ntry {\n // 优先使用 HTTP In 提供的 req.body.schema缺省时回退到 msg.payload。\n const schemaInput = extractSchemaInput(msg);\n schema = parseSchema(schemaInput);\n} catch (error) {\n node.error(`DMS -> Swagger 解析失败:${error.message}`, msg);\n msg.error = error.message;\n return msg;\n}\n\nconst resourceTitle = typeof schema.title === 'string' && schema.title.trim()\n ? schema.title.trim()\n : 'DMS Resource';\n\nconst resourceName = pascalCase(resourceTitle);\nconst collectionName = pluralize(kebabCase(resourceTitle));\nconst identityField = Array.isArray(schema.identityId) && schema.identityId.length > 0\n ? String(schema.identityId[0])\n : 'id';\n\nconst schemaComponent = buildComponentSchema(resourceName, schema);\nconst identitySchema = schemaComponent.properties && schemaComponent.properties[identityField]\n ? clone(schemaComponent.properties[identityField])\n : { type: 'string' };\n\nconst crudConfig = Object.assign({}, msg.crudConfig || {});\n\nconst dmsMeta = extractDmsMeta(msg);\nmsg.dms_meta = dmsMeta;\n\nconst serviceInfo = resolveServiceInfo(dmsMeta && dmsMeta.domain);\nconst apiVersionSegment = DEFAULT_API_SEGMENT;\nconst serviceId = serviceInfo.id;\n\nconst { resourceSegment, versionSegment } = deriveResourceSegments(dmsMeta, schema, collectionName);\nconst listPath = buildListPath(resourceSegment, versionSegment);\nconst singlePath = buildSinglePath(resourceSegment);\nconst detailPath = buildDetailPath(resourceSegment, versionSegment, identityField);\nconst createRequestSchema = buildCreateRequestSchema(resourceName);\nconst updateRequestSchema = buildCreateRequestSchema(resourceName);\nconst deleteRequestSchema = buildDeleteRequestSchema(identityField);\nconst listRequestSchema = buildListRequestSchema();\nconst listResponseSchema = buildListResponseSchema(resourceName);\n\nconst openApiDocument = {\n openapi: DEFAULT_OPENAPI_VERSION,\n info: {\n title: `${resourceTitle} API`,\n version: DEFAULT_API_VERSION,\n description: schema.description || `${schema.$id || ''}`.trim(),\n 'x-dms-sourceId': schema.$id || undefined,\n },\n paths: buildCrudPaths({\n resourceName,\n identityField,\n identitySchema,\n listPath,\n createPath: singlePath,\n detailPath,\n createRequestSchema,\n updateRequestSchema,\n deleteRequestSchema,\n listRequestSchema,\n listResponseSchema,\n }),\n components: {\n schemas: {\n [resourceName]: schemaComponent,\n },\n },\n};\n\nif (!crudConfig.baseUrl) {\n crudConfig.baseUrl = `${BASE_PREFIX}/${serviceId}/${apiVersionSegment}`;\n}\n\nconst headers = Object.assign({}, FALLBACK_HEADERS, crudConfig.headers || {});\nconst dataRegionValue = extractDataRegion(schema, headers.Dataregion);\n\nif (dataRegionValue) {\n headers.Dataregion = dataRegionValue;\n crudConfig.dataRegion = dataRegionValue;\n}\n\ncrudConfig.headers = headers;\ncrudConfig.identityField = crudConfig.identityField || identityField;\n\ncrudConfig.service = crudConfig.service || {\n id: serviceId,\n name: serviceInfo.name,\n};\n\ncrudConfig.list = crudConfig.list || {};\nif (!crudConfig.list.path) {\n crudConfig.list.path = listPath;\n}\ncrudConfig.list.method = crudConfig.list.method || 'POST';\n\ncrudConfig.create = crudConfig.create || {};\nif (!crudConfig.create.path) {\n crudConfig.create.path = singlePath;\n}\ncrudConfig.create.method = crudConfig.create.method || 'POST';\n\ncrudConfig.delete = crudConfig.delete || {};\nif (!crudConfig.delete.path) {\n crudConfig.delete.path = singlePath;\n}\ncrudConfig.delete.method = crudConfig.delete.method || 'POST';\n\ncrudConfig.detailPath = crudConfig.detailPath || detailPath;\ncrudConfig.version = crudConfig.version || versionSegment;\n\nmsg.crudConfig = crudConfig;\nmsg.headers = Object.assign({}, headers);\n\nmsg.oas_def = openApiDocument;\nmsg.payload = openApiDocument;\nreturn msg;\n\nfunction extractDataRegion(dmsSchema, fallback) {\n if (!dmsSchema || typeof dmsSchema !== 'object') {\n return fallback;\n }\n\n if (typeof dmsSchema.dataRegion === 'string' && dmsSchema.dataRegion.trim()) {\n return dmsSchema.dataRegion.trim();\n }\n\n if (dmsSchema.defaultShow && Array.isArray(dmsSchema.defaultShow)) {\n const candidate = dmsSchema.defaultShow.find(item => item && typeof item === 'string' && item.toLowerCase().includes('dataregion'));\n if (candidate) {\n return fallback;\n }\n }\n\n const props = dmsSchema.properties;\n if (props && typeof props === 'object' && props.dataRegion) {\n if (typeof props.dataRegion.default === 'string' && props.dataRegion.default.trim()) {\n return props.dataRegion.default.trim();\n }\n }\n\n return fallback;\n}\n\nfunction extractSchemaInput(message) {\n if (message && message.req && message.req.body && typeof message.req.body === 'object') {\n if (message.req.body.schema !== undefined) {\n return message.req.body.schema;\n }\n if (looksLikeDmsSchema(message.req.body)) {\n return message.req.body;\n }\n }\n\n if (message && message.payload !== undefined) {\n if (message.payload && typeof message.payload === 'object') {\n if (message.payload.schema !== undefined) {\n return message.payload.schema;\n }\n if (looksLikeDmsSchema(message.payload)) {\n return message.payload;\n }\n }\n return message.payload;\n }\n\n throw new Error('未找到schema请在请求体的schema字段或msg.payload中提供');\n}\n\nfunction parseSchema(source) {\n if (typeof source === 'string') {\n try {\n return JSON.parse(source);\n } catch (error) {\n throw new Error(`JSON 解析失败:${error.message}`);\n }\n }\n\n if (!source || typeof source !== 'object') {\n throw new Error('schema 必须是 DMS 定义对象或 JSON 字符串');\n }\n\n return source;\n}\n\nfunction buildComponentSchema(resourceName, dmsSchema) {\n const { properties = {}, required = [], groupView, identityId, naturalKey, defaultShow } = dmsSchema;\n const openApiProps = {};\n\n for (const [propName, propSchema] of Object.entries(properties)) {\n openApiProps[propName] = mapProperty(propSchema);\n }\n\n return {\n type: 'object',\n required: Array.isArray(required) ? required.slice() : [],\n properties: openApiProps,\n description: dmsSchema.description || dmsSchema.title || resourceName,\n 'x-dms-groupView': groupView || undefined,\n 'x-dms-identityId': identityId || undefined,\n 'x-dms-naturalKey': naturalKey || undefined,\n 'x-dms-defaultShow': defaultShow || undefined,\n };\n}\n\nfunction mapProperty(propSchema) {\n if (!propSchema || typeof propSchema !== 'object') {\n return { type: 'string' };\n }\n\n const result = {};\n\n const type = normalizeType(propSchema.type);\n result.type = type.type;\n if (type.format) {\n result.format = type.format;\n }\n if (type.items) {\n result.items = type.items;\n }\n\n if (propSchema.description) {\n result.description = propSchema.description;\n } else if (propSchema.title) {\n result.description = propSchema.title;\n }\n\n if (propSchema.enum) {\n result.enum = propSchema.enum.slice();\n }\n\n if (propSchema.default !== undefined) {\n result.default = propSchema.default;\n }\n\n if (propSchema.mask) {\n result['x-dms-mask'] = propSchema.mask;\n }\n if (propSchema.geom) {\n result['x-dms-geom'] = propSchema.geom;\n }\n if (propSchema.title) {\n result['x-dms-title'] = propSchema.title;\n }\n if (propSchema.type) {\n result['x-dms-originalType'] = propSchema.type;\n }\n\n return result;\n}\n\nfunction normalizeType(typeValue) {\n const normalized = typeof typeValue === 'string' ? typeValue.toLowerCase() : undefined;\n\n switch (normalized) {\n case 'number':\n case 'integer':\n case 'long':\n case 'float':\n case 'double':\n return { type: 'number' };\n case 'boolean':\n return { type: 'boolean' };\n case 'array':\n return { type: 'array', items: { type: 'string' } };\n case 'date':\n return { type: 'string', format: 'date' };\n case 'date-time':\n return { type: 'string', format: 'date-time' };\n case 'object':\n return { type: 'object' };\n case 'string':\n default:\n return { type: 'string' };\n }\n}\n\nfunction buildCrudPaths({\n resourceName,\n identityField,\n identitySchema,\n listPath,\n createPath,\n detailPath,\n createRequestSchema,\n updateRequestSchema,\n deleteRequestSchema,\n listRequestSchema,\n listResponseSchema,\n}) {\n const ref = `#/components/schemas/${resourceName}`;\n const paths = {};\n\n const normalisedListPath = normalisePath(listPath);\n const normalisedCreatePath = normalisePath(createPath);\n const normalisedDetailPath = detailPath ? normalisePath(detailPath) : null;\n\n paths[normalisedListPath] = {\n post: {\n operationId: `list${resourceName}s`,\n summary: `List ${resourceName} resources`,\n requestBody: {\n required: false,\n content: {\n 'application/json': {\n schema: listRequestSchema,\n },\n },\n },\n responses: {\n 200: {\n description: 'Successful response',\n content: {\n 'application/json': {\n schema: listResponseSchema || {\n type: 'array',\n items: { $ref: ref },\n },\n },\n },\n },\n },\n },\n };\n\n paths[normalisedCreatePath] = {\n post: {\n operationId: `create${resourceName}`,\n summary: `Create a ${resourceName}`,\n requestBody: {\n required: true,\n content: {\n 'application/json': {\n schema: createRequestSchema,\n },\n },\n },\n responses: {\n 201: {\n description: 'Created',\n content: {\n 'application/json': {\n schema: { $ref: ref },\n },\n },\n },\n },\n },\n put: {\n operationId: `update${resourceName}`,\n summary: `Update a ${resourceName}`,\n requestBody: {\n required: true,\n content: {\n 'application/json': {\n schema: updateRequestSchema,\n },\n },\n },\n responses: {\n 200: {\n description: 'Successful update',\n content: {\n 'application/json': {\n schema: { $ref: ref },\n },\n },\n },\n },\n },\n delete: {\n operationId: `delete${resourceName}`,\n summary: `Delete ${resourceName} resources`,\n requestBody: {\n required: true,\n content: {\n 'application/json': {\n schema: deleteRequestSchema,\n },\n },\n },\n responses: {\n 200: { description: 'Deleted' },\n },\n },\n };\n\n if (normalisedDetailPath) {\n paths[normalisedDetailPath] = {\n parameters: [\n {\n name: identityField,\n in: 'path',\n required: true,\n schema: identitySchema,\n },\n ],\n get: {\n operationId: `get${resourceName}`,\n summary: `Get a single ${resourceName}`,\n responses: {\n 200: {\n description: 'Successful response',\n content: {\n 'application/json': {\n schema: { $ref: ref },\n },\n },\n },\n 404: { description: `${resourceName} not found` },\n },\n },\n };\n }\n\n return paths;\n}\n\nfunction normalisePath(path) {\n if (!path) {\n return '/';\n }\n return path.startsWith('/') ? path : `/${path}`;\n}\n\nfunction pascalCase(input) {\n return input\n .replace(/[^a-zA-Z0-9]+/g, ' ')\n .split(' ')\n .filter(Boolean)\n .map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())\n .join('') || 'Resource';\n}\n\nfunction kebabCase(input) {\n return input\n .replace(/([a-z])([A-Z])/g, '$1-$2')\n .replace(/[^a-zA-Z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .toLowerCase() || 'resource';\n}\n\nfunction pluralize(word) {\n if (word.endsWith('s')) {\n return word;\n }\n if (word.endsWith('y')) {\n return word.slice(0, -1) + 'ies';\n }\n return `${word}s`;\n}\n\nfunction clone(value) {\n return JSON.parse(JSON.stringify(value));\n}\n\nfunction looksLikeDmsSchema(candidate) {\n if (!candidate || typeof candidate !== 'object') {\n return false;\n }\n if (candidate.properties && typeof candidate.properties === 'object' && candidate.title) {\n return true;\n }\n if (candidate.$id && candidate.type && candidate.type.toLowerCase && candidate.type.toLowerCase() === 'object') {\n return true;\n }\n return false;\n}\n\nfunction extractDmsMeta(message) {\n const candidateReq = message && message.req && message.req.body && message.req.body.dms_meta;\n const parsedReq = parseMetaCandidate(candidateReq);\n if (parsedReq) {\n return parsedReq;\n }\n\n const candidateMsg = message && message.dms_meta;\n const parsedMsg = parseMetaCandidate(candidateMsg);\n if (parsedMsg) {\n return parsedMsg;\n }\n\n return null;\n}\n\nfunction parseMetaCandidate(candidate) {\n if (!candidate) {\n return null;\n }\n if (typeof candidate === 'string') {\n try {\n const value = JSON.parse(candidate);\n return parseMetaCandidate(value);\n } catch (err) {\n return null;\n }\n }\n if (typeof candidate === 'object') {\n return clone(candidate);\n }\n return null;\n}\n\nfunction resolveServiceInfo(domain) {\n if (!domain || typeof domain !== 'string') {\n return SERVICE_DOMAIN_CATALOG[DEFAULT_SERVICE_ID];\n }\n const normalized = domain.toLowerCase();\n let best = null;\n for (const entry of Object.values(SERVICE_DOMAIN_CATALOG)) {\n const matched = entry.keywords.some(keyword => {\n if (typeof keyword !== 'string') return false;\n const normKeyword = keyword.toLowerCase();\n return normalized.includes(normKeyword) || normKeyword.includes(normalized);\n });\n if (matched) {\n best = entry;\n break;\n }\n }\n return best || SERVICE_DOMAIN_CATALOG[DEFAULT_SERVICE_ID];\n}\n\nfunction deriveResourceSegments(meta, schema, collectionName) {\n const defaultResource = (collectionName || '').replace(/^\\//, '');\n let resourceSegment = defaultResource;\n let versionSegment = null;\n\n if (meta && typeof meta === 'object') {\n if (typeof meta.id === 'string' && meta.id.trim()) {\n const parts = meta.id.trim().split('.');\n if (parts.length > 0 && parts[0]) {\n resourceSegment = parts[0];\n }\n if (parts.length > 1) {\n versionSegment = parts.slice(1).join('.');\n }\n } else if (typeof meta.name === 'string' && meta.name.trim()) {\n resourceSegment = meta.name.trim();\n }\n }\n\n if (!resourceSegment && schema && schema.title) {\n resourceSegment = kebabCase(schema.title);\n }\n\n return {\n resourceSegment: resourceSegment || defaultResource || 'resource',\n versionSegment: versionSegment,\n };\n}\n\nfunction buildListPath(resourceSegment, versionSegment) {\n if (versionSegment) {\n return `/${resourceSegment}/${versionSegment}`;\n }\n return `/${resourceSegment}`;\n}\n\nfunction buildSinglePath(resourceSegment) {\n return `/${resourceSegment}`;\n}\n\nfunction buildDetailPath(resourceSegment, versionSegment, identityField) {\n if (versionSegment) {\n return `/${resourceSegment}/${versionSegment}/{${identityField}}`;\n }\n return `/${resourceSegment}/{${identityField}}`;\n}\n\nfunction buildCreateRequestSchema(resourceName) {\n const ref = `#/components/schemas/${resourceName}`;\n return {\n type: 'object',\n required: ['version', 'act', 'data'],\n properties: {\n version: { type: 'string', default: '1.0.0' },\n act: { type: 'integer', default: -1 },\n data: {\n type: 'array',\n items: { $ref: ref },\n },\n },\n };\n}\n\nfunction buildDeleteRequestSchema(identityField) {\n return {\n type: 'object',\n required: ['version', 'data'],\n properties: {\n version: { type: 'string', default: '1.0.0' },\n data: {\n type: 'array',\n items: {\n type: 'string',\n description: `Value of ${identityField}`,\n },\n },\n },\n };\n}\n\nfunction buildListRequestSchema() {\n return {\n type: 'object',\n properties: {\n version: { type: 'string', default: '1.0.0' },\n data: {\n type: 'object',\n properties: {\n pageNo: { type: 'integer', default: 1 },\n pageSize: { type: 'integer', default: 20 },\n isSearchCount: { type: 'boolean', default: true },\n filters: {\n type: 'array',\n items: { type: 'object' },\n },\n },\n },\n },\n };\n}\n\nfunction buildListResponseSchema(resourceName) {\n const ref = `#/components/schemas/${resourceName}`;\n return {\n type: 'object',\n properties: {\n code: { type: 'integer' },\n message: { type: 'string' },\n data: {\n type: 'object',\n properties: {\n list: {\n type: 'array',\n items: { $ref: ref },\n },\n total: { type: 'integer' },\n },\n },\n },\n };\n}\n\nfunction looksLikeDmsSchema(candidate) {\n if (!candidate || typeof candidate !== 'object') {\n return false;\n }\n if (candidate.properties && typeof candidate.properties === 'object' && candidate.title) {\n return true;\n }\n if (candidate.$id && candidate.type && candidate.type.toLowerCase && candidate.type.toLowerCase() === 'object') {\n return true;\n }\n return false;\n}\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 280,
"y": 200,
"wires": [
[
"dfe6ed572461c4a5",
"5231ed8a796d6f17",
"6ab9403df07fcefa",
"9c84414e55654e6a"
]
]
},
{
"id": "ab16f5ccd9266e28",
"type": "inject",
"z": "78d15f59dee4b6d8",
"name": "",
"props": [
{
"p": "req.body.schema",
"v": "{\"$id\":\"https://schema.ideas.cnpc/json/core-data/cd_well.1.0.0.json\",\"type\":\"Object\",\"title\":\"井\",\"schema\":\"http://json-schema.org/draft-07/schema#\",\"required\":[\"bsflag\",\"dataRegion\",\"createUserId\",\"orgId\",\"wellPurpose\",\"createDate\",\"wellLegalName\",\"dsid\",\"wellType\",\"updateUserId\",\"updateDate\",\"wellId\",\"wellCommonName\",\"projectId\"],\"groupView\":[{\"name\":\"groupByWellCommonName\",\"group\":[\"dataRegion\",\"wellCommonName\",\"wellId\"]},{\"name\":\"groupByPlatformName\",\"group\":[\"dataRegion\",\"platformName\"]}],\"identityId\":[\"dsid\"],\"naturalKey\":[\"dataRegion\",\"wellCommonName\"],\"properties\":{\"kb\":{\"type\":\"number\",\"title\":\"补心海拔\",\"description\":\"海拔(高程),补心海拔=地面海拔+补心高\"},\"egl\":{\"type\":\"number\",\"title\":\"地面海拔\",\"description\":\"地面海拔\"},\"kbd\":{\"type\":\"number\",\"title\":\"补心高度\",\"description\":\"补心高度\"},\"dsid\":{\"type\":\"string\",\"title\":\"DSID\",\"description\":\"主键ID\"},\"orgId\":{\"type\":\"string\",\"title\":\"机构ID\",\"description\":\"单位唯一标识符关联CD_ORGANIZATION的主键\"},\"bsflag\":{\"type\":\"number\",\"title\":\"删除标识\",\"description\":\"填写数据逻辑删除标识1=在用,-5=废弃\"},\"canton\":{\"type\":\"string\",\"title\":\"行政区名称\",\"description\":\"填写行政区代码对应的行政区名称\"},\"siteId\":{\"type\":\"string\",\"title\":\"物探工区ID\",\"description\":\"物探工区ID\"},\"wellId\":{\"type\":\"string\",\"title\":\"井ID\",\"description\":\"井标识符非限定唯一。由EPDM系统自动产生维护无需人工干预是用于唯一标识EPDM系统的每一口井的内部机器码\"},\"orgName\":{\"type\":\"string\",\"title\":\"机构名称\",\"description\":\"单位名称,必填\"},\"remarks\":{\"type\":\"string\",\"title\":\"备注\",\"description\":\"备注\"},\"prodDate\":{\"type\":\"date\",\"title\":\"投产日期\",\"description\":\"投产日期\"},\"siteName\":{\"type\":\"string\",\"title\":\"物探工区名称\",\"description\":\"物探工区名称\"},\"spudDate\":{\"type\":\"date\",\"title\":\"开钻日期\",\"description\":\"主井筒的开钻日期\"},\"wellDesc\":{\"type\":\"string\",\"title\":\"曾用名\",\"description\":\"填写这口井的曾用名\"},\"wellType\":{\"type\":\"string\",\"title\":\"井型\",\"description\":\"属性规范值字段引用属性代码WELL_TYPE下的属性值\"},\"checkDate\":{\"type\":\"date\",\"title\":\"审核日期\",\"description\":\"记录数据在本系统的审核时间,需精确到时分秒\"},\"dataGroup\":{\"type\":\"string\",\"title\":\"数据分组\",\"description\":\"数据分组\"},\"projectId\":{\"type\":\"string\",\"title\":\"地质单元ID\",\"description\":\"地质单元唯一标识符根据井别不同选择关联构造单元探井还是油气田单元开发井关联CD_GEO_UNIT表的主键\"},\"stationId\":{\"type\":\"string\",\"title\":\"站库ID\",\"description\":\"站库ID\"},\"wellState\":{\"type\":\"string\",\"title\":\"井状态\",\"description\":\"井状态\"},\"activityId\":{\"type\":\"string\",\"title\":\"项目ID\",\"description\":\"项目唯一标示符关联CD_ACTIVITY表的主键\"},\"cantonCode\":{\"type\":\"string\",\"title\":\"行政区代码\",\"description\":\"属性规范值字段引用属性代码CANTON下的属性值\"},\"createDate\":{\"type\":\"date\",\"title\":\"创建日期\",\"description\":\"记录数据在本系统的创建时间,需精确到时分秒\"},\"dataRegion\":{\"type\":\"string\",\"title\":\"油田标识\",\"description\":\"油田标识\"},\"dataSource\":{\"type\":\"string\",\"title\":\"数据来源\",\"description\":\"填写数据来源的表CODE\"},\"desgWellId\":{\"type\":\"string\",\"title\":\"设计井ID\",\"description\":\"设计井的唯一标识\"},\"energyType\":{\"type\":\"string\",\"title\":\"能源类型\",\"description\":\"描述本井生产的油气资源类型,如煤层气、致密气、页岩气等\"},\"platformId\":{\"type\":\"string\",\"title\":\"平台ID\",\"description\":\"平台ID\"},\"updateDate\":{\"type\":\"date\",\"title\":\"更新日期\",\"description\":\"记录数据在本系统最新的更新时间,需精确到时分秒,默认=创建时间\"},\"wellTypeId\":{\"type\":\"string\",\"title\":\"井型ID\",\"description\":\"属性规范值字段引用属性代码WELL_TYPE下的属性值\"},\"abandonDate\":{\"type\":\"date\",\"title\":\"报废日期\",\"description\":\"报废日期\"},\"abondonType\":{\"type\":\"string\",\"title\":\"报废类型\",\"description\":\"报废类型\"},\"actualXAxis\":{\"geom\":\"Point.x.actual\",\"mask\":\"coordinate\",\"type\":\"number\",\"title\":\"实际X坐标,实际X坐标\"},\"actualYAxis\":{\"geom\":\"Point.y.actual\",\"mask\":\"coordinate\",\"type\":\"number\",\"title\":\"实际Y坐标,实际Y坐标\"},\"checkUserId\":{\"type\":\"string\",\"title\":\"审核用户\",\"description\":\"记录数据在本系统的审核用户\"},\"createAppId\":{\"type\":\"string\",\"title\":\"创建应用\",\"description\":\"填写数据来源的系统名\"},\"designXAxis\":{\"geom\":\"Point.x.design\",\"mask\":\"coordinate\",\"type\":\"number\",\"title\":\"设计X坐标,设计X坐标\"},\"designYAxis\":{\"geom\":\"Point.y.design\",\"mask\":\"coordinate\",\"type\":\"number\",\"title\":\"设计Y坐标,设计Y坐标\"},\"projectName\":{\"type\":\"string\",\"title\":\"地质单元名称\",\"description\":\"地质单元名称,填写地质单元的中文名称,该名称在整个油田公司内不能重名,必填\"},\"stationName\":{\"type\":\"string\",\"title\":\"站库名称\",\"description\":\"站库名称,必填\"},\"wellPurpose\":{\"type\":\"string\",\"title\":\"井别\",\"description\":\"属性规范值字段引用属性代码WELL_PURPOSE下的属性值\"},\"activityName\":{\"type\":\"string\",\"title\":\"项目名称\",\"description\":\"项目名称,必填\"},\"completionMd\":{\"type\":\"number\",\"title\":\"完钻井深\",\"description\":\"完钻井深\"},\"createUserId\":{\"type\":\"string\",\"title\":\"创建用户\",\"description\":\"记录数据在本系统的创建用户\"},\"keyWellLevel\":{\"type\":\"string\",\"title\":\"重点井级别\",\"description\":\"重点井级别\"},\"platformName\":{\"type\":\"string\",\"title\":\"平台名称\",\"description\":\"平台名称\"},\"sourceDataId\":{\"type\":\"string\",\"title\":\"源库ID标识\",\"description\":\"存储数据来源的主键信息\"},\"structurePos\":{\"type\":\"string\",\"title\":\"构造位置\",\"description\":\"构造位置的描述\"},\"updateUserId\":{\"type\":\"string\",\"title\":\"更新用户\",\"description\":\"记录数据在本系统最新的更新用户,默认=创建用户\"},\"geoOffsetEast\":{\"geom\":\"Point.x.wellhead\",\"mask\":\"coordinate\",\"type\":\"number\",\"title\":\"井口横坐标\"},\"seismicLineNo\":{\"type\":\"string\",\"title\":\"井旁地震测线号\",\"description\":\"井旁地震测线号(勘探井)\"},\"wellLegalName\":{\"type\":\"string\",\"title\":\"拼音井号\",\"description\":\"规范的井号名称,井号名称命名规范请参考《主数据库技术标准》\"},\"wellPurposeId\":{\"type\":\"string\",\"title\":\"井别ID\",\"description\":\"属性规范值字段引用属性代码WELL_PURPOSE下的属性值\"},\"completionDate\":{\"type\":\"date\",\"title\":\"完井日期\",\"description\":\"完井日期\"},\"geoDescription\":{\"type\":\"string\",\"title\":\"地理位置\",\"description\":\"地理位置的描述\"},\"geoOffsetNorth\":{\"geom\":\"Point.y.wellhead\",\"mask\":\"coordinate\",\"type\":\"number\",\"title\":\"井口纵坐标\"},\"wellCommonName\":{\"type\":\"string\",\"title\":\"井名\",\"description\":\"通用井名,来源于钻井公报的汉字井名,必填\"},\"endDrillingDate\":{\"type\":\"date\",\"title\":\"完钻日期\",\"description\":\"最后一个井筒的完钻日期\"},\"targetFormation\":{\"type\":\"string\",\"title\":\"目的层\",\"description\":\"目的层\"},\"completionMethod\":{\"type\":\"string\",\"title\":\"完井方法\",\"description\":\"完井方法\"},\"registrationDate\":{\"type\":\"date\",\"title\":\"注册日期\",\"description\":\"井位通知单下达日期\"},\"sourceCreateDate\":{\"type\":\"date\",\"title\":\"源头数据采集时间\",\"description\":\"记录源头系统采集数据的时间\"},\"coordinateSystemId\":{\"geom\":\"Point.srid.wellhead,Point.srid.design,Point.srid.actual\",\"type\":\"string\",\"title\":\"坐标系统ID\",\"description\":\"坐标系统的唯一标示符\"},\"completionFormation\":{\"type\":\"string\",\"title\":\"钻井完钻层位\",\"description\":\"钻井完钻层位\"},\"coordinateSystemName\":{\"type\":\"string\",\"title\":\"坐标系名称\",\"description\":\"坐标系名称\"},\"wellbaseGeoOffsetEast\":{\"type\":\"number\",\"title\":\"井底横坐标\",\"description\":\"井底横坐标\"},\"wellbaseGeoOffsetNorth\":{\"type\":\"number\",\"title\":\"井底纵坐标\",\"description\":\"井底纵坐标\"}},\"defaultShow\":[\"wellCommonName\"]}",
"vt": "json"
},
{
"p": "topic",
"vt": "str"
},
{
"p": "req.body.dms_meta",
"v": "{\"flow_state\":\"5\",\"domain\":\"wb_cd\",\"name\":\"cd_well\",\"add_tree\":\"Y\",\"id\":\"cd_well.1.0.0\",\"create_user\":\"admin\",\"title\":\"井基本信息\",\"type\":\"master-data\",\"version\":\"1.0.0\",\"update_date\":\"2025-10-2110:45:38\",\"tags\":[\"主数据\"]}",
"vt": "json"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"x": 170,
"y": 280,
"wires": [
[
"f414ed448fcf544b"
]
]
},
{
"id": "dfe6ed572461c4a5",
"type": "debug",
"z": "78d15f59dee4b6d8",
"name": "debug 10",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "msg",
"targetType": "jsonata",
"statusVal": "",
"statusType": "auto",
"x": 600,
"y": 140,
"wires": []
},
{
"id": "a252afceaa16c14c",
"type": "catch",
"z": "78d15f59dee4b6d8",
"name": "",
"scope": null,
"uncaught": false,
"x": 160,
"y": 460,
"wires": [
[
"1496f4d2a070111c"
]
]
},
{
"id": "1496f4d2a070111c",
"type": "debug",
"z": "78d15f59dee4b6d8",
"name": "debug 11",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 440,
"y": 460,
"wires": []
},
{
"id": "5231ed8a796d6f17",
"type": "function",
"z": "78d15f59dee4b6d8",
"name": "分页参数检查",
"func": "'use strict';\n\nconst TEST_METADATA = {\n id: 'TC-DMS-PAGINATION-001',\n name: '分页参数检查',\n description: \"检查API请求参数中是否包含标准分页参数pageNo、pageSize和isSearchCount。只有名称含有'查询'、'列表'等并且不含有'详情'一类的API才应用此验证。\",\n severity: 'MEDIUM',\n tags: ['pagination', 'params', 'backend-guide'],\n skip_execution: true,\n};\n\nconst globalApiSpec = msg && msg.oas_def ? msg.oas_def : null;\n\nif (!globalApiSpec || typeof globalApiSpec !== 'object' || !globalApiSpec.paths) {\n const errorMessage = '分页参数检查无法运行msg.oas_def 缺失、不是对象或不包含 paths';\n node.error(errorMessage, msg);\n msg.error = errorMessage;\n msg.testcase = {\n metadata: TEST_METADATA,\n applies: false,\n skipped: true,\n results: [],\n };\n return msg;\n}\n\nconst allResults = [];\nlet testApplied = false;\n\n// 遍历所有路径和方法\nfor (const path in globalApiSpec.paths) {\n if (Object.prototype.hasOwnProperty.call(globalApiSpec.paths, path)) {\n const pathItem = globalApiSpec.paths[path];\n for (const method in pathItem) {\n if (Object.prototype.hasOwnProperty.call(pathItem, method) && ['get', 'post', 'put', 'delete', 'patch', 'options', 'head'].includes(method.toLowerCase())) {\n\n const operation = pathItem[method];\n if (!operation || typeof operation !== 'object') continue;\n\n // 构建 endpointSpec\n const endpointSpec = buildEndpointSpec(path, method, pathItem, operation);\n\n // 检查测试是否适用\n if (shouldApplyTest(endpointSpec)) {\n testApplied = true;\n const evaluation = runPaginationCheck(endpointSpec, globalApiSpec);\n allResults.push(...evaluation.results);\n }\n }\n }\n }\n}\n\nmsg.testcase = {\n metadata: TEST_METADATA,\n applies: testApplied,\n skipped: !testApplied,\n results: allResults,\n};\nmsg.payload = msg.testcase;\nreturn msg;\n\nfunction buildEndpointSpec(path, method, pathItem, operation) {\n const combinedParameters = [];\n if (Array.isArray(pathItem.parameters)) {\n combinedParameters.push(...pathItem.parameters);\n }\n if (Array.isArray(operation.parameters)) {\n combinedParameters.push(...operation.parameters);\n }\n\n const spec = deepClone(operation);\n spec.method = method;\n spec.path = path;\n if (combinedParameters.length > 0) {\n spec.parameters = mergeParameters(combinedParameters);\n }\n return spec;\n}\n\nfunction shouldApplyTest(spec) {\n const method = (spec.method || '').toLowerCase();\n if (method !== 'get' && method !== 'post') {\n return false;\n }\n\n const path = spec.path || '';\n const summary = spec.summary || '';\n const description = spec.description || '';\n const operationId = spec.operationId || '';\n\n const includeKeywords = ['查询', '列表', '分页', 'page', 'list', 'query', 'search', 'find'];\n const excludeKeywords = ['详情', '明细', 'detail', 'info', 'get by id', 'getbyid', '查看'];\n\n const apiDescriptionText = `${summary} ${description} ${operationId} ${path}`.toLowerCase();\n\n const containsInclude = includeKeywords.some(keyword => apiDescriptionText.includes(keyword.toLowerCase()));\n if (!containsInclude) {\n return false;\n }\n\n // 检查是否是获取单个资源的 'get'\n if (method === 'get' && path.includes('{') && path.includes('}')) {\n const getByIdHints = ['get', 'detail', 'info', '查看'];\n const isGetById = getByIdHints.some(hint => apiDescriptionText.includes(hint));\n if (isGetById) return false;\n }\n\n const containsExclude = excludeKeywords.some(keyword => apiDescriptionText.includes(keyword.toLowerCase()));\n return !containsExclude;\n}\n\nfunction extractEndpointSpec(message, apiSpec) {\n // This function is no longer needed as we iterate through all paths and methods.\n // Kept for potential future use if a single endpoint needs to be targeted.\n return null;\n}\n\nfunction resolveEndpointInfo(message) {\n // This function is no longer needed as we iterate through all paths and methods.\n return null;\n}\n\nfunction mergeParameters(parameters) {\n const merged = new Map();\n for (const param of parameters) {\n if (!param || typeof param !== 'object') {\n continue;\n }\n const name = param.name || '';\n const location = param.in || '';\n const key = `${location}:${name}`;\n if (!merged.has(key)) {\n merged.set(key, deepClone(param));\n }\n }\n return Array.from(merged.values());\n}\n\nfunction runPaginationCheck(spec, apiSpec) {\n const results = [];\n const path = spec.path || '';\n const method = (spec.method || '').toLowerCase();\n\n let foundPageNo = false;\n let foundPageSize = false;\n let foundIsSearchCount = false;\n\n if (Array.isArray(spec.parameters)) {\n for (const param of spec.parameters) {\n if (!param || typeof param !== 'object') {\n continue;\n }\n const location = (param.in || '').toLowerCase();\n if (location !== 'query') {\n continue;\n }\n\n if (param.name === 'pageNo') {\n foundPageNo = true;\n } else if (param.name === 'pageSize') {\n foundPageSize = true;\n } else if (param.name === 'isSearchCount') {\n foundIsSearchCount = true;\n }\n }\n }\n\n if (method === 'post' && spec.requestBody && typeof spec.requestBody === 'object') {\n const { content } = spec.requestBody;\n if (content && typeof content === 'object') {\n for (const mediaType of Object.keys(content)) {\n const mediaDefinition = content[mediaType];\n if (!mediaDefinition || typeof mediaDefinition !== 'object') {\n continue;\n }\n const resolvedSchema = resolveSchema(mediaDefinition.schema, apiSpec);\n const properties = resolvedSchema && resolvedSchema.properties;\n if (!properties || typeof properties !== 'object') {\n continue;\n }\n\n if (Object.prototype.hasOwnProperty.call(properties, 'pageNo')) {\n foundPageNo = true;\n }\n if (Object.prototype.hasOwnProperty.call(properties, 'pageSize')) {\n foundPageSize = true;\n }\n if (Object.prototype.hasOwnProperty.call(properties, 'isSearchCount')) {\n foundIsSearchCount = true;\n }\n }\n }\n }\n\n if (foundPageNo && foundPageSize && foundIsSearchCount) {\n results.push(makePassedResult('API请求包含所有标准分页参数pageNo、pageSize和isSearchCount', {\n path,\n method: method.toUpperCase(),\n }));\n } else {\n const missingParams = [];\n if (!foundPageNo) missingParams.push('pageNo');\n if (!foundPageSize) missingParams.push('pageSize');\n if (!foundIsSearchCount) missingParams.push('isSearchCount');\n\n results.push(makeFailedResult(`API请求缺少标准分页参数${missingParams.join(', ')}`, {\n path,\n method: method.toUpperCase(),\n missing_params: missingParams,\n found_params: {\n pageNo: foundPageNo,\n pageSize: foundPageSize,\n isSearchCount: foundIsSearchCount,\n },\n }));\n }\n\n return { results };\n}\n\nfunction deepClone(value) {\n return value == null ? value : JSON.parse(JSON.stringify(value));\n}\n\nfunction makePassedResult(message, details) {\n return {\n passed: true,\n message,\n details: details || {},\n };\n}\n\nfunction makeFailedResult(message, details) {\n return {\n passed: false,\n message,\n details: details || {},\n };\n}\n\nfunction resolveSchema(schemaOrRef, apiSpec) {\n if (!schemaOrRef || typeof schemaOrRef !== 'object') {\n return {};\n }\n\n if (!schemaOrRef.$ref) {\n return schemaOrRef;\n }\n\n if (!apiSpec || typeof apiSpec !== 'object') {\n return schemaOrRef;\n }\n\n const refPath = schemaOrRef.$ref;\n if (typeof refPath !== 'string' || !refPath.startsWith('#/')) {\n return schemaOrRef;\n }\n\n const tokens = refPath.slice(2).split('/').map(unescapeRefToken);\n let current = apiSpec;\n for (const token of tokens) {\n if (current && Object.prototype.hasOwnProperty.call(current, token)) {\n current = current[token];\n } else {\n return schemaOrRef;\n }\n }\n\n return current && typeof current === 'object' ? current : schemaOrRef;\n}\n\nfunction unescapeRefToken(token) {\n return token.replace(/~1/g, '/').replace(/~0/g, '~');\n}\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 500,
"y": 240,
"wires": [
[
"dfe6ed572461c4a5",
"d666851b16cf6482"
]
]
},
{
"id": "9a020a2d438e0d5e",
"type": "file",
"z": "78d15f59dee4b6d8",
"name": "",
"filename": "C:\\workspace\\test2.txt",
"filenameType": "str",
"appendNewline": true,
"createDir": false,
"overwriteFile": "false",
"encoding": "none",
"x": 760,
"y": 200,
"wires": [
[]
]
},
{
"id": "ad6438b1a6e76365",
"type": "inject",
"z": "78d15f59dee4b6d8",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 130,
"y": 320,
"wires": [
[
"f414ed448fcf544b"
]
]
},
{
"id": "6ab9403df07fcefa",
"type": "function",
"z": "78d15f59dee4b6d8",
"name": "参数生成器",
"func": "'use strict';\nmsg.operationId =\"createResource\"\n/**\n * 根据 OpenAPI (Swagger) 规范为指定接口生成示例请求参数。\n *\n * 输入:\n * - msg.oas_def / msg.payload / msg.swagger: OpenAPI 3.x 文档对象或 JSON 字符串\n * - msg.operationId / msg.req.body.operationId: 目标 operationId优先级最高\n * - msg.path + msg.method: 目标路径与方法(可选)\n * - 如果以上字段均缺失且文档仅包含一个 operation则默认使用该 operation\n *\n * 输出:\n * - msg.mock: {\n * operationId,\n * method,\n * path,\n * mediaType,\n * pathParams: {},\n * query: {},\n * headers: {},\n * cookies: {},\n * body: <示例请求体或 undefined>\n * }\n * - msg.payload 同步为 msg.mock 便于调试\n */\n\nlet openApi;\ntry {\n openApi = extractOpenApiDocument(msg);\n} catch (error) {\n node.error(`Swagger 自动填充初始化失败:${error.message}`, msg);\n msg.error = error.message;\n return msg;\n}\n\nconst operationCtx = resolveOperationContext(msg, openApi);\nif (!operationCtx.operation) {\n const message = `未找到匹配的接口定义operationId=${operationCtx.requestedOperationId || '未指定'}, path=${operationCtx.requestedPath || '未指定'}, method=${operationCtx.requestedMethod || '未指定'})`;\n node.error(message, msg);\n msg.error = message;\n return msg;\n}\n\nconst parameterSamples = buildParameterSamples(operationCtx, openApi);\nconst bodySample = buildRequestBodySample(operationCtx.operation, openApi);\n\nmsg.mock = Object.assign({}, msg.mock, {\n operationId: operationCtx.operation.operationId || operationCtx.requestedOperationId || '',\n method: (operationCtx.method || '').toUpperCase(),\n path: operationCtx.path || '',\n mediaType: bodySample.mediaType,\n pathParams: parameterSamples.path,\n query: parameterSamples.query,\n headers: parameterSamples.header,\n cookies: parameterSamples.cookie,\n body: bodySample.payload,\n});\n\nmsg.payload = msg.mock;\nmsg.mockCandidates = operationCtx.candidates;\nif (operationCtx.autoSelected) {\n msg.mockAutoSelected = true;\n}\nreturn msg;\n\nfunction extractOpenApiDocument(message) {\n const candidate =\n message && message.oas_def ? message.oas_def :\n message && message.swagger ? message.swagger :\n message && message.payload ? message.payload :\n null;\n\n if (!candidate) {\n throw new Error('未提供 OpenAPI 文档(需要 msg.oas_def / msg.swagger / msg.payload');\n }\n\n if (typeof candidate === 'string') {\n try {\n return JSON.parse(candidate);\n } catch (error) {\n throw new Error(`OpenAPI JSON 解析失败:${error.message}`);\n }\n }\n\n if (typeof candidate !== 'object') {\n throw new Error('OpenAPI 文档必须是对象或 JSON 字符串');\n }\n\n if (!candidate.paths || typeof candidate.paths !== 'object') {\n throw new Error('OpenAPI 文档缺少 paths 字段');\n }\n\n return candidate;\n}\n\nfunction resolveOperationContext(message, apiDoc) {\n const requestedOperationId =\n (message && message.operationId) ||\n (message && message.req && message.req.body && message.req.body.operationId) ||\n null;\n\n const requestedPath =\n (message && message.path) ||\n (message && message.req && message.req.body && message.req.body.path) ||\n null;\n\n const requestedMethodRaw =\n (message && message.method) ||\n (message && message.req && message.req.body && message.req.body.method) ||\n null;\n const requestedMethod = requestedMethodRaw ? String(requestedMethodRaw).toLowerCase() : null;\n\n const operations = enumerateOperations(apiDoc);\n\n let matched = null;\n if (requestedOperationId) {\n matched = operations.find(op => op.operation.operationId === requestedOperationId);\n }\n\n if (!matched && requestedPath && requestedMethod) {\n matched = operations.find(op => op.path === requestedPath && op.method === requestedMethod);\n }\n\n let autoSelected = false;\n if (!matched && operations.length > 0) {\n matched = operations[0];\n autoSelected = true;\n }\n\n return {\n operation: matched ? matched.operation : undefined,\n method: matched ? matched.method : undefined,\n pathItem: matched ? matched.pathItem : undefined,\n path: matched ? matched.path : undefined,\n autoSelected,\n candidates: operations.map(op => ({\n operationId: op.operation && op.operation.operationId ? op.operation.operationId : '',\n method: op.method ? op.method.toUpperCase() : '',\n path: op.path,\n summary: op.operation && op.operation.summary ? op.operation.summary : '',\n })),\n requestedOperationId,\n requestedPath,\n requestedMethod,\n };\n}\n\nfunction enumerateOperations(apiDoc) {\n const results = [];\n const validMethods = ['get', 'put', 'post', 'delete', 'patch', 'options', 'head', 'trace'];\n for (const path of Object.keys(apiDoc.paths || {})) {\n const pathItem = apiDoc.paths[path];\n if (!pathItem || typeof pathItem !== 'object') {\n continue;\n }\n for (const method of validMethods) {\n if (pathItem[method] && typeof pathItem[method] === 'object') {\n results.push({\n path,\n method,\n operation: pathItem[method],\n pathItem,\n });\n }\n }\n }\n return results;\n}\n\nfunction buildParameterSamples(operationContext, apiDoc) {\n const aggregated = {\n path: {},\n query: {},\n header: {},\n cookie: {},\n };\n\n const seen = new Set();\n const paramSources = [];\n if (Array.isArray(operationContext.pathItem && operationContext.pathItem.parameters)) {\n paramSources.push(operationContext.pathItem.parameters);\n }\n if (Array.isArray(operationContext.operation.parameters)) {\n paramSources.push(operationContext.operation.parameters);\n }\n\n for (const source of paramSources) {\n for (const param of source) {\n if (!param || typeof param !== 'object') {\n continue;\n }\n const name = param.name || '';\n const location = (param.in || 'query').toLowerCase();\n const key = `${location}:${name}`;\n if (!name || seen.has(key)) {\n continue;\n }\n seen.add(key);\n\n const resolvedParam = resolveMaybeRef(param, apiDoc);\n const targetBucket = aggregated[location] || aggregated.query;\n targetBucket[name] = generateParameterSample(resolvedParam, apiDoc, location);\n }\n }\n\n return aggregated;\n}\n\nfunction generateParameterSample(param, apiDoc, location) {\n if (!param || typeof param !== 'object') {\n return 'sample';\n }\n\n const example = pickExample(param);\n if (example !== undefined) {\n return example;\n }\n\n const schema = resolveMaybeRef(param.schema, apiDoc);\n const sample = generateSampleValue(schema, apiDoc);\n if (sample !== undefined) {\n return sample;\n }\n\n // fallback\n switch (location) {\n case 'path':\n return 'sample-id';\n case 'header':\n return 'sample-header';\n case 'cookie':\n return 'sample-cookie';\n default:\n return 'sample';\n }\n}\n\nfunction buildRequestBodySample(operation, apiDoc) {\n const resolvedBody = resolveMaybeRef(operation.requestBody, apiDoc);\n if (!resolvedBody || typeof resolvedBody !== 'object') {\n return { payload: undefined, mediaType: undefined };\n }\n\n const content = resolvedBody.content;\n if (!content || typeof content !== 'object') {\n return { payload: undefined, mediaType: undefined };\n }\n\n const preferredMediaTypes = [\n 'application/json',\n 'application/*+json',\n 'application/x-www-form-urlencoded',\n 'multipart/form-data',\n 'text/plain',\n ];\n\n let chosenMediaType = null;\n for (const mediaType of preferredMediaTypes) {\n if (content[mediaType]) {\n chosenMediaType = mediaType;\n break;\n }\n }\n if (!chosenMediaType) {\n const mediaKeys = Object.keys(content);\n chosenMediaType = mediaKeys.length > 0 ? mediaKeys[0] : null;\n }\n if (!chosenMediaType) {\n return { payload: undefined, mediaType: undefined };\n }\n\n const mediaObject = content[chosenMediaType];\n if (!mediaObject || typeof mediaObject !== 'object') {\n return { payload: undefined, mediaType: chosenMediaType };\n }\n\n if (mediaObject.example !== undefined) {\n return { payload: clone(mediaObject.example), mediaType: chosenMediaType };\n }\n if (mediaObject.examples && typeof mediaObject.examples === 'object') {\n const firstExample = Object.values(mediaObject.examples)[0];\n if (firstExample && typeof firstExample === 'object' && firstExample.value !== undefined) {\n return { payload: clone(firstExample.value), mediaType: chosenMediaType };\n }\n }\n\n const schema = resolveMaybeRef(mediaObject.schema, apiDoc);\n const payload = generateSampleValue(schema, apiDoc);\n return { payload, mediaType: chosenMediaType };\n}\n\nfunction pickExample(node) {\n if (!node || typeof node !== 'object') {\n return undefined;\n }\n if (node.example !== undefined) {\n return clone(node.example);\n }\n if (node.examples && typeof node.examples === 'object') {\n const first = Object.values(node.examples)[0];\n if (first && typeof first === 'object' && first.value !== undefined) {\n return clone(first.value);\n }\n }\n if (node.default !== undefined) {\n return clone(node.default);\n }\n return undefined;\n}\n\nfunction generateSampleValue(schema, apiDoc, depth = 0, seenRefs = new Set()) {\n if (!schema || typeof schema !== 'object') {\n return undefined;\n }\n if (depth > 8) {\n return undefined;\n }\n\n if (schema.example !== undefined) {\n return clone(schema.example);\n }\n if (schema.default !== undefined) {\n return clone(schema.default);\n }\n if (schema.const !== undefined) {\n return clone(schema.const);\n }\n if (Array.isArray(schema.enum) && schema.enum.length > 0) {\n return clone(schema.enum[0]);\n }\n\n if (schema.$ref) {\n if (seenRefs.has(schema.$ref)) {\n return undefined;\n }\n seenRefs.add(schema.$ref);\n const resolved = resolveMaybeRef(schema, apiDoc, seenRefs);\n return generateSampleValue(resolved, apiDoc, depth + 1, seenRefs);\n }\n\n if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0) {\n return generateSampleValue(schema.oneOf[0], apiDoc, depth + 1, seenRefs);\n }\n if (Array.isArray(schema.anyOf) && schema.anyOf.length > 0) {\n return generateSampleValue(schema.anyOf[0], apiDoc, depth + 1, seenRefs);\n }\n if (Array.isArray(schema.allOf) && schema.allOf.length > 0) {\n const merged = schema.allOf\n .map(part => resolveMaybeRef(part, apiDoc, seenRefs))\n .filter(part => part && typeof part === 'object')\n .reduce((acc, part) => Object.assign(acc, part), {});\n return generateSampleValue(merged, apiDoc, depth + 1, seenRefs);\n }\n\n const type = inferSchemaType(schema);\n switch (type) {\n case 'object': {\n const result = {};\n if (schema.properties && typeof schema.properties === 'object') {\n for (const [key, value] of Object.entries(schema.properties)) {\n const resolvedChild = resolveMaybeRef(value, apiDoc, seenRefs);\n const childSample = generateSampleValue(resolvedChild, apiDoc, depth + 1, seenRefs);\n if (childSample !== undefined) {\n result[key] = childSample;\n }\n }\n }\n const required = Array.isArray(schema.required) ? schema.required : [];\n for (const propertyName of required) {\n if (!Object.prototype.hasOwnProperty.call(result, propertyName)) {\n result[propertyName] = pickFallbackByFormat({ type: 'string' });\n }\n }\n return Object.keys(result).length > 0 ? result : {};\n }\n case 'array': {\n const itemSchema = resolveMaybeRef(schema.items, apiDoc, seenRefs) || { type: 'string' };\n const itemSample = generateSampleValue(itemSchema, apiDoc, depth + 1, seenRefs);\n return itemSample !== undefined ? [itemSample] : [];\n }\n case 'integer':\n return Number.isInteger(schema.minimum) ? schema.minimum : 1;\n case 'number':\n if (schema.minimum !== undefined) {\n return typeof schema.minimum === 'number' ? schema.minimum : 0;\n }\n return 1;\n case 'boolean':\n return true;\n case 'string':\n default:\n return pickFallbackByFormat(schema);\n }\n}\n\nfunction inferSchemaType(schema) {\n if (!schema || typeof schema !== 'object') {\n return 'string';\n }\n if (schema.type) {\n return schema.type;\n }\n if (schema.properties) {\n return 'object';\n }\n if (schema.items) {\n return 'array';\n }\n return 'string';\n}\n\nfunction pickFallbackByFormat(schema) {\n const format = schema && schema.format ? schema.format.toLowerCase() : null;\n switch (format) {\n case 'date':\n return '2025-01-01';\n case 'date-time':\n return '2025-01-01T00:00:00Z';\n case 'email':\n return 'user@example.com';\n case 'uuid':\n return '00000000-0000-4000-8000-000000000000';\n case 'uri':\n case 'url':\n return 'https://example.com/resource';\n case 'byte':\n return Buffer.from('sample').toString('base64');\n case 'binary':\n return '<binary>';\n default:\n break;\n }\n\n const pattern = schema && schema.pattern;\n if (pattern && /[0-9]{4}-[0-9]{2}-[0-9]{2}/.test(pattern)) {\n return '2025-01-01';\n }\n\n return 'sample';\n}\n\nfunction resolveMaybeRef(node, apiDoc, seenRefs = new Set()) {\n if (!node || typeof node !== 'object') {\n return node;\n }\n if (!node.$ref) {\n return node;\n }\n\n const ref = node.$ref;\n if (seenRefs.has(ref)) {\n return {};\n }\n seenRefs.add(ref);\n\n const resolved = resolveRef(ref, apiDoc);\n if (!resolved || typeof resolved !== 'object') {\n return node;\n }\n\n const remainder = Object.assign({}, node);\n delete remainder.$ref;\n\n return Object.assign({}, clone(resolved), remainder);\n}\n\nfunction resolveRef(ref, apiDoc) {\n if (typeof ref !== 'string' || !ref.startsWith('#/')) {\n return null;\n }\n\n const tokens = ref.slice(2).split('/').map(unescapeRefToken);\n let current = apiDoc;\n for (const token of tokens) {\n if (current && typeof current === 'object' && Object.prototype.hasOwnProperty.call(current, token)) {\n current = current[token];\n } else {\n return null;\n }\n }\n return current;\n}\n\nfunction unescapeRefToken(token) {\n return token.replace(/~1/g, '/').replace(/~0/g, '~');\n}\n\nfunction clone(value) {\n return value == null ? value : JSON.parse(JSON.stringify(value));\n}\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 470,
"y": 400,
"wires": [
[
"6e56a2ccd9fcaacc"
]
]
},
{
"id": "6e56a2ccd9fcaacc",
"type": "debug",
"z": "78d15f59dee4b6d8",
"name": "参数展示",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "mock",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 700,
"y": 360,
"wires": []
},
{
"id": "60ce224bd2ed8e69",
"type": "function",
"z": "78d15f59dee4b6d8",
"name": "准备llm输入",
"func": "'use strict';\n\n/**\n * Node-RED Function 节点脚本:准备大模型 HTTP 请求。\n * 将生成 prompt、HTTP 请求参数,并把上下文写入 msg.llmContext。\n */\n\nconst DEFAULT_API_KEY = 'sk-lbGrsUPL1iby86h554FaE536C343435dAa9bA65967A840B2';\nconst DEFAULT_BASE_URL = 'https://aiproxy.petrotech.cnpc/v1';\nconst DEFAULT_ENDPOINT_PATH = '/chat/completions';\nconst DEFAULT_MODEL = 'deepseek-v3';\n\nfunction prepareLlmRequest(msg, node) {\n if (!msg.operationId) {\n msg.operationId = 'createResource';\n }\n\n try {\n const apiDoc = extractOpenApiDocument(msg);\n const operationCtx = resolveOperationContext(msg, apiDoc);\n\n if (!operationCtx.operation) {\n const err = `未找到匹配的接口定义operationId=${operationCtx.requestedOperationId || '未指定'}, path=${operationCtx.requestedPath || '未指定'}, method=${operationCtx.requestedMethod || '未指定'})`;\n node.error(err, msg);\n msg.error = err;\n return null;\n }\n\n const prompt = buildPrompt(operationCtx, apiDoc, msg);\n const requestPayload = buildRequestPayload(prompt, msg);\n\n const apiKey = (msg.llm && msg.llm.apiKey) || DEFAULT_API_KEY;\n const baseUrl = (msg.llm && msg.llm.baseUrl) || DEFAULT_BASE_URL;\n const endpointPath = (msg.llm && msg.llm.endpoint) || DEFAULT_ENDPOINT_PATH;\n\n const url = buildUrl(baseUrl, endpointPath);\n msg.method = 'POST';\n msg.url = url;\n msg.headers = Object.assign({}, msg.headers, {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n });\n msg.payload = requestPayload;\n msg.rejectUnauthorized = false;\n\n msg.llmContext = {\n prompt,\n operationCtx,\n provider: 'dashscope',\n requestConfig: {\n baseUrl,\n endpointPath,\n model: requestPayload.model,\n temperature: requestPayload.temperature,\n },\n };\n delete msg.error;\n return msg;\n } catch (error) {\n node.error(`LLM 请求准备失败:${error.message}`, msg);\n msg.error = error.message;\n return null;\n }\n}\n\nfunction extractOpenApiDocument(message) {\n const candidate =\n message && message.oas_def ? message.oas_def :\n message && message.swagger ? message.swagger :\n message && message.payload ? message.payload :\n null;\n\n if (!candidate) {\n throw new Error('未提供 OpenAPI 文档(需要 msg.oas_def / msg.swagger / msg.payload');\n }\n\n if (typeof candidate === 'string') {\n try {\n return JSON.parse(candidate);\n } catch (error) {\n throw new Error(`OpenAPI JSON 解析失败:${error.message}`);\n }\n }\n\n if (typeof candidate !== 'object') {\n throw new Error('OpenAPI 文档必须是对象或 JSON 字符串');\n }\n\n if (!candidate.paths || typeof candidate.paths !== 'object') {\n throw new Error('OpenAPI 文档缺少 paths 字段');\n }\n\n return candidate;\n}\n\nfunction resolveOperationContext(message, apiDoc) {\n const requestedOperationId =\n (message && message.operationId) ||\n (message && message.req && message.req.body && message.req.body.operationId) ||\n null;\n\n const requestedPath =\n (message && message.path) ||\n (message && message.req && message.req.body && message.req.body.path) ||\n null;\n\n const requestedMethodRaw =\n (message && message.method) ||\n (message && message.req && message.req.body && message.req.body.method) ||\n null;\n const requestedMethod = requestedMethodRaw ? String(requestedMethodRaw).toLowerCase() : null;\n\n const operations = enumerateOperations(apiDoc);\n\n let matched = null;\n if (requestedOperationId) {\n matched = operations.find(op => op.operation.operationId === requestedOperationId);\n }\n\n if (!matched && requestedPath && requestedMethod) {\n matched = operations.find(op => op.path === requestedPath && op.method === requestedMethod);\n }\n\n let autoSelected = false;\n if (!matched && operations.length > 0) {\n matched = operations[0];\n autoSelected = true;\n }\n\n return {\n operation: matched ? matched.operation : undefined,\n method: matched ? matched.method : undefined,\n pathItem: matched ? matched.pathItem : undefined,\n path: matched ? matched.path : undefined,\n autoSelected,\n candidates: operations.map(op => ({\n operationId: op.operation && op.operation.operationId ? op.operation.operationId : '',\n method: op.method ? op.method.toUpperCase() : '',\n path: op.path,\n summary: op.operation && op.operation.summary ? op.operation.summary : '',\n })),\n requestedOperationId,\n requestedPath,\n requestedMethod,\n };\n}\n\nfunction enumerateOperations(apiDoc) {\n const results = [];\n const validMethods = ['get', 'put', 'post', 'delete', 'patch', 'options', 'head', 'trace'];\n for (const path of Object.keys(apiDoc.paths || {})) {\n const pathItem = apiDoc.paths[path];\n if (!pathItem || typeof pathItem !== 'object') {\n continue;\n }\n for (const method of validMethods) {\n if (pathItem[method] && typeof pathItem[method] === 'object') {\n results.push({\n path,\n method,\n operation: pathItem[method],\n pathItem,\n });\n }\n }\n }\n return results;\n}\n\nfunction buildPrompt(operationCtx, apiDoc, message) {\n const operation = operationCtx.operation;\n const method = (operationCtx.method || '').toUpperCase();\n const path = operationCtx.path || '';\n const summary = operation.summary || '';\n const description = operation.description || '';\n\n const parameters = gatherParameters(operationCtx, apiDoc);\n const requestBodySchema = gatherRequestBodySchema(operation, apiDoc);\n\n const userNotes = message && message.prompt ? String(message.prompt) : '';\n const requiredFields = extractRequiredFields(requestBodySchema);\n const requiredFieldsText = requiredFields.length > 0\n ? requiredFields.map(field => `- ${field}`).join('\\n')\n : '';\n\n const lines = [];\n lines.push('你是一名负责生成 HTTP 接口测试参数的助手,请严格输出以下结构的 JSON');\n lines.push('{');\n lines.push(' \"pathParams\": { ... },');\n lines.push(' \"query\": { ... },');\n lines.push(' \"headers\": { ... },');\n lines.push(' \"cookies\": { ... },');\n lines.push(' \"body\": { ... },');\n lines.push(' \"notes\": \"...\"');\n lines.push('}');\n lines.push('未用到的分区请返回空对象,不要在 JSON 外输出任何文字。');\n lines.push('');\n lines.push('body.data[0] 必须遵守:');\n lines.push('- 覆盖 schema 中声明的全部必填字段,并给出符合字段类型/格式的真实感样例值。');\n lines.push('- 不得遗漏必填字段;若 schema 内有嵌套对象/数组的必填字段,同样要补齐。');\n lines.push('- 保持数值、日期、字符串等格式,只在 schema 无提示时使用 \"sample\"、\"2025-01-01\" 等占位值。');\n if (requiredFieldsText) {\n lines.push('必填字段清单(必须全部出现在 body.data[0] 中):');\n lines.push(requiredFieldsText);\n }\n lines.push('');\n lines.push('若 schema 中存在数组元素或复合结构的必填字段,也要为这些子字段提供值。');\n lines.push('');\n lines.push(`Target operation: ${method} ${path}`);\n\n if (summary) {\n lines.push(`Summary: ${summary}`);\n }\n if (description) {\n lines.push(`Description: ${description}`);\n }\n if (parameters.length > 0) {\n lines.push('Parameters:');\n for (const param of parameters) {\n lines.push(`- [${param.in}] ${param.name}: ${param.type || 'any'}${param.required ? ' (required)' : ''}${param.description ? ` - ${param.description}` : ''}`);\n }\n } else {\n lines.push('Parameters: none defined.');\n }\n\n if (requestBodySchema) {\n lines.push('Request body schema (JSON Schema excerpt):');\n lines.push(indentSnippet(JSON.stringify(requestBodySchema, null, 2), 2, 900));\n } else {\n lines.push('Request body: not defined.');\n }\n\n if (userNotes) {\n lines.push('');\n lines.push('User notes:');\n lines.push(userNotes);\n }\n\n return lines.join('\\n');\n}\n\nfunction gatherParameters(operationCtx, apiDoc) {\n const aggregated = [];\n const seen = new Set();\n\n const sources = [];\n if (Array.isArray(operationCtx.pathItem && operationCtx.pathItem.parameters)) {\n sources.push(operationCtx.pathItem.parameters);\n }\n if (Array.isArray(operationCtx.operation.parameters)) {\n sources.push(operationCtx.operation.parameters);\n }\n\n for (const list of sources) {\n for (const param of list) {\n if (!param || typeof param !== 'object') {\n continue;\n }\n const resolved = resolveMaybeRef(param, apiDoc);\n const key = `${resolved.in}:${resolved.name}`;\n if (!resolved.name || seen.has(key)) {\n continue;\n }\n seen.add(key);\n aggregated.push({\n name: resolved.name,\n in: resolved.in || 'query',\n required: !!resolved.required,\n description: resolved.description || '',\n type: resolved.schema ? inferFriendlyType(resolved.schema, apiDoc) : '',\n });\n }\n }\n\n return aggregated;\n}\n\nfunction gatherRequestBodySchema(operation, apiDoc) {\n const requestBody = resolveMaybeRef(operation.requestBody, apiDoc);\n if (!requestBody || typeof requestBody !== 'object' || !requestBody.content) {\n return null;\n }\n\n const content = requestBody.content;\n const mediaType = Object.keys(content).find(key => key.includes('json')) || Object.keys(content)[0];\n if (!mediaType) {\n return null;\n }\n\n const mediaObject = resolveMaybeRef(content[mediaType], apiDoc);\n if (!mediaObject || typeof mediaObject !== 'object' || !mediaObject.schema) {\n return null;\n }\n\n const schema = resolveMaybeRef(mediaObject.schema, apiDoc);\n return schema;\n}\n\nfunction inferFriendlyType(schema, apiDoc) {\n if (!schema) {\n return '';\n }\n const resolved = resolveMaybeRef(schema, apiDoc);\n if (!resolved || typeof resolved !== 'object') {\n return '';\n }\n\n if (resolved.type) {\n if (resolved.type === 'array' && resolved.items) {\n const itemType = inferFriendlyType(resolved.items, apiDoc) || 'any';\n return `[${itemType}]`;\n }\n return resolved.type;\n }\n\n if (resolved.enum && Array.isArray(resolved.enum)) {\n return `enum(${resolved.enum.slice(0, 3).join(', ')}${resolved.enum.length > 3 ? ', …' : ''})`;\n }\n\n if (resolved.properties) {\n return 'object';\n }\n\n return '';\n}\n\nfunction buildRequestPayload(prompt, message) {\n const systemPrompt = (message.llm && message.llm.systemPrompt) ||\n 'You write JSON only. Focus on realistic values for testing HTTP APIs.';\n\n const model = (message.llm && message.llm.model) || DEFAULT_MODEL;\n const temperature = (message.llm && typeof message.llm.temperature === 'number')\n ? message.llm.temperature : 0.2;\n\n return {\n model,\n temperature,\n response_format: { type: 'json_object' },\n messages: [\n { role: 'system', content: systemPrompt },\n { role: 'user', content: prompt },\n ],\n };\n}\n\nfunction buildUrl(baseUrl, endpointPath) {\n const normalizedBase = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;\n const path = endpointPath.startsWith('/') ? endpointPath.slice(1) : endpointPath;\n return `${normalizedBase}${path}`;\n}\n\nfunction resolveMaybeRef(node, apiDoc) {\n if (!node || typeof node !== 'object') {\n return node;\n }\n if (!node.$ref) {\n return node;\n }\n\n const resolved = resolveRef(node.$ref, apiDoc);\n if (!resolved || typeof resolved !== 'object') {\n return node;\n }\n\n const remainder = Object.assign({}, node);\n delete remainder.$ref;\n return Object.assign({}, clone(resolved), remainder);\n}\n\nfunction resolveRef(ref, apiDoc) {\n if (typeof ref !== 'string' || !ref.startsWith('#/')) {\n return null;\n }\n\n const tokens = ref.slice(2).split('/').map(unescapeRefToken);\n let current = apiDoc;\n for (const token of tokens) {\n if (current && typeof current === 'object' && Object.prototype.hasOwnProperty.call(current, token)) {\n current = current[token];\n } else {\n return null;\n }\n }\n return current;\n}\n\nfunction unescapeRefToken(token) {\n return token.replace(/~1/g, '/').replace(/~0/g, '~');\n}\n\nfunction indentSnippet(text, indentLevel, maxLength) {\n const trimmed = maxLength && text.length > maxLength ? `${text.slice(0, maxLength)}…` : text;\n const indent = ' '.repeat(indentLevel * 2);\n return trimmed.split('\\n').map(line => `${indent}${line}`).join('\\n');\n}\n\nfunction clone(value) {\n return value == null ? value : JSON.parse(JSON.stringify(value));\n}\n\nfunction extractRequiredFields(schema) {\n const result = new Set();\n collectRequiredFields(schema, result, '');\n return Array.from(result);\n}\n\nfunction collectRequiredFields(schema, result, pathPrefix) {\n if (!schema || typeof schema !== 'object') {\n return;\n }\n\n if (schema.$ref) {\n return;\n }\n\n if (Array.isArray(schema.required) && schema.properties && typeof schema.properties === 'object') {\n for (const key of schema.required) {\n const nextPath = pathPrefix ? `${pathPrefix}.${key}` : key;\n result.add(nextPath);\n collectRequiredFields(schema.properties[key], result, nextPath);\n }\n }\n\n if (schema.type === 'array' && schema.items) {\n collectRequiredFields(schema.items, result, pathPrefix);\n }\n\n if (schema.allOf && Array.isArray(schema.allOf)) {\n for (const part of schema.allOf) {\n collectRequiredFields(part, result, pathPrefix);\n }\n }\n}\n\nreturn prepareLlmRequest(msg, node);\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 350,
"y": 640,
"wires": [
[
"540a5771113ca5dd",
"d87dfcf0db518c75"
]
]
},
{
"id": "e87c2057c28d5eee",
"type": "debug",
"z": "78d15f59dee4b6d8",
"name": "llm参数展示",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "mock",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 790,
"y": 420,
"wires": []
},
{
"id": "20818ab4243934bf",
"type": "function",
"z": "78d15f59dee4b6d8",
"name": "处理llm返回",
"func": "'use strict';\n\n/**\n * Node-RED Function 节点脚本:处理大模型 HTTP 响应。\n * 将响应解析为 mock 数据并写回 msg。\n */\n\nfunction processLlmResponse(msg, node) {\n const context = msg.llmContext || {};\n if (!context.prompt || !context.operationCtx) {\n const err = 'LLM 上下文缺失,无法解析响应';\n node.error(err, msg);\n msg.error = err;\n return msg;\n }\n\n if (msg.statusCode && (msg.statusCode < 200 || msg.statusCode >= 300)) {\n const err = `大模型接口返回状态码 ${msg.statusCode}`;\n node.error(err, msg);\n msg.error = err;\n return msg;\n }\n\n let raw = msg.payload;\n if (Buffer.isBuffer(raw)) {\n raw = raw.toString('utf8');\n }\n if (typeof raw === 'string') {\n try {\n raw = JSON.parse(raw);\n } catch (error) {\n node.error(`无法解析大模型响应:${error.message}`, msg);\n msg.error = error.message;\n return msg;\n }\n }\n\n try {\n const parsed = parseModelResponse(raw);\n\n msg.llmRaw = raw;\n msg.mock = Object.assign({}, msg.mock, parsed.mock);\n msg.mockSource = context.provider || 'dashscope';\n msg.mockPrompt = context.prompt;\n msg.mockCandidates = context.operationCtx.candidates;\n msg.mockAutoSelected = !!context.operationCtx.autoSelected;\n msg.payload = msg.mock;\n\n delete msg.llmContext;\n if (msg.headers && msg.headers.Authorization) {\n delete msg.headers.Authorization;\n }\n\n delete msg.error;\n return msg;\n } catch (error) {\n node.error(`大模型参数生成失败:${error.message}`, msg);\n msg.error = error.message;\n return msg;\n }\n}\n\nfunction parseModelResponse(response) {\n if (!response || typeof response !== 'object') {\n throw new Error('大模型响应为空或不是对象');\n }\n\n if (response.mock && typeof response.mock === 'object') {\n return { mock: response.mock };\n }\n\n const choices = Array.isArray(response.choices) ? response.choices : [];\n const firstChoice = choices[0];\n const message = firstChoice && firstChoice.message ? firstChoice.message : null;\n const content = message && typeof message === 'object' ? message.content : null;\n\n if (!content) {\n throw new Error('响应中缺少 choices[0].message.content');\n }\n\n let mockObject;\n if (typeof content === 'string') {\n try {\n mockObject = JSON.parse(content);\n } catch (err) {\n throw new Error(`无法解析模型返回的 JSON${err.message},原始文本:${content}`);\n }\n } else if (Array.isArray(content)) {\n const jsonPart = content.find(part => part.type === 'output_text' || part.type === 'text' || part.type === 'json');\n const text = jsonPart && jsonPart.text ? jsonPart.text : null;\n if (!text) {\n throw new Error('响应内容不是字符串,且未找到可解析的文本段');\n }\n try {\n mockObject = JSON.parse(text);\n } catch (err) {\n throw new Error(`无法解析模型返回的 JSON${err.message},原始文本:${text}`);\n }\n } else {\n throw new Error('模型返回的 message.content 既不是字符串也不是文本片段数组');\n }\n\n if (!mockObject || typeof mockObject !== 'object') {\n throw new Error('模型返回的 JSON 不是对象');\n }\n\n const normalisedMock = {\n pathParams: mockObject.pathParams || mockObject.path_parameters || {},\n query: mockObject.query || mockObject.query_params || {},\n headers: mockObject.headers || {},\n cookies: mockObject.cookies || {},\n body: mockObject.body || {},\n notes: mockObject.notes || '',\n };\n\n return { mock: normalisedMock };\n}\n\nreturn processLlmResponse(msg, node);\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 730,
"y": 520,
"wires": [
[
"e87c2057c28d5eee",
"31d5f84e4c6f69f6"
]
]
},
{
"id": "540a5771113ca5dd",
"type": "http request",
"z": "78d15f59dee4b6d8",
"name": "",
"method": "POST",
"ret": "txt",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 580,
"y": 600,
"wires": [
[
"20818ab4243934bf",
"44fc6e2d405c2a78"
]
]
},
{
"id": "44fc6e2d405c2a78",
"type": "debug",
"z": "78d15f59dee4b6d8",
"name": "请求",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 750,
"y": 620,
"wires": []
},
{
"id": "9c84414e55654e6a",
"type": "function",
"z": "78d15f59dee4b6d8",
"name": "构建CRUD计划",
"func": "'use strict';\n\n/**\n * 构建 CRUD 执行动作所需的指令集合,保存于 msg.crudFlow。\n * 约定 operationId 采用 dms→oas 转换后默认的命名:\n * list<ResourceName>s / create<ResourceName> / delete<ResourceName>\n * 允许使用 msg.crudConfig.* 进行覆盖。\n */\nconst FALLBACK_BASE_URL = 'https://www.dev.ideas.cnpc/api/dms/well_kd_wellbore_ideas01/v1';\nconst DEFAULT_LIST_PAYLOAD = {\n version: '1.0.0',\n data: [],\n pageNo: 1,\n pageSize: 20,\n isSearchCount: true,\n};\nconst DEFAULT_CREATE_SAMPLE = {\n version: '1.0.0',\n act: -1,\n data: [\n {\n dsid: 'testid2',\n wellId: 'WELL-zzlhTEST-002',\n wellCommonName: 'zzlh测试用井名',\n wellLegalName: 'zzlh-test-01',\n wellPurpose: '开发井',\n wellType: '直井',\n dataRegion: 'ZZLH',\n projectId: 'PROJ-ZZLH-001',\n projectName: 'zzlh测试地质单元',\n orgId: 'ORG-ZZLH-01',\n orgName: 'zzlh采油厂',\n bsflag: 1,\n wellState: '生产中',\n spudDate: '2024-01-15',\n completionDate: '2024-05-20',\n prodDate: '2024-06-01',\n egl: 145.5,\n kbd: 5.2,\n kb: 150.7,\n actualXAxis: 550123.45,\n actualYAxis: 4998765.32,\n coordinateSystemName: 'zzlh测试坐标系',\n geoDescription: '位于zzlh测试区域',\n remarks: '这是一口用于系统测试的生产井。',\n createUserId: 'testuser001',\n createDate: '2025-09-12T10:00:00Z',\n updateUserId: 'testuser001',\n updateDate: '2025-09-12T10:00:00Z',\n },\n ],\n};\nconst DEFAULT_DELETE_TEMPLATE = {\n version: '1.0.0',\n data: ['{{primaryKey}}'],\n};\n\nreturn buildCrudPlan(msg, node);\n\nfunction buildCrudPlan(message, node) {\n const requestBody = getRequestBody(message);\n const crudConfig = mergeDeep({}, requestBody.crudConfig || {}, message.crudConfig || {});\n const apiDoc = normaliseOpenApi(message, crudConfig, requestBody, node);\n const operations = collectOperations(apiDoc);\n\n const listOp = pickOperation('list', operations, crudConfig);\n const createOp = pickOperation('create', operations, crudConfig);\n const deleteOp = pickOperation('delete', operations, crudConfig);\n\n if (!listOp || !createOp || !deleteOp) {\n const missing = [\n !listOp ? 'list' : null,\n !createOp ? 'create' : null,\n !deleteOp ? 'delete' : null,\n ].filter(Boolean).join(', ');\n const errMsg = `未能在 OpenAPI 文档中找到必要的 CRUD 操作:${missing}`;\n node.error(errMsg, message);\n message.error = errMsg;\n return null;\n }\n\n const resourceName = determineResourceName([createOp, listOp, deleteOp]) || 'Resource';\n const identityField = crudConfig.identityField ||\n requestBody.identityField ||\n selectIdentityField(apiDoc, crudConfig) ||\n 'dsid';\n const baseUrl = trimTrailingSlash(\n crudConfig.baseUrl ||\n requestBody.baseUrl ||\n message.baseUrl ||\n FALLBACK_BASE_URL\n );\n const headers = mergeDeep({}, requestBody.headers || {}, crudConfig.headers || {});\n\n const listConfig = mergeDeep(\n {},\n { payload: clone(DEFAULT_LIST_PAYLOAD) },\n requestBody.list || {},\n crudConfig.list || {}\n );\n const createConfig = mergeDeep(\n {},\n { payload: clone(DEFAULT_CREATE_SAMPLE) },\n requestBody.create || {},\n crudConfig.create || {}\n );\n const deleteConfig = mergeDeep(\n {},\n { payloadTemplate: clone(DEFAULT_DELETE_TEMPLATE) },\n requestBody.delete || {},\n crudConfig.delete || {}\n );\n\n if (requestBody.dataRegion) {\n headers.Dataregion = requestBody.dataRegion;\n }\n\n message.crudFlow = {\n resourceName,\n identityField,\n baseUrl,\n headers,\n list: Object.assign({\n operationId: listOp.operationId,\n method: listOp.method,\n path: listOp.path,\n }, listConfig),\n create: Object.assign({\n operationId: createOp.operationId,\n method: createOp.method,\n path: createOp.path,\n samplePayload: clone(DEFAULT_CREATE_SAMPLE),\n }, createConfig),\n delete: Object.assign({\n operationId: deleteOp.operationId,\n method: deleteOp.method,\n path: deleteOp.path,\n }, deleteConfig),\n openapi: apiDoc,\n };\n\n delete message.crudConfig;\n if (message.baseUrl) {\n delete message.baseUrl;\n }\n if (message.headers && !Object.keys(message.headers).length) {\n delete message.headers;\n }\n\n message.oas_def = apiDoc;\n delete message.error;\n return message;\n}\n\nfunction normaliseOpenApi(message, crudConfig, requestBody, node) {\n let candidate =\n crudConfig.openapi ||\n requestBody.openapi ||\n message.oas_def ||\n message.swagger ||\n message.payload;\n if (typeof candidate === 'string') {\n try {\n candidate = JSON.parse(candidate);\n } catch (err) {\n throw new Error(`OpenAPI JSON 解析失败:${err.message}`);\n }\n }\n if (!candidate || typeof candidate !== 'object') {\n throw new Error('未提供合法的 OpenAPI 文档');\n }\n if (!candidate.paths || typeof candidate.paths !== 'object' || Object.keys(candidate.paths).length === 0) {\n throw new Error('OpenAPI 文档缺少 paths 定义');\n }\n return candidate;\n}\n\nfunction collectOperations(apiDoc) {\n const operations = [];\n const allowed = ['get', 'post', 'put', 'delete', 'patch', 'options', 'head', 'trace'];\n for (const path of Object.keys(apiDoc.paths)) {\n const pathItem = apiDoc.paths[path];\n if (!pathItem || typeof pathItem !== 'object') {\n continue;\n }\n for (const method of Object.keys(pathItem)) {\n if (!allowed.includes(method.toLowerCase())) {\n continue;\n }\n const operation = pathItem[method];\n if (!operation || typeof operation !== 'object') {\n continue;\n }\n operations.push({\n operationId: operation.operationId || '',\n summary: operation.summary || '',\n description: operation.description || '',\n method: method.toUpperCase(),\n path,\n operation,\n });\n }\n }\n return operations;\n}\n\nfunction pickOperation(kind, operations, crudConfig) {\n const overrideId =\n (crudConfig[kind] && crudConfig[kind].operationId) ||\n crudConfig[`${kind}OperationId`];\n if (overrideId) {\n return operations.find(op => op.operationId === overrideId);\n }\n\n const matcher = getDefaultMatcher(kind);\n let matched = operations.find(op => matcher.test(op.operationId));\n if (matched) {\n return matched;\n }\n\n // 兜底策略\n switch (kind) {\n case 'list':\n return operations.find(op => op.method === 'GET') || null;\n case 'create':\n return operations.find(op => op.method === 'POST') || null;\n case 'delete':\n return operations.find(op => op.method === 'DELETE') ||\n operations.find(op => op.method === 'POST' && /delete/i.test(op.operationId)) ||\n null;\n default:\n return null;\n }\n}\n\nfunction getDefaultMatcher(kind) {\n switch (kind) {\n case 'list':\n return /^list[A-Z].*s$/;\n case 'create':\n return /^create[A-Z].*/;\n case 'delete':\n return /^delete[A-Z].*/;\n default:\n return /^$/;\n }\n}\n\nfunction determineResourceName(candidates) {\n for (const item of candidates) {\n const opId = item && item.operationId ? item.operationId : '';\n let match;\n if ((match = opId.match(/^list([A-Z].*)s$/))) {\n return match[1];\n }\n if ((match = opId.match(/^create([A-Z].*)$/))) {\n return match[1];\n }\n if ((match = opId.match(/^delete([A-Z].*)$/))) {\n return match[1];\n }\n }\n return null;\n}\n\nfunction selectIdentityField(apiDoc, crudConfig) {\n if (crudConfig.identityField) {\n return crudConfig.identityField;\n }\n if (apiDoc.components && apiDoc.components.schemas) {\n for (const schemaName of Object.keys(apiDoc.components.schemas)) {\n const schema = apiDoc.components.schemas[schemaName];\n if (!schema || typeof schema !== 'object') {\n continue;\n }\n const identity = Array.isArray(schema['x-dms-identityId']) ? schema['x-dms-identityId'][0] : null;\n if (identity) {\n return identity;\n }\n }\n }\n if (apiDoc.components && apiDoc.components.schemas) {\n for (const schemaName of Object.keys(apiDoc.components.schemas)) {\n const schema = apiDoc.components.schemas[schemaName];\n if (schema && schema.properties && Object.prototype.hasOwnProperty.call(schema.properties, 'dsid')) {\n return 'dsid';\n }\n }\n }\n return 'dsid';\n}\n\nfunction trimTrailingSlash(url) {\n if (!url) {\n return '';\n }\n return url.replace(/\\/+$/, '');\n}\n\nfunction getRequestBody(message) {\n if (message && message.req && message.req.body && typeof message.req.body === 'object') {\n return message.req.body;\n }\n return {};\n}\n\nfunction clone(value) {\n return value == null ? value : JSON.parse(JSON.stringify(value));\n}\n\nfunction mergeDeep(target, ...sources) {\n for (const source of sources) {\n if (!isPlainObject(source)) {\n continue;\n }\n for (const key of Object.keys(source)) {\n const value = source[key];\n if (value === undefined) {\n continue;\n }\n if (isPlainObject(value)) {\n const base = isPlainObject(target[key]) ? target[key] : {};\n target[key] = mergeDeep({}, base, value);\n } else {\n target[key] = clone(value);\n }\n }\n }\n return target;\n}\n\nfunction isPlainObject(value) {\n return Object.prototype.toString.call(value) === '[object Object]';\n}\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 320,
"y": 340,
"wires": [
[
"76358bc84ac6e3e1"
]
]
},
{
"id": "76358bc84ac6e3e1",
"type": "function",
"z": "78d15f59dee4b6d8",
"name": "设置create上下文",
"func": "'use strict';\n\n/**\n * 为参数生成器 / LLM 设定目标的 operationId/path并清理 msg.mock。\n */\n\nreturn setCreateContext(msg, node);\n\nfunction setCreateContext(message, node) {\n if (!message.crudFlow || !message.crudFlow.create) {\n const err = '缺少 crudFlow.create 配置,无法准备创建操作';\n node.error(err, message);\n message.error = err;\n return null;\n }\n\n const create = message.crudFlow.create;\n message.operationId = create.operationId;\n message.method = create.method || 'POST';\n message.path = create.path;\n message.mock = {};\n\n if (create.prompt) {\n message.prompt = create.prompt;\n }\n if (!message.oas_def && message.crudFlow.openapi) {\n message.oas_def = message.crudFlow.openapi;\n }\n\n delete message.error;\n return message;\n}\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 270,
"y": 560,
"wires": [
[
"60ce224bd2ed8e69"
]
]
},
{
"id": "31d5f84e4c6f69f6",
"type": "function",
"z": "78d15f59dee4b6d8",
"name": "缓存create结果",
"func": "'use strict';\n\n/**\n * 将 LLM 生成的创建请求体写回 crudFlow提取主键供删除步骤使用。\n */\n\nreturn storeCreateResult(msg, node);\n\nfunction storeCreateResult(message, node) {\n if (!message.crudFlow || !message.crudFlow.create) {\n return message;\n }\n\n const identityField = message.crudFlow.identityField || message.identityField || 'dsid';\n const mockBody = extractMockBody(message);\n\n if (mockBody) {\n message.crudFlow.create.payload = clone(mockBody);\n } else if (!message.crudFlow.create.payload) {\n node.warn('未从模型生成创建参数,继续使用默认样例', message);\n message.crudFlow.create.payload = clone(message.crudFlow.create.samplePayload || {});\n }\n\n const payload = message.crudFlow.create.payload || {};\n const primaryKeyValue = payload[identityField];\n if (primaryKeyValue !== undefined) {\n message.primaryKeyValue = primaryKeyValue;\n message.crudFlow.delete = message.crudFlow.delete || {};\n message.crudFlow.delete.actualKey = primaryKeyValue;\n }\n\n delete message.mock;\n delete message.mockCandidates;\n delete message.mockPrompt;\n delete message.mockSource;\n delete message.mockAutoSelected;\n delete message.llmContext;\n delete message.llmRaw;\n delete message.prompt;\n delete message.method;\n delete message.path;\n delete message.url;\n delete message.headers;\n delete message.statusCode;\n delete message.payload;\n\n return message;\n}\n\nfunction extractMockBody(message) {\n if (message && message.mock && typeof message.mock === 'object' && message.mock.body && typeof message.mock.body === 'object') {\n return message.mock.body;\n }\n if (message && message.payload && typeof message.payload === 'object' && !Array.isArray(message.payload)) {\n return message.payload;\n }\n return null;\n}\n\nfunction clone(value) {\n return value == null ? value : JSON.parse(JSON.stringify(value));\n}\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 940,
"y": 520,
"wires": [
[
"cfa3c9b06af4840e",
"80da1e9b993f73dc"
]
]
},
{
"id": "cfa3c9b06af4840e",
"type": "debug",
"z": "78d15f59dee4b6d8",
"name": "缓存结果",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 1060,
"y": 460,
"wires": []
},
{
"id": "80da1e9b993f73dc",
"type": "link out",
"z": "78d15f59dee4b6d8",
"name": "link out 1",
"mode": "link",
"links": [
"6c06fbe60248c14c"
],
"x": 1155,
"y": 540,
"wires": []
},
{
"id": "d87dfcf0db518c75",
"type": "debug",
"z": "78d15f59dee4b6d8",
"name": "llm请求内容",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 510,
"y": 680,
"wires": []
}
]