@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 824000672@qq.com
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# AI Build Download - Redis 队列系统
|
|
2
|
+
|
|
3
|
+
基于 Redis 队列 + 生产者-消费者模式的 ABAP 递归查询下载系统。
|
|
4
|
+
|
|
5
|
+
## 🎯 核心特性
|
|
6
|
+
|
|
7
|
+
- ✅ **高性能**: 查询次数减少50%,速度提升3-10倍,过滤规则性能提升20倍
|
|
8
|
+
- ✅ **可扩展**: 支持水平扩展,100+并发消费者,优化的连接管理
|
|
9
|
+
- ✅ **可靠性**: 任务持久化,自动重试,断点续传
|
|
10
|
+
- ✅ **零硬编码**: 通用设计,适用于任何 SAP GUI 程序
|
|
11
|
+
- ✅ **资源优化**: Redis连接减少99%,内存占用大幅降低
|
|
12
|
+
|
|
13
|
+
## 📁 项目结构
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
src/
|
|
17
|
+
├── common/ # 通用组件(类型、配置、队列管理、工具函数)
|
|
18
|
+
├── producer/ # 生产者(并发递归查询,内存队列)
|
|
19
|
+
├── consumer/ # 消费者(文件下载,Redis队列)
|
|
20
|
+
└── cli/ # CLI 工具
|
|
21
|
+
├── query-object.ts # 单体模式(推荐)
|
|
22
|
+
├── producer-cli.ts # 只启动生产者
|
|
23
|
+
└── consumer-cli.ts # 只启动消费者
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## 🚀 快速开始
|
|
27
|
+
|
|
28
|
+
### 1. 环境准备
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# 安装 Redis
|
|
32
|
+
docker run -d --name redis -p 6379:6379 redis:latest
|
|
33
|
+
|
|
34
|
+
# 安装依赖
|
|
35
|
+
pnpm install
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 2. 配置环境变量
|
|
39
|
+
|
|
40
|
+
复制 `env.example` 到 `.env` 并修改:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
cp env.example .env
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
最少配置(`.env`):
|
|
47
|
+
|
|
48
|
+
```env
|
|
49
|
+
# SAP 连接(必需)
|
|
50
|
+
SAP_URL=https://your-sap-system.com:8000
|
|
51
|
+
SAP_USERNAME=your_username
|
|
52
|
+
SAP_PASSWORD=your_password
|
|
53
|
+
SAP_CLIENT=100
|
|
54
|
+
|
|
55
|
+
# Redis 连接
|
|
56
|
+
REDIS_HOST=localhost
|
|
57
|
+
REDIS_PORT=6379
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
更多配置选项请参考 `env.example` 文件。
|
|
61
|
+
|
|
62
|
+
### 3. 使用
|
|
63
|
+
|
|
64
|
+
#### 方式一:单体模式(推荐)
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pnpm build
|
|
68
|
+
pnpm query -- PROGRAM SAPLMEGUI -d 3
|
|
69
|
+
|
|
70
|
+
# 带过滤规则
|
|
71
|
+
pnpm query -- CLASS CL_MY_CLASS -d 5 -e Z_OLD -x "*_TEST"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
#### 方式二:分离模式(高性能)
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# 终端1:启动生产者
|
|
78
|
+
pnpm build
|
|
79
|
+
pnpm producer -- PROGRAM SAPLMEGUI -d 3
|
|
80
|
+
# 输出: Job ID: abc-123-def
|
|
81
|
+
|
|
82
|
+
# 终端2:启动消费者
|
|
83
|
+
pnpm consumer -- --job-id abc-123-def -c 50
|
|
84
|
+
|
|
85
|
+
# 或启动多个消费者进程实现水平扩展
|
|
86
|
+
pnpm consumer -- -c 50 # 进程1
|
|
87
|
+
pnpm consumer -- -c 50 # 进程2
|
|
88
|
+
pnpm consumer -- -c 50 # 进程3
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## 🏗️ 架构说明
|
|
92
|
+
|
|
93
|
+
### 工作流程
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
用户请求
|
|
97
|
+
↓
|
|
98
|
+
Producer(生产者)
|
|
99
|
+
├─ 并发递归查询(10并发,内存队列)
|
|
100
|
+
├─ 提取依赖关系
|
|
101
|
+
├─ 缓存对象数据(gzip压缩)
|
|
102
|
+
└─ 生成任务 → Redis 队列
|
|
103
|
+
↓
|
|
104
|
+
Consumers(消费者集群)
|
|
105
|
+
├─ 从 Redis 拉取任务
|
|
106
|
+
├─ 读取缓存数据
|
|
107
|
+
├─ 下载源代码
|
|
108
|
+
└─ 处理附加数据(Include、结构体、屏幕等)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 核心组件
|
|
112
|
+
|
|
113
|
+
- **common/RedisQueueManager**: 队列和缓存管理
|
|
114
|
+
- **producer/RecursiveQueryProducer**: 递归查询(内存队列)
|
|
115
|
+
- **consumer/FileDownloadConsumer**: 文件下载(Redis队列)
|
|
116
|
+
|
|
117
|
+
## 📊 性能对比
|
|
118
|
+
|
|
119
|
+
| 指标 | 旧实现 | 新实现(10消费者) | 新实现(150消费者) | 提升 |
|
|
120
|
+
|------|--------|------------------|-------------------|------|
|
|
121
|
+
| 150对象耗时 | 22.5秒 | 7.5秒 | 2秒 | 最高10倍 |
|
|
122
|
+
| 查询次数 | 300次 | 150次 | 150次 | 减少50% |
|
|
123
|
+
| 并发能力 | 20 | 100+ | 100+ | 5倍+ |
|
|
124
|
+
|
|
125
|
+
## 📦 API 使用
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import {
|
|
129
|
+
RedisQueueManager,
|
|
130
|
+
RecursiveQueryProducer,
|
|
131
|
+
FileDownloadConsumer
|
|
132
|
+
} from '@qwe8652591/ai-build-download';
|
|
133
|
+
|
|
134
|
+
// 创建组件
|
|
135
|
+
const queueManager = new RedisQueueManager();
|
|
136
|
+
const producer = new RecursiveQueryProducer(queueManager);
|
|
137
|
+
|
|
138
|
+
// 提交查询
|
|
139
|
+
const jobId = await producer.submitQuery('PROGRAM', 'SAPLMEGUI', config);
|
|
140
|
+
|
|
141
|
+
// 启动消费者
|
|
142
|
+
const consumer = new FileDownloadConsumer(queueManager, './downloads', [jobId]);
|
|
143
|
+
await consumer.start([jobId]);
|
|
144
|
+
|
|
145
|
+
// 等待完成
|
|
146
|
+
await producer.waitForCompletion(jobId);
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## ⚙️ 环境变量
|
|
150
|
+
|
|
151
|
+
| 变量 | 默认值 | 说明 |
|
|
152
|
+
|------|--------|------|
|
|
153
|
+
| `REDIS_HOST` | localhost | Redis 服务器地址 |
|
|
154
|
+
| `REDIS_PORT` | 6379 | Redis 端口 |
|
|
155
|
+
| `PRODUCER_CONCURRENCY` | 10 | 生产者并发查询数 |
|
|
156
|
+
| `CONCURRENT_CONSUMERS` | 10 | 默认消费者数量 |
|
|
157
|
+
| `QUEUE_MAX_SIZE` | 40 | 队列背压阈值 |
|
|
158
|
+
| `MAX_RETRIES` | 3 | 最大重试次数 |
|
|
159
|
+
| `CACHE_EXPIRY` | 1800 | 缓存过期时间(秒) |
|
|
160
|
+
| `ENABLE_COMPRESSION` | true | 启用 gzip 压缩 |
|
|
161
|
+
|
|
162
|
+
## 🎮 CLI 命令
|
|
163
|
+
|
|
164
|
+
### 单体模式
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
# 基本查询
|
|
168
|
+
pnpm query -- PROGRAM SAPLMEGUI
|
|
169
|
+
|
|
170
|
+
# 带参数
|
|
171
|
+
pnpm query -- -t CLASS -n CL_MY_CLASS -d 5 -c 20
|
|
172
|
+
|
|
173
|
+
# 带过滤规则
|
|
174
|
+
pnpm query -- PROGRAM Z_PROG -e Z_OLD -x "*_TEST" -x "*_TMP"
|
|
175
|
+
|
|
176
|
+
# 完整帮助
|
|
177
|
+
pnpm query -- --help
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### 分离模式
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
# 生产者
|
|
184
|
+
pnpm producer -- PROGRAM SAPLMEGUI -d 3
|
|
185
|
+
|
|
186
|
+
# 消费者(处理特定 Job)
|
|
187
|
+
pnpm consumer -- --job-id <job-id> -c 50
|
|
188
|
+
|
|
189
|
+
# 消费者(处理所有活跃 Job)
|
|
190
|
+
pnpm consumer -- -c 20
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## 🐛 故障排查
|
|
194
|
+
|
|
195
|
+
### Redis 连接失败
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
# 检查 Redis 是否运行
|
|
199
|
+
redis-cli ping
|
|
200
|
+
# 应该返回: PONG
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### 查看队列状态
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
redis-cli
|
|
207
|
+
|
|
208
|
+
# 查看所有 Job
|
|
209
|
+
ZRANGE abap:job:index 0 -1
|
|
210
|
+
|
|
211
|
+
# 查看队列大小
|
|
212
|
+
GET abap:queue:size:<job-id>
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## 📚 技术文档
|
|
216
|
+
|
|
217
|
+
详细的技术设计和实现说明请参考:
|
|
218
|
+
- [技术设计文档](../../docs/RECURSIVE_QUERY_REFACTOR_TECHNICAL_DESIGN.md)
|
|
219
|
+
- [优化总结](./OPTIMIZATION_SUMMARY.md) - 最新的性能优化详情
|
|
220
|
+
|
|
221
|
+
## 📄 License
|
|
222
|
+
|
|
223
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consumer-cli.d.ts","sourceRoot":"","sources":["../../src/cli/consumer-cli.ts"],"names":[],"mappings":";AAEA;;;;GAIG"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* 消费者 CLI 工具
|
|
5
|
+
* 只负责从 Redis 队列拉取任务并下载文件
|
|
6
|
+
* 可以独立部署,支持水平扩展
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
// ⚠️ 重要:必须在所有其他导入之前加载环境变量
|
|
43
|
+
const dotenv = __importStar(require("dotenv"));
|
|
44
|
+
const path_1 = require("path");
|
|
45
|
+
const envPath = (0, path_1.join)(__dirname, '../../.env');
|
|
46
|
+
dotenv.config({ path: envPath });
|
|
47
|
+
// 加载环境变量后再导入其他模块
|
|
48
|
+
const common_1 = require("../common");
|
|
49
|
+
const consumer_1 = require("../consumer");
|
|
50
|
+
/**
|
|
51
|
+
* 解析命令行参数
|
|
52
|
+
*/
|
|
53
|
+
function parseArgs() {
|
|
54
|
+
const args = process.argv.slice(2);
|
|
55
|
+
let jobId;
|
|
56
|
+
let consumerCount = parseInt(process.env.CONCURRENT_CONSUMERS || '10');
|
|
57
|
+
let downloadPath = './downloads';
|
|
58
|
+
let organizeByProgram = true; // 默认按程序分目录
|
|
59
|
+
for (let i = 0; i < args.length; i++) {
|
|
60
|
+
const arg = args[i];
|
|
61
|
+
switch (arg) {
|
|
62
|
+
case '--job-id':
|
|
63
|
+
case '-j':
|
|
64
|
+
jobId = args[++i];
|
|
65
|
+
break;
|
|
66
|
+
case '--consumers':
|
|
67
|
+
case '-c':
|
|
68
|
+
consumerCount = parseInt(args[++i]) || 10;
|
|
69
|
+
break;
|
|
70
|
+
case '--download-path':
|
|
71
|
+
case '-p':
|
|
72
|
+
downloadPath = args[++i] || './downloads';
|
|
73
|
+
break;
|
|
74
|
+
case '--organize-by-program':
|
|
75
|
+
case '-o':
|
|
76
|
+
organizeByProgram = args[++i]?.toLowerCase() !== 'false';
|
|
77
|
+
break;
|
|
78
|
+
case '--help':
|
|
79
|
+
case '-h':
|
|
80
|
+
showHelp();
|
|
81
|
+
process.exit(0);
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return { jobId, consumerCount, downloadPath, organizeByProgram };
|
|
86
|
+
}
|
|
87
|
+
function showHelp() {
|
|
88
|
+
console.error(`
|
|
89
|
+
ABAP 递归查询工具 - 消费者模式
|
|
90
|
+
|
|
91
|
+
用法:
|
|
92
|
+
npm run consumer -- [选项]
|
|
93
|
+
|
|
94
|
+
参数:
|
|
95
|
+
--job-id, -j 指定 Job ID(可选,不指定则处理所有活跃 Job)
|
|
96
|
+
--consumers, -c 消费者数量(默认:10)
|
|
97
|
+
--download-path, -p 下载路径(默认:./downloads)
|
|
98
|
+
--organize-by-program, -o 是否按程序分目录(默认:true)
|
|
99
|
+
true: downloads/PROGRAM_NAME/...
|
|
100
|
+
false: downloads/...
|
|
101
|
+
--help, -h 显示帮助
|
|
102
|
+
|
|
103
|
+
示例:
|
|
104
|
+
# 处理特定 Job
|
|
105
|
+
npm run consumer -- --job-id abc-123-def
|
|
106
|
+
|
|
107
|
+
# 处理所有活跃 Job(自动发现)
|
|
108
|
+
npm run consumer -- -c 20
|
|
109
|
+
|
|
110
|
+
# 不按程序分目录(所有文件在同一目录下)
|
|
111
|
+
npm run consumer -- -o false
|
|
112
|
+
|
|
113
|
+
# 水平扩展:启动多个消费者进程
|
|
114
|
+
npm run consumer -- -c 50 # 进程1
|
|
115
|
+
npm run consumer -- -c 50 # 进程2
|
|
116
|
+
|
|
117
|
+
环境变量:
|
|
118
|
+
REDIS_HOST=localhost, REDIS_PORT=6379
|
|
119
|
+
CONCURRENT_CONSUMERS=10(默认消费者数量)
|
|
120
|
+
|
|
121
|
+
说明:
|
|
122
|
+
此工具从 Redis 队列拉取任务并下载文件。
|
|
123
|
+
可以启动多个消费者进程实现水平扩展。
|
|
124
|
+
`);
|
|
125
|
+
}
|
|
126
|
+
async function main() {
|
|
127
|
+
console.error('ABAP 递归查询工具 - 消费者模式\n');
|
|
128
|
+
const args = parseArgs();
|
|
129
|
+
console.error(`🔧 消费者启动`);
|
|
130
|
+
console.error(`消费者数量: ${args.consumerCount}`);
|
|
131
|
+
console.error(`下载路径: ${args.downloadPath}`);
|
|
132
|
+
console.error(`按程序分目录: ${args.organizeByProgram ? '是' : '否'}`);
|
|
133
|
+
if (args.jobId) {
|
|
134
|
+
console.error(`目标 Job: ${args.jobId}`);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
console.error(`模式: 处理所有活跃 Job`);
|
|
138
|
+
}
|
|
139
|
+
console.error('');
|
|
140
|
+
// 创建队列管理器
|
|
141
|
+
const queueManager = new common_1.RedisQueueManager();
|
|
142
|
+
// 获取要处理的 Job 列表
|
|
143
|
+
const jobIds = args.jobId ? [args.jobId] : await queueManager.getActiveJobIds();
|
|
144
|
+
if (jobIds.length === 0) {
|
|
145
|
+
console.error('⚠️ 没有找到活跃的 Job,等待任务...');
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
console.error(`📋 找到 ${jobIds.length} 个活跃 Job: ${jobIds.join(', ')}\n`);
|
|
149
|
+
}
|
|
150
|
+
// 启动消费者
|
|
151
|
+
const consumers = [];
|
|
152
|
+
const consumerPromises = [];
|
|
153
|
+
for (let i = 0; i < args.consumerCount; i++) {
|
|
154
|
+
const consumer = new consumer_1.FileDownloadConsumer(queueManager, args.downloadPath, jobIds.length > 0 ? jobIds : undefined, args.organizeByProgram);
|
|
155
|
+
consumers.push(consumer);
|
|
156
|
+
consumerPromises.push(consumer.start(jobIds.length > 0 ? jobIds : undefined).catch(err => {
|
|
157
|
+
console.error(`消费者 ${i + 1} 错误:`, err);
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
console.error(`✅ ${args.consumerCount} 个消费者已启动,正在处理任务...\n`);
|
|
161
|
+
console.error('按 Ctrl+C 停止\n');
|
|
162
|
+
// 监听退出信号
|
|
163
|
+
process.on('SIGINT', async () => {
|
|
164
|
+
console.error('\n\n⏹️ 收到停止信号,正在关闭消费者...');
|
|
165
|
+
for (const consumer of consumers) {
|
|
166
|
+
await consumer.stop();
|
|
167
|
+
}
|
|
168
|
+
await queueManager.close();
|
|
169
|
+
console.error('✅ 消费者已停止');
|
|
170
|
+
process.exit(0);
|
|
171
|
+
});
|
|
172
|
+
// 等待所有消费者完成(或用户中断)
|
|
173
|
+
await Promise.allSettled(consumerPromises);
|
|
174
|
+
}
|
|
175
|
+
if (require.main === module) {
|
|
176
|
+
main().catch(error => {
|
|
177
|
+
console.error('未捕获的错误:', error);
|
|
178
|
+
process.exit(1);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"producer-cli.d.ts","sourceRoot":"","sources":["../../src/cli/producer-cli.ts"],"names":[],"mappings":";AAEA;;;;GAIG"}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* 生产者 CLI 工具
|
|
5
|
+
* 只负责递归查询并生成任务,推送到 Redis 队列
|
|
6
|
+
* 可以独立部署
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
// ⚠️ 重要:必须在所有其他导入之前加载环境变量
|
|
43
|
+
const dotenv = __importStar(require("dotenv"));
|
|
44
|
+
const path_1 = require("path");
|
|
45
|
+
const envPath = (0, path_1.join)(__dirname, '../../.env');
|
|
46
|
+
dotenv.config({ path: envPath });
|
|
47
|
+
// 加载环境变量后再导入其他模块
|
|
48
|
+
const uuid_1 = require("uuid");
|
|
49
|
+
const common_1 = require("../common");
|
|
50
|
+
const producer_1 = require("../producer");
|
|
51
|
+
/**
|
|
52
|
+
* 解析命令行参数
|
|
53
|
+
*/
|
|
54
|
+
function parseArgs() {
|
|
55
|
+
const args = process.argv.slice(2);
|
|
56
|
+
let queryType = '';
|
|
57
|
+
let queryName = '';
|
|
58
|
+
let maxDepth = 3;
|
|
59
|
+
let jobId = undefined;
|
|
60
|
+
for (let i = 0; i < args.length; i++) {
|
|
61
|
+
const arg = args[i];
|
|
62
|
+
switch (arg) {
|
|
63
|
+
case '--type':
|
|
64
|
+
case '-t':
|
|
65
|
+
i++;
|
|
66
|
+
if (i >= args.length) {
|
|
67
|
+
console.error(`错误:${arg} 参数缺少值`);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
queryType = args[i];
|
|
71
|
+
break;
|
|
72
|
+
case '--name':
|
|
73
|
+
case '-n':
|
|
74
|
+
i++;
|
|
75
|
+
if (i >= args.length) {
|
|
76
|
+
console.error(`错误:${arg} 参数缺少值`);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
queryName = args[i];
|
|
80
|
+
break;
|
|
81
|
+
case '--max-depth':
|
|
82
|
+
case '-d':
|
|
83
|
+
i++;
|
|
84
|
+
if (i >= args.length) {
|
|
85
|
+
console.error(`错误:${arg} 参数缺少值`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
const depth = parseInt(args[i]);
|
|
89
|
+
if (isNaN(depth) || depth < 0) {
|
|
90
|
+
console.error(`错误:无效的深度值 "${args[i]}",必须是非负整数(0 表示只查询根对象)`);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
maxDepth = depth;
|
|
94
|
+
break;
|
|
95
|
+
case '--job-id':
|
|
96
|
+
case '-j':
|
|
97
|
+
jobId = args[++i];
|
|
98
|
+
break;
|
|
99
|
+
case '--help':
|
|
100
|
+
case '-h':
|
|
101
|
+
showHelp();
|
|
102
|
+
process.exit(0);
|
|
103
|
+
break;
|
|
104
|
+
default:
|
|
105
|
+
if (!arg.startsWith('-') && !queryType) {
|
|
106
|
+
queryType = arg;
|
|
107
|
+
}
|
|
108
|
+
else if (!arg.startsWith('-') && !queryName) {
|
|
109
|
+
queryName = arg;
|
|
110
|
+
}
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (!queryType || !queryName) {
|
|
115
|
+
console.error('错误:查询类型和查询名称都是必需的参数');
|
|
116
|
+
showHelp();
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
return { queryType, queryName, maxDepth, jobId };
|
|
120
|
+
}
|
|
121
|
+
function showHelp() {
|
|
122
|
+
console.error(`
|
|
123
|
+
ABAP 递归查询工具 - 生产者模式
|
|
124
|
+
|
|
125
|
+
用法:
|
|
126
|
+
npm run producer -- <类型> <名称> [选项]
|
|
127
|
+
|
|
128
|
+
参数:
|
|
129
|
+
--type, -t 对象类型(PROGRAM/CLASS/FUNCTION)
|
|
130
|
+
--name, -n 对象名称
|
|
131
|
+
--max-depth, -d 最大递归深度(默认:3,0 表示只查询根对象)
|
|
132
|
+
--job-id, -j 指定 Job ID(可选,不指定则自动生成)
|
|
133
|
+
--help, -h 显示帮助
|
|
134
|
+
|
|
135
|
+
示例:
|
|
136
|
+
npm run producer -- PROGRAM SAPLMEGUI
|
|
137
|
+
npm run producer -- -t CLASS -n CL_MY_CLASS -d 5
|
|
138
|
+
npm run producer -- FUGR Z_MY_FUNCTION_GROUP -d 2
|
|
139
|
+
npm run producer -- -t PROGRAM -n SAPLMEGUI -d 0 # 只查询根对象
|
|
140
|
+
npm run producer -- -t PROGRAM -n SAPLMEGUI -j my-custom-job-id
|
|
141
|
+
|
|
142
|
+
环境变量:
|
|
143
|
+
SAP_URL, SAP_USERNAME, SAP_PASSWORD, SAP_CLIENT
|
|
144
|
+
REDIS_HOST=localhost, REDIS_PORT=6379
|
|
145
|
+
PRODUCER_CONCURRENCY=10
|
|
146
|
+
|
|
147
|
+
说明:
|
|
148
|
+
此工具只负责递归查询并生成任务,推送到 Redis 队列。
|
|
149
|
+
需要单独启动消费者(consumer-cli)来下载文件。
|
|
150
|
+
|
|
151
|
+
使用流程:
|
|
152
|
+
1. 运行生产者,获取 Job ID(或指定自定义 Job ID)
|
|
153
|
+
2. 使用 Job ID 启动消费者
|
|
154
|
+
3. 消费者处理所有任务
|
|
155
|
+
`);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* 格式化时间(毫秒转为易读格式)
|
|
159
|
+
*/
|
|
160
|
+
function formatDuration(ms) {
|
|
161
|
+
const seconds = Math.floor(ms / 1000);
|
|
162
|
+
const minutes = Math.floor(seconds / 60);
|
|
163
|
+
const hours = Math.floor(minutes / 60);
|
|
164
|
+
if (hours > 0) {
|
|
165
|
+
const remainingMinutes = minutes % 60;
|
|
166
|
+
const remainingSeconds = seconds % 60;
|
|
167
|
+
return `${hours}小时 ${remainingMinutes}分钟 ${remainingSeconds}秒`;
|
|
168
|
+
}
|
|
169
|
+
else if (minutes > 0) {
|
|
170
|
+
const remainingSeconds = seconds % 60;
|
|
171
|
+
return `${minutes}分钟 ${remainingSeconds}秒`;
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
return `${seconds}秒`;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async function main() {
|
|
178
|
+
// 记录开始时间
|
|
179
|
+
const startTime = Date.now();
|
|
180
|
+
console.error('ABAP 递归查询工具 - 生产者模式\n');
|
|
181
|
+
const args = parseArgs();
|
|
182
|
+
console.error(`🏭 生产者启动`);
|
|
183
|
+
console.error(`查询对象: ${args.queryType}/${args.queryName}`);
|
|
184
|
+
console.error(`最大深度: ${args.maxDepth}`);
|
|
185
|
+
if (args.jobId) {
|
|
186
|
+
console.error(`Job ID: ${args.jobId}`);
|
|
187
|
+
}
|
|
188
|
+
console.error(`⏱️ 开始时间: ${new Date().toLocaleString('zh-CN')}`);
|
|
189
|
+
console.error('');
|
|
190
|
+
const config = {
|
|
191
|
+
maxRecursionDepth: args.maxDepth,
|
|
192
|
+
downloadRootPath: './downloads',
|
|
193
|
+
downloadIncludes: true,
|
|
194
|
+
enableCircularReferenceCheck: true,
|
|
195
|
+
concurrentRequestLimit: parseInt(process.env.PRODUCER_CONCURRENCY || '10'),
|
|
196
|
+
requestInterval: 100
|
|
197
|
+
};
|
|
198
|
+
// 创建队列管理器
|
|
199
|
+
const queueManager = new common_1.RedisQueueManager();
|
|
200
|
+
// 创建生产者
|
|
201
|
+
const producer = new producer_1.RecursiveQueryProducer(queueManager);
|
|
202
|
+
try {
|
|
203
|
+
// 创建新 Job(使用指定的 jobId 或生成新的)
|
|
204
|
+
const jobId = args.jobId || (0, uuid_1.v4)();
|
|
205
|
+
console.error(`📋 创建新 Job...`);
|
|
206
|
+
if (args.jobId) {
|
|
207
|
+
console.error(` 使用指定的 Job ID: ${jobId}`);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
console.error(` 生成的 Job ID: ${jobId}`);
|
|
211
|
+
}
|
|
212
|
+
await producer.createJob(jobId, args.queryType.toUpperCase(), args.queryName.toUpperCase(), config);
|
|
213
|
+
console.error(`✅ Job 已创建: ${jobId}\n`);
|
|
214
|
+
// 启动递归查询(阻塞模式,等待完成)
|
|
215
|
+
console.error(`🔄 开始递归查询...\n`);
|
|
216
|
+
const queryStartTime = Date.now();
|
|
217
|
+
await producer.generateAllTasks(jobId, args.queryType.toUpperCase(), args.queryName.toUpperCase(), config);
|
|
218
|
+
// 计算总耗时
|
|
219
|
+
const totalDuration = Date.now() - startTime;
|
|
220
|
+
const queryDuration = Date.now() - queryStartTime;
|
|
221
|
+
console.error(`\n✅ 任务生成完成!`);
|
|
222
|
+
console.error(`📝 Job ID: ${jobId}`);
|
|
223
|
+
console.error(`⏱️ 结束时间: ${new Date().toLocaleString('zh-CN')}`);
|
|
224
|
+
console.error(`⏱️ 查询耗时: ${formatDuration(queryDuration)} (${queryDuration}ms)`);
|
|
225
|
+
console.error(`⏱️ 总计耗时: ${formatDuration(totalDuration)} (${totalDuration}ms)`);
|
|
226
|
+
console.error(`\n请启动消费者来处理任务:`);
|
|
227
|
+
console.error(` npm run consumer -- --job-id ${jobId}\n`);
|
|
228
|
+
// 显示 Job 信息
|
|
229
|
+
const job = await queueManager.getJob(jobId);
|
|
230
|
+
if (job) {
|
|
231
|
+
console.error(`📊 任务统计:`);
|
|
232
|
+
console.error(` 总任务数: ${job.totalTasks}`);
|
|
233
|
+
console.error(` 待处理: ${job.totalTasks - job.completedTasks - job.failedTasks - job.skippedTasks}`);
|
|
234
|
+
}
|
|
235
|
+
await queueManager.close();
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
const totalDuration = Date.now() - startTime;
|
|
239
|
+
console.error(`\n❌ 执行失败(耗时: ${formatDuration(totalDuration)}):`, error);
|
|
240
|
+
await queueManager.close();
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (require.main === module) {
|
|
245
|
+
main().catch(error => {
|
|
246
|
+
console.error('未捕获的错误:', error);
|
|
247
|
+
process.exit(1);
|
|
248
|
+
});
|
|
249
|
+
}
|