sks-maximo-utils 1.0.5 → 1.0.7
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/CLI.md +226 -0
- package/README.md +19 -3
- package/cli.js +230 -5
- package/index.js +2 -2
- package/nodeutils/fileUtils.js +62 -1
- package/nodeutils/logger.js +15 -3
- package/nodeutils/maximoTools.js +208 -8
- package/nodeutils/requestHttp.js +70 -7
- package/package.json +5 -2
package/CLI.md
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# CLI 命令使用说明
|
|
2
|
+
|
|
3
|
+
## 命令概览
|
|
4
|
+
|
|
5
|
+
| 命令 | 说明 |
|
|
6
|
+
|------|------|
|
|
7
|
+
| `sks-maximo` | 检查配置文件并打开配置目录 |
|
|
8
|
+
| `sks-maximo <env>` | 加载指定环境配置 |
|
|
9
|
+
| `sks-maximo call <scriptName>` | 调用通用脚本接口 |
|
|
10
|
+
| `sks-maximo import-object <fileName>` | 导入 Maximo 对象配置 |
|
|
11
|
+
| `sks-maximo import-appinfo <fileName>` | 导入应用信息配置 |
|
|
12
|
+
| `sks-maximo import-presentation <fileName>` | 导入应用XML配置 |
|
|
13
|
+
| `sks-maximo import-script <fileName>` | 导入自动化脚本 |
|
|
14
|
+
| `sks-maximo import-domain <fileName>` | 导入域配置 |
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 1. 打开配置目录
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
sks-maximo
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**功能:**
|
|
25
|
+
- 检查 `~/.sks/nodeutils/config.json` 是否存在
|
|
26
|
+
- 不存在则复制 `sample.config.json` 创建
|
|
27
|
+
- 自动打开配置文件所在目录
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 2. 加载环境配置
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
sks-maximo local # 使用本地环境配置
|
|
35
|
+
sks-maximo dev # 使用开发环境配置
|
|
36
|
+
sks-maximo hd # 使用测试环境配置
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**功能:**
|
|
40
|
+
- 加载指定环境的配置到全局
|
|
41
|
+
- 后续所有 API 调用使用该环境的 baseUrl、apiKey 等配置
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 3. 导入命令
|
|
46
|
+
|
|
47
|
+
所有导入命令均支持 `-e` / `--env` 指定环境和 `-l` / `--logname` 指定日志名。
|
|
48
|
+
|
|
49
|
+
### 3.1 导入对象配置
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
sks-maximo import-object <fileName> [-e env] [-l logname]
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**说明:** 导入 Maximo 对象配置(JSON/XML)
|
|
56
|
+
|
|
57
|
+
| 参数 | 说明 | 必填 |
|
|
58
|
+
|------|------|------|
|
|
59
|
+
| `<fileName>` | JSON 或 XML 配置文件路径 | 是 |
|
|
60
|
+
| `-e` / `--env` | 指定环境(默认 local) | 否 |
|
|
61
|
+
| `-l` / `--logname` | 日志打印名称 | 否 |
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
### 3.2 导入应用信息
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
sks-maximo import-appinfo <fileName> [-e env] [-l logname]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**说明:** 导入 Maximo 应用信息配置(JSON)
|
|
72
|
+
|
|
73
|
+
| 参数 | 说明 | 必填 |
|
|
74
|
+
|------|------|------|
|
|
75
|
+
| `<fileName>` | 应用信息 JSON 文件路径 | 是 |
|
|
76
|
+
| `-e` / `--env` | 指定环境(默认 local) | 否 |
|
|
77
|
+
| `-l` / `--logname` | 日志打印名称 | 否 |
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
### 3.3 导入应用XML配置
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
sks-maximo import-presentation <fileName> [-e env] [-l logname]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**说明:** 导入 Maximo 应用 XML 配置
|
|
88
|
+
|
|
89
|
+
| 参数 | 说明 | 必填 |
|
|
90
|
+
|------|------|------|
|
|
91
|
+
| `<fileName>` | XML 配置文件路径 | 是 |
|
|
92
|
+
| `-e` / `--env` | 指定环境(默认 local) | 否 |
|
|
93
|
+
| `-l` / `--logname` | 日志打印名称 | 否 |
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
### 3.4 导入自动化脚本
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
sks-maximo import-script <fileName> [-e env] [-l logname]
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**说明:** 导入 Maximo 自动化脚本。`<fileName>` 可以是 JSON 配置文件路径,也可以是 `.js`/`.py` 脚本文件路径(会自动找同目录下的同名 JSON 配置文件)
|
|
104
|
+
|
|
105
|
+
| 参数 | 说明 | 必填 |
|
|
106
|
+
|------|------|------|
|
|
107
|
+
| `<fileName>` | JSON 配置或脚本文件路径 | 是 |
|
|
108
|
+
| `-e` / `--env` | 指定环境(默认 local) | 否 |
|
|
109
|
+
| `-l` / `--logname` | 日志打印名称 | 否 |
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
### 3.5 导入域配置
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
sks-maximo import-domain <fileName> [-e env] [-l logname]
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**说明:** 导入 Maximo 域配置
|
|
120
|
+
|
|
121
|
+
| 参数 | 说明 | 必填 |
|
|
122
|
+
|------|------|------|
|
|
123
|
+
| `<fileName>` | 域 JSON 文件路径 | 是 |
|
|
124
|
+
| `-e` / `--env` | 指定环境(默认 local) | 否 |
|
|
125
|
+
| `-l` / `--logname` | 日志打印名称 | 否 |
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## 4. 调用通用脚本接口
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
sks-maximo call <scriptName> [--file <fileName>] [--params <params>] [--logname <logname>]
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**参数说明:**
|
|
136
|
+
|
|
137
|
+
| 参数 | 简写 | 说明 | 必填 |
|
|
138
|
+
|------|------|------|------|
|
|
139
|
+
| `<scriptName>` | - | Maximo 脚本接口名称 | 是 |
|
|
140
|
+
| `--file` | `-f` | 请求体文件路径(JSON/XML等) | 否 |
|
|
141
|
+
| `--params` | `-p` | URL 查询参数(JSON格式) | 否 |
|
|
142
|
+
| `--logname` | `-l` | 日志名称 | 否 |
|
|
143
|
+
|
|
144
|
+
### 4.1 GET 请求(无请求体)
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
sks-maximo call SHARPTREE.AUTOSCRIPT.LIBRARY --params '{"name":"TEST"}'
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### 4.2 POST 请求(带请求体文件)
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
# JSON 请求体
|
|
154
|
+
sks-maximo call SKS_DEPLOY_AUTOKEY --file data/autokey.json
|
|
155
|
+
|
|
156
|
+
# XML 请求体
|
|
157
|
+
sks-maximo call SHARPTREE.AUTOSCRIPT.SCREENS --file screens/test.xml
|
|
158
|
+
|
|
159
|
+
# 同时带参数和请求体
|
|
160
|
+
sks-maximo call SKS_IMP_MAXAPPINFO --file appinfo.json --params '{"debug":true}' --logname "导入应用信息"
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### 4.3 请求体文件处理
|
|
164
|
+
|
|
165
|
+
- **`.json` 文件**:自动解析为 JSON 对象作为请求体
|
|
166
|
+
- **其他文件**:读取文件内容作为字符串请求体
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## 配置文件说明
|
|
171
|
+
|
|
172
|
+
配置文件位置:`~/.sks/nodeutils/config.json`
|
|
173
|
+
|
|
174
|
+
```json
|
|
175
|
+
{
|
|
176
|
+
"logLevel": "INFO",
|
|
177
|
+
"langcode": "zh",
|
|
178
|
+
"envs": {
|
|
179
|
+
"local": {
|
|
180
|
+
"baseUrl": "http://127.0.0.1:9080/maximo",
|
|
181
|
+
"apiKey": "your-api-key",
|
|
182
|
+
"maxauth": "",
|
|
183
|
+
"authType": "apiKey",
|
|
184
|
+
"langcode": "zh"
|
|
185
|
+
},
|
|
186
|
+
"dev": {
|
|
187
|
+
"baseUrl": "https://mdev/maximo",
|
|
188
|
+
"apiKey": "your-api-key",
|
|
189
|
+
"maxauth": "",
|
|
190
|
+
"authType": "apiKey"
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## 示例
|
|
199
|
+
|
|
200
|
+
### 示例 1:查询脚本库
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
sks-maximo call SHARPTREE.AUTOSCRIPT.LIBRARY --params '{"name":"MY_SCRIPT"}'
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### 示例 2:部署自动编码
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
sks-maximo call SKS_DEPLOY_AUTOKEY --file autokey/test.json --logname "部署自动编码"
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### 示例 3:导入应用信息
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
sks-maximo call SKS_IMP_MAXAPPINFO --file appinfo/APP001.json
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## 帮助信息
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
sks-maximo --help # 显示帮助
|
|
224
|
+
sks-maximo -h # 显示帮助
|
|
225
|
+
sks-maximo -v # 显示版本号
|
|
226
|
+
```
|
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ npm install -g shoukaiseki-maximo-utils
|
|
|
11
11
|
|
|
12
12
|
### 项目中安装(引用使用)
|
|
13
13
|
```bash
|
|
14
|
-
npm install
|
|
14
|
+
npm install sks-maximo-utils
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
## 使用方式
|
|
@@ -40,9 +40,11 @@ import {
|
|
|
40
40
|
importMaxAutoKey,
|
|
41
41
|
importMaxScript,
|
|
42
42
|
importMaxAppInfo,
|
|
43
|
+
callMaxScript,
|
|
44
|
+
fileUtils,
|
|
43
45
|
loadConfig,
|
|
44
46
|
logger
|
|
45
|
-
} from '
|
|
47
|
+
} from 'sks-maximo-utils';
|
|
46
48
|
|
|
47
49
|
// 加载配置(可选,默认 local)
|
|
48
50
|
loadConfig('local');
|
|
@@ -66,6 +68,16 @@ importMaxScript({ fileName: "scripts/TEST.json" });
|
|
|
66
68
|
|
|
67
69
|
// 导入应用信息配置
|
|
68
70
|
importMaxAppInfo({ fileName: "MAXAPPINFO/appinfo.json", logname: "应用信息" });
|
|
71
|
+
|
|
72
|
+
// 通用脚本接口调用(GET)
|
|
73
|
+
callMaxScript({ apiScriptName: "SHARPTREE.AUTOSCRIPT.LIBRARY", params: { name: "test" }, logname: "查询脚本库" });
|
|
74
|
+
|
|
75
|
+
// 通用脚本接口调用(POST,读取文件内容作为请求体)
|
|
76
|
+
callMaxScript({ apiScriptName: "SKS_DEPLOY_AUTOKEY", fileName: "data/autokey.json", logname: "部署自动编码" });
|
|
77
|
+
|
|
78
|
+
// 文件工具
|
|
79
|
+
fileUtils.listFiles('scripts', '.json');
|
|
80
|
+
fileUtils.readFileContent('test.txt');
|
|
69
81
|
```
|
|
70
82
|
|
|
71
83
|
## 配置文件
|
|
@@ -126,11 +138,15 @@ importMaxAppInfo({ fileName: "MAXAPPINFO/appinfo.json", logname: "应用信息"
|
|
|
126
138
|
|
|
127
139
|
| 方法 | 说明 |
|
|
128
140
|
|------|------|
|
|
141
|
+
| callMaxScript | 通用脚本接口调用(GET/POST) |
|
|
129
142
|
| loadConfig(env) | 加载指定环境配置 |
|
|
130
143
|
| saveScriptHistory(options) | 保存脚本历史记录 |
|
|
131
144
|
| readFileContent(fileName) | 读取文件内容 |
|
|
132
145
|
| readJsonFile(fileName) | 读取并解析 JSON 文件 |
|
|
133
146
|
| fileExists(fileName) | 检查文件是否存在 |
|
|
147
|
+
| fileUtils.listFiles(dirName, extFilter, recursive) | 列出目录文件 |
|
|
148
|
+
| fileUtils.readFileContent(fileName) | 读取文件内容 |
|
|
149
|
+
| fileUtils.readJsonFile(fileName) | 读取并解析 JSON 文件 |
|
|
134
150
|
|
|
135
151
|
### 日志文件
|
|
136
152
|
|
|
@@ -148,7 +164,7 @@ npm install
|
|
|
148
164
|
npm link
|
|
149
165
|
|
|
150
166
|
# 在其他项目中链接测试
|
|
151
|
-
npm link
|
|
167
|
+
npm link sks-maximo-utils
|
|
152
168
|
```
|
|
153
169
|
|
|
154
170
|
## License
|
package/cli.js
CHANGED
|
@@ -1,29 +1,67 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { exec } from 'child_process';
|
|
3
|
+
import { readFileSync } from 'fs';
|
|
4
|
+
import { fileURLToPath, URL } from 'url';
|
|
3
5
|
import { checkAndCreateConfig, getConfigDir, loadConfig } from './nodeutils/config.js';
|
|
6
|
+
import { callMaxScript, importMaxObject, importMaxPresentation, importMaxDomain, importMaxAutoKey, importMaxScript, importMaxAppInfo } from './nodeutils/maximoTools.js';
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = new URL('.', import.meta.url).pathname;
|
|
10
|
+
const packageJson = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf-8'));
|
|
4
11
|
|
|
5
12
|
const args = process.argv.slice(2);
|
|
6
|
-
const env = args[0];
|
|
7
13
|
|
|
8
|
-
|
|
9
|
-
|
|
14
|
+
// 解析全局选项(--env)
|
|
15
|
+
let globalEnv = 'local';
|
|
16
|
+
const filteredArgs = [];
|
|
17
|
+
for (let i = 0; i < args.length; i++) {
|
|
18
|
+
if (args[i] === '--env' || args[i] === '-e') {
|
|
19
|
+
globalEnv = args[i + 1];
|
|
20
|
+
i++; // 跳过值
|
|
21
|
+
} else {
|
|
22
|
+
filteredArgs.push(args[i]);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const cmd = filteredArgs[0];
|
|
27
|
+
const env = filteredArgs[0];
|
|
28
|
+
|
|
29
|
+
if (!cmd || ['-h', '--help', '-?', '--version', '-v'].includes(cmd)) {
|
|
30
|
+
console.log(`sks-maximo v${packageJson.version}`);
|
|
10
31
|
console.log('');
|
|
11
32
|
console.log('用法:');
|
|
12
33
|
console.log(' sks-maximo # 检查配置文件并打开配置目录');
|
|
13
34
|
console.log(' sks-maximo <env> # 加载指定环境配置');
|
|
35
|
+
console.log(' sks-maximo call <scriptName> [options] # 调用脚本接口');
|
|
36
|
+
console.log(' sks-maximo import-object <fileName> # 导入 Maximo 对象配置');
|
|
37
|
+
console.log(' sks-maximo import-appinfo <fileName> # 导入应用信息配置');
|
|
38
|
+
console.log(' sks-maximo import-presentation <fileName> # 导入应用XML配置');
|
|
39
|
+
console.log(' sks-maximo import-script <fileName> # 导入自动化脚本');
|
|
40
|
+
console.log(' sks-maximo import-domain <fileName> # 导入域配置');
|
|
14
41
|
console.log('');
|
|
15
42
|
console.log('环境:');
|
|
16
43
|
console.log(' local # 使用本地环境配置');
|
|
17
44
|
console.log(' dev # 使用开发环境配置');
|
|
18
45
|
console.log(' hd # 使用测试环境配置');
|
|
19
46
|
console.log('');
|
|
47
|
+
console.log('命令:');
|
|
48
|
+
console.log(' call # 调用通用脚本接口');
|
|
49
|
+
console.log(' import-object # 导入 Maximo 对象配置');
|
|
50
|
+
console.log(' import-appinfo # 导入应用信息配置');
|
|
51
|
+
console.log(' import-presentation # 导入应用XML配置');
|
|
52
|
+
console.log(' import-script # 导入自动化脚本');
|
|
53
|
+
console.log(' import-domain # 导入域配置');
|
|
54
|
+
console.log('');
|
|
20
55
|
console.log('选项:');
|
|
21
56
|
console.log(' -h, --help # 显示帮助信息');
|
|
22
57
|
console.log(' -v, --version # 显示版本信息');
|
|
58
|
+
console.log(' --env, -e <env> # 指定环境(默认 local)');
|
|
59
|
+
console.log(' --file <fileName> # 请求体文件路径(JSON)');
|
|
60
|
+
console.log(' --params <params> # URL参数(JSON格式)');
|
|
23
61
|
console.log('');
|
|
24
62
|
console.log('配置文件位置: ~/.sks/nodeutils/config.json');
|
|
25
63
|
|
|
26
|
-
if (!
|
|
64
|
+
if (!cmd) {
|
|
27
65
|
const configPath = checkAndCreateConfig();
|
|
28
66
|
const configDir = getConfigDir();
|
|
29
67
|
|
|
@@ -48,8 +86,195 @@ if (!env || ['-h', '--help', '-?', '--version', '-v'].includes(env)) {
|
|
|
48
86
|
}
|
|
49
87
|
});
|
|
50
88
|
}
|
|
89
|
+
} else if (cmd === 'call') {
|
|
90
|
+
const scriptName = args[1];
|
|
91
|
+
|
|
92
|
+
if (!scriptName) {
|
|
93
|
+
console.error('错误: 请指定脚本名称');
|
|
94
|
+
console.log('用法: sks-maximo call <scriptName> [--file <fileName>] [--params <params>]');
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let fileName = null;
|
|
99
|
+
let params = {};
|
|
100
|
+
let logname = null;
|
|
101
|
+
|
|
102
|
+
for (let i = 2; i < args.length; i++) {
|
|
103
|
+
if (args[i] === '--file' || args[i] === '-f') {
|
|
104
|
+
fileName = args[i + 1];
|
|
105
|
+
i++;
|
|
106
|
+
} else if (args[i] === '--params' || args[i] === '-p') {
|
|
107
|
+
try {
|
|
108
|
+
params = JSON.parse(args[i + 1]);
|
|
109
|
+
} catch (e) {
|
|
110
|
+
console.error(`参数解析失败: ${e.message}`);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
i++;
|
|
114
|
+
} else if (args[i] === '--logname' || args[i] === '-l') {
|
|
115
|
+
logname = args[i + 1];
|
|
116
|
+
i++;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
loadConfig(globalEnv);
|
|
121
|
+
|
|
122
|
+
(async () => {
|
|
123
|
+
try {
|
|
124
|
+
const response = await callMaxScript({ apiScriptName: scriptName, fileName, params, logname });
|
|
125
|
+
if (response) {
|
|
126
|
+
console.log('响应:', JSON.stringify(response, null, 2));
|
|
127
|
+
} else {
|
|
128
|
+
console.error('调用失败');
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error('调用出错:', error.message);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
})();
|
|
136
|
+
} else if (cmd === 'import-object') {
|
|
137
|
+
const fileName = filteredArgs[1];
|
|
138
|
+
if (!fileName) {
|
|
139
|
+
console.error('错误: 请指定 JSON 文件路径');
|
|
140
|
+
console.log('用法: sks-maximo import-object <fileName> [-e env]');
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
loadConfig(globalEnv);
|
|
144
|
+
(async () => {
|
|
145
|
+
try {
|
|
146
|
+
const result = await importMaxObject({ fileName, logname: fileName });
|
|
147
|
+
if (result) {
|
|
148
|
+
console.log('导入成功');
|
|
149
|
+
} else {
|
|
150
|
+
console.error('导入失败');
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.error('导入出错:', error.message);
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
})();
|
|
158
|
+
} else if (cmd === 'import-appinfo') {
|
|
159
|
+
const fileName = filteredArgs[1];
|
|
160
|
+
if (!fileName) {
|
|
161
|
+
console.error('错误: 请指定 JSON 文件路径');
|
|
162
|
+
console.log('用法: sks-maximo import-appinfo <fileName> [-e env] [-l logname]');
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
let logname = null;
|
|
166
|
+
for (let i = 2; i < filteredArgs.length; i++) {
|
|
167
|
+
if (filteredArgs[i] === '--logname' || filteredArgs[i] === '-l') {
|
|
168
|
+
logname = filteredArgs[i + 1];
|
|
169
|
+
i++;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
loadConfig(globalEnv);
|
|
173
|
+
(async () => {
|
|
174
|
+
try {
|
|
175
|
+
const result = await importMaxAppInfo({ fileName, logname: logname || fileName });
|
|
176
|
+
if (result) {
|
|
177
|
+
console.log('导入成功');
|
|
178
|
+
} else {
|
|
179
|
+
console.error('导入失败');
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
} catch (error) {
|
|
183
|
+
console.error('导入出错:', error.message);
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
})();
|
|
187
|
+
} else if (cmd === 'import-presentation') {
|
|
188
|
+
const fileName = filteredArgs[1];
|
|
189
|
+
if (!fileName) {
|
|
190
|
+
console.error('错误: 请指定 XML 文件路径');
|
|
191
|
+
console.log('用法: sks-maximo import-presentation <fileName> [-e env] [-l logname]');
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
let logname = null;
|
|
195
|
+
for (let i = 2; i < filteredArgs.length; i++) {
|
|
196
|
+
if (filteredArgs[i] === '--logname' || filteredArgs[i] === '-l') {
|
|
197
|
+
logname = filteredArgs[i + 1];
|
|
198
|
+
i++;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
loadConfig(globalEnv);
|
|
202
|
+
(async () => {
|
|
203
|
+
try {
|
|
204
|
+
const result = await importMaxPresentation({ fileName, logname: logname || fileName });
|
|
205
|
+
if (result) {
|
|
206
|
+
console.log('导入成功');
|
|
207
|
+
} else {
|
|
208
|
+
console.error('导入失败');
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.error('导入出错:', error.message);
|
|
213
|
+
process.exit(1);
|
|
214
|
+
}
|
|
215
|
+
})();
|
|
216
|
+
} else if (cmd === 'import-script') {
|
|
217
|
+
const fileName = filteredArgs[1];
|
|
218
|
+
if (!fileName) {
|
|
219
|
+
console.error('错误: 请指定脚本 JSON 配置文件路径');
|
|
220
|
+
console.log('用法: sks-maximo import-script <fileName> [-e env] [-l logname]');
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
let logname = null;
|
|
224
|
+
for (let i = 2; i < filteredArgs.length; i++) {
|
|
225
|
+
if (filteredArgs[i] === '--logname' || filteredArgs[i] === '-l') {
|
|
226
|
+
logname = filteredArgs[i + 1];
|
|
227
|
+
i++;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
loadConfig(globalEnv);
|
|
231
|
+
(async () => {
|
|
232
|
+
try {
|
|
233
|
+
const result = await importMaxScript({ fileName, logname: logname || fileName });
|
|
234
|
+
if (result) {
|
|
235
|
+
console.log('导入成功');
|
|
236
|
+
} else {
|
|
237
|
+
console.error('导入失败');
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
} catch (error) {
|
|
241
|
+
console.error('导入出错:', error.message);
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
})();
|
|
245
|
+
} else if (cmd === 'import-domain') {
|
|
246
|
+
const fileName = filteredArgs[1];
|
|
247
|
+
if (!fileName) {
|
|
248
|
+
console.error('错误: 请指定域 JSON 文件路径');
|
|
249
|
+
console.log('用法: sks-maximo import-domain <fileName> [-e env] [-l logname]');
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
let logname = null;
|
|
253
|
+
for (let i = 2; i < filteredArgs.length; i++) {
|
|
254
|
+
if (filteredArgs[i] === '--logname' || filteredArgs[i] === '-l') {
|
|
255
|
+
logname = filteredArgs[i + 1];
|
|
256
|
+
i++;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
loadConfig(globalEnv);
|
|
260
|
+
(async () => {
|
|
261
|
+
try {
|
|
262
|
+
const result = await importMaxDomain({ fileName, logname: logname || fileName });
|
|
263
|
+
if (result) {
|
|
264
|
+
console.log('导入成功');
|
|
265
|
+
} else {
|
|
266
|
+
console.error('导入失败');
|
|
267
|
+
process.exit(1);
|
|
268
|
+
}
|
|
269
|
+
} catch (error) {
|
|
270
|
+
console.error('导入出错:', error.message);
|
|
271
|
+
process.exit(1);
|
|
272
|
+
}
|
|
273
|
+
})();
|
|
51
274
|
} else {
|
|
52
|
-
|
|
275
|
+
console.error(`不支持的命令: ${cmd}`);
|
|
276
|
+
console.log('使用 sks-maximo --help 查看帮助信息');
|
|
277
|
+
process.exit(1);
|
|
53
278
|
}
|
|
54
279
|
|
|
55
280
|
export * from './nodeutils/maximoTools.js';
|
package/index.js
CHANGED
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
export { loadConfig, getConfig, getWorkDir } from './nodeutils/config.js';
|
|
3
3
|
export { default as logger } from './nodeutils/logger.js';
|
|
4
4
|
export { default as request } from './nodeutils/requestHttp.js';
|
|
5
|
-
export
|
|
6
|
-
export { importMaxObject, importMaxPresentation, importMaxDomain, importMaxAutoKey, importMaxScript, saveScriptHistory, importMaxAppInfo } from './nodeutils/maximoTools.js';
|
|
5
|
+
export * as fileUtils from './nodeutils/fileUtils.js';
|
|
6
|
+
export { importMaxObject, importMaxPresentation, importMaxDomain, importMaxAutoKey, importMaxScript, saveScriptHistory, importMaxAppInfo, callMaxScript, batchImport, printBatchStats } from './nodeutils/maximoTools.js';
|
|
7
7
|
export { sendMockData, sendMockDataBatch, sendPOMockData, sendPOMockDataBatch, sendRlImportMockDataBatch } from './nodeutils/mockdataTools.js';
|
package/nodeutils/fileUtils.js
CHANGED
|
@@ -88,9 +88,70 @@ function fileExists(fileNameIn) {
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
/**
|
|
92
|
+
* 通用文件路径转换 - 将各种形式的文件路径转换为规范化后的实际可用路径
|
|
93
|
+
* @param {string} fileName - 文件名或路径(支持相对路径、绝对路径)
|
|
94
|
+
* @returns {string} 规范化后的绝对路径
|
|
95
|
+
*/
|
|
96
|
+
function resolveFilePath(fileName) {
|
|
97
|
+
if (!fileName) return '';
|
|
98
|
+
// 统一分隔符
|
|
99
|
+
fileName = fileName.replace(/\\/g, '/');
|
|
100
|
+
// 绝对路径直接规范化返回
|
|
101
|
+
if (path.isAbsolute(fileName)) {
|
|
102
|
+
return path.normalize(fileName);
|
|
103
|
+
}
|
|
104
|
+
// 相对路径拼接工作目录
|
|
105
|
+
return path.normalize(path.join(getWorkDir(), fileName));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 列出目录中的文件
|
|
110
|
+
* @param {string} dirName - 目录名(相对或绝对路径)
|
|
111
|
+
* @param {string} [extFilter] - 文件扩展名过滤,如 '.json'、'.js',不传则返回所有文件
|
|
112
|
+
* @param {boolean} [recursive=true] - 是否递归检索子目录
|
|
113
|
+
* @returns {string[]} 文件全路径数组
|
|
114
|
+
*/
|
|
115
|
+
function listFiles(dirName, extFilter, recursive = true) {
|
|
116
|
+
const dirPath = path.isAbsolute(dirName.replace(/\\/g, '/')) ? dirName : path.join(getWorkDir(), dirName);
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
if (!fs.existsSync(dirPath)) {
|
|
120
|
+
console.error(`目录不存在: ${dirPath}`);
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const results = [];
|
|
125
|
+
|
|
126
|
+
function scanDir(currentPath, baseRelative) {
|
|
127
|
+
const entries = fs.readdirSync(currentPath, { withFileTypes: true });
|
|
128
|
+
for (const entry of entries) {
|
|
129
|
+
const fullPath = path.join(currentPath, entry.name);
|
|
130
|
+
const relativePath = baseRelative ? path.join(baseRelative, entry.name) : entry.name;
|
|
131
|
+
|
|
132
|
+
if (entry.isFile()) {
|
|
133
|
+
if (!extFilter || entry.name.endsWith(extFilter)) {
|
|
134
|
+
results.push(fullPath);
|
|
135
|
+
}
|
|
136
|
+
} else if (entry.isDirectory() && recursive) {
|
|
137
|
+
scanDir(fullPath, relativePath);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
scanDir(dirPath, '');
|
|
143
|
+
return results;
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.error(`列出目录文件失败: ${dirName}`, error.message);
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
91
150
|
export {
|
|
92
151
|
readFileContent,
|
|
93
152
|
readJsonFile,
|
|
94
153
|
readXmlFile,
|
|
95
|
-
fileExists
|
|
154
|
+
fileExists,
|
|
155
|
+
listFiles,
|
|
156
|
+
resolveFilePath
|
|
96
157
|
};
|
package/nodeutils/logger.js
CHANGED
|
@@ -8,6 +8,17 @@ const LEVELS = {
|
|
|
8
8
|
FATAL: 5
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
// 颜色定义
|
|
12
|
+
const COLORS = {
|
|
13
|
+
TRACE: '\x1b[90m', // 灰色
|
|
14
|
+
DEBUG: '\x1b[36m', // 青色
|
|
15
|
+
INFO: '\x1b[32m', // 绿色
|
|
16
|
+
WARN: '\x1b[33m', // 黄色
|
|
17
|
+
ERROR: '\x1b[31m', // 红色
|
|
18
|
+
FATAL: '\x1b[1;31m' // 粗体红
|
|
19
|
+
};
|
|
20
|
+
const RESET = '\x1b[0m';
|
|
21
|
+
|
|
11
22
|
class ConsoleLogger {
|
|
12
23
|
constructor(level = 'INFO') {
|
|
13
24
|
this.level = LEVELS[level] !== undefined ? LEVELS[level] : LEVELS.INFO;
|
|
@@ -27,7 +38,8 @@ class ConsoleLogger {
|
|
|
27
38
|
if (levelValue < this.level) return;
|
|
28
39
|
|
|
29
40
|
const timestamp = new Date().toLocaleTimeString();
|
|
30
|
-
const
|
|
41
|
+
const color = COLORS[levelName] || '';
|
|
42
|
+
const prefix = `${color}[${timestamp}] [${levelName}]${RESET}`;
|
|
31
43
|
|
|
32
44
|
switch (levelValue) {
|
|
33
45
|
case LEVELS.TRACE:
|
|
@@ -36,11 +48,11 @@ class ConsoleLogger {
|
|
|
36
48
|
console.log(prefix, ...args);
|
|
37
49
|
break;
|
|
38
50
|
case LEVELS.WARN:
|
|
39
|
-
console.warn(prefix, ...args);
|
|
51
|
+
console.warn(prefix, ...args);
|
|
40
52
|
break;
|
|
41
53
|
case LEVELS.ERROR:
|
|
42
54
|
case LEVELS.FATAL:
|
|
43
|
-
console.error(prefix, ...args);
|
|
55
|
+
console.error(prefix, ...args);
|
|
44
56
|
break;
|
|
45
57
|
}
|
|
46
58
|
}
|
package/nodeutils/maximoTools.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import request from '../nodeutils/requestHttp.js'
|
|
2
|
-
import { readFileContent, readJsonFile, readXmlFile } from './fileUtils.js'
|
|
2
|
+
import { readFileContent, readJsonFile, readXmlFile, resolveFilePath, listFiles } from './fileUtils.js'
|
|
3
3
|
import logger from './logger.js';
|
|
4
4
|
import { getConfig } from './config.js';
|
|
5
5
|
import os from 'os'
|
|
6
|
+
import path from 'path'
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* 导入Maximo对象配置
|
|
@@ -177,7 +178,7 @@ export async function importMaxDomain({fileName, logname}) {
|
|
|
177
178
|
|
|
178
179
|
logger.warn(`[${logname}]收到响应`);
|
|
179
180
|
logger.warn(`[${logname}]响应类型:`, typeof response);
|
|
180
|
-
logger.
|
|
181
|
+
logger.warn(`[${logname}]响应内容:`, response);
|
|
181
182
|
|
|
182
183
|
// 检查响应 - requestHttp的响应拦截器已返回res.data
|
|
183
184
|
if (response !== null && response !== undefined) {
|
|
@@ -255,10 +256,23 @@ export async function saveScriptHistory({autoscript, source, version = '', alias
|
|
|
255
256
|
*/
|
|
256
257
|
export async function importMaxScript({fileName, logname}) {
|
|
257
258
|
try {
|
|
258
|
-
|
|
259
|
+
// 使用通用路径转换方法解析路径
|
|
260
|
+
const resolvedFilePath = resolveFilePath(fileName);
|
|
261
|
+
const fileExt = path.extname(resolvedFilePath).toLowerCase();
|
|
262
|
+
|
|
263
|
+
// 如果不是JSON文件(如直接传入了.js/.py脚本文件路径),自动找同目录下的同名JSON配置文件
|
|
264
|
+
let jsonFilePath = resolvedFilePath;
|
|
265
|
+
if (fileExt !== '.json') {
|
|
266
|
+
const dir = path.dirname(resolvedFilePath);
|
|
267
|
+
const baseName = path.basename(resolvedFilePath, fileExt);
|
|
268
|
+
jsonFilePath = path.join(dir, `${baseName}.json`);
|
|
269
|
+
logger.info(`[导入脚本] 传入的是脚本文件,尝试查找配置文件: ${jsonFilePath}`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const jsonContent = readJsonFile(jsonFilePath);
|
|
259
273
|
|
|
260
274
|
if (!jsonContent) {
|
|
261
|
-
logger.error(
|
|
275
|
+
logger.error(`配置文件读取失败: ${jsonFilePath}`);
|
|
262
276
|
return false;
|
|
263
277
|
}
|
|
264
278
|
|
|
@@ -275,12 +289,13 @@ export async function importMaxScript({fileName, logname}) {
|
|
|
275
289
|
const scriptExt = isPython ? '.py' : '.js';
|
|
276
290
|
|
|
277
291
|
const actualLogname = logname || autoscript;
|
|
278
|
-
logger.info(`[${actualLogname}]成功读取配置文件: ${
|
|
292
|
+
logger.info(`[${actualLogname}]成功读取配置文件: ${jsonFilePath}`);
|
|
279
293
|
logger.warn(`[${actualLogname}]脚本名称: ${autoscript}`);
|
|
280
294
|
logger.warn(`[${actualLogname}]脚本语言: ${scriptLanguage}`);
|
|
281
295
|
|
|
282
|
-
|
|
283
|
-
const
|
|
296
|
+
// 脚本文件路径:在配置文件同目录下,按 autoscript 名称查找
|
|
297
|
+
const dirname = path.dirname(jsonFilePath);
|
|
298
|
+
const scriptFileName = path.join(dirname, `${autoscript}${scriptExt}`);
|
|
284
299
|
|
|
285
300
|
const fileContent = readFileContent(scriptFileName);
|
|
286
301
|
|
|
@@ -607,4 +622,189 @@ export async function importMaxAppInfo({fileName, logname}) {
|
|
|
607
622
|
}
|
|
608
623
|
return false
|
|
609
624
|
}
|
|
610
|
-
}
|
|
625
|
+
}
|
|
626
|
+
export async function callMaxScript({ apiScriptName, fileName, params = {}, logname }) {
|
|
627
|
+
if (!apiScriptName) {
|
|
628
|
+
logger.error('[callMaxScript]脚本名称不能为空')
|
|
629
|
+
return false
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const actualLogname = logname || apiScriptName
|
|
633
|
+
|
|
634
|
+
try {
|
|
635
|
+
logger.info(`[${actualLogname}]开始调用脚本: ${apiScriptName}`)
|
|
636
|
+
|
|
637
|
+
let data = null
|
|
638
|
+
if (fileName) {
|
|
639
|
+
const fileContent = readFileContent(fileName)
|
|
640
|
+
if (!fileContent) {
|
|
641
|
+
logger.error(`[${actualLogname}]文件读取失败: ${fileName}`)
|
|
642
|
+
return false
|
|
643
|
+
}
|
|
644
|
+
logger.info(`[${actualLogname}]已读取请求体文件: ${fileName}`)
|
|
645
|
+
|
|
646
|
+
if (fileName.endsWith('.json')) {
|
|
647
|
+
try {
|
|
648
|
+
data = JSON.parse(fileContent)
|
|
649
|
+
} catch (e) {
|
|
650
|
+
logger.error(`[${actualLogname}]JSON解析失败: ${e.message}`)
|
|
651
|
+
return false
|
|
652
|
+
}
|
|
653
|
+
} else {
|
|
654
|
+
data = fileContent
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const requestConfig = {
|
|
659
|
+
url: `/api/script/${apiScriptName}`,
|
|
660
|
+
method: data ? 'POST' : 'GET',
|
|
661
|
+
params: {
|
|
662
|
+
...params,
|
|
663
|
+
_langcode: getConfig().langcode,
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
if (data !== null) {
|
|
668
|
+
requestConfig.data = data
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
const response = await request(requestConfig)
|
|
672
|
+
|
|
673
|
+
logger.info(`[${actualLogname}]收到响应`)
|
|
674
|
+
logger.warn(`[${actualLogname}]响应内容:`, response)
|
|
675
|
+
|
|
676
|
+
if (response !== null && response !== undefined) {
|
|
677
|
+
logger.info(`[${actualLogname}]调用成功`)
|
|
678
|
+
return response
|
|
679
|
+
} else {
|
|
680
|
+
logger.error(`[${actualLogname}]调用失败,响应为空或null`)
|
|
681
|
+
return null
|
|
682
|
+
}
|
|
683
|
+
} catch (error) {
|
|
684
|
+
logger.error(`[${actualLogname}]请求出错:`, error.message)
|
|
685
|
+
if (error.response) {
|
|
686
|
+
logger.error(`[${actualLogname}]服务器返回错误:`, error.response.data)
|
|
687
|
+
} else if (error.request) {
|
|
688
|
+
logger.error(`[${actualLogname}]请求已发出但无响应`)
|
|
689
|
+
} else {
|
|
690
|
+
logger.error(`[${actualLogname}]错误详情:`, error)
|
|
691
|
+
}
|
|
692
|
+
return null
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* 通用批量导入 - 支持各种导入功能(对象、脚本、应用信息、域、应用XML等)的批量处理
|
|
698
|
+
* @param {Object} options - 配置选项
|
|
699
|
+
* @param {string} [options.dirName] - 目录名(与files二选一),自动列出目录下符合条件的文件
|
|
700
|
+
* @param {string[]} [options.files] - 文件路径数组(与dirName二选一),直接指定要导入的文件
|
|
701
|
+
* @param {string} [options.extFilter] - 扩展名过滤,如 '.json'、'.xml'(配合dirName使用)
|
|
702
|
+
* @param {boolean} [options.recursive=true] - 是否递归检索子目录(配合dirName使用)
|
|
703
|
+
* @param {Function} options.importFn - 导入函数,接收 {fileName, logname} 参数,如 importMaxObject、importMaxScript 等
|
|
704
|
+
* @param {string} [options.logname] - 日志打印名称
|
|
705
|
+
* @param {number} [options.concurrency=1] - 并发数量,默认1串行执行
|
|
706
|
+
* @param {boolean} [options.continueOnError=true] - 单个文件失败时是否继续,默认继续
|
|
707
|
+
* @returns {Promise<Object>} 批量导入统计结果
|
|
708
|
+
*/
|
|
709
|
+
export async function batchImport({ dirName, files, extFilter, recursive = true, importFn, logname = '批量导入', concurrency = 1, continueOnError = true }) {
|
|
710
|
+
if (typeof importFn !== 'function') {
|
|
711
|
+
logger.error('[batchImport]必须指定importFn导入函数')
|
|
712
|
+
return null;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
let fileNames = [];
|
|
716
|
+
if (files && Array.isArray(files) && files.length > 0) {
|
|
717
|
+
fileNames = files;
|
|
718
|
+
} else if (dirName) {
|
|
719
|
+
fileNames = listFiles(dirName, extFilter, recursive);
|
|
720
|
+
} else {
|
|
721
|
+
logger.error('[batchImport]必须指定dirName或files');
|
|
722
|
+
return null;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
if (fileNames.length === 0) {
|
|
726
|
+
logger.warn('[batchImport]没有找到需要导入的文件');
|
|
727
|
+
return null;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
const stats = {
|
|
731
|
+
fileCount: fileNames.length,
|
|
732
|
+
successCount: 0,
|
|
733
|
+
failCount: 0,
|
|
734
|
+
successList: [],
|
|
735
|
+
failList: [],
|
|
736
|
+
startTime: new Date().getTime(),
|
|
737
|
+
endTime: null,
|
|
738
|
+
totalTime: null
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
logger.warn(`[${logname}]开始批量导入,共 ${fileNames.length} 个文件,并发数: ${concurrency}`);
|
|
742
|
+
|
|
743
|
+
// 并发池处理
|
|
744
|
+
let index = 0;
|
|
745
|
+
const worker = async () => {
|
|
746
|
+
while (index < fileNames.length) {
|
|
747
|
+
const cur = index++;
|
|
748
|
+
const filename = fileNames[cur];
|
|
749
|
+
try {
|
|
750
|
+
const result = await importFn({ fileName: filename, logname });
|
|
751
|
+
if (result) {
|
|
752
|
+
stats.successCount++;
|
|
753
|
+
stats.successList.push(filename);
|
|
754
|
+
} else {
|
|
755
|
+
stats.failCount++;
|
|
756
|
+
stats.failList.push(filename);
|
|
757
|
+
}
|
|
758
|
+
} catch (error) {
|
|
759
|
+
stats.failCount++;
|
|
760
|
+
stats.failList.push(filename);
|
|
761
|
+
logger.error(`[${logname}]导入异常: ${filename}`, error.message);
|
|
762
|
+
if (!continueOnError) {
|
|
763
|
+
throw error;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
logger.warn(`[${logname}]进度: ${cur + 1}/${fileNames.length} 成功: ${stats.successCount} 失败: ${stats.failCount}`);
|
|
767
|
+
}
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
const workerCount = Math.max(1, Math.min(concurrency, fileNames.length));
|
|
771
|
+
await Promise.all(Array.from({ length: workerCount }, () => worker()));
|
|
772
|
+
|
|
773
|
+
stats.endTime = new Date().getTime();
|
|
774
|
+
stats.totalTime = stats.endTime - stats.startTime;
|
|
775
|
+
|
|
776
|
+
logger.warn(`[${logname}]批量导入完成,耗时: ${stats.totalTime}ms,成功: ${stats.successCount},失败: ${stats.failCount}`);
|
|
777
|
+
if (stats.failList.length > 0) {
|
|
778
|
+
logger.warn(`[${logname}]失败文件列表:`);
|
|
779
|
+
stats.failList.forEach(f => logger.warn(` ${f}`));
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
return stats;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* 打印批量导入统计结果
|
|
787
|
+
* @param {Object} stats - batchImport 返回的统计结果
|
|
788
|
+
* @param {string} [logname] - 日志打印名称
|
|
789
|
+
*/
|
|
790
|
+
export function printBatchStats(stats, logname = '批量导入') {
|
|
791
|
+
if (!stats) {
|
|
792
|
+
logger.warn(`[${logname}]无统计结果可打印`);
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
const formatDateTime = (timestamp) => {
|
|
796
|
+
const d = new Date(timestamp);
|
|
797
|
+
const pad = n => String(n).padStart(2, '0');
|
|
798
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
799
|
+
};
|
|
800
|
+
logger.warn(`[${logname}]========== 批量导入统计 ==========`);
|
|
801
|
+
logger.warn(`[${logname}]总文件数: ${stats.fileCount}`);
|
|
802
|
+
logger.warn(`[${logname}]成功数量: ${stats.successCount}`);
|
|
803
|
+
logger.warn(`[${logname}]失败数量: ${stats.failCount}`);
|
|
804
|
+
logger.warn(`[${logname}]开始时间: ${formatDateTime(stats.startTime)} 结束时间: ${formatDateTime(stats.endTime)} 耗时: ${stats.totalTime}ms`);
|
|
805
|
+
logger.warn(`[${logname}]成功文件列表:`);
|
|
806
|
+
stats.successList.forEach(f => logger.warn(` ${f}`));
|
|
807
|
+
logger.warn(`[${logname}]失败文件列表:`);
|
|
808
|
+
stats.failList.forEach(f => logger.warn(` ${f}`));
|
|
809
|
+
logger.warn(`[${logname}]====================================`);
|
|
810
|
+
}
|
package/nodeutils/requestHttp.js
CHANGED
|
@@ -27,6 +27,10 @@ function limitLogOutput(data, maxLength = 200) {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
function joinUrl(base, path) {
|
|
31
|
+
return `${base.replace(/\/$/, '')}/${path.replace(/^\//, '')}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
30
34
|
function getLogDir() {
|
|
31
35
|
return path.join(os.homedir(), '.sks', 'nodeutils', 'logs');
|
|
32
36
|
}
|
|
@@ -39,7 +43,7 @@ function saveRequestLog(config, globalConfig) {
|
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
const method = config.method?.toUpperCase() || 'GET';
|
|
42
|
-
let url =
|
|
46
|
+
let url = joinUrl(globalConfig.baseUrl, config.url);
|
|
43
47
|
|
|
44
48
|
if (config.params && Object.keys(config.params).length > 0) {
|
|
45
49
|
const paramsStr = Object.entries(config.params)
|
|
@@ -79,6 +83,44 @@ function saveRequestLog(config, globalConfig) {
|
|
|
79
83
|
}
|
|
80
84
|
}
|
|
81
85
|
|
|
86
|
+
function saveErrorLog(error) {
|
|
87
|
+
try {
|
|
88
|
+
const logDir = getLogDir();
|
|
89
|
+
if (!fs.existsSync(logDir)) {
|
|
90
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
94
|
+
const method = error.config?.method?.toUpperCase() || 'GET';
|
|
95
|
+
const fullUrl = error.config ? joinUrl(getConfig().baseUrl, error.config.url) : 'unknown';
|
|
96
|
+
|
|
97
|
+
const errorLog = {
|
|
98
|
+
timestamp: timestamp,
|
|
99
|
+
method: method,
|
|
100
|
+
url: fullUrl,
|
|
101
|
+
message: error.message,
|
|
102
|
+
status: error.response?.status,
|
|
103
|
+
statusText: error.response?.statusText,
|
|
104
|
+
headers: error.response?.headers,
|
|
105
|
+
data: error.response?.data || error.data,
|
|
106
|
+
config: {
|
|
107
|
+
url: error.config?.url,
|
|
108
|
+
method: error.config?.method,
|
|
109
|
+
params: error.config?.params,
|
|
110
|
+
data: error.config?.data
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const fileName = `response-error-${method}-${timestamp}.json`;
|
|
115
|
+
const filePath = path.join(logDir, fileName);
|
|
116
|
+
|
|
117
|
+
fs.writeFileSync(filePath, JSON.stringify(errorLog, null, 2), 'utf-8');
|
|
118
|
+
|
|
119
|
+
logger.warn(`[HTTP Log] 错误日志已保存: ${filePath}`);
|
|
120
|
+
} catch (e) {
|
|
121
|
+
logger.error('[HTTP Log] 保存错误日志失败:', e.message);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
82
124
|
function saveResponseLog(response) {
|
|
83
125
|
try {
|
|
84
126
|
const logDir = getLogDir();
|
|
@@ -88,12 +130,24 @@ function saveResponseLog(response) {
|
|
|
88
130
|
|
|
89
131
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
90
132
|
const method = response.config.method?.toUpperCase() || 'GET';
|
|
133
|
+
const fullUrl = joinUrl(getConfig().baseUrl, response.config.url);
|
|
134
|
+
|
|
135
|
+
const responseLog = {
|
|
136
|
+
timestamp: timestamp,
|
|
137
|
+
method: method,
|
|
138
|
+
url: fullUrl,
|
|
139
|
+
status: response.status,
|
|
140
|
+
statusText: response.statusText,
|
|
141
|
+
headers: response.headers,
|
|
142
|
+
data: response.data
|
|
143
|
+
};
|
|
144
|
+
|
|
91
145
|
const fileName = `response-${method}-${timestamp}.json`;
|
|
92
146
|
const filePath = path.join(logDir, fileName);
|
|
93
147
|
|
|
94
|
-
fs.writeFileSync(filePath, JSON.stringify(
|
|
148
|
+
fs.writeFileSync(filePath, JSON.stringify(responseLog, null, 2), 'utf-8');
|
|
95
149
|
|
|
96
|
-
logger.
|
|
150
|
+
logger.warn(`[HTTP Log] 响应日志已保存: ${filePath}`);
|
|
97
151
|
} catch (error) {
|
|
98
152
|
logger.error('[HTTP Log] 保存响应日志失败:', error.message);
|
|
99
153
|
}
|
|
@@ -140,7 +194,7 @@ function createService() {
|
|
|
140
194
|
if (!config.params['_langcode']) {
|
|
141
195
|
config.params['_langcode'] = globalConfig.langcode || 'zh';
|
|
142
196
|
}
|
|
143
|
-
var url = `${config.method} ${globalConfig.baseUrl
|
|
197
|
+
var url = `${config.method} ${joinUrl(globalConfig.baseUrl, config.url)}`;
|
|
144
198
|
if (config.params && Object.keys(config.params).length > 0) {
|
|
145
199
|
if (url.indexOf('?') > -1) {
|
|
146
200
|
url += `&`;
|
|
@@ -200,18 +254,27 @@ function createService() {
|
|
|
200
254
|
return Promise.reject(new Error('未知错误'));
|
|
201
255
|
}
|
|
202
256
|
}, error => {
|
|
203
|
-
logger.error('[Response Error] 请求错误:', error.message);
|
|
204
257
|
try{
|
|
205
258
|
if(error){
|
|
259
|
+
logger.error('[Response Error] 请求错误:', error.message);
|
|
260
|
+
saveErrorLog(error);
|
|
206
261
|
logger.debug('[Response Error] 错误对象:', error);
|
|
207
262
|
if (error.data) {
|
|
208
263
|
logger.info('[Response Error] 错误error.data:', error.data);
|
|
209
264
|
}
|
|
210
265
|
if (error.response&&error.response.data) {
|
|
211
266
|
logger.info('[Response Error] 错误error.response.data:', error.response.data);
|
|
212
|
-
if(error.response.data['oslc:Error']
|
|
213
|
-
|
|
267
|
+
if (error.response.data['oslc:Error']) {
|
|
268
|
+
if (error.response.data['oslc:Error']['oslc:message']) {
|
|
269
|
+
logger.error('[Response Error] 错误error.response.data.oslc:Error.oslc:message:\x1b[32m['+error.response.data['oslc:Error']['oslc:message']+"]\x1b[0m");
|
|
270
|
+
}else if (error.response.data['oslc:Error']['oslc:extendedError']) {
|
|
271
|
+
if (error.response.data['oslc:Error']['oslc:extendedError']['oslc:moreInfo']) {
|
|
272
|
+
logger.error('[Response Error] 错误error.response.data:', error.response.data['oslc:Error']['oslc:extendedError']['oslc:moreInfo']);
|
|
214
273
|
|
|
274
|
+
}
|
|
275
|
+
} else {
|
|
276
|
+
logger.error('[Response Error] 错误error.response.data.oslc:Error', error.response.data['oslc:Error']);
|
|
277
|
+
}
|
|
215
278
|
}
|
|
216
279
|
}
|
|
217
280
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sks-maximo-utils",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Maximo helper tools for updating table structures and data",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -10,7 +10,10 @@
|
|
|
10
10
|
"scripts": {
|
|
11
11
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
12
12
|
},
|
|
13
|
-
"keywords": [
|
|
13
|
+
"keywords": [
|
|
14
|
+
"maximo",
|
|
15
|
+
"sks"
|
|
16
|
+
],
|
|
14
17
|
"author": "shoukaiseki",
|
|
15
18
|
"license": "ISC",
|
|
16
19
|
"dependencies": {
|