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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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 () =>
|
|
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
|
-
|
|
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 () =>
|
|
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
|
-
重试次数: (
|
|
870
|
-
最后等待毫秒: (
|
|
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
|
-
|
|
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
|
-
|
|
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) => {
|