@nest-omni/core 4.1.3-22 → 4.1.3-24
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/http-client/examples/axios-config-extended.example.d.ts +17 -0
- package/http-client/examples/axios-config-extended.example.js +313 -0
- package/http-client/examples/index.d.ts +2 -0
- package/http-client/examples/index.js +2 -0
- package/http-client/examples/ssl-certificate.example.d.ts +47 -0
- package/http-client/examples/ssl-certificate.example.js +431 -0
- package/http-client/index.d.ts +1 -1
- package/http-client/interfaces/api-client-config.interface.d.ts +23 -12
- package/http-client/interfaces/http-client-config.interface.d.ts +73 -0
- package/http-client/services/api-client-registry.service.d.ts +5 -0
- package/http-client/services/api-client-registry.service.js +17 -25
- package/http-client/services/http-client.service.d.ts +2 -0
- package/http-client/services/http-client.service.js +109 -27
- package/package.json +1 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { HttpClientConfig } from '../interfaces/http-client-config.interface';
|
|
2
|
+
/**
|
|
3
|
+
* axiosConfig 透传配置示例
|
|
4
|
+
* 展示如何通过 axiosConfig 直接传递 Axios 原生配置
|
|
5
|
+
*/
|
|
6
|
+
export declare function httpAgentExample(): void;
|
|
7
|
+
export declare function redirectAndValidationExample(): void;
|
|
8
|
+
export declare function responseTypeExample(): void;
|
|
9
|
+
export declare function sizeAndRateLimitExample(): void;
|
|
10
|
+
export declare function paramsSerializationExample(): void;
|
|
11
|
+
export declare function formSerializationExample(): void;
|
|
12
|
+
export declare function securityOptionsExample(): void;
|
|
13
|
+
export declare function abortControllerExample(): void;
|
|
14
|
+
export declare function transitionalOptionsExample(): void;
|
|
15
|
+
export declare function progressEventsExample(): void;
|
|
16
|
+
export declare function comprehensiveConfigExample(): HttpClientConfig;
|
|
17
|
+
export declare function environmentSpecificConfig(): HttpClientConfig;
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.httpAgentExample = httpAgentExample;
|
|
4
|
+
exports.redirectAndValidationExample = redirectAndValidationExample;
|
|
5
|
+
exports.responseTypeExample = responseTypeExample;
|
|
6
|
+
exports.sizeAndRateLimitExample = sizeAndRateLimitExample;
|
|
7
|
+
exports.paramsSerializationExample = paramsSerializationExample;
|
|
8
|
+
exports.formSerializationExample = formSerializationExample;
|
|
9
|
+
exports.securityOptionsExample = securityOptionsExample;
|
|
10
|
+
exports.abortControllerExample = abortControllerExample;
|
|
11
|
+
exports.transitionalOptionsExample = transitionalOptionsExample;
|
|
12
|
+
exports.progressEventsExample = progressEventsExample;
|
|
13
|
+
exports.comprehensiveConfigExample = comprehensiveConfigExample;
|
|
14
|
+
exports.environmentSpecificConfig = environmentSpecificConfig;
|
|
15
|
+
const http_1 = require("http");
|
|
16
|
+
const https_1 = require("https");
|
|
17
|
+
/**
|
|
18
|
+
* axiosConfig 透传配置示例
|
|
19
|
+
* 展示如何通过 axiosConfig 直接传递 Axios 原生配置
|
|
20
|
+
*/
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// 示例 1: 使用 httpAgent 和 httpsAgent 启用连接池
|
|
23
|
+
// ============================================================================
|
|
24
|
+
function httpAgentExample() {
|
|
25
|
+
const config = {
|
|
26
|
+
baseURL: 'https://api.example.com',
|
|
27
|
+
timeout: 30000,
|
|
28
|
+
// 通过 axiosConfig 透传 Axios 原生配置
|
|
29
|
+
axiosConfig: {
|
|
30
|
+
// 启用 HTTP keep-alive 连接池
|
|
31
|
+
httpAgent: new http_1.Agent({
|
|
32
|
+
keepAlive: true,
|
|
33
|
+
keepAliveMsecs: 1000,
|
|
34
|
+
maxSockets: 50,
|
|
35
|
+
maxFreeSockets: 10,
|
|
36
|
+
timeout: 60000,
|
|
37
|
+
}),
|
|
38
|
+
// 启用 HTTPS keep-alive 连接池
|
|
39
|
+
httpsAgent: new https_1.Agent({
|
|
40
|
+
keepAlive: true,
|
|
41
|
+
keepAliveMsecs: 1000,
|
|
42
|
+
maxSockets: 50,
|
|
43
|
+
maxFreeSockets: 10,
|
|
44
|
+
timeout: 60000,
|
|
45
|
+
// 可选:跳过证书验证(不推荐用于生产环境)
|
|
46
|
+
rejectUnauthorized: true,
|
|
47
|
+
}),
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
// 创建 HttpClientService 实例...
|
|
51
|
+
}
|
|
52
|
+
// ============================================================================
|
|
53
|
+
// 示例 2: 配置重定向和响应验证
|
|
54
|
+
// ============================================================================
|
|
55
|
+
function redirectAndValidationExample() {
|
|
56
|
+
const config = {
|
|
57
|
+
baseURL: 'https://api.example.com',
|
|
58
|
+
axiosConfig: {
|
|
59
|
+
// 禁止自动跟随重定向
|
|
60
|
+
maxRedirects: 0,
|
|
61
|
+
// 或者限制重定向次数
|
|
62
|
+
// maxRedirects: 5,
|
|
63
|
+
// 自定义状态码验证逻辑
|
|
64
|
+
validateStatus: (status) => {
|
|
65
|
+
// 将 400-499 视为成功(不抛出异常)
|
|
66
|
+
return status >= 200 && status < 500;
|
|
67
|
+
},
|
|
68
|
+
// 重定向前回调
|
|
69
|
+
beforeRedirect: (options, { headers }) => {
|
|
70
|
+
// 可以在这里修改重定向请求
|
|
71
|
+
if (options.hostname === 'restricted.com') {
|
|
72
|
+
throw new Error('Redirect to restricted domain not allowed');
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
// ============================================================================
|
|
79
|
+
// 示例 3: 配置响应类型和数据转换
|
|
80
|
+
// ============================================================================
|
|
81
|
+
function responseTypeExample() {
|
|
82
|
+
const config = {
|
|
83
|
+
baseURL: 'https://api.example.com',
|
|
84
|
+
axiosConfig: {
|
|
85
|
+
// 指定响应类型
|
|
86
|
+
responseType: 'arraybuffer', // 'arraybuffer' | 'document' | 'json' | 'text' | 'stream' | 'blob'
|
|
87
|
+
responseEncoding: 'utf8',
|
|
88
|
+
// 自定义请求/响应转换器
|
|
89
|
+
transformRequest: [
|
|
90
|
+
(data, headers) => {
|
|
91
|
+
// 在发送前修改数据
|
|
92
|
+
if (typeof data === 'object') {
|
|
93
|
+
// 自动添加时间戳
|
|
94
|
+
data.timestamp = Date.now();
|
|
95
|
+
}
|
|
96
|
+
return JSON.stringify(data);
|
|
97
|
+
},
|
|
98
|
+
...require('axios').defaults.transformRequest,
|
|
99
|
+
],
|
|
100
|
+
transformResponse: [
|
|
101
|
+
(data) => {
|
|
102
|
+
// 在接收后修改数据
|
|
103
|
+
if (typeof data === 'string') {
|
|
104
|
+
try {
|
|
105
|
+
const parsed = JSON.parse(data);
|
|
106
|
+
// 统一处理响应格式
|
|
107
|
+
return parsed.data || parsed;
|
|
108
|
+
}
|
|
109
|
+
catch (_a) {
|
|
110
|
+
return data;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return data;
|
|
114
|
+
},
|
|
115
|
+
...require('axios').defaults.transformResponse,
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
// ============================================================================
|
|
121
|
+
// 示例 4: 配置大小限制和速率限制
|
|
122
|
+
// ============================================================================
|
|
123
|
+
function sizeAndRateLimitExample() {
|
|
124
|
+
const config = {
|
|
125
|
+
baseURL: 'https://api.example.com',
|
|
126
|
+
axiosConfig: {
|
|
127
|
+
// 响应体大小限制(字节)
|
|
128
|
+
maxContentLength: 10 * 1024 * 1024, // 10MB
|
|
129
|
+
// 请求体大小限制(字节)
|
|
130
|
+
maxBodyLength: 5 * 1024 * 1024, // 5MB
|
|
131
|
+
// 速率限制 [上传 B/s, 下载 B/s]
|
|
132
|
+
maxRate: [1024 * 1024, 2 * 1024 * 1024], // 上传 1MB/s, 下载 2MB/s
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
// ============================================================================
|
|
137
|
+
// 示例 5: 配置参数序列化
|
|
138
|
+
// ============================================================================
|
|
139
|
+
function paramsSerializationExample() {
|
|
140
|
+
const config = {
|
|
141
|
+
baseURL: 'https://api.example.com',
|
|
142
|
+
axiosConfig: {
|
|
143
|
+
paramsSerializer: {
|
|
144
|
+
// 自定义编码函数
|
|
145
|
+
encode: (param) => {
|
|
146
|
+
return encodeURIComponent(param)
|
|
147
|
+
.replace(/%40/gi, '@')
|
|
148
|
+
.replace(/%3A/gi, ':')
|
|
149
|
+
.replace(/%24/g, '$')
|
|
150
|
+
.replace(/%2C/gi, ',')
|
|
151
|
+
.replace(/%20/g, '+');
|
|
152
|
+
},
|
|
153
|
+
// 数组索引格式
|
|
154
|
+
indexes: true, // true=a[0]=b, false=a[]=b, null=a=b
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
// ============================================================================
|
|
160
|
+
// 示例 6: 配置表单序列化
|
|
161
|
+
// ============================================================================
|
|
162
|
+
function formSerializationExample() {
|
|
163
|
+
const config = {
|
|
164
|
+
baseURL: 'https://api.example.com',
|
|
165
|
+
axiosConfig: {
|
|
166
|
+
formSerializer: {
|
|
167
|
+
// 自定义访问器
|
|
168
|
+
visitor: (value, key, path) => {
|
|
169
|
+
// 自定义值转换逻辑
|
|
170
|
+
if (value instanceof Date) {
|
|
171
|
+
return value.toISOString();
|
|
172
|
+
}
|
|
173
|
+
return value;
|
|
174
|
+
},
|
|
175
|
+
// 使用点符号而不是括号
|
|
176
|
+
dots: true,
|
|
177
|
+
// 保留特殊结尾(如 {})
|
|
178
|
+
metaTokens: true,
|
|
179
|
+
// 数组索引格式
|
|
180
|
+
indexes: true,
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
// ============================================================================
|
|
186
|
+
// 示例 7: 配置安全选项
|
|
187
|
+
// ============================================================================
|
|
188
|
+
function securityOptionsExample() {
|
|
189
|
+
const config = {
|
|
190
|
+
baseURL: 'https://api.example.com',
|
|
191
|
+
axiosConfig: {
|
|
192
|
+
// 是否允许绝对 URL 覆盖 baseURL
|
|
193
|
+
allowAbsoluteUrls: true,
|
|
194
|
+
// 跨域请求是否携带凭证
|
|
195
|
+
withCredentials: true,
|
|
196
|
+
// 禁用自动解压
|
|
197
|
+
decompress: false,
|
|
198
|
+
// 使用不安全的 HTTP 解析器(允许无效的 HTTP 头)
|
|
199
|
+
// 警告:仅在与非标准 HTTP 实现交互时使用
|
|
200
|
+
insecureHTTPParser: false,
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
// ============================================================================
|
|
205
|
+
// 示例 8: 使用 AbortSignal 取消请求
|
|
206
|
+
// ============================================================================
|
|
207
|
+
function abortControllerExample() {
|
|
208
|
+
const config = {
|
|
209
|
+
baseURL: 'https://api.example.com',
|
|
210
|
+
axiosConfig: {
|
|
211
|
+
// 可以在每次请求时动态设置
|
|
212
|
+
// signal: new AbortController().signal,
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
// 使用示例:
|
|
216
|
+
// const abortController = new AbortController();
|
|
217
|
+
// config.axiosConfig!.signal = abortController.signal;
|
|
218
|
+
//
|
|
219
|
+
// setTimeout(() => abortController.abort(), 5000); // 5秒后取消
|
|
220
|
+
}
|
|
221
|
+
// ============================================================================
|
|
222
|
+
// 示例 9: 向后兼容选项
|
|
223
|
+
// ============================================================================
|
|
224
|
+
function transitionalOptionsExample() {
|
|
225
|
+
const config = {
|
|
226
|
+
baseURL: 'https://api.example.com',
|
|
227
|
+
axiosConfig: {
|
|
228
|
+
transitional: {
|
|
229
|
+
// 静默 JSON 解析错误(旧行为)
|
|
230
|
+
silentJSONParsing: false,
|
|
231
|
+
// 即使 responseType 不是 'json' 也尝试解析
|
|
232
|
+
forcedJSONParsing: true,
|
|
233
|
+
// 超时时抛出明确的 ETIMEDOUT 错误
|
|
234
|
+
clarifyTimeoutError: true,
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
// ============================================================================
|
|
240
|
+
// 示例 10: 进度事件(浏览器环境)
|
|
241
|
+
// ============================================================================
|
|
242
|
+
function progressEventsExample() {
|
|
243
|
+
const config = {
|
|
244
|
+
baseURL: 'https://api.example.com',
|
|
245
|
+
axiosConfig: {
|
|
246
|
+
// 上传进度
|
|
247
|
+
onUploadProgress: (progressEvent) => {
|
|
248
|
+
const percentCompleted = Math.round((progressEvent.loaded * 100) / (progressEvent.total || 1));
|
|
249
|
+
console.log(`Upload ${percentCompleted}%`);
|
|
250
|
+
},
|
|
251
|
+
// 下载进度
|
|
252
|
+
onDownloadProgress: (progressEvent) => {
|
|
253
|
+
const percentCompleted = Math.round((progressEvent.loaded * 100) / (progressEvent.total || 1));
|
|
254
|
+
console.log(`Download ${percentCompleted}%`);
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
// ============================================================================
|
|
260
|
+
// 示例 11: 综合配置
|
|
261
|
+
// ============================================================================
|
|
262
|
+
function comprehensiveConfigExample() {
|
|
263
|
+
const config = {
|
|
264
|
+
// 显式配置(优先级高)
|
|
265
|
+
baseURL: 'https://api.example.com',
|
|
266
|
+
timeout: 30000,
|
|
267
|
+
// 内置功能配置
|
|
268
|
+
retry: {
|
|
269
|
+
enabled: true,
|
|
270
|
+
retries: 3,
|
|
271
|
+
},
|
|
272
|
+
circuitBreaker: {
|
|
273
|
+
enabled: true,
|
|
274
|
+
failureThreshold: 5,
|
|
275
|
+
},
|
|
276
|
+
logging: {
|
|
277
|
+
enabled: true,
|
|
278
|
+
logLevel: 'info',
|
|
279
|
+
},
|
|
280
|
+
// Axios 原生配置透传(优先级低,会被显式配置覆盖)
|
|
281
|
+
axiosConfig: {
|
|
282
|
+
httpAgent: new http_1.Agent({ keepAlive: true }),
|
|
283
|
+
httpsAgent: new https_1.Agent({ keepAlive: true }),
|
|
284
|
+
maxRedirects: 5,
|
|
285
|
+
responseType: 'json',
|
|
286
|
+
validateStatus: (status) => status >= 200 && status < 300,
|
|
287
|
+
maxContentLength: 10 * 1024 * 1024,
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
return config;
|
|
291
|
+
}
|
|
292
|
+
// ============================================================================
|
|
293
|
+
// 示例 12: 环境相关的配置
|
|
294
|
+
// ============================================================================
|
|
295
|
+
function environmentSpecificConfig() {
|
|
296
|
+
const isDevelopment = process.env.NODE_ENV === 'development';
|
|
297
|
+
const config = {
|
|
298
|
+
baseURL: isDevelopment
|
|
299
|
+
? 'https://dev-api.example.com'
|
|
300
|
+
: 'https://api.example.com',
|
|
301
|
+
axiosConfig: {
|
|
302
|
+
// 开发环境使用更宽松的验证
|
|
303
|
+
validateStatus: isDevelopment
|
|
304
|
+
? (status) => status >= 200 && status < 500
|
|
305
|
+
: (status) => status >= 200 && status < 300,
|
|
306
|
+
// 开发环境禁用 keep-alive 以便调试
|
|
307
|
+
httpAgent: isDevelopment
|
|
308
|
+
? undefined
|
|
309
|
+
: new http_1.Agent({ keepAlive: true }),
|
|
310
|
+
},
|
|
311
|
+
};
|
|
312
|
+
return config;
|
|
313
|
+
}
|
|
@@ -17,3 +17,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
__exportStar(require("./basic-usage.example"), exports);
|
|
18
18
|
__exportStar(require("./advanced-usage.example"), exports);
|
|
19
19
|
__exportStar(require("./multi-api-configuration.example"), exports);
|
|
20
|
+
__exportStar(require("./axios-config-extended.example"), exports);
|
|
21
|
+
__exportStar(require("./ssl-certificate.example"), exports);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { HttpClientConfig } from '../index';
|
|
2
|
+
/**
|
|
3
|
+
* SSL/TLS 证书配置示例
|
|
4
|
+
* 解决自签名证书、客户端证书认证等问题
|
|
5
|
+
*/
|
|
6
|
+
export declare function disableSslVerificationExample(): void;
|
|
7
|
+
export declare function selfSignedCaExample(): HttpClientConfig;
|
|
8
|
+
export declare function multipleCaCertsExample(): HttpClientConfig;
|
|
9
|
+
export declare function clientCertificateExample(): HttpClientConfig;
|
|
10
|
+
export declare function tlsVersionAndCiphersExample(): HttpClientConfig;
|
|
11
|
+
export declare function certFromEnvExample(): HttpClientConfig;
|
|
12
|
+
export declare function productionSslConfigExample(): HttpClientConfig;
|
|
13
|
+
export declare function environmentBasedSslConfig(): HttpClientConfig;
|
|
14
|
+
export declare function handleCertExpirationExample(): HttpClientConfig;
|
|
15
|
+
export declare function directHttpsAgentExample(): HttpClientConfig;
|
|
16
|
+
export declare function sslErrorHandlingExample(): Promise<void>;
|
|
17
|
+
export declare class DynamicCertificateManager {
|
|
18
|
+
private circuitBreakerService;
|
|
19
|
+
private loggingService;
|
|
20
|
+
private certPaths;
|
|
21
|
+
private httpClient;
|
|
22
|
+
private certCache;
|
|
23
|
+
private lastUpdate;
|
|
24
|
+
private cacheDuration;
|
|
25
|
+
constructor(circuitBreakerService: any, loggingService: any, certPaths: {
|
|
26
|
+
ca?: string;
|
|
27
|
+
cert?: string;
|
|
28
|
+
key?: string;
|
|
29
|
+
});
|
|
30
|
+
private loadCerts;
|
|
31
|
+
private buildConfig;
|
|
32
|
+
refreshCerts(): Promise<void>;
|
|
33
|
+
get(url: string): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
export declare const sslEnvVars: {
|
|
36
|
+
SSL_CA_PATH: string;
|
|
37
|
+
SSL_CLIENT_CERT_PATH: string;
|
|
38
|
+
SSL_CLIENT_KEY_PATH: string;
|
|
39
|
+
SSL_PASSPHRASE: string;
|
|
40
|
+
SSL_REJECT_UNAUTHORIZED: string;
|
|
41
|
+
SSL_MIN_VERSION: string;
|
|
42
|
+
SSL_CIPHERS: string;
|
|
43
|
+
SSL_CA_CERT: string;
|
|
44
|
+
SSL_CLIENT_CERT: string;
|
|
45
|
+
SSL_CLIENT_KEY: string;
|
|
46
|
+
};
|
|
47
|
+
export declare function diagnoseSslConfig(apiUrl: string): Promise<unknown>;
|
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.sslEnvVars = exports.DynamicCertificateManager = void 0;
|
|
13
|
+
exports.disableSslVerificationExample = disableSslVerificationExample;
|
|
14
|
+
exports.selfSignedCaExample = selfSignedCaExample;
|
|
15
|
+
exports.multipleCaCertsExample = multipleCaCertsExample;
|
|
16
|
+
exports.clientCertificateExample = clientCertificateExample;
|
|
17
|
+
exports.tlsVersionAndCiphersExample = tlsVersionAndCiphersExample;
|
|
18
|
+
exports.certFromEnvExample = certFromEnvExample;
|
|
19
|
+
exports.productionSslConfigExample = productionSslConfigExample;
|
|
20
|
+
exports.environmentBasedSslConfig = environmentBasedSslConfig;
|
|
21
|
+
exports.handleCertExpirationExample = handleCertExpirationExample;
|
|
22
|
+
exports.directHttpsAgentExample = directHttpsAgentExample;
|
|
23
|
+
exports.sslErrorHandlingExample = sslErrorHandlingExample;
|
|
24
|
+
exports.diagnoseSslConfig = diagnoseSslConfig;
|
|
25
|
+
const index_1 = require("../index");
|
|
26
|
+
const fs_1 = require("fs");
|
|
27
|
+
const path_1 = require("path");
|
|
28
|
+
/**
|
|
29
|
+
* SSL/TLS 证书配置示例
|
|
30
|
+
* 解决自签名证书、客户端证书认证等问题
|
|
31
|
+
*/
|
|
32
|
+
// ============================================================================
|
|
33
|
+
// 示例 1: 禁用证书验证(仅用于开发环境)
|
|
34
|
+
// ============================================================================
|
|
35
|
+
function disableSslVerificationExample() {
|
|
36
|
+
const config = {
|
|
37
|
+
baseURL: 'https://self-signed.example.com',
|
|
38
|
+
timeout: 30000,
|
|
39
|
+
axiosConfig: {
|
|
40
|
+
ssl: {
|
|
41
|
+
// ⚠️ 危险:禁用证书验证,仅用于开发/测试环境
|
|
42
|
+
// 生产环境严禁使用!
|
|
43
|
+
rejectUnauthorized: false,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
// 使用示例
|
|
48
|
+
// const httpClient = new HttpClientService(circuitBreakerService, loggingService, null, config);
|
|
49
|
+
// return httpClient.get('/api/data');
|
|
50
|
+
}
|
|
51
|
+
// ============================================================================
|
|
52
|
+
// 示例 2: 使用自签名 CA 证书(推荐)
|
|
53
|
+
// ============================================================================
|
|
54
|
+
function selfSignedCaExample() {
|
|
55
|
+
// 方式1: 从文件读取 CA 证书
|
|
56
|
+
const caCert = (0, fs_1.readFileSync)((0, path_1.join)(process.cwd(), 'certs/ca.crt'));
|
|
57
|
+
const config = {
|
|
58
|
+
baseURL: 'https://self-signed.example.com',
|
|
59
|
+
timeout: 30000,
|
|
60
|
+
axiosConfig: {
|
|
61
|
+
ssl: {
|
|
62
|
+
// 指定信任的 CA 证书
|
|
63
|
+
ca: caCert,
|
|
64
|
+
// 仍然验证证书,但信任我们的自签名 CA
|
|
65
|
+
rejectUnauthorized: true,
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
return config;
|
|
70
|
+
}
|
|
71
|
+
// ============================================================================
|
|
72
|
+
// 示例 3: 使用多个 CA 证书
|
|
73
|
+
// ============================================================================
|
|
74
|
+
function multipleCaCertsExample() {
|
|
75
|
+
const ca1 = (0, fs_1.readFileSync)((0, path_1.join)(process.cwd(), 'certs/ca1.crt'), 'utf8');
|
|
76
|
+
const ca2 = (0, fs_1.readFileSync)((0, path_1.join)(process.cwd(), 'certs/ca2.crt'), 'utf8');
|
|
77
|
+
const ca3 = (0, fs_1.readFileSync)((0, path_1.join)(process.cwd(), 'certs/ca3.crt'), 'utf8');
|
|
78
|
+
const config = {
|
|
79
|
+
baseURL: 'https://multi-ca.example.com',
|
|
80
|
+
timeout: 30000,
|
|
81
|
+
axiosConfig: {
|
|
82
|
+
ssl: {
|
|
83
|
+
// 支持多个 CA 证书
|
|
84
|
+
ca: [ca1, ca2, ca3],
|
|
85
|
+
rejectUnauthorized: true,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
return config;
|
|
90
|
+
}
|
|
91
|
+
// ============================================================================
|
|
92
|
+
// 示例 4: 使用客户端证书(双向 TLS 认证)
|
|
93
|
+
// ============================================================================
|
|
94
|
+
function clientCertificateExample() {
|
|
95
|
+
const caCert = (0, fs_1.readFileSync)((0, path_1.join)(process.cwd(), 'certs/ca.crt'));
|
|
96
|
+
const clientCert = (0, fs_1.readFileSync)((0, path_1.join)(process.cwd(), 'certs/client.crt'));
|
|
97
|
+
const clientKey = (0, fs_1.readFileSync)((0, path_1.join)(process.cwd(), 'certs/client.key'));
|
|
98
|
+
const config = {
|
|
99
|
+
baseURL: 'https://mutual-tls.example.com',
|
|
100
|
+
timeout: 30000,
|
|
101
|
+
axiosConfig: {
|
|
102
|
+
ssl: {
|
|
103
|
+
// CA 证书(验证服务器)
|
|
104
|
+
ca: caCert,
|
|
105
|
+
// 客户端证书(服务器验证客户端)
|
|
106
|
+
cert: clientCert,
|
|
107
|
+
// 客户端私钥
|
|
108
|
+
key: clientKey,
|
|
109
|
+
// 如果私钥有密码
|
|
110
|
+
passphrase: 'your-private-key-password',
|
|
111
|
+
rejectUnauthorized: true,
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
return config;
|
|
116
|
+
}
|
|
117
|
+
// ============================================================================
|
|
118
|
+
// 示例 5: 配置 TLS 版本和加密套件
|
|
119
|
+
// ============================================================================
|
|
120
|
+
function tlsVersionAndCiphersExample() {
|
|
121
|
+
const config = {
|
|
122
|
+
baseURL: 'https://secure-api.example.com',
|
|
123
|
+
timeout: 30000,
|
|
124
|
+
axiosConfig: {
|
|
125
|
+
ssl: {
|
|
126
|
+
// 限制 TLS 版本
|
|
127
|
+
minVersion: 'TLSv1.2',
|
|
128
|
+
maxVersion: 'TLSv1.3',
|
|
129
|
+
// 指定加密套件
|
|
130
|
+
ciphers: 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:' +
|
|
131
|
+
'ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384',
|
|
132
|
+
rejectUnauthorized: true,
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
return config;
|
|
137
|
+
}
|
|
138
|
+
// ============================================================================
|
|
139
|
+
// 示例 6: 从环境变量加载证书
|
|
140
|
+
// ============================================================================
|
|
141
|
+
function certFromEnvExample() {
|
|
142
|
+
const config = {
|
|
143
|
+
baseURL: process.env.API_BASE_URL,
|
|
144
|
+
timeout: 30000,
|
|
145
|
+
axiosConfig: {
|
|
146
|
+
ssl: {
|
|
147
|
+
// CA 证书路径或内容
|
|
148
|
+
ca: process.env.SSL_CA_CERT,
|
|
149
|
+
// 客户端证书
|
|
150
|
+
cert: process.env.SSL_CLIENT_CERT,
|
|
151
|
+
// 客户端私钥
|
|
152
|
+
key: process.env.SSL_CLIENT_KEY,
|
|
153
|
+
// 私钥密码
|
|
154
|
+
passphrase: process.env.SSL_PASSPHRASE,
|
|
155
|
+
rejectUnauthorized: true,
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
return config;
|
|
160
|
+
}
|
|
161
|
+
// ============================================================================
|
|
162
|
+
// 示例 7: 完整的 SSL 配置(生产环境)
|
|
163
|
+
// ============================================================================
|
|
164
|
+
function productionSslConfigExample() {
|
|
165
|
+
// 在实际应用中,证书应该从安全的配置中心加载
|
|
166
|
+
const caCert = (0, fs_1.readFileSync)(process.env.SSL_CA_PATH || '/etc/ssl/certs/ca.pem');
|
|
167
|
+
const clientCert = (0, fs_1.readFileSync)(process.env.SSL_CLIENT_CERT_PATH || '/etc/ssl/certs/client.pem');
|
|
168
|
+
const clientKey = (0, fs_1.readFileSync)(process.env.SSL_CLIENT_KEY_PATH || '/etc/ssl/private/client.key');
|
|
169
|
+
return {
|
|
170
|
+
baseURL: process.env.API_BASE_URL,
|
|
171
|
+
timeout: 30000,
|
|
172
|
+
// 重试配置
|
|
173
|
+
retry: {
|
|
174
|
+
enabled: true,
|
|
175
|
+
retries: 3,
|
|
176
|
+
},
|
|
177
|
+
// 熔断器配置
|
|
178
|
+
circuitBreaker: {
|
|
179
|
+
enabled: true,
|
|
180
|
+
failureThreshold: 5,
|
|
181
|
+
},
|
|
182
|
+
// 日志配置
|
|
183
|
+
logging: {
|
|
184
|
+
enabled: true,
|
|
185
|
+
logLevel: 'info',
|
|
186
|
+
},
|
|
187
|
+
// SSL 配置
|
|
188
|
+
axiosConfig: {
|
|
189
|
+
ssl: {
|
|
190
|
+
ca: caCert,
|
|
191
|
+
cert: clientCert,
|
|
192
|
+
key: clientKey,
|
|
193
|
+
passphrase: process.env.SSL_PASSPHRASE,
|
|
194
|
+
rejectUnauthorized: true, // 生产环境必须验证证书
|
|
195
|
+
minVersion: 'TLSv1.2', // 最低 TLS 1.2
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
// ============================================================================
|
|
201
|
+
// 示例 8: 不同环境使用不同 SSL 配置
|
|
202
|
+
// ============================================================================
|
|
203
|
+
function environmentBasedSslConfig() {
|
|
204
|
+
const isDevelopment = process.env.NODE_ENV === 'development';
|
|
205
|
+
const isTest = process.env.NODE_ENV === 'test';
|
|
206
|
+
const baseConfig = {
|
|
207
|
+
baseURL: process.env.API_BASE_URL,
|
|
208
|
+
timeout: 30000,
|
|
209
|
+
};
|
|
210
|
+
// 开发/测试环境:禁用证书验证
|
|
211
|
+
if (isDevelopment || isTest) {
|
|
212
|
+
baseConfig.axiosConfig = {
|
|
213
|
+
ssl: {
|
|
214
|
+
rejectUnauthorized: false, // 仅用于开发/测试
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
// 生产环境:使用证书验证
|
|
220
|
+
baseConfig.axiosConfig = {
|
|
221
|
+
ssl: {
|
|
222
|
+
ca: (0, fs_1.readFileSync)(process.env.SSL_CA_PATH),
|
|
223
|
+
rejectUnauthorized: true,
|
|
224
|
+
minVersion: 'TLSv1.2',
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
return baseConfig;
|
|
229
|
+
}
|
|
230
|
+
// ============================================================================
|
|
231
|
+
// 示例 9: 处理证书过期问题
|
|
232
|
+
// ============================================================================
|
|
233
|
+
function handleCertExpirationExample() {
|
|
234
|
+
const config = {
|
|
235
|
+
baseURL: 'https://api.example.com',
|
|
236
|
+
timeout: 30000,
|
|
237
|
+
axiosConfig: {
|
|
238
|
+
ssl: {
|
|
239
|
+
// 设置服务器名称(SNI)
|
|
240
|
+
servername: 'api.example.com',
|
|
241
|
+
// 如果服务器证书过期但仍需要连接(不推荐)
|
|
242
|
+
rejectUnauthorized: process.env.ALLOW_EXPIRED_CERT === 'true',
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
// 重试配置:证书问题通常不需要重试
|
|
246
|
+
retry: {
|
|
247
|
+
enabled: true,
|
|
248
|
+
retries: 1,
|
|
249
|
+
retryCondition: (error) => {
|
|
250
|
+
// 不重试证书错误
|
|
251
|
+
if (error.code === 'CERT_HAS_EXPIRED' ||
|
|
252
|
+
error.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE') {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
return !error.response || error.response.status >= 500;
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
return config;
|
|
260
|
+
}
|
|
261
|
+
// ============================================================================
|
|
262
|
+
// 示例 10: 使用 https.Agent 直接配置(兼容旧方式)
|
|
263
|
+
// ============================================================================
|
|
264
|
+
function directHttpsAgentExample() {
|
|
265
|
+
const https = require('https');
|
|
266
|
+
const fs = require('fs');
|
|
267
|
+
// 直接创建 https.Agent
|
|
268
|
+
const httpsAgent = new https.Agent({
|
|
269
|
+
ca: fs.readFileSync('/path/to/ca.crt'),
|
|
270
|
+
cert: fs.readFileSync('/path/to/client.crt'),
|
|
271
|
+
key: fs.readFileSync('/path/to/client.key'),
|
|
272
|
+
rejectUnauthorized: true,
|
|
273
|
+
keepAlive: true,
|
|
274
|
+
});
|
|
275
|
+
const config = {
|
|
276
|
+
baseURL: 'https://api.example.com',
|
|
277
|
+
timeout: 30000,
|
|
278
|
+
axiosConfig: {
|
|
279
|
+
// 直接使用 httpsAgent
|
|
280
|
+
httpsAgent: httpsAgent,
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
return config;
|
|
284
|
+
}
|
|
285
|
+
// ============================================================================
|
|
286
|
+
// 示例 11: 错误处理和调试
|
|
287
|
+
// ============================================================================
|
|
288
|
+
function sslErrorHandlingExample() {
|
|
289
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
290
|
+
const config = {
|
|
291
|
+
baseURL: 'https://self-signed.example.com',
|
|
292
|
+
timeout: 30000,
|
|
293
|
+
axiosConfig: {
|
|
294
|
+
ssl: {
|
|
295
|
+
rejectUnauthorized: true, // 先启用验证
|
|
296
|
+
ca: (0, fs_1.readFileSync)((0, path_1.join)(process.cwd(), 'certs/ca.crt')),
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
// const httpClient = new HttpClientService(circuitBreakerService, loggingService, null, config);
|
|
301
|
+
try {
|
|
302
|
+
// return await httpClient.get('/api/data');
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
// 常见的 SSL 错误码
|
|
306
|
+
switch (error.code) {
|
|
307
|
+
case 'CERT_HAS_EXPIRED':
|
|
308
|
+
console.error('证书已过期');
|
|
309
|
+
break;
|
|
310
|
+
case 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY':
|
|
311
|
+
console.error('无法获取本地颁发者证书,请检查 CA 配置');
|
|
312
|
+
break;
|
|
313
|
+
case 'SELF_SIGNED_CERT_IN_CHAIN':
|
|
314
|
+
console.error('证书链中有自签名证书,请添加 CA 证书');
|
|
315
|
+
break;
|
|
316
|
+
case 'DEPTH_ZERO_SELF_SIGNED_CERT':
|
|
317
|
+
console.error('自签名证书,需要设置 rejectUnauthorized: false 或添加 CA');
|
|
318
|
+
break;
|
|
319
|
+
case 'CERT_REVOKED':
|
|
320
|
+
console.error('证书已被吊销');
|
|
321
|
+
break;
|
|
322
|
+
default:
|
|
323
|
+
console.error('SSL 错误:', error.message);
|
|
324
|
+
}
|
|
325
|
+
throw error;
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
// ============================================================================
|
|
330
|
+
// 示例 12: 动态加载证书(支持证书更新)
|
|
331
|
+
// ============================================================================
|
|
332
|
+
class DynamicCertificateManager {
|
|
333
|
+
constructor(circuitBreakerService, loggingService, certPaths) {
|
|
334
|
+
this.circuitBreakerService = circuitBreakerService;
|
|
335
|
+
this.loggingService = loggingService;
|
|
336
|
+
this.certPaths = certPaths;
|
|
337
|
+
this.certCache = {};
|
|
338
|
+
this.lastUpdate = 0;
|
|
339
|
+
this.cacheDuration = 5 * 60 * 1000; // 5分钟
|
|
340
|
+
this.httpClient = new index_1.HttpClientService(this.circuitBreakerService, this.loggingService, null, this.buildConfig());
|
|
341
|
+
}
|
|
342
|
+
loadCerts() {
|
|
343
|
+
const now = Date.now();
|
|
344
|
+
if (now - this.lastUpdate < this.cacheDuration && Object.keys(this.certCache).length > 0) {
|
|
345
|
+
return this.certCache;
|
|
346
|
+
}
|
|
347
|
+
// 重新加载证书
|
|
348
|
+
if (this.certPaths.ca) {
|
|
349
|
+
this.certCache.ca = (0, fs_1.readFileSync)(this.certPaths.ca);
|
|
350
|
+
}
|
|
351
|
+
if (this.certPaths.cert) {
|
|
352
|
+
this.certCache.cert = (0, fs_1.readFileSync)(this.certPaths.cert);
|
|
353
|
+
}
|
|
354
|
+
if (this.certPaths.key) {
|
|
355
|
+
this.certCache.key = (0, fs_1.readFileSync)(this.certPaths.key);
|
|
356
|
+
}
|
|
357
|
+
this.lastUpdate = now;
|
|
358
|
+
return this.certCache;
|
|
359
|
+
}
|
|
360
|
+
buildConfig() {
|
|
361
|
+
const certs = this.loadCerts();
|
|
362
|
+
return {
|
|
363
|
+
baseURL: process.env.API_BASE_URL,
|
|
364
|
+
timeout: 30000,
|
|
365
|
+
axiosConfig: {
|
|
366
|
+
ssl: Object.assign(Object.assign({}, certs), { rejectUnauthorized: true }),
|
|
367
|
+
},
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
// 重新创建客户端以使用新证书
|
|
371
|
+
refreshCerts() {
|
|
372
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
373
|
+
this.lastUpdate = 0;
|
|
374
|
+
const config = this.buildConfig();
|
|
375
|
+
// 在实际应用中,可能需要重新创建 HttpClientService 实例
|
|
376
|
+
// 或者提供更新配置的方法
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
get(url) {
|
|
380
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
381
|
+
// 每次请求前检查是否需要更新证书
|
|
382
|
+
this.loadCerts();
|
|
383
|
+
// return this.httpClient.get(url);
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
exports.DynamicCertificateManager = DynamicCertificateManager;
|
|
388
|
+
// ============================================================================
|
|
389
|
+
// 环境变量配置示例
|
|
390
|
+
// ============================================================================
|
|
391
|
+
exports.sslEnvVars = {
|
|
392
|
+
// SSL 配置
|
|
393
|
+
SSL_CA_PATH: '/path/to/ca.crt', // CA 证书文件路径
|
|
394
|
+
SSL_CLIENT_CERT_PATH: '/path/to/client.crt', // 客户端证书文件路径
|
|
395
|
+
SSL_CLIENT_KEY_PATH: '/path/to/client.key', // 客户端私钥文件路径
|
|
396
|
+
SSL_PASSPHRASE: 'your-passphrase', // 私钥密码
|
|
397
|
+
SSL_REJECT_UNAUTHORIZED: 'true', // 是否验证证书(true/false)
|
|
398
|
+
SSL_MIN_VERSION: 'TLSv1.2', // 最低 TLS 版本
|
|
399
|
+
SSL_CIPHERS: 'ECDHE-RSA-AES128-GCM-SHA256:...', // 加密套件
|
|
400
|
+
// 或者直接提供证书内容
|
|
401
|
+
SSL_CA_CERT: '-----BEGIN CERTIFICATE-----\n...', // CA 证书内容
|
|
402
|
+
SSL_CLIENT_CERT: '-----BEGIN CERTIFICATE-----\n...', // 客户端证书内容
|
|
403
|
+
SSL_CLIENT_KEY: '-----BEGIN PRIVATE KEY-----\n...', // 客户端私钥内容
|
|
404
|
+
};
|
|
405
|
+
// ============================================================================
|
|
406
|
+
// 快速诊断脚本
|
|
407
|
+
// ============================================================================
|
|
408
|
+
function diagnoseSslConfig(apiUrl) {
|
|
409
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
410
|
+
const https = require('https');
|
|
411
|
+
return new Promise((resolve, reject) => {
|
|
412
|
+
const req = https.get(apiUrl, (res) => {
|
|
413
|
+
const cert = res.socket.getPeerCertificate();
|
|
414
|
+
console.log('服务器证书信息:');
|
|
415
|
+
console.log(' 主题:', cert.subject);
|
|
416
|
+
console.log(' 颁发者:', cert.issuer);
|
|
417
|
+
console.log(' 有效期:', new Date(cert.valid_from), '至', new Date(cert.valid_to));
|
|
418
|
+
console.log(' 序列号:', cert.serialNumber);
|
|
419
|
+
resolve(cert);
|
|
420
|
+
});
|
|
421
|
+
req.on('error', (error) => {
|
|
422
|
+
console.error('SSL 连接失败:', error.code, error.message);
|
|
423
|
+
reject(error);
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
// 使用示例:
|
|
429
|
+
// diagnoseSslConfig('https://self-signed.example.com')
|
|
430
|
+
// .then(cert => console.log('证书 OK'))
|
|
431
|
+
// .catch(err => console.log('证书错误'));
|
package/http-client/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { HttpClientConfig, RetryConfig, CircuitBreakerConfig, ProxyConfig, LoggingConfig, InterceptorConfig, ConnectionPoolConfig, HttpContext, HttpInterceptor, HttpMethod, HttpStats, } from './interfaces/http-client-config.interface';
|
|
1
|
+
export { HttpClientConfig, AxiosNativeConfig, SslCertificateConfig, RetryConfig, CircuitBreakerConfig, ProxyConfig, LoggingConfig, InterceptorConfig, ConnectionPoolConfig, HttpContext, HttpInterceptor, HttpMethod, HttpStats, } from './interfaces/http-client-config.interface';
|
|
2
2
|
export * from './interfaces/api-client-config.interface';
|
|
3
3
|
export { HttpLogEntity, RetryRecord } from './entities';
|
|
4
4
|
export * from './decorators';
|
|
@@ -116,17 +116,36 @@ export interface ResponseTransformerConfig {
|
|
|
116
116
|
}
|
|
117
117
|
/**
|
|
118
118
|
* API客户端配置
|
|
119
|
+
* 用于创建多个具有不同配置的 HttpClient 实例
|
|
119
120
|
*/
|
|
120
121
|
export interface ApiClientConfig {
|
|
121
122
|
/** API名称(用于标识和日志) */
|
|
122
123
|
name: string;
|
|
123
124
|
/** 基础URL */
|
|
124
125
|
baseURL: string;
|
|
125
|
-
/**
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
/**
|
|
127
|
+
* HttpClient 配置
|
|
128
|
+
* 包含所有 HttpClientConfig 的配置项(timeout, retry, logging, axiosConfig 等)
|
|
129
|
+
*/
|
|
130
|
+
httpConfig?: Partial<HttpClientConfig>;
|
|
131
|
+
/**
|
|
132
|
+
* 重试配置(快捷方式,会合并到 httpConfig.retry)
|
|
133
|
+
* @deprecated 使用 httpConfig.retry 代替
|
|
134
|
+
*/
|
|
135
|
+
retry?: {
|
|
136
|
+
enabled?: boolean;
|
|
137
|
+
retries?: number;
|
|
138
|
+
retryCondition?: (error: any) => boolean;
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* 鉴权配置
|
|
142
|
+
* API客户端特有的鉴权功能
|
|
143
|
+
*/
|
|
128
144
|
auth?: AuthConfig;
|
|
129
|
-
/**
|
|
145
|
+
/**
|
|
146
|
+
* 响应转换配置
|
|
147
|
+
* API客户端特有的响应处理
|
|
148
|
+
*/
|
|
130
149
|
responseTransformer?: ResponseTransformerConfig;
|
|
131
150
|
/** 默认请求头 */
|
|
132
151
|
defaultHeaders?: Record<string, string>;
|
|
@@ -138,20 +157,12 @@ export interface ApiClientConfig {
|
|
|
138
157
|
response?: Array<(response: AxiosResponse) => AxiosResponse | Promise<AxiosResponse>>;
|
|
139
158
|
error?: Array<(error: any) => any>;
|
|
140
159
|
};
|
|
141
|
-
/** 重试配置(覆盖全局配置) */
|
|
142
|
-
retry?: {
|
|
143
|
-
enabled?: boolean;
|
|
144
|
-
retries?: number;
|
|
145
|
-
retryCondition?: (error: any) => boolean;
|
|
146
|
-
};
|
|
147
160
|
/** 是否启用响应验证 */
|
|
148
161
|
enableValidation?: boolean;
|
|
149
162
|
/** 响应时间警告阈值(毫秒) */
|
|
150
163
|
responseTimeWarningThreshold?: number;
|
|
151
164
|
/** 自定义标签(用于分类和查询) */
|
|
152
165
|
tags?: string[];
|
|
153
|
-
/** 是否启用详细日志 */
|
|
154
|
-
enableDetailedLogging?: boolean;
|
|
155
166
|
}
|
|
156
167
|
/**
|
|
157
168
|
* API客户端实例配置
|
|
@@ -1,6 +1,73 @@
|
|
|
1
1
|
import { AxiosRequestConfig, AxiosResponse } from 'axios';
|
|
2
2
|
import { LogCleanupConfig } from '../services/log-cleanup.service';
|
|
3
3
|
import { SSRFProtectionConfig, URLValidationConfig } from '../utils/security-validator.util';
|
|
4
|
+
import type { AgentOptions } from 'https';
|
|
5
|
+
/**
|
|
6
|
+
* SSL/TLS 证书配置
|
|
7
|
+
* 兼容 Node.js https.Agent 的选项
|
|
8
|
+
* 参考: https://nodejs.org/api/tls.html#tls_tls_createsecurecontext_options
|
|
9
|
+
*/
|
|
10
|
+
export interface SslCertificateConfig extends Partial<Omit<AgentOptions, 'host' | 'port' | 'path'>> {
|
|
11
|
+
/**
|
|
12
|
+
* 是否禁用证书验证(仅用于开发/测试环境)
|
|
13
|
+
* 警告:设置为 true 会使连接不安全,生产环境严禁使用
|
|
14
|
+
*/
|
|
15
|
+
rejectUnauthorized?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* CA 证书内容(PEM 格式)
|
|
18
|
+
* 可以是单个证书或多个证书的字符串
|
|
19
|
+
*/
|
|
20
|
+
ca?: string | string[] | Buffer;
|
|
21
|
+
/**
|
|
22
|
+
* 客户端证书内容(PEM 格式)
|
|
23
|
+
*/
|
|
24
|
+
cert?: string | string[] | Buffer;
|
|
25
|
+
/**
|
|
26
|
+
* 客户端私钥内容(PEM 格式)
|
|
27
|
+
*/
|
|
28
|
+
key?: string | string[] | Buffer;
|
|
29
|
+
/**
|
|
30
|
+
* 私钥密码
|
|
31
|
+
*/
|
|
32
|
+
passphrase?: string;
|
|
33
|
+
/**
|
|
34
|
+
* 服务器名称(用于 SNI)
|
|
35
|
+
*/
|
|
36
|
+
servername?: string;
|
|
37
|
+
/**
|
|
38
|
+
* 支持的 TLS 版本
|
|
39
|
+
*/
|
|
40
|
+
minVersion?: 'TLSv1' | 'TLSv1.1' | 'TLSv1.2' | 'TLSv1.3';
|
|
41
|
+
maxVersion?: 'TLSv1' | 'TLSv1.1' | 'TLSv1.2' | 'TLSv1.3';
|
|
42
|
+
/**
|
|
43
|
+
* 支持的加密套件
|
|
44
|
+
*/
|
|
45
|
+
ciphers?: string;
|
|
46
|
+
/**
|
|
47
|
+
* 是否支持 DH 参数
|
|
48
|
+
*/
|
|
49
|
+
dhparam?: string | Buffer;
|
|
50
|
+
/**
|
|
51
|
+
* 是否使用安全 renegotiation
|
|
52
|
+
*/
|
|
53
|
+
secureOptions?: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Axios 原生配置透传
|
|
57
|
+
* 基于 AxiosRequestConfig,排除已在 HttpClientConfig 中定义的字段
|
|
58
|
+
*
|
|
59
|
+
* 使用方式:
|
|
60
|
+
* - 直接传 Axios 原生配置对象
|
|
61
|
+
* - 类型安全,支持所有 Axios 配置项
|
|
62
|
+
* - ssl 配置会被自动转换为 httpsAgent
|
|
63
|
+
*/
|
|
64
|
+
export type AxiosNativeConfig = Omit<AxiosRequestConfig, 'baseURL' | 'timeout' | 'proxy' | 'url' | 'method' | 'data' | 'headers' | 'params'> & {
|
|
65
|
+
/**
|
|
66
|
+
* SSL/TLS 证书配置(简化版)
|
|
67
|
+
* 会自动转换为 httpsAgent,优先级高于直接指定的 httpsAgent
|
|
68
|
+
*/
|
|
69
|
+
ssl?: SslCertificateConfig;
|
|
70
|
+
};
|
|
4
71
|
/**
|
|
5
72
|
* HTTP客户端配置接口
|
|
6
73
|
* 基于Spring Boot的@ConfigurationProperties设计理念
|
|
@@ -33,6 +100,12 @@ export interface HttpClientConfig {
|
|
|
33
100
|
/** 是否启用安全验证 */
|
|
34
101
|
enabled?: boolean;
|
|
35
102
|
};
|
|
103
|
+
/**
|
|
104
|
+
* Axios 原生配置透传
|
|
105
|
+
* 用于直接传递 Axios 支持的任何配置项,提供最大灵活性
|
|
106
|
+
* 优先级低于上述显式配置,会进行深度合并
|
|
107
|
+
*/
|
|
108
|
+
axiosConfig?: AxiosNativeConfig;
|
|
36
109
|
}
|
|
37
110
|
/**
|
|
38
111
|
* 重试配置
|
|
@@ -19,6 +19,11 @@ export declare class ApiClientRegistryService {
|
|
|
19
19
|
* 使用 HttpClientService.createApiClient 静态方法创建
|
|
20
20
|
*/
|
|
21
21
|
createClient<T = any>(config: ApiClientInstanceConfig): T;
|
|
22
|
+
/**
|
|
23
|
+
* 深度合并配置(已废弃,不再需要)
|
|
24
|
+
* @deprecated 配置结构简化后不再需要此方法
|
|
25
|
+
*/
|
|
26
|
+
private deepMergeConfigs;
|
|
22
27
|
/**
|
|
23
28
|
* 获取已注册的客户端
|
|
24
29
|
*/
|
|
@@ -62,7 +62,6 @@ let ApiClientRegistryService = ApiClientRegistryService_1 = class ApiClientRegis
|
|
|
62
62
|
* 使用 HttpClientService.createApiClient 静态方法创建
|
|
63
63
|
*/
|
|
64
64
|
createClient(config) {
|
|
65
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
66
65
|
const clientName = config.name;
|
|
67
66
|
const existingClient = this.clients.get(clientName);
|
|
68
67
|
if (existingClient && !config.override) {
|
|
@@ -71,6 +70,15 @@ let ApiClientRegistryService = ApiClientRegistryService_1 = class ApiClientRegis
|
|
|
71
70
|
}
|
|
72
71
|
// 合并环境特定配置
|
|
73
72
|
const finalConfig = this.mergeEnvironmentConfig(config);
|
|
73
|
+
// 构建 HttpClient 配置
|
|
74
|
+
// 1. 从全局默认配置开始
|
|
75
|
+
// 2. 合并 ApiClient 的 httpConfig
|
|
76
|
+
// 3. 应用 ApiClient 特有的 retry 配置(兼容旧代码)
|
|
77
|
+
let mergedHttpConfig = Object.assign(Object.assign({}, this.globalDefaults.globalDefaults), finalConfig.httpConfig);
|
|
78
|
+
// 兼容:如果 ApiClientConfig 顶层有 retry 配置,合并到 httpConfig.retry
|
|
79
|
+
if (finalConfig.retry) {
|
|
80
|
+
mergedHttpConfig.retry = Object.assign(Object.assign({}, mergedHttpConfig.retry), finalConfig.retry);
|
|
81
|
+
}
|
|
74
82
|
// 使用静态工厂方法创建 HttpClientService 实例
|
|
75
83
|
const httpClient = http_client_service_1.HttpClientService.createApiClient({
|
|
76
84
|
circuitBreakerService: this.circuitBreakerService,
|
|
@@ -78,30 +86,7 @@ let ApiClientRegistryService = ApiClientRegistryService_1 = class ApiClientRegis
|
|
|
78
86
|
}, {
|
|
79
87
|
name: clientName,
|
|
80
88
|
baseURL: finalConfig.baseURL,
|
|
81
|
-
|
|
82
|
-
httpConfig: Object.assign({ retry: {
|
|
83
|
-
enabled: ((_b = finalConfig.retry) === null || _b === void 0 ? void 0 : _b.enabled) !== false &&
|
|
84
|
-
this.globalDefaults.enableGlobalRetry,
|
|
85
|
-
retries: ((_c = finalConfig.retry) === null || _c === void 0 ? void 0 : _c.retries) ||
|
|
86
|
-
((_e = (_d = this.globalDefaults.globalDefaults) === null || _d === void 0 ? void 0 : _d.retry) === null || _e === void 0 ? void 0 : _e.retries) ||
|
|
87
|
-
3,
|
|
88
|
-
retryDelay: (retryCount) => Math.pow(2, retryCount) * 1000,
|
|
89
|
-
retryCondition: ((_f = finalConfig.retry) === null || _f === void 0 ? void 0 : _f.retryCondition) ||
|
|
90
|
-
((error) => {
|
|
91
|
-
if (!error.response)
|
|
92
|
-
return true;
|
|
93
|
-
const status = error.response.status;
|
|
94
|
-
return status >= 500 || status === 429;
|
|
95
|
-
}),
|
|
96
|
-
shouldResetTimeout: true,
|
|
97
|
-
}, logging: Object.assign({ enabled: true, logRequests: true, logResponses: true, logErrors: true, logHeaders: true, logBody: true, maxBodyLength: 10000, sanitizeHeaders: ['authorization', 'api-key'], logLevel: 'info' }, (_g = this.globalDefaults.globalDefaults) === null || _g === void 0 ? void 0 : _g.logging), circuitBreaker: {
|
|
98
|
-
enabled: this.globalDefaults.enableCircuitBreaker,
|
|
99
|
-
failureThreshold: 5,
|
|
100
|
-
recoveryTimeoutMs: 60000,
|
|
101
|
-
monitoringPeriodMs: 10000,
|
|
102
|
-
minimumThroughputThreshold: 10,
|
|
103
|
-
countHalfOpenCalls: true,
|
|
104
|
-
} }, finalConfig.httpConfig),
|
|
89
|
+
httpConfig: mergedHttpConfig,
|
|
105
90
|
auth: finalConfig.auth,
|
|
106
91
|
});
|
|
107
92
|
// 存储客户端和配置
|
|
@@ -110,6 +95,13 @@ let ApiClientRegistryService = ApiClientRegistryService_1 = class ApiClientRegis
|
|
|
110
95
|
this.logger.log(`Created API client: ${clientName}`);
|
|
111
96
|
return httpClient;
|
|
112
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* 深度合并配置(已废弃,不再需要)
|
|
100
|
+
* @deprecated 配置结构简化后不再需要此方法
|
|
101
|
+
*/
|
|
102
|
+
deepMergeConfigs(defaults, userConfig) {
|
|
103
|
+
return Object.assign(Object.assign({}, defaults), userConfig);
|
|
104
|
+
}
|
|
113
105
|
/**
|
|
114
106
|
* 获取已注册的客户端
|
|
115
107
|
*/
|
|
@@ -135,6 +135,7 @@ export declare class HttpClientService {
|
|
|
135
135
|
private calculateRetryDelay;
|
|
136
136
|
/**
|
|
137
137
|
* 应用装饰器配置
|
|
138
|
+
* 只在装饰器配置值有效时才覆盖请求配置
|
|
138
139
|
*/
|
|
139
140
|
private applyDecoratorConfig;
|
|
140
141
|
/**
|
|
@@ -147,6 +148,7 @@ export declare class HttpClientService {
|
|
|
147
148
|
private mergeWithDefaults;
|
|
148
149
|
/**
|
|
149
150
|
* 深度合并对象
|
|
151
|
+
* 只在源值有效(非 undefined)时才覆盖目标值
|
|
150
152
|
*/
|
|
151
153
|
private deepMerge;
|
|
152
154
|
/**
|
|
@@ -17,6 +17,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
17
17
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
18
18
|
});
|
|
19
19
|
};
|
|
20
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
21
|
+
var t = {};
|
|
22
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
23
|
+
t[p] = s[p];
|
|
24
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
25
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
26
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
27
|
+
t[p[i]] = s[p[i]];
|
|
28
|
+
}
|
|
29
|
+
return t;
|
|
30
|
+
};
|
|
20
31
|
var HttpClientService_1;
|
|
21
32
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
33
|
exports.HttpClientService = void 0;
|
|
@@ -68,14 +79,23 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
|
|
|
68
79
|
* @returns HttpClientService 实例
|
|
69
80
|
*/
|
|
70
81
|
static createApiClient(dependencies, config) {
|
|
71
|
-
|
|
82
|
+
// 构建配置,只在值有效时才覆盖
|
|
83
|
+
const mergedConfig = Object.assign({}, config.httpConfig);
|
|
84
|
+
// 只在显式提供且值有效时才覆盖
|
|
85
|
+
if (config.baseURL !== undefined) {
|
|
86
|
+
mergedConfig.baseURL = config.baseURL;
|
|
87
|
+
}
|
|
88
|
+
if (config.timeout !== undefined) {
|
|
89
|
+
mergedConfig.timeout = config.timeout;
|
|
90
|
+
}
|
|
91
|
+
return new HttpClientService_1(dependencies.circuitBreakerService, dependencies.loggingService, dependencies.redisLockService, mergedConfig, config.name, config.auth);
|
|
72
92
|
}
|
|
73
93
|
/**
|
|
74
94
|
* 执行HTTP请求
|
|
75
95
|
*/
|
|
76
96
|
request(config, decoratorContext, clientName) {
|
|
77
97
|
return __awaiter(this, void 0, void 0, function* () {
|
|
78
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
98
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
79
99
|
// Use the instance's clientName as fallback if not provided
|
|
80
100
|
const effectiveClientName = clientName || this.clientName;
|
|
81
101
|
// If no decorator context provided, try to get it from CallStackExtractor
|
|
@@ -83,22 +103,36 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
|
|
|
83
103
|
const effectiveDecoratorContext = decoratorContext || call_stack_extractor_util_1.CallStackExtractor.getDecoratorContext();
|
|
84
104
|
// ========== 安全验证开始 ==========
|
|
85
105
|
// 构造完整URL进行验证
|
|
86
|
-
const
|
|
106
|
+
const requestURL = config.url || '';
|
|
87
107
|
const baseURL = config.baseURL || this.defaultConfig.baseURL || '';
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
108
|
+
// 构建完整的URL用于验证
|
|
109
|
+
let fullURL = requestURL;
|
|
110
|
+
if (requestURL && !requestURL.startsWith('http://') && !requestURL.startsWith('https://') && baseURL) {
|
|
111
|
+
// 处理相对URL
|
|
112
|
+
try {
|
|
113
|
+
fullURL = new URL(requestURL, baseURL).href;
|
|
114
|
+
}
|
|
115
|
+
catch (_m) {
|
|
116
|
+
// 如果URL构建失败,使用原始URL
|
|
117
|
+
fullURL = requestURL;
|
|
118
|
+
}
|
|
97
119
|
}
|
|
98
|
-
//
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
|
|
120
|
+
// 执行URL安全验证(只对完整URL进行验证)
|
|
121
|
+
if (fullURL && (fullURL.startsWith('http://') || fullURL.startsWith('https://'))) {
|
|
122
|
+
const sanitizeResult = security_validator_util_1.SecurityValidator.sanitizeURL(fullURL, {
|
|
123
|
+
urlConfig: (_a = this.defaultConfig.security) === null || _a === void 0 ? void 0 : _a.urlValidation,
|
|
124
|
+
ssrfConfig: (_b = this.defaultConfig.security) === null || _b === void 0 ? void 0 : _b.ssrfProtection,
|
|
125
|
+
});
|
|
126
|
+
if (!sanitizeResult.valid) {
|
|
127
|
+
const error = new Error(`URL validation failed: ${sanitizeResult.error}`);
|
|
128
|
+
this.logger.error(`URL validation failed for ${fullURL}: ${sanitizeResult.error}`);
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
// 如果URL被修改,更新配置
|
|
132
|
+
if (sanitizeResult.url !== fullURL) {
|
|
133
|
+
// 仅更新完整URL,保留原始的相对路径
|
|
134
|
+
this.logger.debug(`URL sanitized from "${fullURL}" to "${sanitizeResult.url}"`);
|
|
135
|
+
}
|
|
102
136
|
}
|
|
103
137
|
// ========== 安全验证结束 ==========
|
|
104
138
|
// Capture the calling context information
|
|
@@ -156,7 +190,7 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
|
|
|
156
190
|
this.loggingService.logRequestSuccess(response, startTime, requestId, loggingOptions, databaseLogging, retryRecorder.getRecords(), circuitBreakerState, decoratorContext, effectiveClientName, callingContext);
|
|
157
191
|
}
|
|
158
192
|
// 更新统计信息
|
|
159
|
-
this.updateRequestStats(true, Date.now() - startTime, response.config.method, response.status);
|
|
193
|
+
this.updateRequestStats(true, Date.now() - startTime, ((_f = response.config) === null || _f === void 0 ? void 0 : _f.method) || 'GET', response.status);
|
|
160
194
|
return response.data;
|
|
161
195
|
}
|
|
162
196
|
catch (error) {
|
|
@@ -164,16 +198,16 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
|
|
|
164
198
|
const decoratorLogging = effectiveDecoratorContext
|
|
165
199
|
? decorators_1.HttpDecoratorUtils.getLoggingOptions(effectiveDecoratorContext.target, effectiveDecoratorContext.propertyKey)
|
|
166
200
|
: {};
|
|
167
|
-
const loggingOptions = Object.assign(Object.assign(Object.assign({}, this.defaultConfig.logging), decoratorLogging), { databaseLogging: (
|
|
201
|
+
const loggingOptions = Object.assign(Object.assign(Object.assign({}, this.defaultConfig.logging), decoratorLogging), { databaseLogging: (_g = this.defaultConfig.logging) === null || _g === void 0 ? void 0 : _g.databaseLogging });
|
|
168
202
|
if (loggingOptions === null || loggingOptions === void 0 ? void 0 : loggingOptions.enabled) {
|
|
169
203
|
const databaseLogging = (loggingOptions === null || loggingOptions === void 0 ? void 0 : loggingOptions.databaseLog) ||
|
|
170
|
-
((
|
|
204
|
+
((_j = (_h = this.defaultConfig.logging) === null || _h === void 0 ? void 0 : _h.databaseLogging) === null || _j === void 0 ? void 0 : _j.enabled);
|
|
171
205
|
const records = retryRecorder.getRecords();
|
|
172
206
|
this.loggingService.logRequestError(error, startTime, requestId, (records === null || records === void 0 ? void 0 : records.length) ? Math.max(...records.map((r) => r.attempt)) + 1 : 1, loggingOptions, databaseLogging, records, circuitBreakerState, effectiveDecoratorContext, effectiveClientName, callingContext);
|
|
173
207
|
}
|
|
174
208
|
// 更新统计信息
|
|
175
|
-
const errorMethod = (
|
|
176
|
-
const errorStatus = (
|
|
209
|
+
const errorMethod = (_k = error.config) === null || _k === void 0 ? void 0 : _k.method;
|
|
210
|
+
const errorStatus = (_l = error.response) === null || _l === void 0 ? void 0 : _l.status;
|
|
177
211
|
this.updateRequestStats(false, Date.now() - startTime, errorMethod, errorStatus);
|
|
178
212
|
throw error;
|
|
179
213
|
}
|
|
@@ -436,14 +470,49 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
|
|
|
436
470
|
* 创建Axios实例并配置axios-retry
|
|
437
471
|
*/
|
|
438
472
|
createAxiosInstance() {
|
|
439
|
-
var _a;
|
|
440
|
-
|
|
473
|
+
var _a, _b, _c;
|
|
474
|
+
// 合并代理配置和 axiosConfig 透传配置
|
|
475
|
+
const resolvedProxy = this.resolveProxyConfig(this.defaultConfig.proxy);
|
|
476
|
+
// 处理 SSL 配置和 httpsAgent
|
|
477
|
+
let httpsAgent = (_a = this.defaultConfig.axiosConfig) === null || _a === void 0 ? void 0 : _a.httpsAgent;
|
|
478
|
+
if ((_b = this.defaultConfig.axiosConfig) === null || _b === void 0 ? void 0 : _b.ssl) {
|
|
479
|
+
const ssl = this.defaultConfig.axiosConfig.ssl;
|
|
480
|
+
const https = require('https');
|
|
481
|
+
if (!httpsAgent) {
|
|
482
|
+
// 如果没有预定义的 httpsAgent,使用 SSL 配置创建
|
|
483
|
+
httpsAgent = new https.Agent(Object.assign(Object.assign({}, ssl), { keepAlive: true }));
|
|
484
|
+
}
|
|
485
|
+
else {
|
|
486
|
+
// 如果同时提供了 httpsAgent 和 ssl 配置
|
|
487
|
+
// ssl 配置会覆盖 httpsAgent 的相关选项
|
|
488
|
+
this.logger.warn('Both httpsAgent and ssl config provided. SSL config will take precedence for certificate options.');
|
|
489
|
+
// 创建新的 Agent,合并原有配置和 SSL 配置
|
|
490
|
+
httpsAgent = new https.Agent(Object.assign({
|
|
491
|
+
// 保留原 Agent 的选项(除了证书相关)
|
|
492
|
+
keepAlive: true }, ssl));
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
// 构建 axios 创建配置
|
|
496
|
+
const axiosCreateConfig = {
|
|
441
497
|
baseURL: this.defaultConfig.baseURL,
|
|
442
498
|
timeout: this.defaultConfig.timeout,
|
|
443
|
-
proxy:
|
|
444
|
-
|
|
499
|
+
proxy: resolvedProxy !== undefined ? resolvedProxy : undefined,
|
|
500
|
+
// 应用处理后的 httpsAgent
|
|
501
|
+
httpsAgent: httpsAgent,
|
|
502
|
+
};
|
|
503
|
+
// 透传所有其他 axiosConfig 配置(排除已特殊处理的字段)
|
|
504
|
+
// 只在值有效时才覆盖
|
|
505
|
+
if (this.defaultConfig.axiosConfig) {
|
|
506
|
+
const _d = this.defaultConfig.axiosConfig, { ssl, httpsAgent: _ } = _d, restAxiosConfig = __rest(_d, ["ssl", "httpsAgent"]);
|
|
507
|
+
for (const key in restAxiosConfig) {
|
|
508
|
+
if (restAxiosConfig[key] !== undefined) {
|
|
509
|
+
axiosCreateConfig[key] = restAxiosConfig[key];
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
const instance = require('axios').create(axiosCreateConfig);
|
|
445
514
|
// 配置axios-retry
|
|
446
|
-
if ((
|
|
515
|
+
if ((_c = this.defaultConfig.retry) === null || _c === void 0 ? void 0 : _c.enabled) {
|
|
447
516
|
(0, axios_retry_1.default)(instance, {
|
|
448
517
|
retries: this.defaultConfig.retry.retries || 3,
|
|
449
518
|
retryDelay: this.defaultConfig.retry.retryDelay ||
|
|
@@ -546,12 +615,20 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
|
|
|
546
615
|
}
|
|
547
616
|
/**
|
|
548
617
|
* 应用装饰器配置
|
|
618
|
+
* 只在装饰器配置值有效时才覆盖请求配置
|
|
549
619
|
*/
|
|
550
620
|
applyDecoratorConfig(config, decoratorContext) {
|
|
551
621
|
if (!decoratorContext)
|
|
552
622
|
return Object.assign({}, config);
|
|
553
623
|
const httpClientConfig = decorators_1.HttpDecoratorUtils.getHttpClientOptions(decoratorContext.target);
|
|
554
|
-
|
|
624
|
+
// 只在值有效时才合并
|
|
625
|
+
const result = Object.assign({}, config);
|
|
626
|
+
for (const key in httpClientConfig) {
|
|
627
|
+
if (httpClientConfig[key] !== undefined) {
|
|
628
|
+
result[key] = httpClientConfig[key];
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
return result;
|
|
555
632
|
}
|
|
556
633
|
// Note: applyDecoratorConfig is not currently being called in executeWithFeatures
|
|
557
634
|
// The decorator config is already being applied via effectiveDecoratorContext
|
|
@@ -622,10 +699,15 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
|
|
|
622
699
|
}
|
|
623
700
|
/**
|
|
624
701
|
* 深度合并对象
|
|
702
|
+
* 只在源值有效(非 undefined)时才覆盖目标值
|
|
625
703
|
*/
|
|
626
704
|
deepMerge(target, source) {
|
|
627
705
|
const result = Object.assign({}, target);
|
|
628
706
|
for (const key in source) {
|
|
707
|
+
// 跳过 undefined 值,不覆盖目标值
|
|
708
|
+
if (source[key] === undefined) {
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
629
711
|
if (source[key] &&
|
|
630
712
|
typeof source[key] === 'object' &&
|
|
631
713
|
!Array.isArray(source[key])) {
|
package/package.json
CHANGED