n8n-nodes-kingsoft-airscript 2.0.3 → 2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,86 +10,67 @@ const API_CONFIG = {
10
10
  GET_TASK: '/api/v3/script/task'
11
11
  }
12
12
  };
13
+ const 创建并发闸门 = (上限) => {
14
+ let running = 0;
15
+ const queue = [];
16
+ const acquire = async () => {
17
+ if (running < 上限) {
18
+ running++;
19
+ return;
20
+ }
21
+ await new Promise(resolve => queue.push(resolve));
22
+ running++;
23
+ };
24
+ const release = () => {
25
+ running--;
26
+ const next = queue.shift();
27
+ if (next)
28
+ next();
29
+ };
30
+ return { acquire, release };
31
+ };
13
32
  class KingsoftAirscript {
14
33
  constructor() {
15
34
  this.description = {
16
- displayName: 'Kingsoft AirScript',
35
+ displayName: 'Kingsoft AirScript 连接器',
17
36
  name: 'kingsoftAirscript',
18
37
  icon: 'file:kingsoftAirscript.svg',
19
38
  group: ['transform'],
20
39
  version: 3,
21
40
  subtitle: '={{$parameter["operation"]}}',
22
- description: `
23
- <div style="padding: 16px; background-color: #f8f9fa; border-radius: 8px; margin-bottom: 16px;">
24
- <h3>📖 金山 AirScript 执行器</h3>
25
- <p>一个功能强大的通用执行器,支持同步/异步执行脚本、自动分批处理和智能参数生成。</p>
26
-
27
- <h4>🚀 主要功能</h4>
28
- <ul>
29
- <li>支持同步/异步执行脚本</li>
30
- <li>自动分批处理大量数据</li>
31
- <li>智能参数模板生成</li>
32
- <li>完善的错误处理和重试机制</li>
33
- <li>支持附件、公式、级联字段等复杂数据类型</li>
34
- </ul>
35
-
36
- <h4>💡 使用指南</h4>
37
- <ol>
38
- <li><strong>选择操作类型</strong>:同步执行/异步执行/获取任务状态</li>
39
- <li><strong>输入 ID</strong>:通过 Webhook 链接或手动输入 File ID 和 Script ID</li>
40
- <li><strong>配置参数</strong>:
41
- <ul>
42
- <li>选择 <strong>参数模板</strong> 快速生成参数结构</li>
43
- <li>或在 <strong>脚本参数 (Argv)</strong> 中手动输入 JSON</li>
44
- </ul>
45
- </li>
46
- <li><strong>高级选项</strong>:配置超时、重试、分批等高级设置</li>
47
- <li><strong>执行工作流</strong>:运行工作流执行脚本</li>
48
- </ol>
49
-
50
- <h4>📚 示例库</h4>
51
- <p>参数模板按功能分类:</p>
52
-
53
- <h5>🔄 数据录入类</h5>
54
- <ul>
55
- <li>基本数据录入:向指定数据表中录入新数据</li>
56
- <li>使用 N8N 表达式:在 N8N 工作流中动态处理输入数据</li>
57
- <li>处理附件字段:录入包含图片等附件的数据</li>
58
- <li>处理级联字段:录入包含级联字段的数据</li>
59
- </ul>
60
-
61
- <h5>🔄 数据更新类</h5>
62
- <ul>
63
- <li>使用复合唯一键:使用多个字段作为复合唯一键进行更新</li>
64
- <li>批量迁移数据:将数据从一个表迁移到另一个表</li>
65
- </ul>
66
-
67
- <h5>🔄 结构配置类</h5>
68
- <ul>
69
- <li>设置公式字段:向表中设置公式字段的表达式</li>
70
- <li>设置级联选项:配置级联字段的选项结构</li>
71
- <li>设置关联属性:配置关联字段的属性</li>
72
- <li>智能创建表格:根据数据结构智能创建表格和字段</li>
73
- </ul>
74
-
75
- <h4>❓ 常见问题</h4>
76
- <ul>
77
- <li><strong>Q: 节点不出现在 n8n 界面中?</strong><br>A: 请确保运行 <code>npm run build</code> 并正确链接节点,或手动复制构建文件到 <code>%APPDATA%\n8n\nodes</code> 目录。</li>
78
- <li><strong>Q: 如何获取 Webhook 链接?</strong><br>A: 在金山文档脚本编辑器中,点击 "发布" → "Webhook",复制生成的完整链接。</li>
79
- <li><strong>Q: 分批处理如何工作?</strong><br>A: 开启 "自动分批执行" 后,节点会根据 "分批字段路径" 从 argv 中提取数组,按批次大小分割并并发执行。</li>
80
- <li><strong>Q: 如何处理附件字段?</strong><br>A: 使用 "处理附件字段" 模板,在 data 数组中包含 base64 编码的附件数据。</li>
81
- <li><strong>Q: 如何使用 N8N 表达式?</strong><br>A: 使用 "使用 N8N 表达式" 模板,或在手动输入中使用 <code>{{ JSON.stringify( $input.all().map(i => i.json) ) }}</code> 格式。</li>
82
- </ul>
83
-
84
- <h4>🔗 更多资源</h4>
85
- <p>访问 <a href="https://www.kdocs.cn/l/cho73TDGgMPP" target="_blank">官方指南 & 脚本库</a> 获取更多帮助和示例脚本。</p>
86
- </div>
87
- `,
41
+ description: '连接并执行 Kingsoft AirScript 脚本,支持同步/异步执行、任务状态查询和智能批处理。',
88
42
  defaults: { name: 'Kingsoft AirScript' },
89
43
  inputs: ['main'],
90
44
  outputs: ['main'],
91
45
  credentials: [{ name: 'kingsoftAirscriptApi', required: true }],
46
+ hints: [
47
+ {
48
+ message: "当处理大量输入项时,建议在节点设置中启用 <b>Execute Once</b> 选项以提高性能。",
49
+ type: 'info',
50
+ location: 'inputPane',
51
+ whenToDisplay: 'always'
52
+ },
53
+ {
54
+ message: "全局最大并发数设置过高可能导致系统资源消耗过大,请根据服务器性能适当调整。",
55
+ type: 'warning',
56
+ location: 'inputPane',
57
+ whenToDisplay: 'always'
58
+ },
59
+ {
60
+ message: "处理大量数据时,建议启用自动分批写入功能,避免内存溢出。",
61
+ type: 'info',
62
+ location: 'inputPane',
63
+ whenToDisplay: 'always'
64
+ }
65
+ ],
92
66
  properties: [
67
+ {
68
+ displayName: '📖 金山 AirScript 连接器 v3',
69
+ name: 'notice',
70
+ type: 'notice',
71
+ default: '',
72
+ description: '支持同步/异步执行脚本、自动分批处理和智能参数生成。',
73
+ },
93
74
  {
94
75
  displayName: '操作',
95
76
  name: 'operation',
@@ -122,6 +103,7 @@ class KingsoftAirscript {
122
103
  type: 'string',
123
104
  default: '',
124
105
  required: true,
106
+ hint: '格式示例:https://www.kdocs.cn/api/v3/ide/file/xxx/script/yyy/sync_task',
125
107
  displayOptions: {
126
108
  show: {
127
109
  operation: ['runScriptSync', 'runScriptAsync'],
@@ -137,6 +119,7 @@ class KingsoftAirscript {
137
119
  type: 'string',
138
120
  default: '',
139
121
  required: true,
122
+ hint: '从 Webhook 链接中提取的长字符串',
140
123
  displayOptions: {
141
124
  show: {
142
125
  operation: ['runScriptSync', 'runScriptAsync'],
@@ -151,6 +134,7 @@ class KingsoftAirscript {
151
134
  type: 'string',
152
135
  default: '',
153
136
  required: true,
137
+ hint: '从 Webhook 链接中提取的长字符串',
154
138
  displayOptions: {
155
139
  show: {
156
140
  operation: ['runScriptSync', 'runScriptAsync'],
@@ -302,6 +286,7 @@ class KingsoftAirscript {
302
286
  type: 'string',
303
287
  default: '',
304
288
  required: true,
289
+ hint: '从异步执行结果中获取,格式为长字符串',
305
290
  displayOptions: { show: { operation: ['getTaskStatus'] } },
306
291
  description: '从异步执行脚本操作中获取到的任务 ID',
307
292
  },
@@ -318,7 +303,7 @@ class KingsoftAirscript {
318
303
  name: 'a_parallelExecution',
319
304
  type: 'boolean',
320
305
  default: false,
321
- description: 'Whether enabled, the node will process all input items in parallel',
306
+ description: 'Whether enabled, 节点将并行处理所有输入项',
322
307
  },
323
308
  {
324
309
  displayName: '查询任务超时(秒)',
@@ -342,7 +327,7 @@ class KingsoftAirscript {
342
327
  name: 'c_waitForCompletion',
343
328
  type: 'boolean',
344
329
  default: false,
345
- description: 'Whether enabled, when running async the node will poll until the task finishes',
330
+ description: 'Whether enabled, 异步执行时节点将轮询直到任务完成',
346
331
  displayOptions: { show: { '/operation': ['runScriptAsync'] } },
347
332
  },
348
333
  {
@@ -350,7 +335,7 @@ class KingsoftAirscript {
350
335
  name: 'w_debug',
351
336
  type: 'boolean',
352
337
  default: false,
353
- description: 'Whether enabled, include a `_debug` field with latency and retry details in the output',
338
+ description: 'Whether enabled, 输出中会包含 `_调试` 字段,包含延迟和重试详情',
354
339
  },
355
340
  {
356
341
  displayName: '分批输出模式',
@@ -369,6 +354,7 @@ class KingsoftAirscript {
369
354
  name: 's_chunkFieldPath',
370
355
  type: 'string',
371
356
  default: '写表行列表',
357
+ hint: '指定包含大量数据的数组字段',
372
358
  description: '例:写表行列表 或 data.写表行列表(从 argv 根开始)',
373
359
  displayOptions: { show: { '/r_autoChunkEnabled': [true] } },
374
360
  },
@@ -403,6 +389,7 @@ class KingsoftAirscript {
403
389
  name: 't_chunkSize',
404
390
  type: 'number',
405
391
  default: 200,
392
+ hint: '处理大量数据时建议减小此值',
406
393
  typeOptions: { minValue: 1 },
407
394
  displayOptions: { show: { '/r_autoChunkEnabled': [true] } },
408
395
  description: '自动分批时,每批数组元素数量。',
@@ -424,12 +411,21 @@ class KingsoftAirscript {
424
411
  typeOptions: { minValue: 5 },
425
412
  description: '调用 sync_task/task 的 HTTP 超时(秒)。',
426
413
  },
414
+ {
415
+ displayName: '全局最大并发数',
416
+ name: 'x_globalMaxConcurrency',
417
+ type: 'number',
418
+ default: 5,
419
+ hint: '建议根据服务器性能设置,默认 5',
420
+ typeOptions: { minValue: 1, maxValue: 50 },
421
+ description: '全局最大并发 HTTP 请求数,防止并发乘法爆炸(输入并发 × 批次并发)。',
422
+ },
427
423
  {
428
424
  displayName: '失败判定:Finished 但 Error 不为空算失败',
429
425
  name: 'n_failOnScriptError',
430
426
  type: 'boolean',
431
427
  default: true,
432
- description: 'Whether enabled, treat `status=finished` with non-empty `error` as a failure',
428
+ description: 'Whether enabled, `status=finished` `error` 不为空的情况视为失败',
433
429
  },
434
430
  {
435
431
  displayName: '输出格式',
@@ -449,7 +445,7 @@ class KingsoftAirscript {
449
445
  name: 'g_parseResultString',
450
446
  type: 'boolean',
451
447
  default: false,
452
- description: 'Whether enabled, if `result` is a JSON string it will be parsed automatically',
448
+ description: 'Whether enabled, 如果 `result` JSON 字符串,将自动解析',
453
449
  displayOptions: { show: { '/operation': ['runScriptSync', 'runScriptAsync'] } },
454
450
  },
455
451
  {
@@ -473,7 +469,8 @@ class KingsoftAirscript {
473
469
  name: 'r_autoChunkEnabled',
474
470
  type: 'boolean',
475
471
  default: false,
476
- description: 'Whether enabled, split the array at the configured field path in argv and run the script in chunks',
472
+ hint: '处理大量数据时建议启用',
473
+ description: 'Whether enabled, 会按配置的字段路径分割 argv 中的数组,并分块运行脚本',
477
474
  },
478
475
  {
479
476
  displayName: '最大重试次数',
@@ -488,7 +485,7 @@ class KingsoftAirscript {
488
485
  name: 'm_honorRetryAfter',
489
486
  type: 'boolean',
490
487
  default: true,
491
- description: 'Whether enabled, when receiving HTTP 429 with Retry-After header, wait accordingly before retrying',
488
+ description: 'Whether enabled, 当收到带有 Retry-After 头的 HTTP 429 响应时,会按指示等待后重试',
492
489
  },
493
490
  ],
494
491
  },
@@ -653,6 +650,7 @@ class KingsoftAirscript {
653
650
  e_batchSize: 取数('e_batchSize', 10),
654
651
  f_outputFormat: 取字符串('f_outputFormat', 'fullResponse'),
655
652
  g_parseResultString: 取布尔('g_parseResultString', false),
653
+ x_globalMaxConcurrency: 取数('x_globalMaxConcurrency', 5),
656
654
  h_requestTimeoutSeconds: 取数('h_requestTimeoutSeconds', 120),
657
655
  i_statusTimeoutSeconds: 取数('i_statusTimeoutSeconds', 30),
658
656
  j_maxRetries: 取数('j_maxRetries', 2),
@@ -836,7 +834,15 @@ class KingsoftAirscript {
836
834
  json: true,
837
835
  timeout: 参数.请求超时毫秒,
838
836
  };
839
- const res = (await 带重试(async () => (await this.helpers.httpRequest(options)), 参数.重试, 调试统计));
837
+ const res = (await 带重试(async () => {
838
+ await 参数.并发闸门.acquire();
839
+ try {
840
+ return (await this.helpers.httpRequest(options));
841
+ }
842
+ finally {
843
+ 参数.并发闸门.release();
844
+ }
845
+ }, 参数.重试, 调试统计));
840
846
  if (参数.调试) {
841
847
  参数.调试['执行脚本'] = {
842
848
  url: `/api/v3/ide/file/${脱敏ID(参数.文件ID)}/script/${脱敏ID(参数.脚本ID)}/${参数.类型 === 'sync' ? 'sync_task' : 'task'}`,
@@ -848,26 +854,35 @@ class KingsoftAirscript {
848
854
  return res;
849
855
  };
850
856
  const 获取任务状态 = async (参数) => {
851
- var _a, _b;
852
- const 请求URL = `${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.GET_TASK}`;
857
+ var _a, _b, _c;
858
+ if (!参数.taskId || !参数.taskId.trim()) {
859
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'taskId 不能为空', { itemIndex: (_a = 参数.itemIndex) !== null && _a !== void 0 ? _a : 0 });
860
+ }
861
+ const 请求URL = `${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.GET_TASK}?task_id=${encodeURIComponent(参数.taskId)}`;
853
862
  const t0 = Date.now();
854
863
  const 调试统计 = {};
855
- const taskIdEncoded = encodeURIComponent(参数.taskId);
856
864
  const options = {
857
865
  method: 'GET',
858
866
  url: 请求URL,
859
867
  headers: { 'Content-Type': 'application/json', 'AirScript-Token': 凭证.apiToken },
860
- qs: { task_id: taskIdEncoded },
861
868
  json: true,
862
869
  timeout: 参数.状态超时毫秒,
863
870
  };
864
- const res = (await 带重试(async () => (await this.helpers.httpRequest(options)), 参数.重试, 调试统计));
871
+ const res = (await 带重试(async () => {
872
+ await 参数.并发闸门.acquire();
873
+ try {
874
+ return (await this.helpers.httpRequest(options));
875
+ }
876
+ finally {
877
+ 参数.并发闸门.release();
878
+ }
879
+ }, 参数.重试, 调试统计));
865
880
  if (参数.调试) {
866
881
  参数.调试['获取任务状态'] = {
867
882
  task_id: 脱敏ID(参数.taskId),
868
883
  耗时毫秒: Date.now() - t0,
869
- 重试次数: (_a = 调试统计.重试次数) !== null && _a !== void 0 ? _a : 0,
870
- 最后等待毫秒: (_b = 调试统计.最后等待毫秒) !== null && _b !== void 0 ? _b : null,
884
+ 重试次数: (_b = 调试统计.重试次数) !== null && _b !== void 0 ? _b : 0,
885
+ 最后等待毫秒: (_c = 调试统计.最后等待毫秒) !== null && _c !== void 0 ? _c : null,
871
886
  };
872
887
  }
873
888
  return res;
@@ -887,6 +902,7 @@ class KingsoftAirscript {
887
902
  状态超时毫秒: 参数.状态超时毫秒,
888
903
  重试: 参数.重试,
889
904
  调试: 参数.调试,
905
+ 并发闸门: 参数.并发闸门,
890
906
  });
891
907
  const 状态 = typeof 状态响应.status === 'string' ? 状态响应.status : '';
892
908
  if (状态 === 'finished') {
@@ -903,7 +919,7 @@ class KingsoftAirscript {
903
919
  itemIndex: 参数.itemIndex,
904
920
  });
905
921
  };
906
- const 格式化输出 = (响应数据, 索引, opt, 附加) => {
922
+ const 格式化输出 = (响应数据, 索引, opt, 附加, operation) => {
907
923
  var _a, _b, _c, _d;
908
924
  const data = ((_a = 响应数据.data) !== null && _a !== void 0 ? _a : undefined);
909
925
  if (opt.g_parseResultString && data && typeof data.result === 'string') {
@@ -915,11 +931,33 @@ class KingsoftAirscript {
915
931
  data.result = data.result.replace(/\\n/g, '\n');
916
932
  }
917
933
  }
934
+ const _meta = {
935
+ operation: operation || 'unknown',
936
+ };
937
+ const sheetNameFromData = (data === null || data === void 0 ? void 0 : data.sheet_name) || (data === null || data === void 0 ? void 0 : data.sheetName);
938
+ const sheetNameFromResponse = 是IDataObject(响应数据) ? (响应数据.sheet_name || 响应数据.sheetName) : undefined;
939
+ const sheetNameFromArgv = 是IDataObject(附加) && 是IDataObject(附加.argv) ? (附加.argv.sheet_name || 附加.argv.sheetName) : undefined;
940
+ if (sheetNameFromData)
941
+ _meta.sheet_name = sheetNameFromData;
942
+ else if (sheetNameFromResponse)
943
+ _meta.sheet_name = sheetNameFromResponse;
944
+ else if (sheetNameFromArgv)
945
+ _meta.sheet_name = sheetNameFromArgv;
946
+ const taskIdFromData = data === null || data === void 0 ? void 0 : data.task_id;
947
+ const taskIdFromResponse = 响应数据.task_id;
948
+ if (taskIdFromData)
949
+ _meta.task_id = taskIdFromData;
950
+ else if (taskIdFromResponse)
951
+ _meta.task_id = taskIdFromResponse;
952
+ if ((附加 === null || 附加 === void 0 ? void 0 : 附加.批次索引) !== undefined) {
953
+ _meta.batchIndex = 附加.批次索引;
954
+ }
918
955
  const out = [];
919
956
  if (opt.f_outputFormat === 'resultOnly') {
920
957
  out.push({
921
958
  json: {
922
959
  ...(附加 !== null && 附加 !== void 0 ? 附加 : {}),
960
+ _meta,
923
961
  result: (_b = data === null || data === void 0 ? void 0 : data.result) !== null && _b !== void 0 ? _b : null,
924
962
  status: ((_c = 响应数据.status) !== null && _c !== void 0 ? _c : null),
925
963
  error: ((_d = 响应数据.error) !== null && _d !== void 0 ? _d : ''),
@@ -933,7 +971,7 @@ class KingsoftAirscript {
933
971
  if (Array.isArray(logs)) {
934
972
  for (const 日志条目 of logs) {
935
973
  if (日志条目 && typeof 日志条目 === 'object') {
936
- out.push({ json: { ...(附加 !== null && 附加 !== void 0 ? 附加 : {}), ...日志条目 }, pairedItem: { item: 索引 } });
974
+ out.push({ json: { ...(附加 !== null && 附加 !== void 0 ? 附加 : {}), _meta, ...日志条目 }, pairedItem: { item: 索引 } });
937
975
  }
938
976
  }
939
977
  }
@@ -946,13 +984,13 @@ class KingsoftAirscript {
946
984
  if (日志条目 && typeof 日志条目 === 'object') {
947
985
  const level = 日志条目.level;
948
986
  if (level === 'error')
949
- out.push({ json: { ...(附加 !== null && 附加 !== void 0 ? 附加 : {}), ...日志条目 }, pairedItem: { item: 索引 } });
987
+ out.push({ json: { ...(附加 !== null && 附加 !== void 0 ? 附加 : {}), _meta, ...日志条目 }, pairedItem: { item: 索引 } });
950
988
  }
951
989
  }
952
990
  }
953
991
  return out;
954
992
  }
955
- out.push({ json: { ...(附加 !== null && 附加 !== void 0 ? 附加 : {}), ...响应数据 }, pairedItem: { item: 索引 } });
993
+ out.push({ json: { ...(附加 !== null && 附加 !== void 0 ? 附加 : {}), _meta, ...响应数据 }, pairedItem: { item: 索引 } });
956
994
  return out;
957
995
  };
958
996
  const 处理单个项目 = async (索引) => {
@@ -965,19 +1003,35 @@ class KingsoftAirscript {
965
1003
  尊重RetryAfter: opt.m_honorRetryAfter,
966
1004
  };
967
1005
  const 调试字段 = opt.w_debug ? {} : undefined;
1006
+ const 并发闸门 = 创建并发闸门(opt.x_globalMaxConcurrency);
968
1007
  if (操作类型 === 'getTaskStatus') {
969
1008
  const taskId = this.getNodeParameter('taskId', 索引, '');
1009
+ if (!taskId || !taskId.trim()) {
1010
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Task ID 不能为空', { itemIndex: 索引 });
1011
+ }
970
1012
  const 状态响应 = await 获取任务状态({
971
1013
  taskId,
972
1014
  状态超时毫秒: opt.i_statusTimeoutSeconds * 1000,
973
1015
  重试,
974
1016
  调试: 调试字段,
1017
+ 并发闸门,
975
1018
  });
976
1019
  校验脚本是否失败(状态响应, 索引, opt.n_failOnScriptError);
977
1020
  const 附加 = opt.w_debug ? { _调试: 调试字段 } : undefined;
978
- return 格式化输出(状态响应, 索引, opt, 附加);
1021
+ return 格式化输出(状态响应, 索引, opt, 附加, 操作类型);
979
1022
  }
980
1023
  const { 文件ID, 脚本ID, argv参数, 上下文 } = 解析执行信息(索引);
1024
+ const 组装批次失败响应 = (错误信息, 堆栈) => ({
1025
+ status: 'finished',
1026
+ error: 错误信息,
1027
+ error_details: {
1028
+ name: 'BatchError',
1029
+ msg: 错误信息,
1030
+ stack: 堆栈 ? 堆栈.split('\n') : [],
1031
+ unix_time: Date.now(),
1032
+ },
1033
+ data: { result: null, logs: [] },
1034
+ });
981
1035
  const 执行一次 = async (上下文入参, 调试对象) => {
982
1036
  if (操作类型 === 'runScriptSync') {
983
1037
  const 响应 = await 调用脚本接口({
@@ -988,6 +1042,7 @@ class KingsoftAirscript {
988
1042
  请求超时毫秒: opt.h_requestTimeoutSeconds * 1000,
989
1043
  重试,
990
1044
  调试: 调试对象,
1045
+ 并发闸门,
991
1046
  });
992
1047
  校验脚本是否失败(响应, 索引, opt.n_failOnScriptError);
993
1048
  return 响应;
@@ -1001,6 +1056,7 @@ class KingsoftAirscript {
1001
1056
  请求超时毫秒: opt.h_requestTimeoutSeconds * 1000,
1002
1057
  重试,
1003
1058
  调试: 调试对象,
1059
+ 并发闸门,
1004
1060
  });
1005
1061
  if (!opt.c_waitForCompletion) {
1006
1062
  return 初始响应;
@@ -1019,6 +1075,7 @@ class KingsoftAirscript {
1019
1075
  重试,
1020
1076
  finished但error算失败: opt.n_failOnScriptError,
1021
1077
  调试: 调试对象,
1078
+ 并发闸门,
1022
1079
  });
1023
1080
  }
1024
1081
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), `未知的操作类型: ${操作类型}`, { itemIndex: 索引 });
@@ -1029,35 +1086,75 @@ class KingsoftAirscript {
1029
1086
  if (!Array.isArray(目标数组)) {
1030
1087
  const 响应 = await 执行一次(上下文, 调试字段);
1031
1088
  const 附加 = opt.w_debug ? { _调试: 调试字段 } : undefined;
1032
- return 格式化输出(响应, 索引, opt, 附加);
1089
+ return 格式化输出(响应, 索引, opt, 附加, 操作类型);
1033
1090
  }
1034
1091
  const 批次大小 = Math.max(1, opt.t_chunkSize);
1035
- const 批次列表 = [];
1036
- for (let i = 0; i < 目标数组.length; i += 批次大小) {
1037
- 批次列表.push(目标数组.slice(i, i + 批次大小));
1038
- }
1039
- if (批次列表.length === 0) {
1092
+ const 并发数 = Math.max(1, opt.u_chunkMaxConcurrency);
1093
+ if (目标数组.length === 0) {
1040
1094
  const 响应 = await 执行一次(上下文, 调试字段);
1041
1095
  const 附加 = opt.w_debug ? { _调试: 调试字段 } : undefined;
1042
- return 格式化输出(响应, 索引, opt, 附加);
1096
+ return 格式化输出(响应, 索引, opt, 附加, 操作类型);
1097
+ }
1098
+ const 任务列表 = [];
1099
+ for (let i = 0; i < 目标数组.length; i += 批次大小) {
1100
+ const 批次 = 目标数组.slice(i, i + 批次大小);
1101
+ const 批次索引 = Math.floor(i / 批次大小);
1102
+ 任务列表.push(async () => {
1103
+ try {
1104
+ const 批次调试对象 = opt.w_debug ? {} : undefined;
1105
+ const 批次上下文 = {
1106
+ ...上下文,
1107
+ argv: 以不可变方式写入路径(argv参数, 数组路径, 批次),
1108
+ };
1109
+ const 响应 = await 执行一次(批次上下文, 批次调试对象);
1110
+ const 批次附加 = opt.w_debug
1111
+ ? { _调试: { ...(批次调试对象 !== null && 批次调试对象 !== void 0 ? 批次调试对象 : {}), 批次索引 } }
1112
+ : { 批次索引 };
1113
+ return 格式化输出(响应, 索引, opt, 批次附加, 操作类型);
1114
+ }
1115
+ catch (错误) {
1116
+ if (!this.continueOnFail()) {
1117
+ throw 错误;
1118
+ }
1119
+ const err = 错误 instanceof Error ? 错误 : new Error(String(错误));
1120
+ const 错误对象 = 组装批次失败响应(err.message, err.stack);
1121
+ const 错误附加 = opt.w_debug
1122
+ ? { _调试: { 批次索引, 错误: err.message } }
1123
+ : { 批次索引, 错误: err.message };
1124
+ return 格式化输出(错误对象, 索引, opt, 错误附加, 操作类型);
1125
+ }
1126
+ });
1043
1127
  }
1044
- const 并发数 = Math.max(1, opt.u_chunkMaxConcurrency);
1045
- const 任务列表 = 批次列表.map((批次, 批次索引) => async () => {
1046
- const 批次调试对象 = opt.w_debug ? {} : undefined;
1047
- const 批次上下文 = {
1048
- ...上下文,
1049
- argv: 以不可变方式写入路径(argv参数, 数组路径, 批次),
1050
- };
1051
- const 响应 = await 执行一次(批次上下文, 批次调试对象);
1052
- const 批次附加 = opt.w_debug
1053
- ? { _调试: { ...(批次调试对象 !== null && 批次调试对象 !== void 0 ? 批次调试对象 : {}), 批次索引 } }
1054
- : { 批次索引 };
1055
- return 格式化输出(响应, 索引, opt, 批次附加);
1056
- });
1057
1128
  const 批次结果 = await 并发执行(任务列表, 并发数);
1058
1129
  if (opt.v_chunkOutputMode === 'merge') {
1130
+ const 批次输出 = 批次结果.flat();
1131
+ const 批次输出列表 = 批次输出.map(x => x.json);
1132
+ const taskIds = 批次输出
1133
+ .map(x => x.json)
1134
+ .filter((json) => typeof json === 'object' && json !== null)
1135
+ .map(json => {
1136
+ var _a;
1137
+ const fromData = (_a = json.data) === null || _a === void 0 ? void 0 : _a.task_id;
1138
+ const fromTop = json.task_id;
1139
+ return fromData !== null && fromData !== void 0 ? fromData : fromTop;
1140
+ })
1141
+ .filter((id) => typeof id === 'string' && id.length > 0);
1142
+ const 批次数 = Math.ceil(目标数组.length / 批次大小);
1143
+ const 分批汇总 = {
1144
+ 批次数: 批次数,
1145
+ 每批条数: Array.from({ length: 批次数 }, (_, i) => {
1146
+ const start = i * 批次大小;
1147
+ const end = Math.min(start + 批次大小, 目标数组.length);
1148
+ return end - start;
1149
+ }),
1150
+ 字段路径: 数组路径,
1151
+ 总数据条数: 目标数组.length,
1152
+ 并发数: 并发数,
1153
+ };
1059
1154
  const 合并结果 = {
1060
- 批次输出列表: 批次结果.flat().map(x => x.json),
1155
+ 分批汇总,
1156
+ 批次输出列表,
1157
+ ...(taskIds.length === 1 ? { task_id: taskIds[0] } : taskIds.length > 1 ? { task_ids: taskIds } : {}),
1061
1158
  ...(opt.w_debug ? { _调试: 调试字段 } : {}),
1062
1159
  };
1063
1160
  return [{ json: 合并结果, pairedItem: { item: 索引 } }];
@@ -1069,7 +1166,7 @@ class KingsoftAirscript {
1069
1166
  else {
1070
1167
  const 响应 = await 执行一次(上下文, 调试字段);
1071
1168
  const 附加 = opt.w_debug ? { _调试: 调试字段 } : undefined;
1072
- return 格式化输出(响应, 索引, opt, 附加);
1169
+ return 格式化输出(响应, 索引, opt, 附加, 操作类型);
1073
1170
  }
1074
1171
  };
1075
1172
  const 安全执行单个项目 = async (itemIndex) => {