@qwe8652591/abap-recursive-query 1.1.0
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.
- package/LICENSE +21 -0
- package/README.md +223 -0
- package/dist/cli/consumer-cli.d.ts +8 -0
- package/dist/cli/consumer-cli.d.ts.map +1 -0
- package/dist/cli/consumer-cli.js +180 -0
- package/dist/cli/producer-cli.d.ts +8 -0
- package/dist/cli/producer-cli.d.ts.map +1 -0
- package/dist/cli/producer-cli.js +249 -0
- package/dist/cli/query-object.d.ts +3 -0
- package/dist/cli/query-object.d.ts.map +1 -0
- package/dist/cli/query-object.js +486 -0
- package/dist/common/FilterCache.d.ts +62 -0
- package/dist/common/FilterCache.d.ts.map +1 -0
- package/dist/common/FilterCache.js +119 -0
- package/dist/common/RedisQueueManager.d.ts +170 -0
- package/dist/common/RedisQueueManager.d.ts.map +1 -0
- package/dist/common/RedisQueueManager.js +663 -0
- package/dist/common/abapStructures.d.ts +391 -0
- package/dist/common/abapStructures.d.ts.map +1 -0
- package/dist/common/abapStructures.js +2 -0
- package/dist/common/config.d.ts +66 -0
- package/dist/common/config.d.ts.map +1 -0
- package/dist/common/config.js +56 -0
- package/dist/common/index.d.ts +13 -0
- package/dist/common/index.d.ts.map +1 -0
- package/dist/common/index.js +38 -0
- package/dist/common/recursiveQueryConfig.d.ts +77 -0
- package/dist/common/recursiveQueryConfig.d.ts.map +1 -0
- package/dist/common/recursiveQueryConfig.js +129 -0
- package/dist/common/tableStructures.d.ts +176 -0
- package/dist/common/tableStructures.d.ts.map +1 -0
- package/dist/common/tableStructures.js +10 -0
- package/dist/common/types.d.ts +104 -0
- package/dist/common/types.d.ts.map +1 -0
- package/dist/common/types.js +31 -0
- package/dist/common/utils.d.ts +57 -0
- package/dist/common/utils.d.ts.map +1 -0
- package/dist/common/utils.js +300 -0
- package/dist/consumer/FileDownloadConsumer.d.ts +127 -0
- package/dist/consumer/FileDownloadConsumer.d.ts.map +1 -0
- package/dist/consumer/FileDownloadConsumer.js +1003 -0
- package/dist/consumer/generators/baseGenerator.d.ts +38 -0
- package/dist/consumer/generators/baseGenerator.d.ts.map +1 -0
- package/dist/consumer/generators/baseGenerator.js +103 -0
- package/dist/consumer/generators/domainGenerator.d.ts +78 -0
- package/dist/consumer/generators/domainGenerator.d.ts.map +1 -0
- package/dist/consumer/generators/domainGenerator.js +241 -0
- package/dist/consumer/generators/guiStatusGenerator.d.ts +16 -0
- package/dist/consumer/generators/guiStatusGenerator.d.ts.map +1 -0
- package/dist/consumer/generators/guiStatusGenerator.js +48 -0
- package/dist/consumer/generators/guiTitleGenerator.d.ts +16 -0
- package/dist/consumer/generators/guiTitleGenerator.d.ts.map +1 -0
- package/dist/consumer/generators/guiTitleGenerator.js +48 -0
- package/dist/consumer/generators/index.d.ts +14 -0
- package/dist/consumer/generators/index.d.ts.map +1 -0
- package/dist/consumer/generators/index.js +38 -0
- package/dist/consumer/generators/messageGenerator.d.ts +16 -0
- package/dist/consumer/generators/messageGenerator.d.ts.map +1 -0
- package/dist/consumer/generators/messageGenerator.js +73 -0
- package/dist/consumer/generators/metadataGenerator.d.ts +29 -0
- package/dist/consumer/generators/metadataGenerator.d.ts.map +1 -0
- package/dist/consumer/generators/metadataGenerator.js +135 -0
- package/dist/consumer/generators/screenGenerator.d.ts +173 -0
- package/dist/consumer/generators/screenGenerator.d.ts.map +1 -0
- package/dist/consumer/generators/screenGenerator.js +859 -0
- package/dist/consumer/generators/structureGenerator.d.ts +36 -0
- package/dist/consumer/generators/structureGenerator.d.ts.map +1 -0
- package/dist/consumer/generators/structureGenerator.js +131 -0
- package/dist/consumer/generators/textElementGenerator.d.ts +16 -0
- package/dist/consumer/generators/textElementGenerator.d.ts.map +1 -0
- package/dist/consumer/generators/textElementGenerator.js +70 -0
- package/dist/consumer/handlers/handleGetClass.d.ts +8 -0
- package/dist/consumer/handlers/handleGetClass.d.ts.map +1 -0
- package/dist/consumer/handlers/handleGetClass.js +18 -0
- package/dist/consumer/handlers/handleGetFunction.d.ts +8 -0
- package/dist/consumer/handlers/handleGetFunction.d.ts.map +1 -0
- package/dist/consumer/handlers/handleGetFunction.js +19 -0
- package/dist/consumer/handlers/handleGetFunctionGroup.d.ts +8 -0
- package/dist/consumer/handlers/handleGetFunctionGroup.d.ts.map +1 -0
- package/dist/consumer/handlers/handleGetFunctionGroup.js +19 -0
- package/dist/consumer/handlers/handleGetInclude.d.ts +8 -0
- package/dist/consumer/handlers/handleGetInclude.d.ts.map +1 -0
- package/dist/consumer/handlers/handleGetInclude.js +18 -0
- package/dist/consumer/handlers/handleGetProgram.d.ts +8 -0
- package/dist/consumer/handlers/handleGetProgram.d.ts.map +1 -0
- package/dist/consumer/handlers/handleGetProgram.js +27 -0
- package/dist/consumer/handlers/handleZTableQuery.d.ts +8 -0
- package/dist/consumer/handlers/handleZTableQuery.d.ts.map +1 -0
- package/dist/consumer/handlers/handleZTableQuery.js +20 -0
- package/dist/consumer/handlers/index.d.ts +10 -0
- package/dist/consumer/handlers/index.d.ts.map +1 -0
- package/dist/consumer/handlers/index.js +27 -0
- package/dist/consumer/index.d.ts +9 -0
- package/dist/consumer/index.d.ts.map +1 -0
- package/dist/consumer/index.js +24 -0
- package/dist/consumer/utils/download.d.ts +13 -0
- package/dist/consumer/utils/download.d.ts.map +1 -0
- package/dist/consumer/utils/download.js +38 -0
- package/dist/consumer/utils/index.d.ts +5 -0
- package/dist/consumer/utils/index.d.ts.map +1 -0
- package/dist/consumer/utils/index.js +20 -0
- package/dist/handlers/handleGetClass.d.ts +8 -0
- package/dist/handlers/handleGetClass.d.ts.map +1 -0
- package/dist/handlers/handleGetClass.js +19 -0
- package/dist/handlers/handleGetFunction.d.ts +8 -0
- package/dist/handlers/handleGetFunction.d.ts.map +1 -0
- package/dist/handlers/handleGetFunction.js +20 -0
- package/dist/handlers/handleGetFunctionGroup.d.ts +8 -0
- package/dist/handlers/handleGetFunctionGroup.d.ts.map +1 -0
- package/dist/handlers/handleGetFunctionGroup.js +19 -0
- package/dist/handlers/handleGetInclude.d.ts +8 -0
- package/dist/handlers/handleGetInclude.d.ts.map +1 -0
- package/dist/handlers/handleGetInclude.js +19 -0
- package/dist/handlers/handleGetInterface.d.ts +8 -0
- package/dist/handlers/handleGetInterface.d.ts.map +1 -0
- package/dist/handlers/handleGetInterface.js +19 -0
- package/dist/handlers/handleGetPackage.d.ts +8 -0
- package/dist/handlers/handleGetPackage.d.ts.map +1 -0
- package/dist/handlers/handleGetPackage.js +42 -0
- package/dist/handlers/handleGetProgram.d.ts +8 -0
- package/dist/handlers/handleGetProgram.d.ts.map +1 -0
- package/dist/handlers/handleGetProgram.js +19 -0
- package/dist/handlers/handleGetStructure.d.ts +8 -0
- package/dist/handlers/handleGetStructure.d.ts.map +1 -0
- package/dist/handlers/handleGetStructure.js +19 -0
- package/dist/handlers/handleGetTable.d.ts +8 -0
- package/dist/handlers/handleGetTable.d.ts.map +1 -0
- package/dist/handlers/handleGetTable.js +19 -0
- package/dist/handlers/handleGetTableContents.d.ts +8 -0
- package/dist/handlers/handleGetTableContents.d.ts.map +1 -0
- package/dist/handlers/handleGetTableContents.js +22 -0
- package/dist/handlers/handleGetTransaction.d.ts +8 -0
- package/dist/handlers/handleGetTransaction.d.ts.map +1 -0
- package/dist/handlers/handleGetTransaction.js +19 -0
- package/dist/handlers/handleGetTypeInfo.d.ts +8 -0
- package/dist/handlers/handleGetTypeInfo.d.ts.map +1 -0
- package/dist/handlers/handleGetTypeInfo.js +32 -0
- package/dist/handlers/handleSearchObject.d.ts +8 -0
- package/dist/handlers/handleSearchObject.d.ts.map +1 -0
- package/dist/handlers/handleSearchObject.js +20 -0
- package/dist/handlers/handleZObjectQuery.d.ts +8 -0
- package/dist/handlers/handleZObjectQuery.d.ts.map +1 -0
- package/dist/handlers/handleZObjectQuery.js +25 -0
- package/dist/handlers/handleZTableQuery.d.ts +8 -0
- package/dist/handlers/handleZTableQuery.d.ts.map +1 -0
- package/dist/handlers/handleZTableQuery.js +20 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +45 -0
- package/dist/lib/download.d.ts +49 -0
- package/dist/lib/download.d.ts.map +1 -0
- package/dist/lib/download.js +78 -0
- package/dist/lib/index.d.ts +9 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +23 -0
- package/dist/lib/utils.d.ts +31 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +272 -0
- package/dist/producer/RecursiveQueryProducer.d.ts +92 -0
- package/dist/producer/RecursiveQueryProducer.d.ts.map +1 -0
- package/dist/producer/RecursiveQueryProducer.js +496 -0
- package/dist/producer/handlers/handleZObjectQuery.d.ts +8 -0
- package/dist/producer/handlers/handleZObjectQuery.d.ts.map +1 -0
- package/dist/producer/handlers/handleZObjectQuery.js +24 -0
- package/dist/producer/handlers/index.d.ts +6 -0
- package/dist/producer/handlers/index.d.ts.map +1 -0
- package/dist/producer/handlers/index.js +21 -0
- package/dist/producer/index.d.ts +7 -0
- package/dist/producer/index.d.ts.map +1 -0
- package/dist/producer/index.js +22 -0
- package/dist/recursive/abapStructures.d.ts +377 -0
- package/dist/recursive/abapStructures.d.ts.map +1 -0
- package/dist/recursive/abapStructures.js +2 -0
- package/dist/recursive/generate/baseGenerator.d.ts +34 -0
- package/dist/recursive/generate/baseGenerator.d.ts.map +1 -0
- package/dist/recursive/generate/baseGenerator.js +112 -0
- package/dist/recursive/generate/domainGenerator.d.ts +26 -0
- package/dist/recursive/generate/domainGenerator.d.ts.map +1 -0
- package/dist/recursive/generate/domainGenerator.js +128 -0
- package/dist/recursive/generate/functionGroupGenerator.d.ts +30 -0
- package/dist/recursive/generate/functionGroupGenerator.d.ts.map +1 -0
- package/dist/recursive/generate/functionGroupGenerator.js +90 -0
- package/dist/recursive/generate/index.d.ts +12 -0
- package/dist/recursive/generate/index.d.ts.map +1 -0
- package/dist/recursive/generate/index.js +34 -0
- package/dist/recursive/generate/messageGenerator.d.ts +16 -0
- package/dist/recursive/generate/messageGenerator.d.ts.map +1 -0
- package/dist/recursive/generate/messageGenerator.js +73 -0
- package/dist/recursive/generate/screenGenerator.d.ts +173 -0
- package/dist/recursive/generate/screenGenerator.d.ts.map +1 -0
- package/dist/recursive/generate/screenGenerator.js +858 -0
- package/dist/recursive/generate/structureGenerator.d.ts +22 -0
- package/dist/recursive/generate/structureGenerator.d.ts.map +1 -0
- package/dist/recursive/generate/structureGenerator.js +88 -0
- package/dist/recursive/generate/textElementGenerator.d.ts +16 -0
- package/dist/recursive/generate/textElementGenerator.d.ts.map +1 -0
- package/dist/recursive/generate/textElementGenerator.js +68 -0
- package/dist/recursive/handleRecursiveObjectQuery.d.ts +94 -0
- package/dist/recursive/handleRecursiveObjectQuery.d.ts.map +1 -0
- package/dist/recursive/handleRecursiveObjectQuery.js +219 -0
- package/dist/recursive/recursiveObjectQuery.d.ts +159 -0
- package/dist/recursive/recursiveObjectQuery.d.ts.map +1 -0
- package/dist/recursive/recursiveObjectQuery.js +1358 -0
- package/dist/recursive/recursiveQueryConfig.d.ts +129 -0
- package/dist/recursive/recursiveQueryConfig.d.ts.map +1 -0
- package/dist/recursive/recursiveQueryConfig.js +133 -0
- package/dist/recursive/tableStructures.d.ts +196 -0
- package/dist/recursive/tableStructures.d.ts.map +1 -0
- package/dist/recursive/tableStructures.js +10 -0
- package/package.json +47 -0
|
@@ -0,0 +1,1003 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* 文件下载消费者
|
|
4
|
+
* 负责从队列拉取任务并下载文件
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.FileDownloadConsumer = void 0;
|
|
41
|
+
const { v4: uuidv4 } = require('uuid');
|
|
42
|
+
const common_1 = require("../common");
|
|
43
|
+
const handleGetClass_1 = require("./handlers/handleGetClass");
|
|
44
|
+
const handleGetFunction_1 = require("./handlers/handleGetFunction");
|
|
45
|
+
const handleGetProgram_1 = require("./handlers/handleGetProgram");
|
|
46
|
+
const handleGetInclude_1 = require("./handlers/handleGetInclude");
|
|
47
|
+
const download_1 = require("./utils/download");
|
|
48
|
+
const generators_1 = require("./generators");
|
|
49
|
+
const core_1 = require("@abaplint/core");
|
|
50
|
+
const path = __importStar(require("path"));
|
|
51
|
+
const fs = __importStar(require("fs"));
|
|
52
|
+
const { join, dirname } = path;
|
|
53
|
+
const { mkdirSync, existsSync } = fs;
|
|
54
|
+
/**
|
|
55
|
+
* 文件下载消费者
|
|
56
|
+
*/
|
|
57
|
+
class FileDownloadConsumer {
|
|
58
|
+
workerId;
|
|
59
|
+
queueManager;
|
|
60
|
+
isRunning = false;
|
|
61
|
+
activeJobIds = [];
|
|
62
|
+
downloadedFiles = [];
|
|
63
|
+
downloadRootPath;
|
|
64
|
+
organizeByProgram;
|
|
65
|
+
abaplintRegistry; // ✅ 复用的 abaplint Registry 实例
|
|
66
|
+
constructor(queueManager, downloadRootPath, jobIds, organizeByProgram = true) {
|
|
67
|
+
this.queueManager = queueManager;
|
|
68
|
+
this.workerId = `worker-${uuidv4()}`;
|
|
69
|
+
this.activeJobIds = jobIds || [];
|
|
70
|
+
this.downloadRootPath = downloadRootPath;
|
|
71
|
+
this.organizeByProgram = organizeByProgram;
|
|
72
|
+
// ✅ 优化:创建一次 abaplint Registry,后续复用
|
|
73
|
+
this.abaplintRegistry = new core_1.Registry(core_1.Config.getDefault());
|
|
74
|
+
// 确保下载目录存在
|
|
75
|
+
if (!existsSync(this.downloadRootPath)) {
|
|
76
|
+
mkdirSync(this.downloadRootPath, { recursive: true });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* 启动消费者
|
|
81
|
+
*/
|
|
82
|
+
async start(jobIds) {
|
|
83
|
+
this.isRunning = true;
|
|
84
|
+
this.activeJobIds = jobIds || await this.getActiveJobs();
|
|
85
|
+
console.error(`[Consumer ${this.workerId}] 🚀 消费者启动`);
|
|
86
|
+
if (this.activeJobIds.length > 0) {
|
|
87
|
+
console.error(`[Consumer ${this.workerId}] 📋 监听 ${this.activeJobIds.length} 个Job: ${this.activeJobIds.join(', ')}`);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
console.error(`[Consumer ${this.workerId}] ⏳ 等待Job...`);
|
|
91
|
+
}
|
|
92
|
+
while (this.isRunning) {
|
|
93
|
+
try {
|
|
94
|
+
// 从指定Job的队列拉取任务
|
|
95
|
+
const task = this.activeJobIds.length === 1
|
|
96
|
+
? await this.queueManager.pullTask(this.activeJobIds[0], 5)
|
|
97
|
+
: await this.queueManager.pullTaskFromAnyJob(this.activeJobIds, 5);
|
|
98
|
+
if (!task) {
|
|
99
|
+
// 超时,检查是否有新的Job
|
|
100
|
+
const previousJobIds = this.activeJobIds;
|
|
101
|
+
this.activeJobIds = await this.getActiveJobs();
|
|
102
|
+
// 如果Job列表发生变化,输出日志
|
|
103
|
+
if (JSON.stringify(previousJobIds) !== JSON.stringify(this.activeJobIds)) {
|
|
104
|
+
if (this.activeJobIds.length > 0) {
|
|
105
|
+
console.error(`[Consumer ${this.workerId}] 📋 更新Job列表: ${this.activeJobIds.join(', ')}`);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
console.error(`[Consumer ${this.workerId}] ⏳ 等待新的Job...`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// 如果没有活跃的Job,等待一段时间
|
|
112
|
+
if (this.activeJobIds.length === 0) {
|
|
113
|
+
await this.sleep(1000);
|
|
114
|
+
}
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
// 处理任务
|
|
118
|
+
await this.processTask(task);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
console.error(`[Consumer ${this.workerId}] ⚠️ 处理错误:`, error);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// 输出统计信息
|
|
125
|
+
const successCount = this.downloadedFiles.filter(f => f.success).length;
|
|
126
|
+
const failCount = this.downloadedFiles.filter(f => !f.success).length;
|
|
127
|
+
console.error(`[Consumer ${this.workerId}] 🛑 消费者已停止 (成功: ${successCount}, 失败: ${failCount}, 总计: ${this.downloadedFiles.length})`);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* 停止消费者
|
|
131
|
+
*/
|
|
132
|
+
async stop() {
|
|
133
|
+
this.isRunning = false;
|
|
134
|
+
console.error(`[Consumer ${this.workerId}] ⏹️ 正在停止消费者...`);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* 获取下载的文件列表
|
|
138
|
+
*/
|
|
139
|
+
getDownloadedFiles() {
|
|
140
|
+
return this.downloadedFiles;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* 处理任务
|
|
144
|
+
*/
|
|
145
|
+
async processTask(task) {
|
|
146
|
+
const startTime = Date.now();
|
|
147
|
+
try {
|
|
148
|
+
// 📝 开始处理任务
|
|
149
|
+
const retryInfo = task.retryCount > 0 ? ` [重试 ${task.retryCount}/${task.maxRetries}]` : '';
|
|
150
|
+
console.error(`[Consumer ${this.workerId}] ⏳ 开始处理任务: ${task.objectKey} (深度: ${task.depth}, 优先级: ${task.priority})${retryInfo}`);
|
|
151
|
+
// 更新任务状态为运行中
|
|
152
|
+
await this.queueManager.updateTaskStatus(task.taskId, 'RUNNING', this.workerId);
|
|
153
|
+
// 1. 从Redis读取缓存的对象数据
|
|
154
|
+
const cachedData = await this.queueManager.getCache(task.jobId, task.objectKey);
|
|
155
|
+
if (!cachedData) {
|
|
156
|
+
throw new Error(`缓存数据不存在: ${task.objectKey}`);
|
|
157
|
+
}
|
|
158
|
+
console.error(`[Consumer ${this.workerId}] ✓ 读取缓存成功: ${task.objectKey}`);
|
|
159
|
+
// 🎯 声明对象特定的根路径:根据配置决定是否按程序分目录
|
|
160
|
+
// - organizeByProgram = true: downloadRootPath/sanitizedObjectName/source/
|
|
161
|
+
// - organizeByProgram = false: downloadRootPath/source/
|
|
162
|
+
const objectRootPath = this.organizeByProgram
|
|
163
|
+
? join(this.downloadRootPath, (0, common_1.sanitizeObjectName)(task.objectName), 'source')
|
|
164
|
+
: join(this.downloadRootPath, 'source');
|
|
165
|
+
// 确保对象根目录存在
|
|
166
|
+
if (!existsSync(objectRootPath)) {
|
|
167
|
+
mkdirSync(objectRootPath, { recursive: true });
|
|
168
|
+
}
|
|
169
|
+
// 2. 下载源代码
|
|
170
|
+
await this.downloadObjectSource(task, cachedData, objectRootPath);
|
|
171
|
+
console.error(`[Consumer ${this.workerId}] ✓ 源代码下载完成: ${task.objectKey}`);
|
|
172
|
+
// 3. 处理附加数据
|
|
173
|
+
await this.processAdditionalData(task, cachedData, objectRootPath);
|
|
174
|
+
console.error(`[Consumer ${this.workerId}] ✓ 附加数据处理完成: ${task.objectKey}`);
|
|
175
|
+
// 4. ✅ 先标记任务完成(避免缓存删除后崩溃导致重试失败)
|
|
176
|
+
await this.queueManager.completeTask(task.taskId);
|
|
177
|
+
// 5. ✅ 再清理缓存(即使失败也不影响任务状态)
|
|
178
|
+
try {
|
|
179
|
+
await this.queueManager.deleteCache(task.jobId, task.objectKey);
|
|
180
|
+
console.error(`[Consumer ${this.workerId}] ✓ 缓存已清理: ${task.objectKey}`);
|
|
181
|
+
}
|
|
182
|
+
catch (cacheError) {
|
|
183
|
+
// 缓存删除失败不影响任务状态,只记录日志
|
|
184
|
+
console.error(`[Consumer ${this.workerId}] ⚠️ 缓存清理失败(不影响任务): ${cacheError}`);
|
|
185
|
+
}
|
|
186
|
+
// 6. 获取并显示Job进度
|
|
187
|
+
const progress = await this.queueManager.getProgress(task.jobId);
|
|
188
|
+
const duration = Date.now() - startTime;
|
|
189
|
+
if (progress) {
|
|
190
|
+
const progressBar = this.generateProgressBar(progress.percentage);
|
|
191
|
+
console.error(`[Consumer ${this.workerId}] ✅ 任务完成: ${task.objectKey} (耗时: ${duration}ms)`);
|
|
192
|
+
console.error(`[Consumer ${this.workerId}] 📊 Job进度: ${progressBar} ${progress.percentage.toFixed(1)}% (${progress.completedTasks}/${progress.totalTasks})`);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
console.error(`[Consumer ${this.workerId}] ✅ 任务完成: ${task.objectKey} (耗时: ${duration}ms)`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
const duration = Date.now() - startTime;
|
|
200
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
201
|
+
const retryInfo = task.retryCount < task.maxRetries
|
|
202
|
+
? `,将重试 (${task.retryCount + 1}/${task.maxRetries})`
|
|
203
|
+
: ',已达最大重试次数';
|
|
204
|
+
console.error(`[Consumer ${this.workerId}] ❌ 任务失败: ${task.objectKey} (耗时: ${duration}ms)`);
|
|
205
|
+
console.error(`[Consumer ${this.workerId}] 错误: ${errorMessage}${retryInfo}`);
|
|
206
|
+
// 标记任务失败(会自动重试)
|
|
207
|
+
await this.queueManager.failTask(task.taskId, errorMessage);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* 生成进度条
|
|
212
|
+
*/
|
|
213
|
+
generateProgressBar(percentage, length = 20) {
|
|
214
|
+
const filled = Math.floor((percentage / 100) * length);
|
|
215
|
+
const empty = length - filled;
|
|
216
|
+
return `[${'█'.repeat(filled)}${'░'.repeat(empty)}]`;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* 下载对象源代码
|
|
220
|
+
*/
|
|
221
|
+
async downloadObjectSource(task, cachedData, objectRootPath) {
|
|
222
|
+
const { objectType, objectName } = task;
|
|
223
|
+
// ✅ 优化:生成文件路径并检查是否已存在
|
|
224
|
+
const filePath = this.generateFilePath(objectName, objectType, objectRootPath);
|
|
225
|
+
if (existsSync(filePath)) {
|
|
226
|
+
const stats = fs.statSync(filePath);
|
|
227
|
+
// 检查文件大小(避免空文件或损坏文件)
|
|
228
|
+
if (stats.size > 0) {
|
|
229
|
+
console.error(`[Consumer ${this.workerId}] 文件已存在,跳过下载: ${filePath} (${stats.size} bytes)`);
|
|
230
|
+
// 记录到下载列表
|
|
231
|
+
this.downloadedFiles.push({
|
|
232
|
+
objectName,
|
|
233
|
+
objectType,
|
|
234
|
+
filePath,
|
|
235
|
+
success: true
|
|
236
|
+
});
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
console.error(`[Consumer ${this.workerId}] 发现空文件,重新下载: ${filePath}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
let sourceResult;
|
|
245
|
+
let functionGroup = null;
|
|
246
|
+
switch (objectType.toUpperCase()) {
|
|
247
|
+
case 'CLASS':
|
|
248
|
+
sourceResult = await (0, handleGetClass_1.handleGetClass)({ class_name: objectName });
|
|
249
|
+
break;
|
|
250
|
+
case 'FUNCTION':
|
|
251
|
+
// 从缓存数据中提取函数组
|
|
252
|
+
functionGroup = this.extractFunctionGroup(cachedData);
|
|
253
|
+
if (!functionGroup) {
|
|
254
|
+
functionGroup = objectName.split('_')[0] || objectName;
|
|
255
|
+
}
|
|
256
|
+
sourceResult = await (0, handleGetFunction_1.handleGetFunction)({
|
|
257
|
+
function_name: objectName,
|
|
258
|
+
function_group: functionGroup
|
|
259
|
+
});
|
|
260
|
+
break;
|
|
261
|
+
case 'PROGRAM':
|
|
262
|
+
sourceResult = await (0, handleGetProgram_1.handleGetProgram)({ program_name: objectName });
|
|
263
|
+
break;
|
|
264
|
+
default:
|
|
265
|
+
console.error(`[Consumer ${this.workerId}] 不支持的对象类型: ${objectType}`);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
// 检查结果
|
|
269
|
+
if (!sourceResult || sourceResult.isError) {
|
|
270
|
+
const errorText = Array.isArray(sourceResult?.content) && sourceResult.content.length > 0
|
|
271
|
+
? sourceResult.content[0].text || '未知错误'
|
|
272
|
+
: '未知错误';
|
|
273
|
+
throw new Error(`获取源代码失败: ${errorText}`);
|
|
274
|
+
}
|
|
275
|
+
if (!sourceResult.content || !Array.isArray(sourceResult.content) || sourceResult.content.length === 0) {
|
|
276
|
+
throw new Error('源代码为空');
|
|
277
|
+
}
|
|
278
|
+
// 提取源代码
|
|
279
|
+
const sourceContent = sourceResult.content.map((item) => item.text || '').join('\n');
|
|
280
|
+
// 确保目录存在(filePath 已在方法开头生成)
|
|
281
|
+
const dirPath = dirname(filePath);
|
|
282
|
+
if (!existsSync(dirPath)) {
|
|
283
|
+
mkdirSync(dirPath, { recursive: true });
|
|
284
|
+
}
|
|
285
|
+
// 下载文件
|
|
286
|
+
const downloadResult = await (0, download_1.downloadFile)(sourceContent, filePath, 'abap');
|
|
287
|
+
this.downloadedFiles.push({
|
|
288
|
+
objectName,
|
|
289
|
+
objectType,
|
|
290
|
+
filePath,
|
|
291
|
+
success: downloadResult.success,
|
|
292
|
+
error: downloadResult.success ? undefined : downloadResult.message
|
|
293
|
+
});
|
|
294
|
+
if (downloadResult.success) {
|
|
295
|
+
console.error(`[Consumer ${this.workerId}] 源代码下载成功: ${objectType}/${objectName} -> ${filePath}`);
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
console.error(`[Consumer ${this.workerId}] 源代码下载失败: ${objectType}/${objectName} - ${downloadResult.message}`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
catch (error) {
|
|
302
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
303
|
+
console.error(`[Consumer ${this.workerId}] 下载源代码失败: ${objectType}/${objectName} - ${errorMessage}`);
|
|
304
|
+
throw error;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* 处理附加数据
|
|
309
|
+
*/
|
|
310
|
+
async processAdditionalData(task, cachedData, objectRootPath) {
|
|
311
|
+
const { objectType, objectName } = task;
|
|
312
|
+
try {
|
|
313
|
+
// ✅ 处理对象元数据(对所有对象类型)
|
|
314
|
+
await this.processObjectMetadata(task, cachedData, objectRootPath);
|
|
315
|
+
// 根据对象类型处理不同的附加数据
|
|
316
|
+
if (objectType.toUpperCase() === 'FUNCTION' && cachedData.ET_FUNCTION) {
|
|
317
|
+
await this.processFunctionData(task, cachedData.ET_FUNCTION, objectRootPath);
|
|
318
|
+
}
|
|
319
|
+
else if (objectType.toUpperCase() === 'PROGRAM' && cachedData.ET_PROGRAM) {
|
|
320
|
+
await this.processProgramData(task, cachedData.ET_PROGRAM, objectRootPath);
|
|
321
|
+
}
|
|
322
|
+
else if (objectType.toUpperCase() === 'CLASS' && cachedData.ET_CLASSES) {
|
|
323
|
+
await this.processClassData(task, cachedData.ET_CLASSES, objectRootPath);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
catch (error) {
|
|
327
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
328
|
+
console.error(`[Consumer ${this.workerId}] 处理附加数据失败: ${task.objectKey} - ${errorMessage}`);
|
|
329
|
+
// 不抛出错误,允许主任务继续
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* 处理对象元数据
|
|
334
|
+
*/
|
|
335
|
+
async processObjectMetadata(task, cachedData, objectRootPath) {
|
|
336
|
+
try {
|
|
337
|
+
// 提取元数据(当前包含依赖关系,未来可扩展更多信息)
|
|
338
|
+
const metadata = {
|
|
339
|
+
ET_REFCLASS: cachedData.ET_REFCLASS,
|
|
340
|
+
ET_REFFUNCTION: cachedData.ET_REFFUNCTION,
|
|
341
|
+
ET_REFPROGRAM: cachedData.ET_REFPROGRAM
|
|
342
|
+
// 未来可扩展:
|
|
343
|
+
// ET_TABLES: cachedData.ET_TABLES,
|
|
344
|
+
// ET_DDIC: cachedData.ET_DDIC,
|
|
345
|
+
// 等等...
|
|
346
|
+
};
|
|
347
|
+
// 生成元数据 XML
|
|
348
|
+
await (0, generators_1.generateMetadataXml)(metadata, task.objectName, task.objectType, objectRootPath, this.downloadedFiles);
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
// 元数据处理失败不影响主流程
|
|
352
|
+
console.error(`[Consumer ${this.workerId}] 处理对象元数据失败: ${task.objectKey} -`, error);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* 处理函数的附加数据
|
|
357
|
+
*/
|
|
358
|
+
async processFunctionData(task, functions, objectRootPath) {
|
|
359
|
+
if (!functions || functions.length === 0) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
for (const func of functions) {
|
|
363
|
+
const functionName = func.FUNCTIONNAME || task.objectName;
|
|
364
|
+
const progName = func.PROGNAME || '';
|
|
365
|
+
// 处理 Include 文件
|
|
366
|
+
if (func.IINCLUDES && Array.isArray(func.IINCLUDES)) {
|
|
367
|
+
for (const include of func.IINCLUDES) {
|
|
368
|
+
if (include.INCLUDENAME) {
|
|
369
|
+
await this.downloadInclude(include.INCLUDENAME, objectRootPath);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
// 处理结构体(保存为 JSON)
|
|
374
|
+
if (func.IDICTSTRUCT && Array.isArray(func.IDICTSTRUCT) && func.IDICTSTRUCT.length > 0) {
|
|
375
|
+
console.error(`[Consumer ${this.workerId}] 处理函数 ${functionName} 的结构体,共 ${func.IDICTSTRUCT.length} 个`);
|
|
376
|
+
await this.processStructures(func.IDICTSTRUCT, objectRootPath);
|
|
377
|
+
}
|
|
378
|
+
// 文本元素、屏幕等需要程序名
|
|
379
|
+
if (!progName) {
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
// 处理文本元素(添加去重)
|
|
383
|
+
if (func.ITEXTELEMENTS && Array.isArray(func.ITEXTELEMENTS) && func.ITEXTELEMENTS.length > 0) {
|
|
384
|
+
// 检查是否已处理
|
|
385
|
+
if (await this.queueManager.hasProcessed(task.jobId, 'textelements', progName)) {
|
|
386
|
+
console.error(`[Consumer ${this.workerId}] 跳过重复的文本元素处理: ${progName} (已处理)`);
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
try {
|
|
390
|
+
// 标记为已处理
|
|
391
|
+
await this.queueManager.markProcessed(task.jobId, 'textelements', progName);
|
|
392
|
+
await (0, generators_1.generateTextElementsXml)(func.ITEXTELEMENTS, progName, 'PROGRAM', objectRootPath, this.downloadedFiles);
|
|
393
|
+
}
|
|
394
|
+
catch (error) {
|
|
395
|
+
console.error(`[Consumer ${this.workerId}] 处理文本元素失败:`, error);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
// 处理消息(添加去重)
|
|
400
|
+
if (func.IMESSAGES && Array.isArray(func.IMESSAGES) && func.IMESSAGES.length > 0) {
|
|
401
|
+
// 检查是否已处理
|
|
402
|
+
if (await this.queueManager.hasProcessed(task.jobId, 'messages', progName)) {
|
|
403
|
+
console.error(`[Consumer ${this.workerId}] 跳过重复的消息处理: ${progName} (已处理)`);
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
try {
|
|
407
|
+
// 标记为已处理
|
|
408
|
+
await this.queueManager.markProcessed(task.jobId, 'messages', progName);
|
|
409
|
+
await (0, generators_1.generateMessageClassXml)(func.IMESSAGES, progName, objectRootPath, this.downloadedFiles);
|
|
410
|
+
}
|
|
411
|
+
catch (error) {
|
|
412
|
+
console.error(`[Consumer ${this.workerId}] 处理消息失败:`, error);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
// 处理GUI标题(添加去重)
|
|
417
|
+
if (func.IGUITITLE && Array.isArray(func.IGUITITLE) && func.IGUITITLE.length > 0) {
|
|
418
|
+
// 检查是否已处理
|
|
419
|
+
if (await this.queueManager.hasProcessed(task.jobId, 'guititle', progName)) {
|
|
420
|
+
console.error(`[Consumer ${this.workerId}] 跳过重复的GUI标题处理: ${progName} (已处理)`);
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
try {
|
|
424
|
+
// 标记为已处理
|
|
425
|
+
await this.queueManager.markProcessed(task.jobId, 'guititle', progName);
|
|
426
|
+
await (0, generators_1.generateGuiTitlesJson)(func.IGUITITLE, progName, 'PROGRAM', objectRootPath, this.downloadedFiles);
|
|
427
|
+
}
|
|
428
|
+
catch (error) {
|
|
429
|
+
console.error(`[Consumer ${this.workerId}] 处理GUI标题失败:`, error);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
// 处理GUI状态(添加去重)
|
|
434
|
+
if (func.IGUISTATUS && Array.isArray(func.IGUISTATUS) && func.IGUISTATUS.length > 0) {
|
|
435
|
+
// 检查是否已处理
|
|
436
|
+
if (await this.queueManager.hasProcessed(task.jobId, 'guistatus', progName)) {
|
|
437
|
+
console.error(`[Consumer ${this.workerId}] 跳过重复的GUI状态处理: ${progName} (已处理)`);
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
try {
|
|
441
|
+
// 标记为已处理
|
|
442
|
+
await this.queueManager.markProcessed(task.jobId, 'guistatus', progName);
|
|
443
|
+
await (0, generators_1.generateGuiStatusJson)(func.IGUISTATUS, progName, 'PROGRAM', objectRootPath, this.downloadedFiles);
|
|
444
|
+
}
|
|
445
|
+
catch (error) {
|
|
446
|
+
console.error(`[Consumer ${this.workerId}] 处理GUI状态失败:`, error);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
// 处理屏幕(添加去重)
|
|
451
|
+
if (func.ISCREENFIELD || func.ISCREENFLOW || func.ISCREENDESC) {
|
|
452
|
+
// 检查是否已处理
|
|
453
|
+
if (await this.queueManager.hasProcessed(task.jobId, 'screens', progName)) {
|
|
454
|
+
console.error(`[Consumer ${this.workerId}] 跳过重复的屏幕处理: ${progName} (已处理)`);
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
// 标记为已处理
|
|
458
|
+
await this.queueManager.markProcessed(task.jobId, 'screens', progName);
|
|
459
|
+
await this.processScreens(func.ISCREENFIELD, func.ISCREENFLOW, func.ISCREENDESC, progName, objectRootPath);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* 处理程序的附加数据
|
|
466
|
+
*/
|
|
467
|
+
async processProgramData(task, programs, objectRootPath) {
|
|
468
|
+
if (!programs || programs.length === 0) {
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
for (const prog of programs) {
|
|
472
|
+
const programName = prog.PROGNAME || task.objectName;
|
|
473
|
+
// 处理 Include 文件
|
|
474
|
+
if (prog.IINCLUDES && Array.isArray(prog.IINCLUDES)) {
|
|
475
|
+
for (const include of prog.IINCLUDES) {
|
|
476
|
+
if (include.INCLUDENAME) {
|
|
477
|
+
await this.downloadInclude(include.INCLUDENAME, objectRootPath);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
// 处理结构体
|
|
482
|
+
if (prog.IDICTSTRUCT && Array.isArray(prog.IDICTSTRUCT) && prog.IDICTSTRUCT.length > 0) {
|
|
483
|
+
console.error(`[Consumer ${this.workerId}] 处理程序 ${programName} 的结构体,共 ${prog.IDICTSTRUCT.length} 个`);
|
|
484
|
+
await this.processStructures(prog.IDICTSTRUCT, objectRootPath);
|
|
485
|
+
}
|
|
486
|
+
// 处理文本元素(添加去重)
|
|
487
|
+
if (prog.ITEXTELEMENTS && Array.isArray(prog.ITEXTELEMENTS) && prog.ITEXTELEMENTS.length > 0) {
|
|
488
|
+
// 检查是否已处理
|
|
489
|
+
if (await this.queueManager.hasProcessed(task.jobId, 'textelements', programName)) {
|
|
490
|
+
console.error(`[Consumer ${this.workerId}] 跳过重复的文本元素处理: ${programName} (已处理)`);
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
try {
|
|
494
|
+
// 标记为已处理
|
|
495
|
+
await this.queueManager.markProcessed(task.jobId, 'textelements', programName);
|
|
496
|
+
await (0, generators_1.generateTextElementsXml)(prog.ITEXTELEMENTS, programName, 'PROGRAM', objectRootPath, this.downloadedFiles);
|
|
497
|
+
}
|
|
498
|
+
catch (error) {
|
|
499
|
+
console.error(`[Consumer ${this.workerId}] 处理程序文本元素失败:`, error);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
// 处理消息(添加去重)
|
|
504
|
+
if (prog.IMESSAGES && Array.isArray(prog.IMESSAGES) && prog.IMESSAGES.length > 0) {
|
|
505
|
+
// 检查是否已处理
|
|
506
|
+
if (await this.queueManager.hasProcessed(task.jobId, 'messages', programName)) {
|
|
507
|
+
console.error(`[Consumer ${this.workerId}] 跳过重复的消息处理: ${programName} (已处理)`);
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
try {
|
|
511
|
+
// 标记为已处理
|
|
512
|
+
await this.queueManager.markProcessed(task.jobId, 'messages', programName);
|
|
513
|
+
await (0, generators_1.generateMessageClassXml)(prog.IMESSAGES, programName, objectRootPath, this.downloadedFiles);
|
|
514
|
+
}
|
|
515
|
+
catch (error) {
|
|
516
|
+
console.error(`[Consumer ${this.workerId}] 处理程序消息失败:`, error);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
// 处理GUI标题(添加去重)
|
|
521
|
+
if (prog.IGUITITLE && Array.isArray(prog.IGUITITLE) && prog.IGUITITLE.length > 0) {
|
|
522
|
+
// 检查是否已处理
|
|
523
|
+
if (await this.queueManager.hasProcessed(task.jobId, 'guititle', programName)) {
|
|
524
|
+
console.error(`[Consumer ${this.workerId}] 跳过重复的GUI标题处理: ${programName} (已处理)`);
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
try {
|
|
528
|
+
// 标记为已处理
|
|
529
|
+
await this.queueManager.markProcessed(task.jobId, 'guititle', programName);
|
|
530
|
+
await (0, generators_1.generateGuiTitlesJson)(prog.IGUITITLE, programName, 'PROGRAM', objectRootPath, this.downloadedFiles);
|
|
531
|
+
}
|
|
532
|
+
catch (error) {
|
|
533
|
+
console.error(`[Consumer ${this.workerId}] 处理程序GUI标题失败:`, error);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
// 处理GUI状态(添加去重)
|
|
538
|
+
if (prog.IGUISTATUS && Array.isArray(prog.IGUISTATUS) && prog.IGUISTATUS.length > 0) {
|
|
539
|
+
// 检查是否已处理
|
|
540
|
+
if (await this.queueManager.hasProcessed(task.jobId, 'guistatus', programName)) {
|
|
541
|
+
console.error(`[Consumer ${this.workerId}] 跳过重复的GUI状态处理: ${programName} (已处理)`);
|
|
542
|
+
}
|
|
543
|
+
else {
|
|
544
|
+
try {
|
|
545
|
+
// 标记为已处理
|
|
546
|
+
await this.queueManager.markProcessed(task.jobId, 'guistatus', programName);
|
|
547
|
+
await (0, generators_1.generateGuiStatusJson)(prog.IGUISTATUS, programName, 'PROGRAM', objectRootPath, this.downloadedFiles);
|
|
548
|
+
}
|
|
549
|
+
catch (error) {
|
|
550
|
+
console.error(`[Consumer ${this.workerId}] 处理程序GUI状态失败:`, error);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
// 处理屏幕(添加去重)
|
|
555
|
+
if (prog.ISCREENFIELD || prog.ISCREENFLOW || prog.ISCREENDESC) {
|
|
556
|
+
// 检查是否已处理
|
|
557
|
+
if (await this.queueManager.hasProcessed(task.jobId, 'screens', programName)) {
|
|
558
|
+
console.error(`[Consumer ${this.workerId}] 跳过重复的屏幕处理: ${programName} (已处理)`);
|
|
559
|
+
}
|
|
560
|
+
else {
|
|
561
|
+
// 标记为已处理
|
|
562
|
+
await this.queueManager.markProcessed(task.jobId, 'screens', programName);
|
|
563
|
+
await this.processScreens(prog.ISCREENFIELD, prog.ISCREENFLOW, prog.ISCREENDESC, programName, objectRootPath);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* 处理类的附加数据
|
|
570
|
+
*/
|
|
571
|
+
async processClassData(task, classes, objectRootPath) {
|
|
572
|
+
if (!classes || classes.length === 0) {
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
for (const cls of classes) {
|
|
576
|
+
const className = cls.CLSNAME || task.objectName;
|
|
577
|
+
// 处理结构体
|
|
578
|
+
if (cls.IDICTSTRUCT && Array.isArray(cls.IDICTSTRUCT) && cls.IDICTSTRUCT.length > 0) {
|
|
579
|
+
console.error(`[Consumer ${this.workerId}] 处理类 ${className} 的结构体,共 ${cls.IDICTSTRUCT.length} 个`);
|
|
580
|
+
await this.processStructures(cls.IDICTSTRUCT, objectRootPath);
|
|
581
|
+
}
|
|
582
|
+
// 处理文本元素(添加去重)
|
|
583
|
+
if (cls.ITEXTELEMENTS && Array.isArray(cls.ITEXTELEMENTS) && cls.ITEXTELEMENTS.length > 0) {
|
|
584
|
+
// 检查是否已处理
|
|
585
|
+
if (await this.queueManager.hasProcessed(task.jobId, 'textelements', className)) {
|
|
586
|
+
console.error(`[Consumer ${this.workerId}] 跳过重复的文本元素处理: ${className} (已处理)`);
|
|
587
|
+
}
|
|
588
|
+
else {
|
|
589
|
+
try {
|
|
590
|
+
// 标记为已处理
|
|
591
|
+
await this.queueManager.markProcessed(task.jobId, 'textelements', className);
|
|
592
|
+
await (0, generators_1.generateTextElementsXml)(cls.ITEXTELEMENTS, className, 'CLASS', objectRootPath, this.downloadedFiles);
|
|
593
|
+
}
|
|
594
|
+
catch (error) {
|
|
595
|
+
console.error(`[Consumer ${this.workerId}] 处理类文本元素失败:`, error);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
// 处理消息(添加去重)
|
|
600
|
+
if (cls.IMESSAGES && Array.isArray(cls.IMESSAGES) && cls.IMESSAGES.length > 0) {
|
|
601
|
+
// 检查是否已处理
|
|
602
|
+
if (await this.queueManager.hasProcessed(task.jobId, 'messages', className)) {
|
|
603
|
+
console.error(`[Consumer ${this.workerId}] 跳过重复的消息处理: ${className} (已处理)`);
|
|
604
|
+
}
|
|
605
|
+
else {
|
|
606
|
+
try {
|
|
607
|
+
// 标记为已处理
|
|
608
|
+
await this.queueManager.markProcessed(task.jobId, 'messages', className);
|
|
609
|
+
await (0, generators_1.generateMessageClassXml)(cls.IMESSAGES, className, objectRootPath, this.downloadedFiles);
|
|
610
|
+
}
|
|
611
|
+
catch (error) {
|
|
612
|
+
console.error(`[Consumer ${this.workerId}] 处理类消息失败:`, error);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* 下载 Include 文件并根据内容自动检测类型,添加符合 abaplint 规范的后缀
|
|
620
|
+
*/
|
|
621
|
+
async downloadInclude(includeName, objectRootPath) {
|
|
622
|
+
try {
|
|
623
|
+
// 1. 获取 include 文件内容
|
|
624
|
+
const includeResult = await (0, handleGetInclude_1.handleGetInclude)({ include_name: includeName });
|
|
625
|
+
// 统一处理错误情况
|
|
626
|
+
if (!includeResult) {
|
|
627
|
+
throw new Error(`获取包含文件 ${includeName} 失败: 无响应数据`);
|
|
628
|
+
}
|
|
629
|
+
if (includeResult.isError) {
|
|
630
|
+
const errorText = Array.isArray(includeResult.content) && includeResult.content.length > 0
|
|
631
|
+
? includeResult.content[0].text || '未知错误'
|
|
632
|
+
: '未知错误';
|
|
633
|
+
throw new Error(`获取包含文件 ${includeName} 失败: ${errorText}`);
|
|
634
|
+
}
|
|
635
|
+
if (!includeResult.content) {
|
|
636
|
+
throw new Error(`获取包含文件 ${includeName} 失败: 无有效内容返回`);
|
|
637
|
+
}
|
|
638
|
+
const content = Array.isArray(includeResult.content)
|
|
639
|
+
? includeResult.content.map(item => item.text || '').join('\n')
|
|
640
|
+
: String(includeResult.content || '');
|
|
641
|
+
// 2. 分析内容并提取类型和真实名称
|
|
642
|
+
const { type: includeType, realName } = this.analyzeIncludeContent(content, includeName);
|
|
643
|
+
console.error(`[Consumer ${this.workerId}] 分析结果 - 类型: ${includeType}, 真实名称: ${realName}${realName !== includeName ? ` (原名: ${includeName})` : ''}`);
|
|
644
|
+
// 3. 根据真实名称和类型生成文件路径
|
|
645
|
+
const finalFilePath = this.generateIncludeFilePath(realName, includeType, objectRootPath);
|
|
646
|
+
// 确保目录存在
|
|
647
|
+
const dirPath = dirname(finalFilePath);
|
|
648
|
+
if (!existsSync(dirPath)) {
|
|
649
|
+
mkdirSync(dirPath, { recursive: true });
|
|
650
|
+
}
|
|
651
|
+
// 5. 下载文件
|
|
652
|
+
const downloadResult = await (0, download_1.downloadFile)(content, finalFilePath, 'abap');
|
|
653
|
+
this.downloadedFiles.push({
|
|
654
|
+
objectName: includeName,
|
|
655
|
+
objectType: 'INCLUDE',
|
|
656
|
+
filePath: finalFilePath,
|
|
657
|
+
success: downloadResult.success,
|
|
658
|
+
error: downloadResult.success ? undefined : downloadResult.message
|
|
659
|
+
});
|
|
660
|
+
if (downloadResult.success) {
|
|
661
|
+
const nameInfo = realName !== includeName ? `${includeName} → ${realName}` : includeName;
|
|
662
|
+
console.error(`[Consumer ${this.workerId}] Include 下载成功: ${nameInfo} [${includeType}] -> ${finalFilePath}`);
|
|
663
|
+
}
|
|
664
|
+
else {
|
|
665
|
+
console.error(`[Consumer ${this.workerId}] Include 下载失败: ${includeName} - ${downloadResult.message}`);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
catch (error) {
|
|
669
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
670
|
+
console.error(`[Consumer ${this.workerId}] 下载 Include 失败: ${includeName} - ${errorMessage}`);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* ✅ 分析 include 文件内容,返回类型和真实名称
|
|
675
|
+
* 使用 abaplint 进行精确的语法分析
|
|
676
|
+
* - 只对 FUNCTION 类型提取真实函数名
|
|
677
|
+
* - 其他类型(类、表单等)保持使用原始 include 名称
|
|
678
|
+
* - 遵循零硬编码原则:所有信息从源代码中动态提取
|
|
679
|
+
*/
|
|
680
|
+
analyzeIncludeContent(content, fileName) {
|
|
681
|
+
console.error(`[Consumer ${this.workerId}] [内容分析] 使用 abaplint 分析文件: ${fileName}`);
|
|
682
|
+
let detectedType = 'STANDARD';
|
|
683
|
+
let extractedName = fileName; // 默认使用原始文件名
|
|
684
|
+
try {
|
|
685
|
+
// ✅ 优化:清空 Registry(保留配置),而不是创建新实例
|
|
686
|
+
this.abaplintRegistry.clear();
|
|
687
|
+
// abaplint 需要 .prog.abap 或类似后缀才能识别
|
|
688
|
+
const tempFileName = `${fileName}.prog.abap`;
|
|
689
|
+
const memFile = new core_1.MemoryFile(tempFileName, content);
|
|
690
|
+
this.abaplintRegistry.addFile(memFile);
|
|
691
|
+
this.abaplintRegistry.parse();
|
|
692
|
+
// 获取对象(abaplint 会尝试识别文件类型)
|
|
693
|
+
const obj = this.abaplintRegistry.findObjectForFile(memFile);
|
|
694
|
+
console.error(`[Consumer ${this.workerId}] [abaplint] 对象类型: ${obj ? obj.getType() : 'null'}`);
|
|
695
|
+
if (obj && obj instanceof core_1.ABAPObject) {
|
|
696
|
+
const abapFiles = obj.getABAPFiles();
|
|
697
|
+
console.error(`[Consumer ${this.workerId}] [abaplint] ABAP 文件数: ${abapFiles.length}`);
|
|
698
|
+
if (abapFiles.length > 0) {
|
|
699
|
+
const file = abapFiles[0];
|
|
700
|
+
const fileInfo = file.getInfo();
|
|
701
|
+
const statements = file.getStatements();
|
|
702
|
+
// 🎯 优先检查:FUNCTION 模块(仅对此类型提取真实名称)
|
|
703
|
+
const functionStatements = statements.filter(s => s.get() && s.get().constructor.name === 'FunctionModule');
|
|
704
|
+
if (functionStatements.length > 0) {
|
|
705
|
+
detectedType = 'FUNCTION';
|
|
706
|
+
// ✅ 使用正则表达式提取函数名(支持小写、大写、混合,以及 / 命名空间)
|
|
707
|
+
// 匹配模式:FUNCTION function_name.
|
|
708
|
+
// SAP 函数名可以包含: 字母、数字、下划线、斜杠(/)
|
|
709
|
+
const functionMatch = content.match(/^\s*FUNCTION\s+([a-zA-Z_/][a-zA-Z0-9_/]*)\s*\./mi);
|
|
710
|
+
if (functionMatch && functionMatch[1]) {
|
|
711
|
+
extractedName = functionMatch[1].toUpperCase();
|
|
712
|
+
console.error(`[Consumer ${this.workerId}] [正则] 提取函数名: ${extractedName} (原名: ${fileName})`);
|
|
713
|
+
}
|
|
714
|
+
else {
|
|
715
|
+
console.error(`[Consumer ${this.workerId}] [警告] 无法提取函数名,使用原名: ${fileName}`);
|
|
716
|
+
}
|
|
717
|
+
console.error(`[Consumer ${this.workerId}] [abaplint] 检测到 ${functionStatements.length} 个函数模块 -> FUNCTION: ${extractedName}`);
|
|
718
|
+
return { type: detectedType, realName: extractedName };
|
|
719
|
+
}
|
|
720
|
+
// ✅ 以下类型不提取名称,保持使用原始 include 名称
|
|
721
|
+
// 检查测试类
|
|
722
|
+
const testClasses = fileInfo.listClassDefinitions().filter(cls => cls.isForTesting);
|
|
723
|
+
if (testClasses.length > 0) {
|
|
724
|
+
detectedType = 'TESTCLASSES';
|
|
725
|
+
console.error(`[Consumer ${this.workerId}] [abaplint] 检测到测试类 -> TESTCLASSES: ${fileName}`);
|
|
726
|
+
return { type: detectedType, realName: fileName };
|
|
727
|
+
}
|
|
728
|
+
// 检查类定义和实现
|
|
729
|
+
const classDefinitions = fileInfo.listClassDefinitions();
|
|
730
|
+
const classImplementations = fileInfo.listClassImplementations();
|
|
731
|
+
console.error(`[Consumer ${this.workerId}] [abaplint] 类定义: ${classDefinitions.length}, 类实现: ${classImplementations.length}`);
|
|
732
|
+
if (classDefinitions.length > 0 && classImplementations.length === 0) {
|
|
733
|
+
detectedType = 'LOCALS_DEF';
|
|
734
|
+
console.error(`[Consumer ${this.workerId}] [abaplint] 检测为 LOCALS_DEF: ${fileName}`);
|
|
735
|
+
return { type: detectedType, realName: fileName };
|
|
736
|
+
}
|
|
737
|
+
if (classImplementations.length > 0) {
|
|
738
|
+
detectedType = 'LOCALS_IMP';
|
|
739
|
+
console.error(`[Consumer ${this.workerId}] [abaplint] 检测为 LOCALS_IMP: ${fileName}`);
|
|
740
|
+
return { type: detectedType, realName: fileName };
|
|
741
|
+
}
|
|
742
|
+
// 检查接口定义
|
|
743
|
+
const interfaces = fileInfo.listInterfaceDefinitions();
|
|
744
|
+
if (interfaces.length > 0) {
|
|
745
|
+
detectedType = 'LOCALS_DEF';
|
|
746
|
+
console.error(`[Consumer ${this.workerId}] [abaplint] 检测到接口定义 -> LOCALS_DEF: ${fileName}`);
|
|
747
|
+
return { type: detectedType, realName: fileName };
|
|
748
|
+
}
|
|
749
|
+
// 检查 FORM 子例程
|
|
750
|
+
const forms = fileInfo.listFormDefinitions();
|
|
751
|
+
console.error(`[Consumer ${this.workerId}] [abaplint] FORM 子例程数: ${forms.length}`);
|
|
752
|
+
if (forms.length >= 1) {
|
|
753
|
+
detectedType = 'SUBROUTINES';
|
|
754
|
+
console.error(`[Consumer ${this.workerId}] [abaplint] 检测到子例程 -> SUBROUTINES: ${fileName}`);
|
|
755
|
+
return { type: detectedType, realName: fileName };
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
else {
|
|
760
|
+
console.error(`[Consumer ${this.workerId}] [abaplint] 无法识别为 ABAPObject`);
|
|
761
|
+
}
|
|
762
|
+
console.error(`[Consumer ${this.workerId}] [abaplint] 未检测到特殊类型 -> STANDARD: ${fileName}`);
|
|
763
|
+
return { type: 'STANDARD', realName: fileName };
|
|
764
|
+
}
|
|
765
|
+
catch (error) {
|
|
766
|
+
console.error(`[Consumer ${this.workerId}] [abaplint] 解析失败: ${error instanceof Error ? error.message : String(error)}`);
|
|
767
|
+
if (error instanceof Error && error.stack) {
|
|
768
|
+
console.error(`[Consumer ${this.workerId}] [abaplint] 堆栈: ${error.stack.substring(0, 500)}`);
|
|
769
|
+
}
|
|
770
|
+
// 解析失败时返回标准类型和原始文件名
|
|
771
|
+
return { type: 'STANDARD', realName: fileName };
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* 根据 include 类型生成符合 abaplint 规范的文件路径
|
|
776
|
+
*/
|
|
777
|
+
generateIncludeFilePath(includeName, includeType, objectRootPath) {
|
|
778
|
+
const sanitizedName = (0, common_1.sanitizeObjectName)(includeName);
|
|
779
|
+
let extension;
|
|
780
|
+
switch (includeType) {
|
|
781
|
+
case 'FUNCTION':
|
|
782
|
+
extension = 'func.abap';
|
|
783
|
+
break;
|
|
784
|
+
case 'LOCALS_DEF':
|
|
785
|
+
extension = 'clas.locals_def.abap';
|
|
786
|
+
break;
|
|
787
|
+
case 'LOCALS_IMP':
|
|
788
|
+
extension = 'clas.locals_imp.abap';
|
|
789
|
+
break;
|
|
790
|
+
case 'TESTCLASSES':
|
|
791
|
+
extension = 'clas.testclasses.abap';
|
|
792
|
+
break;
|
|
793
|
+
case 'MACROS':
|
|
794
|
+
extension = 'clas.macros.abap';
|
|
795
|
+
break;
|
|
796
|
+
case 'SCREENS':
|
|
797
|
+
extension = 'prog.screens.abap';
|
|
798
|
+
break;
|
|
799
|
+
case 'SUBROUTINES':
|
|
800
|
+
extension = 'prog.subs.abap';
|
|
801
|
+
break;
|
|
802
|
+
case 'STANDARD':
|
|
803
|
+
default:
|
|
804
|
+
extension = 'prog.abap';
|
|
805
|
+
break;
|
|
806
|
+
}
|
|
807
|
+
return join(objectRootPath, 'includes', `${sanitizedName}.${extension}`);
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* 提取函数组名称
|
|
811
|
+
*/
|
|
812
|
+
extractFunctionGroup(cachedData) {
|
|
813
|
+
if (cachedData.ET_FUNCTION && Array.isArray(cachedData.ET_FUNCTION) && cachedData.ET_FUNCTION.length > 0) {
|
|
814
|
+
const func = cachedData.ET_FUNCTION[0];
|
|
815
|
+
return func.FUNCTIONGROUP || null;
|
|
816
|
+
}
|
|
817
|
+
return null;
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* 生成文件路径
|
|
821
|
+
*/
|
|
822
|
+
generateFilePath(objectName, objectType, objectRootPath) {
|
|
823
|
+
const sanitizedName = (0, common_1.sanitizeObjectName)(objectName);
|
|
824
|
+
return join(objectRootPath, this.getObjectDirectory(objectType), `${sanitizedName}.${this.getDefaultExtension(objectType)}`);
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* 获取默认文件扩展名
|
|
828
|
+
*/
|
|
829
|
+
getDefaultExtension(objectType) {
|
|
830
|
+
const extensionMap = {
|
|
831
|
+
'CLASS': 'clas.abap',
|
|
832
|
+
'FUNCTION': 'func.abap',
|
|
833
|
+
'PROGRAM': 'prog.abap',
|
|
834
|
+
'INCLUDE': 'prog.abap'
|
|
835
|
+
};
|
|
836
|
+
return extensionMap[objectType.toUpperCase()] || 'abap';
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* 获取对象目录
|
|
840
|
+
*/
|
|
841
|
+
getObjectDirectory(objectType) {
|
|
842
|
+
const directoryMap = {
|
|
843
|
+
'CLASS': 'classes',
|
|
844
|
+
'FUNCTION': 'functions',
|
|
845
|
+
'PROGRAM': 'programs',
|
|
846
|
+
'INCLUDE': 'includes'
|
|
847
|
+
};
|
|
848
|
+
return directoryMap[objectType.toUpperCase()] || 'others';
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* 获取活跃的 Job 列表
|
|
852
|
+
*/
|
|
853
|
+
async getActiveJobs() {
|
|
854
|
+
return await this.queueManager.getActiveJobIds();
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* 延迟函数
|
|
858
|
+
*/
|
|
859
|
+
sleep(ms) {
|
|
860
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* 处理结构体列表
|
|
864
|
+
*/
|
|
865
|
+
async processStructures(structures, objectRootPath) {
|
|
866
|
+
if (!structures || structures.length === 0) {
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
for (const structure of structures) {
|
|
870
|
+
if (!structure.TABLENAME) {
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
873
|
+
await this.processSingleStructure(structure.TABLENAME, objectRootPath);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* 处理单个结构体
|
|
878
|
+
*/
|
|
879
|
+
async processSingleStructure(structureName, objectRootPath) {
|
|
880
|
+
try {
|
|
881
|
+
console.error(`[Consumer ${this.workerId}] 处理结构体: ${structureName}`);
|
|
882
|
+
// 获取表结构数据
|
|
883
|
+
const tableData = await (0, generators_1.fetchTableData)(structureName);
|
|
884
|
+
if (!tableData) {
|
|
885
|
+
console.error(`[Consumer ${this.workerId}] 获取表结构数据失败: ${structureName}`);
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
const tabClass = tableData.TABCLASS;
|
|
889
|
+
// 保存到 structures 目录
|
|
890
|
+
const structureResult = await (0, generators_1.saveStructureJson)(tableData, structureName, objectRootPath);
|
|
891
|
+
this.downloadedFiles.push({
|
|
892
|
+
objectName: structureName,
|
|
893
|
+
objectType: 'STRUCTURE',
|
|
894
|
+
filePath: structureResult.filePath || join(objectRootPath, 'structures', `${structureName}.json`),
|
|
895
|
+
success: structureResult.success,
|
|
896
|
+
error: structureResult.error
|
|
897
|
+
});
|
|
898
|
+
// 如果是透明表,额外保存到 tables 目录
|
|
899
|
+
if (tabClass === 'TRANSP') {
|
|
900
|
+
const tableResult = await (0, generators_1.saveTableJson)(tableData, structureName, objectRootPath);
|
|
901
|
+
this.downloadedFiles.push({
|
|
902
|
+
objectName: structureName,
|
|
903
|
+
objectType: 'TABLE',
|
|
904
|
+
filePath: tableResult.filePath || join(objectRootPath, 'tables', `${structureName}.json`),
|
|
905
|
+
success: tableResult.success,
|
|
906
|
+
error: tableResult.error
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
// 处理 domain 定义
|
|
910
|
+
if (tableData.GT_FIELDS && tableData.GT_FIELDS.length > 0) {
|
|
911
|
+
await (0, generators_1.processTableDomains)(tableData.GT_FIELDS, structureName, objectRootPath, this.downloadedFiles);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
catch (error) {
|
|
915
|
+
console.error(`[Consumer ${this.workerId}] 处理结构体失败: ${structureName} -`, error);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* 处理屏幕数据
|
|
920
|
+
*/
|
|
921
|
+
async processScreens(fields, flowLogic, screenDescs, programName, objectRootPath) {
|
|
922
|
+
const hasFields = fields && fields.length > 0;
|
|
923
|
+
const hasFlowLogic = flowLogic && flowLogic.length > 0;
|
|
924
|
+
const hasScreenDescs = screenDescs && screenDescs.length > 0;
|
|
925
|
+
if (!hasFields && !hasFlowLogic && !hasScreenDescs) {
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
try {
|
|
929
|
+
console.error(`[Consumer ${this.workerId}] 处理程序 ${programName} 的屏幕数据`);
|
|
930
|
+
// 按屏幕号分组数据
|
|
931
|
+
const screenDataMap = new Map();
|
|
932
|
+
// 处理字段
|
|
933
|
+
if (hasFields) {
|
|
934
|
+
for (const field of fields) {
|
|
935
|
+
const screenNumber = field.SCREEN;
|
|
936
|
+
if (!screenNumber)
|
|
937
|
+
continue;
|
|
938
|
+
if (!screenDataMap.has(screenNumber)) {
|
|
939
|
+
screenDataMap.set(screenNumber, {});
|
|
940
|
+
}
|
|
941
|
+
const data = screenDataMap.get(screenNumber);
|
|
942
|
+
if (!data.fields) {
|
|
943
|
+
data.fields = [];
|
|
944
|
+
}
|
|
945
|
+
data.fields.push(field);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
// 处理流程逻辑
|
|
949
|
+
if (hasFlowLogic) {
|
|
950
|
+
for (const flow of flowLogic) {
|
|
951
|
+
const screenNumber = flow.SCREEN;
|
|
952
|
+
if (!screenNumber)
|
|
953
|
+
continue;
|
|
954
|
+
if (!screenDataMap.has(screenNumber)) {
|
|
955
|
+
screenDataMap.set(screenNumber, {});
|
|
956
|
+
}
|
|
957
|
+
const data = screenDataMap.get(screenNumber);
|
|
958
|
+
if (!data.flowLogic) {
|
|
959
|
+
data.flowLogic = [];
|
|
960
|
+
}
|
|
961
|
+
data.flowLogic.push(flow);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
// 处理屏幕描述
|
|
965
|
+
if (hasScreenDescs) {
|
|
966
|
+
for (const desc of screenDescs) {
|
|
967
|
+
const screenNumber = desc.SCREEN;
|
|
968
|
+
if (!screenNumber)
|
|
969
|
+
continue;
|
|
970
|
+
if (!screenDataMap.has(screenNumber)) {
|
|
971
|
+
screenDataMap.set(screenNumber, {});
|
|
972
|
+
}
|
|
973
|
+
const data = screenDataMap.get(screenNumber);
|
|
974
|
+
data.screenDesc = desc;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
if (screenDataMap.size === 0) {
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
// 构建 ScreenData 数组
|
|
981
|
+
const screens = [];
|
|
982
|
+
for (const [screenNumber, data] of screenDataMap) {
|
|
983
|
+
screens.push({
|
|
984
|
+
screenNumber,
|
|
985
|
+
programName,
|
|
986
|
+
fields: data.fields,
|
|
987
|
+
flowLogic: data.flowLogic,
|
|
988
|
+
screenDesc: data.screenDesc
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
// 按屏幕号排序
|
|
992
|
+
screens.sort((a, b) => a.screenNumber.localeCompare(b.screenNumber));
|
|
993
|
+
// 生成屏幕XML文件
|
|
994
|
+
await (0, generators_1.generateMultipleScreensXml)(screens, programName, objectRootPath, this.downloadedFiles, true // 包含PROGDIR
|
|
995
|
+
);
|
|
996
|
+
console.error(`[Consumer ${this.workerId}] 程序 ${programName} 的屏幕XML生成成功`);
|
|
997
|
+
}
|
|
998
|
+
catch (error) {
|
|
999
|
+
console.error(`[Consumer ${this.workerId}] 处理屏幕失败:`, error);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
exports.FileDownloadConsumer = FileDownloadConsumer;
|