@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,496 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* 递归查询生产者
|
|
4
|
+
* 负责并发递归查询对象,生成所有下载任务
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.RecursiveQueryProducer = void 0;
|
|
8
|
+
const { v4: uuidv4 } = require('uuid');
|
|
9
|
+
const common_1 = require("../common");
|
|
10
|
+
const handlers_1 = require("./handlers");
|
|
11
|
+
/**
|
|
12
|
+
* 异步任务队列(内部并发控制)
|
|
13
|
+
* 复用现有的 AsyncTaskQueue 实现
|
|
14
|
+
*/
|
|
15
|
+
class AsyncTaskQueue {
|
|
16
|
+
queue = [];
|
|
17
|
+
running = 0;
|
|
18
|
+
maxConcurrent;
|
|
19
|
+
taskResults = new Map();
|
|
20
|
+
processing = false;
|
|
21
|
+
processingRequests = 0;
|
|
22
|
+
waiters = [];
|
|
23
|
+
constructor(maxConcurrent) {
|
|
24
|
+
this.maxConcurrent = Math.max(1, maxConcurrent);
|
|
25
|
+
}
|
|
26
|
+
async add(taskId, task) {
|
|
27
|
+
if (this.taskResults.has(taskId)) {
|
|
28
|
+
return this.taskResults.get(taskId);
|
|
29
|
+
}
|
|
30
|
+
const taskPromise = new Promise((resolve, reject) => {
|
|
31
|
+
const wrappedTask = async () => {
|
|
32
|
+
try {
|
|
33
|
+
this.running++;
|
|
34
|
+
const result = await task();
|
|
35
|
+
resolve(result);
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
reject(error);
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
this.running--;
|
|
44
|
+
this.notifyWaiters();
|
|
45
|
+
process.nextTick(() => this.processQueue());
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
this.queue.push(wrappedTask);
|
|
49
|
+
});
|
|
50
|
+
this.taskResults.set(taskId, taskPromise);
|
|
51
|
+
taskPromise.finally(() => {
|
|
52
|
+
setTimeout(() => {
|
|
53
|
+
this.taskResults.delete(taskId);
|
|
54
|
+
}, 1000);
|
|
55
|
+
}).catch(() => { });
|
|
56
|
+
this.processQueue();
|
|
57
|
+
return taskPromise;
|
|
58
|
+
}
|
|
59
|
+
processQueue() {
|
|
60
|
+
this.processingRequests++;
|
|
61
|
+
if (this.processing) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
this.processing = true;
|
|
65
|
+
process.nextTick(() => {
|
|
66
|
+
try {
|
|
67
|
+
while (this.running < this.maxConcurrent && this.queue.length > 0) {
|
|
68
|
+
const task = this.queue.shift();
|
|
69
|
+
if (task) {
|
|
70
|
+
task().catch(() => { });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
this.processing = false;
|
|
76
|
+
this.processingRequests--;
|
|
77
|
+
if (this.processingRequests > 0 || (this.queue.length > 0 && this.running < this.maxConcurrent)) {
|
|
78
|
+
this.processingRequests = 0;
|
|
79
|
+
this.processQueue();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
getQueueSize() {
|
|
85
|
+
return this.queue.length;
|
|
86
|
+
}
|
|
87
|
+
getRunningCount() {
|
|
88
|
+
return this.running;
|
|
89
|
+
}
|
|
90
|
+
notifyWaiters() {
|
|
91
|
+
const waiters = [...this.waiters];
|
|
92
|
+
this.waiters = [];
|
|
93
|
+
waiters.forEach(waiter => waiter());
|
|
94
|
+
}
|
|
95
|
+
async waitForAll() {
|
|
96
|
+
while (this.queue.length > 0 || this.running > 0) {
|
|
97
|
+
await new Promise((resolve) => {
|
|
98
|
+
if (this.queue.length === 0 && this.running === 0) {
|
|
99
|
+
resolve();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
this.waiters.push(resolve);
|
|
103
|
+
const timeout = setTimeout(() => {
|
|
104
|
+
const index = this.waiters.indexOf(resolve);
|
|
105
|
+
if (index !== -1) {
|
|
106
|
+
this.waiters.splice(index, 1);
|
|
107
|
+
}
|
|
108
|
+
resolve();
|
|
109
|
+
}, 100);
|
|
110
|
+
const originalResolve = resolve;
|
|
111
|
+
this.waiters[this.waiters.length - 1] = () => {
|
|
112
|
+
clearTimeout(timeout);
|
|
113
|
+
originalResolve();
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
if (this.queue.length > 0 && this.running < this.maxConcurrent) {
|
|
117
|
+
this.processQueue();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (this.taskResults.size > 0) {
|
|
121
|
+
await Promise.allSettled(Array.from(this.taskResults.values()));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* 递归查询生产者
|
|
127
|
+
*/
|
|
128
|
+
class RecursiveQueryProducer {
|
|
129
|
+
queueManager;
|
|
130
|
+
taskQueue;
|
|
131
|
+
jobId;
|
|
132
|
+
filterCache; // ✅ 优化:缓存过滤规则
|
|
133
|
+
constructor(queueManager) {
|
|
134
|
+
this.queueManager = queueManager;
|
|
135
|
+
this.taskQueue = new AsyncTaskQueue(common_1.queueConfig.producerConcurrency);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* 创建 Job 并返回 Job ID(不阻塞)
|
|
139
|
+
* @param jobId Job ID(必须由外部传入)
|
|
140
|
+
* @param objectType 对象类型
|
|
141
|
+
* @param objectName 对象名称
|
|
142
|
+
* @param config 递归查询配置
|
|
143
|
+
* @returns Job ID
|
|
144
|
+
*/
|
|
145
|
+
async createJob(jobId, objectType, objectName, config) {
|
|
146
|
+
this.jobId = jobId;
|
|
147
|
+
console.error(`[Producer] 创建 Job: ${jobId}`);
|
|
148
|
+
console.error(`[Producer] 对象: ${objectType}/${objectName}`);
|
|
149
|
+
console.error(`[Producer] 配置: 最大深度=${config.maxRecursionDepth}, 并发=${common_1.queueConfig.producerConcurrency}`);
|
|
150
|
+
// ✅ 优化:初始化过滤规则缓存(预编译正则表达式)
|
|
151
|
+
this.initializeFilterCache(config);
|
|
152
|
+
// 创建 Job
|
|
153
|
+
await this.queueManager.createJob(jobId, objectType, objectName, config);
|
|
154
|
+
await this.queueManager.updateJobStatus(jobId, 'RUNNING');
|
|
155
|
+
return jobId;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* 提交查询任务(兼容旧版本,阻塞模式)
|
|
159
|
+
* @param objectType 对象类型
|
|
160
|
+
* @param objectName 对象名称
|
|
161
|
+
* @param config 递归查询配置
|
|
162
|
+
* @returns Job ID
|
|
163
|
+
*/
|
|
164
|
+
async submitQuery(objectType, objectName, config) {
|
|
165
|
+
const jobId = uuidv4();
|
|
166
|
+
await this.createJob(jobId, objectType, objectName, config);
|
|
167
|
+
// 同步模式: 等待所有任务生成完成
|
|
168
|
+
await this.generateAllTasks(jobId, objectType, objectName, config);
|
|
169
|
+
return jobId;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* 生成所有任务(可以被外部调用)
|
|
173
|
+
*/
|
|
174
|
+
async generateAllTasks(jobId, objectType, objectName, config) {
|
|
175
|
+
try {
|
|
176
|
+
await this.recursivelyGenerateTasks(jobId, objectType, objectName, 0, config);
|
|
177
|
+
// 等待所有查询任务完成
|
|
178
|
+
await this.taskQueue.waitForAll();
|
|
179
|
+
console.error(`[Producer] Job ${jobId} 所有任务已生成`);
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
console.error(`[Producer] Job ${jobId} 生成任务失败:`, error);
|
|
183
|
+
await this.queueManager.updateJobStatus(jobId, 'FAILED');
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* 等待 Job 完成
|
|
189
|
+
*/
|
|
190
|
+
async waitForCompletion(jobId, timeout) {
|
|
191
|
+
const startTime = Date.now();
|
|
192
|
+
const maxTimeout = timeout || common_1.queueConfig.jobTimeout;
|
|
193
|
+
console.error(`[Producer] 等待 Job ${jobId} 完成...`);
|
|
194
|
+
while (true) {
|
|
195
|
+
const job = await this.queueManager.getJob(jobId);
|
|
196
|
+
if (!job) {
|
|
197
|
+
throw new Error(`Job ${jobId} 不存在`);
|
|
198
|
+
}
|
|
199
|
+
if (job.status === 'COMPLETED' || job.status === 'FAILED' || job.status === 'CANCELLED') {
|
|
200
|
+
console.error(`[Producer] Job ${jobId} 完成,状态: ${job.status}`);
|
|
201
|
+
return job;
|
|
202
|
+
}
|
|
203
|
+
// 检查超时
|
|
204
|
+
if (Date.now() - startTime > maxTimeout) {
|
|
205
|
+
throw new Error(`Job ${jobId} 超时`);
|
|
206
|
+
}
|
|
207
|
+
// 等待一段时间后重试
|
|
208
|
+
await this.sleep(common_1.queueConfig.pollInterval);
|
|
209
|
+
// 打印进度
|
|
210
|
+
const progress = await this.queueManager.getProgress(jobId);
|
|
211
|
+
if (progress) {
|
|
212
|
+
console.error(`[Producer] Job ${jobId} 进度: ${progress.percentage.toFixed(1)}% (${progress.completedTasks}/${progress.totalTasks})`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* 获取进度
|
|
218
|
+
*/
|
|
219
|
+
async getProgress(jobId) {
|
|
220
|
+
return await this.queueManager.getProgress(jobId);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* 取消 Job
|
|
224
|
+
*/
|
|
225
|
+
async cancelJob(jobId) {
|
|
226
|
+
await this.queueManager.updateJobStatus(jobId, 'CANCELLED');
|
|
227
|
+
console.error(`[Producer] Job ${jobId} 已取消`);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* 递归生成任务(并发版本)
|
|
231
|
+
*/
|
|
232
|
+
async recursivelyGenerateTasks(jobId, objectType, objectName, depth, config) {
|
|
233
|
+
// 1. 检查深度限制
|
|
234
|
+
if (depth > config.maxRecursionDepth) {
|
|
235
|
+
console.error(`[Producer] 达到最大递归深度 ${config.maxRecursionDepth},停止处理`);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const objectKey = `${objectType}/${objectName}`;
|
|
239
|
+
// 2. 应用过滤规则
|
|
240
|
+
if (this.shouldFilter(objectType, objectName, config)) {
|
|
241
|
+
console.error(`[Producer] 对象被过滤规则排除,跳过: ${objectKey}`);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
// 3. 原子性地检查并标记访问(✅ 优化:从2次Redis操作减少到1次)
|
|
245
|
+
const isFirstVisit = await this.queueManager.markVisitedIfNotExists(jobId, objectKey);
|
|
246
|
+
if (!isFirstVisit) {
|
|
247
|
+
console.error(`[Producer] 对象已访问,跳过: ${objectKey}`);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
// 5. 背压控制:检查队列大小
|
|
251
|
+
await this.waitForQueueSpace(jobId, common_1.queueConfig.queueMaxSize);
|
|
252
|
+
// 6. 使用内部并发队列处理
|
|
253
|
+
await this.taskQueue.add(objectKey, async () => {
|
|
254
|
+
console.error(`[Producer] 开始处理对象: ${objectType}/${objectName} (深度: ${depth})`);
|
|
255
|
+
console.error(`[Producer] 队列状态 - 运行中: ${this.taskQueue.getRunningCount()}, 等待中: ${this.taskQueue.getQueueSize()}`);
|
|
256
|
+
// 6.1 查询对象
|
|
257
|
+
const queryResult = await this.queryObject(objectType, objectName);
|
|
258
|
+
if (!queryResult || !queryResult.success || !queryResult.data) {
|
|
259
|
+
const errorMsg = `查询对象失败: ${objectType}/${objectName} - ${queryResult?.error || '未知错误'}`;
|
|
260
|
+
console.error(`[Producer] ${errorMsg}`);
|
|
261
|
+
// ✅ 记录错误到 Job
|
|
262
|
+
await this.queueManager.recordJobError(jobId, errorMsg);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const objectData = queryResult.data;
|
|
266
|
+
// 6.2 提取依赖关系
|
|
267
|
+
const dependencies = this.extractDependencies(objectData);
|
|
268
|
+
// 6.3 准备缓存数据(移除引用关系)
|
|
269
|
+
const cacheData = this.prepareCacheData(objectData);
|
|
270
|
+
// 6.4 压缩并缓存到Redis
|
|
271
|
+
await this.queueManager.cacheObject(jobId, objectKey, cacheData);
|
|
272
|
+
// 6.5 创建下载任务
|
|
273
|
+
const task = {
|
|
274
|
+
taskId: uuidv4(),
|
|
275
|
+
jobId,
|
|
276
|
+
objectKey,
|
|
277
|
+
taskType: 'DOWNLOAD',
|
|
278
|
+
objectType,
|
|
279
|
+
objectName,
|
|
280
|
+
depth,
|
|
281
|
+
priority: this.calculatePriority(depth),
|
|
282
|
+
status: 'PENDING',
|
|
283
|
+
retryCount: 0,
|
|
284
|
+
maxRetries: common_1.queueConfig.maxRetries,
|
|
285
|
+
createdAt: new Date(),
|
|
286
|
+
metadata: {
|
|
287
|
+
cacheKey: `abap:cache:${jobId}:${objectKey}`,
|
|
288
|
+
dependencyCount: dependencies.length
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
await this.queueManager.createTask(task);
|
|
292
|
+
console.error(`[Producer] 创建任务: ${objectKey} (优先级: ${task.priority})`);
|
|
293
|
+
// 6.6 递归处理依赖
|
|
294
|
+
if (depth < config.maxRecursionDepth && dependencies.length > 0) {
|
|
295
|
+
console.error(`[Producer] 处理 ${dependencies.length} 个依赖 (深度: ${depth + 1})`);
|
|
296
|
+
const childPromises = dependencies.map(dep => this.recursivelyGenerateTasks(jobId, dep.objectType, dep.objectName, depth + 1, config));
|
|
297
|
+
await Promise.allSettled(childPromises);
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* 背压控制:等待队列有空间(✅ 优化:事件驱动替代轮询)
|
|
303
|
+
*/
|
|
304
|
+
async waitForQueueSpace(jobId, maxSize) {
|
|
305
|
+
let waitCount = 0;
|
|
306
|
+
while (true) {
|
|
307
|
+
const queueSize = await this.queueManager.getQueueSize(jobId);
|
|
308
|
+
if (queueSize < maxSize) {
|
|
309
|
+
if (waitCount > 0) {
|
|
310
|
+
console.error(`[背压控制] Job ${jobId} 队列恢复 (${queueSize}/${maxSize}),继续生产`);
|
|
311
|
+
}
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
waitCount++;
|
|
315
|
+
if (waitCount === 1) {
|
|
316
|
+
console.error(`[背压控制] Job ${jobId} 队列已满 (${queueSize}/${maxSize}),等待消费者处理...`);
|
|
317
|
+
}
|
|
318
|
+
// ✅ 使用事件通知替代轮询
|
|
319
|
+
await new Promise((resolve) => {
|
|
320
|
+
let unsubscribe = null;
|
|
321
|
+
let timeout = null;
|
|
322
|
+
let resolved = false;
|
|
323
|
+
const cleanup = async () => {
|
|
324
|
+
if (!resolved) {
|
|
325
|
+
resolved = true;
|
|
326
|
+
if (timeout)
|
|
327
|
+
clearTimeout(timeout);
|
|
328
|
+
if (unsubscribe)
|
|
329
|
+
await unsubscribe();
|
|
330
|
+
resolve();
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
// 订阅队列空间事件
|
|
334
|
+
this.queueManager.subscribeQueueSpace(jobId, () => {
|
|
335
|
+
cleanup().catch(() => { });
|
|
336
|
+
}).then(unsub => {
|
|
337
|
+
unsubscribe = unsub;
|
|
338
|
+
}).catch(() => {
|
|
339
|
+
cleanup().catch(() => { });
|
|
340
|
+
});
|
|
341
|
+
// 设置超时(防止消息丢失)
|
|
342
|
+
timeout = setTimeout(() => {
|
|
343
|
+
cleanup().catch(() => { });
|
|
344
|
+
}, common_1.queueConfig.backpressureCheckInterval);
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* 查询对象
|
|
350
|
+
*/
|
|
351
|
+
async queryObject(objectType, objectName) {
|
|
352
|
+
try {
|
|
353
|
+
const result = await (0, handlers_1.handleZObjectQuery)({
|
|
354
|
+
query_type: objectType,
|
|
355
|
+
query_name: objectName
|
|
356
|
+
});
|
|
357
|
+
if (result && typeof result === 'object' && 'isError' in result && result.isError) {
|
|
358
|
+
const errorText = Array.isArray(result.content) && result.content.length > 0
|
|
359
|
+
? result.content[0].text || '未知错误'
|
|
360
|
+
: '未知错误';
|
|
361
|
+
return { success: false, error: errorText };
|
|
362
|
+
}
|
|
363
|
+
let actualData;
|
|
364
|
+
if (result && typeof result === 'object' && 'content' in result && Array.isArray(result.content)) {
|
|
365
|
+
if (result.content.length > 0 && result.content[0].text) {
|
|
366
|
+
actualData = result.content[0].text;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
if (actualData) {
|
|
370
|
+
return { success: true, data: actualData };
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
return { success: false, error: '无有效数据返回' };
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
catch (error) {
|
|
377
|
+
return {
|
|
378
|
+
success: false,
|
|
379
|
+
error: error instanceof Error ? error.message : String(error)
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* 提取依赖关系
|
|
385
|
+
*/
|
|
386
|
+
extractDependencies(objectData) {
|
|
387
|
+
const dependencies = [];
|
|
388
|
+
// 提取类依赖
|
|
389
|
+
if (objectData.ET_REFCLASS && Array.isArray(objectData.ET_REFCLASS)) {
|
|
390
|
+
for (const cls of objectData.ET_REFCLASS) {
|
|
391
|
+
if (cls.CLSNAME) {
|
|
392
|
+
dependencies.push({
|
|
393
|
+
objectType: 'CLASS',
|
|
394
|
+
objectName: cls.CLSNAME
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
// 提取函数依赖
|
|
400
|
+
if (objectData.ET_REFFUNCTION && Array.isArray(objectData.ET_REFFUNCTION)) {
|
|
401
|
+
for (const func of objectData.ET_REFFUNCTION) {
|
|
402
|
+
// 优先使用 PNAME(程序名),如果不存在则使用 FUNCTIONNAME
|
|
403
|
+
// PNAME 通常是函数组的主程序名,更准确地表示依赖关系
|
|
404
|
+
const dependencyName = func.PNAME || func.FUNCTIONNAME;
|
|
405
|
+
if (dependencyName) {
|
|
406
|
+
dependencies.push({
|
|
407
|
+
objectType: func.PNAME ? 'PROGRAM' : 'FUNCTION',
|
|
408
|
+
objectName: dependencyName
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
// 提取程序依赖
|
|
414
|
+
if (objectData.ET_REFPROGRAM && Array.isArray(objectData.ET_REFPROGRAM)) {
|
|
415
|
+
for (const prog of objectData.ET_REFPROGRAM) {
|
|
416
|
+
if (prog.PROGNAME) {
|
|
417
|
+
dependencies.push({
|
|
418
|
+
objectType: 'PROGRAM',
|
|
419
|
+
objectName: prog.PROGNAME
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return dependencies;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* 准备缓存数据(包含依赖关系)
|
|
428
|
+
*/
|
|
429
|
+
prepareCacheData(objectData) {
|
|
430
|
+
return {
|
|
431
|
+
ET_FUNCTION: objectData.ET_FUNCTION,
|
|
432
|
+
ET_PROGRAM: objectData.ET_PROGRAM,
|
|
433
|
+
ET_CLASSES: objectData.ET_CLASSES,
|
|
434
|
+
// ✅ 缓存依赖关系(用于生成依赖项XML)
|
|
435
|
+
ET_REFCLASS: objectData.ET_REFCLASS,
|
|
436
|
+
ET_REFFUNCTION: objectData.ET_REFFUNCTION,
|
|
437
|
+
ET_REFPROGRAM: objectData.ET_REFPROGRAM
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* 计算任务优先级
|
|
442
|
+
*/
|
|
443
|
+
calculatePriority(depth) {
|
|
444
|
+
// 深度越小,优先级越高(数值越小)
|
|
445
|
+
return depth * 1000 + (Date.now() % 1000);
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* 初始化过滤规则缓存
|
|
449
|
+
* ✅ 优化:预编译所有正则表达式,避免每次检查时重复编译
|
|
450
|
+
* @param config 递归查询配置
|
|
451
|
+
*/
|
|
452
|
+
initializeFilterCache(config) {
|
|
453
|
+
// 合并默认过滤规则和用户自定义过滤规则
|
|
454
|
+
const excludeNames = [
|
|
455
|
+
...(common_1.defaultFilters.excludeNames || []),
|
|
456
|
+
...(config.recursionFilters?.excludeNames || [])
|
|
457
|
+
];
|
|
458
|
+
const excludeNamePatterns = [
|
|
459
|
+
...(common_1.defaultFilters.excludeNamePatterns || []),
|
|
460
|
+
...(config.recursionFilters?.excludeNamePatterns || [])
|
|
461
|
+
];
|
|
462
|
+
// 创建过滤缓存(一次性预编译所有规则)
|
|
463
|
+
this.filterCache = new common_1.FilterCache(excludeNames, excludeNamePatterns);
|
|
464
|
+
const stats = this.filterCache.getStats();
|
|
465
|
+
console.error(`[Producer] 过滤规则已加载: 精确匹配=${stats.exactMatchCount}, 模式匹配=${stats.patternCount}`);
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* 检查对象是否应该被过滤
|
|
469
|
+
* ✅ 优化:使用预编译的 FilterCache,避免每次检查时重复编译正则表达式
|
|
470
|
+
* @param objectType 对象类型
|
|
471
|
+
* @param objectName 对象名称
|
|
472
|
+
* @param config 递归查询配置
|
|
473
|
+
* @returns true 表示应该过滤(不递归查询),false 表示不过滤
|
|
474
|
+
*/
|
|
475
|
+
shouldFilter(objectType, objectName, config) {
|
|
476
|
+
// 如果没有初始化过滤缓存,先初始化
|
|
477
|
+
if (!this.filterCache) {
|
|
478
|
+
this.initializeFilterCache(config);
|
|
479
|
+
}
|
|
480
|
+
// 使用预编译的过滤缓存(性能优化)
|
|
481
|
+
const isFiltered = this.filterCache.matches(objectName);
|
|
482
|
+
if (isFiltered) {
|
|
483
|
+
// 获取匹配的模式(用于日志)
|
|
484
|
+
const matchedPatterns = this.filterCache.getMatchedPatterns(objectName);
|
|
485
|
+
console.error(`[Producer] 过滤规则: 排除 "${objectName}" (${matchedPatterns.join(', ')})`);
|
|
486
|
+
}
|
|
487
|
+
return isFiltered;
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* 延迟函数
|
|
491
|
+
*/
|
|
492
|
+
sleep(ms) {
|
|
493
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
exports.RecursiveQueryProducer = RecursiveQueryProducer;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handleZObjectQuery.d.ts","sourceRoot":"","sources":["../../../src/producer/handlers/handleZObjectQuery.ts"],"names":[],"mappings":"AAEA,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,GAAG;;;;;;GAmBjD"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleZObjectQuery = handleZObjectQuery;
|
|
4
|
+
const utils_1 = require("../../common/utils");
|
|
5
|
+
async function handleZObjectQuery(args) {
|
|
6
|
+
try {
|
|
7
|
+
if (!args?.query_type) {
|
|
8
|
+
console.error('query_type:', args);
|
|
9
|
+
throw new utils_1.McpError(utils_1.ErrorCode.InvalidParams, 'Query type is required');
|
|
10
|
+
}
|
|
11
|
+
if (!args?.query_name) {
|
|
12
|
+
console.error('query_name:', args);
|
|
13
|
+
throw new utils_1.McpError(utils_1.ErrorCode.InvalidParams, 'Query name is required');
|
|
14
|
+
}
|
|
15
|
+
const encodedQueryType = encodeURIComponent(args.query_type);
|
|
16
|
+
const encodedQueryName = encodeURIComponent(args.query_name);
|
|
17
|
+
const url = `${await (0, utils_1.getBaseUrl)()}/sap/bc/z_object_query?type=${encodedQueryType}&name=${encodedQueryName}`;
|
|
18
|
+
const response = await (0, utils_1.makeAdtRequest)(url, 'GET', 100000);
|
|
19
|
+
return (0, utils_1.return_response)(response);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
return (0, utils_1.return_error)(error);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/producer/handlers/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* 生产者使用的 Handlers
|
|
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
18
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
19
|
+
};
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
__exportStar(require("./handleZObjectQuery"), exports);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/producer/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,0BAA0B,CAAC;AACzC,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
18
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
19
|
+
};
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
__exportStar(require("./RecursiveQueryProducer"), exports);
|
|
22
|
+
__exportStar(require("./handlers"), exports);
|