@nest-omni/core 4.1.3-23 → 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/interfaces/api-client-config.interface.d.ts +23 -12
- 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 +64 -23
- package/package.json +1 -1
|
@@ -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客户端实例配置
|
|
@@ -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
|
/**
|
|
@@ -79,14 +79,23 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
|
|
|
79
79
|
* @returns HttpClientService 实例
|
|
80
80
|
*/
|
|
81
81
|
static createApiClient(dependencies, config) {
|
|
82
|
-
|
|
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);
|
|
83
92
|
}
|
|
84
93
|
/**
|
|
85
94
|
* 执行HTTP请求
|
|
86
95
|
*/
|
|
87
96
|
request(config, decoratorContext, clientName) {
|
|
88
97
|
return __awaiter(this, void 0, void 0, function* () {
|
|
89
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
98
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
90
99
|
// Use the instance's clientName as fallback if not provided
|
|
91
100
|
const effectiveClientName = clientName || this.clientName;
|
|
92
101
|
// If no decorator context provided, try to get it from CallStackExtractor
|
|
@@ -94,22 +103,36 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
|
|
|
94
103
|
const effectiveDecoratorContext = decoratorContext || call_stack_extractor_util_1.CallStackExtractor.getDecoratorContext();
|
|
95
104
|
// ========== 安全验证开始 ==========
|
|
96
105
|
// 构造完整URL进行验证
|
|
97
|
-
const
|
|
106
|
+
const requestURL = config.url || '';
|
|
98
107
|
const baseURL = config.baseURL || this.defaultConfig.baseURL || '';
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
+
}
|
|
108
119
|
}
|
|
109
|
-
//
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
|
|
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
|
+
}
|
|
113
136
|
}
|
|
114
137
|
// ========== 安全验证结束 ==========
|
|
115
138
|
// Capture the calling context information
|
|
@@ -167,7 +190,7 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
|
|
|
167
190
|
this.loggingService.logRequestSuccess(response, startTime, requestId, loggingOptions, databaseLogging, retryRecorder.getRecords(), circuitBreakerState, decoratorContext, effectiveClientName, callingContext);
|
|
168
191
|
}
|
|
169
192
|
// 更新统计信息
|
|
170
|
-
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);
|
|
171
194
|
return response.data;
|
|
172
195
|
}
|
|
173
196
|
catch (error) {
|
|
@@ -175,16 +198,16 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
|
|
|
175
198
|
const decoratorLogging = effectiveDecoratorContext
|
|
176
199
|
? decorators_1.HttpDecoratorUtils.getLoggingOptions(effectiveDecoratorContext.target, effectiveDecoratorContext.propertyKey)
|
|
177
200
|
: {};
|
|
178
|
-
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 });
|
|
179
202
|
if (loggingOptions === null || loggingOptions === void 0 ? void 0 : loggingOptions.enabled) {
|
|
180
203
|
const databaseLogging = (loggingOptions === null || loggingOptions === void 0 ? void 0 : loggingOptions.databaseLog) ||
|
|
181
|
-
((
|
|
204
|
+
((_j = (_h = this.defaultConfig.logging) === null || _h === void 0 ? void 0 : _h.databaseLogging) === null || _j === void 0 ? void 0 : _j.enabled);
|
|
182
205
|
const records = retryRecorder.getRecords();
|
|
183
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);
|
|
184
207
|
}
|
|
185
208
|
// 更新统计信息
|
|
186
|
-
const errorMethod = (
|
|
187
|
-
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;
|
|
188
211
|
this.updateRequestStats(false, Date.now() - startTime, errorMethod, errorStatus);
|
|
189
212
|
throw error;
|
|
190
213
|
}
|
|
@@ -478,9 +501,14 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
|
|
|
478
501
|
httpsAgent: httpsAgent,
|
|
479
502
|
};
|
|
480
503
|
// 透传所有其他 axiosConfig 配置(排除已特殊处理的字段)
|
|
504
|
+
// 只在值有效时才覆盖
|
|
481
505
|
if (this.defaultConfig.axiosConfig) {
|
|
482
506
|
const _d = this.defaultConfig.axiosConfig, { ssl, httpsAgent: _ } = _d, restAxiosConfig = __rest(_d, ["ssl", "httpsAgent"]);
|
|
483
|
-
|
|
507
|
+
for (const key in restAxiosConfig) {
|
|
508
|
+
if (restAxiosConfig[key] !== undefined) {
|
|
509
|
+
axiosCreateConfig[key] = restAxiosConfig[key];
|
|
510
|
+
}
|
|
511
|
+
}
|
|
484
512
|
}
|
|
485
513
|
const instance = require('axios').create(axiosCreateConfig);
|
|
486
514
|
// 配置axios-retry
|
|
@@ -587,12 +615,20 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
|
|
|
587
615
|
}
|
|
588
616
|
/**
|
|
589
617
|
* 应用装饰器配置
|
|
618
|
+
* 只在装饰器配置值有效时才覆盖请求配置
|
|
590
619
|
*/
|
|
591
620
|
applyDecoratorConfig(config, decoratorContext) {
|
|
592
621
|
if (!decoratorContext)
|
|
593
622
|
return Object.assign({}, config);
|
|
594
623
|
const httpClientConfig = decorators_1.HttpDecoratorUtils.getHttpClientOptions(decoratorContext.target);
|
|
595
|
-
|
|
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;
|
|
596
632
|
}
|
|
597
633
|
// Note: applyDecoratorConfig is not currently being called in executeWithFeatures
|
|
598
634
|
// The decorator config is already being applied via effectiveDecoratorContext
|
|
@@ -663,10 +699,15 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
|
|
|
663
699
|
}
|
|
664
700
|
/**
|
|
665
701
|
* 深度合并对象
|
|
702
|
+
* 只在源值有效(非 undefined)时才覆盖目标值
|
|
666
703
|
*/
|
|
667
704
|
deepMerge(target, source) {
|
|
668
705
|
const result = Object.assign({}, target);
|
|
669
706
|
for (const key in source) {
|
|
707
|
+
// 跳过 undefined 值,不覆盖目标值
|
|
708
|
+
if (source[key] === undefined) {
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
670
711
|
if (source[key] &&
|
|
671
712
|
typeof source[key] === 'object' &&
|
|
672
713
|
!Array.isArray(source[key])) {
|
package/package.json
CHANGED