@nest-omni/core 4.1.3-19 → 4.1.3-20

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.
Files changed (45) hide show
  1. package/cache/cache.module.d.ts +0 -6
  2. package/cache/cache.module.js +7 -7
  3. package/cache/cache.service.js +12 -0
  4. package/cache/dependencies/db.dependency.d.ts +0 -13
  5. package/cache/dependencies/db.dependency.js +0 -16
  6. package/cache/dependencies/tag.dependency.d.ts +39 -4
  7. package/cache/dependencies/tag.dependency.js +109 -11
  8. package/cache/interfaces/cache-options.interface.d.ts +8 -0
  9. package/cache/providers/memory-cache.provider.d.ts +20 -0
  10. package/cache/providers/memory-cache.provider.js +40 -0
  11. package/http-client/config/http-client.config.d.ts +5 -0
  12. package/http-client/config/http-client.config.js +24 -13
  13. package/http-client/decorators/http-client.decorators.d.ts +1 -25
  14. package/http-client/decorators/http-client.decorators.js +97 -90
  15. package/http-client/entities/http-log.entity.d.ts +0 -20
  16. package/http-client/entities/http-log.entity.js +0 -12
  17. package/http-client/examples/advanced-usage.example.d.ts +4 -5
  18. package/http-client/examples/advanced-usage.example.js +4 -56
  19. package/http-client/http-client.module.d.ts +35 -2
  20. package/http-client/http-client.module.js +80 -75
  21. package/http-client/index.d.ts +1 -1
  22. package/http-client/interfaces/api-client-config.interface.d.ts +1 -91
  23. package/http-client/interfaces/http-client-config.interface.d.ts +53 -62
  24. package/http-client/services/api-client-registry.service.d.ts +5 -23
  25. package/http-client/services/api-client-registry.service.js +41 -284
  26. package/http-client/services/circuit-breaker.service.d.ts +69 -2
  27. package/http-client/services/circuit-breaker.service.js +185 -7
  28. package/http-client/services/http-client.service.d.ts +58 -23
  29. package/http-client/services/http-client.service.js +294 -150
  30. package/http-client/services/http-log-query.service.js +0 -13
  31. package/http-client/services/index.d.ts +0 -1
  32. package/http-client/services/index.js +0 -1
  33. package/http-client/services/logging.service.d.ts +79 -10
  34. package/http-client/services/logging.service.js +246 -51
  35. package/http-client/utils/call-stack-extractor.util.d.ts +26 -0
  36. package/http-client/utils/call-stack-extractor.util.js +35 -0
  37. package/http-client/utils/security-validator.util.d.ts +118 -0
  38. package/http-client/utils/security-validator.util.js +352 -0
  39. package/package.json +1 -1
  40. package/redis-lock/lock-heartbeat.service.d.ts +2 -0
  41. package/redis-lock/lock-heartbeat.service.js +12 -2
  42. package/redis-lock/redis-lock.service.d.ts +4 -0
  43. package/redis-lock/redis-lock.service.js +61 -8
  44. package/http-client/services/cache.service.d.ts +0 -76
  45. package/http-client/services/cache.service.js +0 -333
@@ -22,26 +22,29 @@ Object.defineProperty(exports, "__esModule", { value: true });
22
22
  exports.HttpClientService = void 0;
23
23
  const common_1 = require("@nestjs/common");
24
24
  const axios_retry_1 = require("axios-retry");
25
- const cache_1 = require("../../cache");
25
+ const api_client_config_interface_1 = require("../interfaces/api-client-config.interface");
26
26
  const circuit_breaker_service_1 = require("./circuit-breaker.service");
27
27
  const logging_service_1 = require("./logging.service");
28
- const cache_service_1 = require("./cache.service");
29
28
  const decorators_1 = require("../decorators");
30
29
  const context_extractor_util_1 = require("../utils/context-extractor.util");
31
30
  const curl_generator_util_1 = require("../utils/curl-generator.util");
31
+ const call_stack_extractor_util_1 = require("../utils/call-stack-extractor.util");
32
32
  const retry_recorder_util_1 = require("../utils/retry-recorder.util");
33
33
  const request_id_util_1 = require("../utils/request-id.util");
34
34
  const proxy_environment_util_1 = require("../utils/proxy-environment.util");
35
35
  const redis_lock_service_1 = require("../../redis-lock/redis-lock.service");
36
+ const security_validator_util_1 = require("../utils/security-validator.util");
36
37
  /**
37
38
  * HTTP客户端服务
38
39
  * 基于Spring RestTemplate的设计理念,集成axios-retry库
40
+ * 支持两种创建模式:
41
+ * 1. 直接创建: 用于简单的HTTP请求场景
42
+ * 2. API客户端模式: 用于需要认证、统计等高级功能的API客户端
39
43
  */
40
44
  let HttpClientService = HttpClientService_1 = class HttpClientService {
41
- constructor(circuitBreakerService, loggingService, cacheService, redisLockService, config = {}) {
45
+ constructor(circuitBreakerService, loggingService, redisLockService, config = {}, clientName, authConfig) {
42
46
  this.circuitBreakerService = circuitBreakerService;
43
47
  this.loggingService = loggingService;
44
- this.cacheService = cacheService;
45
48
  this.redisLockService = redisLockService;
46
49
  this.logger = new common_1.Logger(HttpClientService_1.name);
47
50
  this.requestStats = {
@@ -49,55 +52,84 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
49
52
  successfulRequests: 0,
50
53
  failedRequests: 0,
51
54
  totalResponseTime: 0,
55
+ averageResponseTime: 0,
52
56
  requestsByMethod: {},
53
57
  requestsByStatus: {},
54
58
  };
55
59
  this.defaultConfig = this.mergeWithDefaults(config);
60
+ this.clientName = clientName;
61
+ this.authConfig = authConfig;
56
62
  this.axiosInstance = this.createAxiosInstance();
57
63
  }
64
+ /**
65
+ * 静态工厂方法:创建API客户端模式的 HttpClientService
66
+ * @param dependencies 依赖服务
67
+ * @param config API客户端配置
68
+ * @returns HttpClientService 实例
69
+ */
70
+ static createApiClient(dependencies, config) {
71
+ return new HttpClientService_1(dependencies.circuitBreakerService, dependencies.loggingService, dependencies.redisLockService, Object.assign({ baseURL: config.baseURL, timeout: config.timeout || 30000 }, config.httpConfig), config.name, config.auth);
72
+ }
58
73
  /**
59
74
  * 执行HTTP请求
60
75
  */
61
- request(config, decoratorContext) {
76
+ request(config, decoratorContext, clientName) {
62
77
  return __awaiter(this, void 0, void 0, function* () {
63
- var _a, _b, _c, _d, _e, _f, _g;
78
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
79
+ // Use the instance's clientName as fallback if not provided
80
+ const effectiveClientName = clientName || this.clientName;
81
+ // If no decorator context provided, try to get it from CallStackExtractor
82
+ // This allows decorators on service methods to pass context to HTTP client
83
+ const effectiveDecoratorContext = decoratorContext || call_stack_extractor_util_1.CallStackExtractor.getDecoratorContext();
84
+ // ========== 安全验证开始 ==========
85
+ // 构造完整URL进行验证
86
+ const fullURL = config.url || '';
87
+ const baseURL = config.baseURL || this.defaultConfig.baseURL || '';
88
+ // 执行URL安全验证
89
+ const sanitizeResult = security_validator_util_1.SecurityValidator.sanitizeURL(fullURL, {
90
+ urlConfig: (_a = this.defaultConfig.security) === null || _a === void 0 ? void 0 : _a.urlValidation,
91
+ ssrfConfig: (_b = this.defaultConfig.security) === null || _b === void 0 ? void 0 : _b.ssrfProtection,
92
+ });
93
+ if (!sanitizeResult.valid) {
94
+ const error = new Error(`URL validation failed: ${sanitizeResult.error}`);
95
+ this.logger.error(`URL validation failed for ${fullURL}: ${sanitizeResult.error}`);
96
+ throw error;
97
+ }
98
+ // 如果URL被修改,更新配置
99
+ if (sanitizeResult.url !== fullURL) {
100
+ config.url = sanitizeResult.url;
101
+ this.logger.debug(`URL sanitized from "${fullURL}" to "${sanitizeResult.url}"`);
102
+ }
103
+ // ========== 安全验证结束 ==========
104
+ // Capture the calling context information
105
+ const callingContext = this.captureCallingContext();
64
106
  const startTime = Date.now();
65
107
  const retryRecorder = retry_recorder_util_1.RetryRecorder.create();
66
108
  let requestId;
67
109
  let circuitBreakerState;
68
- let cacheHit = false;
69
110
  try {
111
+ // 应用认证配置
112
+ let authConfig = yield this.applyAuthToConfig(config);
70
113
  // 应用装饰器配置
71
- const enhancedConfig = this.applyDecoratorConfig(config, decoratorContext);
114
+ const enhancedConfig = this.applyDecoratorConfig(authConfig, decoratorContext);
115
+ // 将 retryRecorder 存储到 config metadata 中,供 onRetry 回调使用
116
+ enhancedConfig.metadata = enhancedConfig.metadata || {};
117
+ enhancedConfig.metadata.retryRecorder = retryRecorder;
72
118
  // 获取装饰器配置
73
- const decoratorConfigs = decoratorContext
74
- ? decorators_1.HttpDecoratorUtils.getAllDecoratorConfigs(decoratorContext.target, decoratorContext.propertyKey)
119
+ const decoratorConfigs = effectiveDecoratorContext
120
+ ? decorators_1.HttpDecoratorUtils.getAllDecoratorConfigs(effectiveDecoratorContext.target, effectiveDecoratorContext.propertyKey)
75
121
  : {};
76
122
  // 日志记录开始
77
- const loggingOptions = decoratorConfigs.logging || this.defaultConfig.logging;
123
+ const decoratorLogging = decoratorConfigs.logging || {};
124
+ this.logger.debug(`Logging config merge: decoratorLogging=${JSON.stringify(decoratorLogging)}, defaultLogging=${JSON.stringify(this.defaultConfig.logging)}`);
125
+ const loggingOptions = Object.assign(Object.assign(Object.assign({}, this.defaultConfig.logging), decoratorLogging), { databaseLogging: (_c = this.defaultConfig.logging) === null || _c === void 0 ? void 0 : _c.databaseLogging });
126
+ this.logger.debug(`Merged loggingOptions: databaseLog=${loggingOptions.databaseLog}, hasDatabaseLogging=${!!loggingOptions.databaseLogging}`);
78
127
  if (loggingOptions === null || loggingOptions === void 0 ? void 0 : loggingOptions.enabled) {
79
128
  requestId = this.loggingService.logRequestStart(enhancedConfig, loggingOptions);
80
129
  // 将requestId保存到config中
81
130
  enhancedConfig.metadata = enhancedConfig.metadata || {};
82
131
  enhancedConfig.metadata.requestId = requestId;
83
132
  }
84
- // 缓存检查
85
- const cacheConfig = decoratorConfigs.cache || this.defaultConfig.cache;
86
- if ((cacheConfig === null || cacheConfig === void 0 ? void 0 : cacheConfig.enabled) &&
87
- this.shouldCacheRequest(enhancedConfig, cacheConfig)) {
88
- const cachedResponse = yield this.cacheService.get(enhancedConfig, cacheConfig);
89
- if (cachedResponse) {
90
- cacheHit = true;
91
- if (loggingOptions === null || loggingOptions === void 0 ? void 0 : loggingOptions.enabled) {
92
- const databaseLogging = (loggingOptions === null || loggingOptions === void 0 ? void 0 : loggingOptions.databaseLog) ||
93
- ((_b = (_a = this.defaultConfig.logging) === null || _a === void 0 ? void 0 : _a.databaseLogging) === null || _b === void 0 ? void 0 : _b.enabled);
94
- this.loggingService.logRequestSuccess(cachedResponse, startTime, requestId, loggingOptions, databaseLogging, undefined, // no retry records for cache hit
95
- cacheHit, undefined, // circuit breaker state
96
- decoratorContext);
97
- }
98
- return cachedResponse.data;
99
- }
100
- }
101
133
  // 应用超时配置
102
134
  const timeout = decoratorConfigs.timeout || this.defaultConfig.timeout;
103
135
  if (timeout) {
@@ -111,46 +143,38 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
111
143
  enhancedConfig.proxy = resolvedProxy;
112
144
  }
113
145
  }
114
- // 配置axios-retry的onRetry回调以记录重试
115
- if ((_c = this.defaultConfig.retry) === null || _c === void 0 ? void 0 : _c.enabled) {
116
- const originalOnRetry = this.defaultConfig.retry.onRetry;
117
- this.defaultConfig.retry.onRetry = (retryCount, error, requestConfig) => {
118
- // 记录重试
119
- const retryRecord = retry_recorder_util_1.RetryRecorder.recordRetry(retryCount, error, requestConfig, this.calculateRetryDelay(retryCount, this.defaultConfig.retry));
120
- retryRecorder.addRecord(retryRecord);
121
- // 调用原始回调
122
- if (originalOnRetry) {
123
- originalOnRetry(retryCount, error, requestConfig);
124
- }
125
- };
126
- }
127
146
  // 执行请求(带重试和熔断器)
128
- const response = yield this.executeWithFeatures(enhancedConfig, decoratorConfigs, requestId, startTime, retryRecorder, (state) => {
129
- circuitBreakerState = state;
130
- });
131
- // 缓存响应
132
- if ((cacheConfig === null || cacheConfig === void 0 ? void 0 : cacheConfig.enabled) &&
133
- this.shouldCacheRequest(enhancedConfig, cacheConfig)) {
134
- yield this.cacheService.set(enhancedConfig, response, cacheConfig);
135
- }
147
+ const { response, circuitBreakerState: state } = yield this.executeWithFeatures(enhancedConfig, decoratorConfigs, requestId, startTime, retryRecorder);
148
+ // 更新 circuitBreakerState 变量
149
+ circuitBreakerState = state;
150
+ this.logger.debug(`Request completed, circuitBreakerState: ${circuitBreakerState || 'undefined'}`);
136
151
  // 成功日志
137
152
  if (loggingOptions === null || loggingOptions === void 0 ? void 0 : loggingOptions.enabled) {
138
153
  const databaseLogging = (loggingOptions === null || loggingOptions === void 0 ? void 0 : loggingOptions.databaseLogging) ||
139
154
  ((_e = (_d = this.defaultConfig.logging) === null || _d === void 0 ? void 0 : _d.databaseLogging) === null || _e === void 0 ? void 0 : _e.enabled);
140
- this.loggingService.logRequestSuccess(response, startTime, requestId, loggingOptions, databaseLogging, retryRecorder.getRecords(), cacheHit, circuitBreakerState, decoratorContext);
155
+ this.logger.debug(`Logging request success with circuitBreakerState: ${circuitBreakerState || 'undefined'}`);
156
+ this.loggingService.logRequestSuccess(response, startTime, requestId, loggingOptions, databaseLogging, retryRecorder.getRecords(), circuitBreakerState, decoratorContext, effectiveClientName, callingContext);
141
157
  }
158
+ // 更新统计信息
159
+ this.updateRequestStats(true, Date.now() - startTime, response.config.method, response.status);
142
160
  return response.data;
143
161
  }
144
162
  catch (error) {
145
163
  // 错误日志
146
- const loggingOptions = decoratorContext
147
- ? decorators_1.HttpDecoratorUtils.getLoggingOptions(decoratorContext.target, decoratorContext.propertyKey)
148
- : this.defaultConfig.logging;
164
+ const decoratorLogging = effectiveDecoratorContext
165
+ ? decorators_1.HttpDecoratorUtils.getLoggingOptions(effectiveDecoratorContext.target, effectiveDecoratorContext.propertyKey)
166
+ : {};
167
+ const loggingOptions = Object.assign(Object.assign(Object.assign({}, this.defaultConfig.logging), decoratorLogging), { databaseLogging: (_f = this.defaultConfig.logging) === null || _f === void 0 ? void 0 : _f.databaseLogging });
149
168
  if (loggingOptions === null || loggingOptions === void 0 ? void 0 : loggingOptions.enabled) {
150
169
  const databaseLogging = (loggingOptions === null || loggingOptions === void 0 ? void 0 : loggingOptions.databaseLog) ||
151
- ((_g = (_f = this.defaultConfig.logging) === null || _f === void 0 ? void 0 : _f.databaseLogging) === null || _g === void 0 ? void 0 : _g.enabled);
152
- this.loggingService.logRequestError(error, startTime, requestId, retryRecorder.getRecords().length || 1, loggingOptions, databaseLogging, retryRecorder.getRecords(), cacheHit, circuitBreakerState, decoratorContext);
170
+ ((_h = (_g = this.defaultConfig.logging) === null || _g === void 0 ? void 0 : _g.databaseLogging) === null || _h === void 0 ? void 0 : _h.enabled);
171
+ const records = retryRecorder.getRecords();
172
+ 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);
153
173
  }
174
+ // 更新统计信息
175
+ const errorMethod = (_j = error.config) === null || _j === void 0 ? void 0 : _j.method;
176
+ const errorStatus = (_k = error.response) === null || _k === void 0 ? void 0 : _k.status;
177
+ this.updateRequestStats(false, Date.now() - startTime, errorMethod, errorStatus);
154
178
  throw error;
155
179
  }
156
180
  });
@@ -171,14 +195,21 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
171
195
  * 获取请求统计信息
172
196
  */
173
197
  getStats() {
174
- const avgResponseTime = this.requestStats.totalRequests > 0
175
- ? this.requestStats.totalResponseTime / this.requestStats.totalRequests
176
- : 0;
177
- return Object.assign(Object.assign({}, this.requestStats), { averageResponseTime: avgResponseTime, successRate: this.requestStats.totalRequests > 0
198
+ return {
199
+ totalRequests: this.requestStats.totalRequests,
200
+ successfulRequests: this.requestStats.successfulRequests,
201
+ failedRequests: this.requestStats.failedRequests,
202
+ totalResponseTime: this.requestStats.totalResponseTime,
203
+ averageResponseTime: this.requestStats.averageResponseTime,
204
+ successRate: this.requestStats.totalRequests > 0
178
205
  ? (this.requestStats.successfulRequests /
179
206
  this.requestStats.totalRequests) *
180
207
  100
181
- : 0, circuitBreakerStats: this.circuitBreakerService.getAllStates(), cacheStats: this.cacheService.getStats() });
208
+ : 0,
209
+ requestsByMethod: this.requestStats.requestsByMethod,
210
+ requestsByStatus: this.requestStats.requestsByStatus,
211
+ circuitBreakerStats: this.circuitBreakerService.getAllStates(),
212
+ };
182
213
  }
183
214
  /**
184
215
  * 重置统计信息
@@ -189,34 +220,69 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
189
220
  successfulRequests: 0,
190
221
  failedRequests: 0,
191
222
  totalResponseTime: 0,
223
+ averageResponseTime: 0,
192
224
  requestsByMethod: {},
193
225
  requestsByStatus: {},
194
226
  };
195
227
  }
228
+ /**
229
+ * 获取客户端名称
230
+ */
231
+ getName() {
232
+ return this.clientName;
233
+ }
234
+ /**
235
+ * Capture calling context information
236
+ * @returns Calling context with service class and method name
237
+ */
238
+ captureCallingContext() {
239
+ try {
240
+ // Use CallStackExtractor to get the calling context
241
+ const stackInfo = call_stack_extractor_util_1.CallStackExtractor.getCallInfo();
242
+ // Skip internal HTTP client calls
243
+ if (stackInfo.serviceClass && stackInfo.methodName) {
244
+ // Check if it's an internal call
245
+ const isInternal = call_stack_extractor_util_1.CallStackExtractor['isInternalCall'](stackInfo.serviceClass);
246
+ if (!isInternal) {
247
+ return {
248
+ serviceClass: stackInfo.serviceClass,
249
+ methodName: stackInfo.methodName,
250
+ operationName: stackInfo.operationName,
251
+ };
252
+ }
253
+ }
254
+ // If no useful context found, return empty
255
+ return {};
256
+ }
257
+ catch (error) {
258
+ this.logger.warn('Failed to capture calling context:', error.message);
259
+ return {};
260
+ }
261
+ }
196
262
  // 便捷方法
197
- get(url, config) {
263
+ get(url, config, clientName) {
198
264
  return __awaiter(this, void 0, void 0, function* () {
199
- return this.request(Object.assign(Object.assign({}, config), { method: 'GET', url }));
265
+ return this.request(Object.assign(Object.assign({}, config), { method: 'GET', url }), undefined, clientName);
200
266
  });
201
267
  }
202
- post(url, data, config) {
268
+ post(url, data, config, clientName) {
203
269
  return __awaiter(this, void 0, void 0, function* () {
204
- return this.request(Object.assign(Object.assign({}, config), { method: 'POST', url, data }));
270
+ return this.request(Object.assign(Object.assign({}, config), { method: 'POST', url, data }), undefined, clientName);
205
271
  });
206
272
  }
207
- put(url, data, config) {
273
+ put(url, data, config, clientName) {
208
274
  return __awaiter(this, void 0, void 0, function* () {
209
- return this.request(Object.assign(Object.assign({}, config), { method: 'PUT', url, data }));
275
+ return this.request(Object.assign(Object.assign({}, config), { method: 'PUT', url, data }), undefined, clientName);
210
276
  });
211
277
  }
212
- patch(url, data, config) {
278
+ patch(url, data, config, clientName) {
213
279
  return __awaiter(this, void 0, void 0, function* () {
214
- return this.request(Object.assign(Object.assign({}, config), { method: 'PATCH', url, data }));
280
+ return this.request(Object.assign(Object.assign({}, config), { method: 'PATCH', url, data }), undefined, clientName);
215
281
  });
216
282
  }
217
- delete(url, config) {
283
+ delete(url, config, clientName) {
218
284
  return __awaiter(this, void 0, void 0, function* () {
219
- return this.request(Object.assign(Object.assign({}, config), { method: 'DELETE', url }));
285
+ return this.request(Object.assign(Object.assign({}, config), { method: 'DELETE', url }), undefined, clientName);
220
286
  });
221
287
  }
222
288
  /**
@@ -228,11 +294,12 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
228
294
  * @param options 等待锁选项
229
295
  * @returns 响应数据
230
296
  */
231
- authRequest(config, tokenProvider, options) {
297
+ authRequest(config, tokenProvider, options, clientName) {
232
298
  return __awaiter(this, void 0, void 0, function* () {
299
+ const effectiveClientName = clientName || this.clientName;
233
300
  if (!this.redisLockService) {
234
301
  this.logger.warn('RedisLockService not available, proceeding without lock');
235
- return this.request(config);
302
+ return this.request(config, undefined, effectiveClientName);
236
303
  }
237
304
  const { lockKey = `auth-request:${(0, request_id_util_1.generateRequestId)()}`, lockTimeout = 30000, waitTimeout = 60000, enableRetry = true, } = options || {};
238
305
  try {
@@ -243,7 +310,7 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
243
310
  // 添加认证头
244
311
  const authConfig = Object.assign(Object.assign({}, config), { headers: Object.assign(Object.assign({}, config.headers), { Authorization: `Bearer ${token}` }) });
245
312
  // 执行请求
246
- return this.request(authConfig);
313
+ return this.request(authConfig, undefined, effectiveClientName);
247
314
  }), {
248
315
  ttl: lockTimeout,
249
316
  retryCount: enableRetry ? 3 : 0,
@@ -262,7 +329,7 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
262
329
  this.logger.warn('Retrying auth request without lock');
263
330
  const token = yield tokenProvider();
264
331
  const authConfig = Object.assign(Object.assign({}, config), { headers: Object.assign(Object.assign({}, config.headers), { Authorization: `Bearer ${token}` }) });
265
- return this.request(authConfig);
332
+ return this.request(authConfig, undefined, effectiveClientName);
266
333
  }
267
334
  throw error;
268
335
  }
@@ -277,8 +344,9 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
277
344
  * @param options 等待锁选项
278
345
  * @returns 响应数组
279
346
  */
280
- authBatchRequest(requests, tokenProvider, options) {
347
+ authBatchRequest(requests, tokenProvider, options, clientName) {
281
348
  return __awaiter(this, void 0, void 0, function* () {
349
+ const effectiveClientName = clientName || this.clientName;
282
350
  if (!this.redisLockService) {
283
351
  this.logger.warn('RedisLockService not available, proceeding batch auth requests without lock');
284
352
  // 无锁批量执行
@@ -286,7 +354,7 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
286
354
  const promises = requests.map((request) => __awaiter(this, void 0, void 0, function* () {
287
355
  try {
288
356
  const authConfig = Object.assign(Object.assign({}, request.config), { headers: Object.assign(Object.assign({}, request.config.headers), { Authorization: `Bearer ${token}` }) });
289
- return yield this.request(authConfig);
357
+ return yield this.request(authConfig, undefined, effectiveClientName);
290
358
  }
291
359
  catch (error) {
292
360
  this.logger.error(`Batch auth request failed for key: ${request.key}`, error);
@@ -308,7 +376,7 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
308
376
  const executeRequest = (requestConfig, key, index) => __awaiter(this, void 0, void 0, function* () {
309
377
  try {
310
378
  const authConfig = Object.assign(Object.assign({}, requestConfig), { headers: Object.assign(Object.assign({}, requestConfig.headers), { Authorization: `Bearer ${token}` }) });
311
- const result = yield this.request(authConfig);
379
+ const result = yield this.request(authConfig, undefined, effectiveClientName);
312
380
  results[index] = result;
313
381
  return result;
314
382
  }
@@ -388,11 +456,27 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
388
456
  return status >= 500 || status === 429; // 5xx错误或429限流
389
457
  }),
390
458
  shouldResetTimeout: this.defaultConfig.retry.shouldResetTimeout !== false,
391
- onRetry: this.defaultConfig.retry.onRetry ||
392
- ((retryCount, error, requestConfig) => {
393
- var _a;
394
- this.logger.warn(`Retrying request (attempt ${retryCount}): ${(_a = requestConfig.method) === null || _a === void 0 ? void 0 : _a.toUpperCase()} ${requestConfig.url}`);
395
- }),
459
+ onRetry: (retryCount, error, requestConfig) => {
460
+ var _a, _b, _c, _d;
461
+ // 从 request config metadata 中获取 retryRecorder
462
+ const retryRecorder = (_a = requestConfig.metadata) === null || _a === void 0 ? void 0 : _a.retryRecorder;
463
+ if (retryRecorder) {
464
+ this.logger.debug(`Recording retry attempt ${retryCount} for ${(_b = requestConfig.method) === null || _b === void 0 ? void 0 : _b.toUpperCase()} ${requestConfig.url}`);
465
+ const retryRecord = retry_recorder_util_1.RetryRecorder.recordRetry(retryCount, error, requestConfig, this.calculateRetryDelay(retryCount, this.defaultConfig.retry));
466
+ retryRecorder.addRecord(retryRecord);
467
+ }
468
+ else {
469
+ this.logger.warn(`RetryRecorder not found in request config metadata for ${(_c = requestConfig.method) === null || _c === void 0 ? void 0 : _c.toUpperCase()} ${requestConfig.url}. Retry records will not be saved.`);
470
+ }
471
+ // 调用用户自定义的 onRetry 回调
472
+ if (this.defaultConfig.retry.onRetry) {
473
+ this.defaultConfig.retry.onRetry(retryCount, error, requestConfig);
474
+ }
475
+ else {
476
+ // 默认日志
477
+ this.logger.warn(`Retrying request (attempt ${retryCount}): ${(_d = requestConfig.method) === null || _d === void 0 ? void 0 : _d.toUpperCase()} ${requestConfig.url}`);
478
+ }
479
+ },
396
480
  });
397
481
  }
398
482
  // 设置请求拦截器
@@ -405,7 +489,8 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
405
489
  if (context.userId) {
406
490
  config.headers['X-User-ID'] = context.userId;
407
491
  }
408
- // 存储requestId到config中供后续使用
492
+ // 存储requestId到config metadata中供后续使用
493
+ // 注意:不要覆盖已存在的 metadata,保留 retryRecorder 等其他数据
409
494
  config.metadata = config.metadata || {};
410
495
  config.metadata.requestId = requestId;
411
496
  return config;
@@ -414,17 +499,19 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
414
499
  return Promise.reject(error);
415
500
  });
416
501
  // 设置响应拦截器
502
+ // 注意: 统计信息已移至 request() 方法中统一管理,避免双重计数
417
503
  instance.interceptors.response.use((response) => {
418
- this.updateStats(response);
504
+ // 不再在这里更新统计,由 request() 方法统一管理
419
505
  return response;
420
506
  }, (error) => {
421
- this.updateErrorStats(error);
507
+ // 不再在这里更新统计,由 request() 方法统一管理
422
508
  return Promise.reject(error);
423
509
  });
424
510
  return instance;
425
511
  }
426
512
  /**
427
513
  * 执行带特性的请求(重试、熔断器等)
514
+ * 返回响应和熔断器状态
428
515
  */
429
516
  executeWithFeatures(config, decoratorConfigs, requestId, startTime, retryRecorder, onCircuitBreakerStateChange) {
430
517
  return __awaiter(this, void 0, void 0, function* () {
@@ -433,10 +520,17 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
433
520
  const circuitBreakerConfig = decoratorConfigs.circuitBreaker || this.defaultConfig.circuitBreaker;
434
521
  if (circuitBreakerConfig === null || circuitBreakerConfig === void 0 ? void 0 : circuitBreakerConfig.enabled) {
435
522
  const circuitBreakerKey = this.generateCircuitBreakerKey(config);
436
- return this.circuitBreakerService.executeWithCircuitBreaker(circuitBreakerKey, requestFn, circuitBreakerConfig);
523
+ this.logger.debug(`Circuit breaker key: ${circuitBreakerKey}`);
524
+ // 执行请求
525
+ const result = yield this.circuitBreakerService.executeWithCircuitBreaker(circuitBreakerKey, requestFn, circuitBreakerConfig, onCircuitBreakerStateChange);
526
+ // 请求完成后,主动获取当前熔断器状态
527
+ const currentState = this.circuitBreakerService.getCircuitBreakerState(circuitBreakerKey);
528
+ this.logger.debug(`After request, circuit breaker state: ${currentState || 'undefined'}`);
529
+ return { response: result, circuitBreakerState: currentState };
437
530
  }
438
531
  else {
439
- return requestFn();
532
+ const result = yield requestFn();
533
+ return { response: result, circuitBreakerState: undefined };
440
534
  }
441
535
  });
442
536
  }
@@ -459,60 +553,27 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
459
553
  const httpClientConfig = decorators_1.HttpDecoratorUtils.getHttpClientOptions(decoratorContext.target);
460
554
  return Object.assign(Object.assign({}, config), httpClientConfig);
461
555
  }
462
- /**
463
- * 判断是否应该缓存请求
464
- */
465
- shouldCacheRequest(config, cacheConfig) {
466
- var _a;
467
- const method = (_a = config.method) === null || _a === void 0 ? void 0 : _a.toLowerCase();
468
- return cacheConfig.cacheableMethods.includes(method || 'get');
469
- }
556
+ // Note: applyDecoratorConfig is not currently being called in executeWithFeatures
557
+ // The decorator config is already being applied via effectiveDecoratorContext
470
558
  /**
471
559
  * 生成熔断器键
472
560
  */
473
561
  generateCircuitBreakerKey(config) {
474
562
  var _a;
475
563
  const method = ((_a = config.method) === null || _a === void 0 ? void 0 : _a.toUpperCase()) || 'UNKNOWN';
476
- const url = new URL(config.url || '', this.defaultConfig.baseURL || 'http://localhost');
477
- return `${method}:${url.host}${url.pathname}`;
478
- }
479
- /**
480
- * 更新请求统计
481
- */
482
- updateStats(response) {
483
- var _a, _b;
484
- this.requestStats.totalRequests++;
485
- this.requestStats.successfulRequests++;
486
- const method = ((_a = response.config.method) === null || _a === void 0 ? void 0 : _a.toUpperCase()) || 'UNKNOWN';
487
- const status = response.status;
488
- this.requestStats.requestsByMethod[method] =
489
- (this.requestStats.requestsByMethod[method] || 0) + 1;
490
- this.requestStats.requestsByStatus[status] =
491
- (this.requestStats.requestsByStatus[status] || 0) + 1;
492
- if ((_b = response.config.metadata) === null || _b === void 0 ? void 0 : _b.startTime) {
493
- const responseTime = Date.now() -
494
- response.config.metadata
495
- .startTime;
496
- this.requestStats.totalResponseTime += responseTime;
564
+ let url;
565
+ try {
566
+ // Try to construct URL with proper base
567
+ url = new URL(config.url || '', this.defaultConfig.baseURL || 'http://localhost');
497
568
  }
498
- }
499
- /**
500
- * 更新错误统计
501
- */
502
- updateErrorStats(error) {
503
- var _a;
504
- this.requestStats.totalRequests++;
505
- this.requestStats.failedRequests++;
506
- if (error.config) {
507
- const method = ((_a = error.config.method) === null || _a === void 0 ? void 0 : _a.toUpperCase()) || 'UNKNOWN';
508
- this.requestStats.requestsByMethod[method] =
509
- (this.requestStats.requestsByMethod[method] || 0) + 1;
510
- if (error.response) {
511
- const status = error.response.status;
512
- this.requestStats.requestsByStatus[status] =
513
- (this.requestStats.requestsByStatus[status] || 0) + 1;
514
- }
569
+ catch (error) {
570
+ // If URL construction fails, fall back to simple string format
571
+ this.logger.warn(`Failed to construct URL for circuit breaker key: ${config.url}, using fallback format`);
572
+ const baseURL = this.defaultConfig.baseURL || 'http://localhost';
573
+ const urlPath = config.url || '/';
574
+ return `${method}:${baseURL}${urlPath}`;
515
575
  }
576
+ return `${method}:${url.host}${url.pathname}`;
516
577
  }
517
578
  /**
518
579
  * 合并默认配置
@@ -541,15 +602,6 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
541
602
  minimumThroughputThreshold: 10,
542
603
  countHalfOpenCalls: true,
543
604
  },
544
- cache: {
545
- enabled: true,
546
- defaultTtl: 300000, // 5分钟
547
- cacheableMethods: ['get'],
548
- cacheableStatusCodes: [200, 201, 202, 204, 301, 302, 304],
549
- options: {
550
- layers: [cache_1.CacheLayer.MEMORY, cache_1.CacheLayer.REDIS],
551
- },
552
- },
553
605
  logging: {
554
606
  enabled: true,
555
607
  logRequests: true,
@@ -561,9 +613,8 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
561
613
  sanitizeHeaders: ['authorization', 'apikey', 'password', 'token'],
562
614
  logLevel: 'info',
563
615
  databaseLogging: {
564
- enabled: false,
616
+ enabled: true,
565
617
  dataSource: 'default',
566
- tableName: 'http_logs',
567
618
  },
568
619
  },
569
620
  };
@@ -594,6 +645,7 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
594
645
  * @returns 解析后的代理配置,如果不应使用代理则返回 false 或 undefined
595
646
  */
596
647
  resolveProxyConfig(proxyConfig, targetUrl) {
648
+ var _a, _b, _c, _d;
597
649
  if (!(proxyConfig === null || proxyConfig === void 0 ? void 0 : proxyConfig.enabled)) {
598
650
  return undefined;
599
651
  }
@@ -619,31 +671,123 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
619
671
  this.logger.debug('Proxy bypassed based on environment configuration');
620
672
  return false;
621
673
  }
622
- return {
674
+ const result = {
623
675
  host: envProxy.host,
624
676
  port: envProxy.port,
625
677
  protocol: envProxy.protocol,
626
- auth: envProxy.auth,
627
678
  };
679
+ // 只有当 auth 存在且包含有效值时才添加
680
+ if (((_a = envProxy.auth) === null || _a === void 0 ? void 0 : _a.username) && ((_b = envProxy.auth) === null || _b === void 0 ? void 0 : _b.password)) {
681
+ result.auth = {
682
+ username: envProxy.auth.username,
683
+ password: envProxy.auth.password,
684
+ };
685
+ }
686
+ return result;
628
687
  }
629
688
  // 使用手动配置
630
689
  if (proxyConfig.host && proxyConfig.port) {
631
- return {
690
+ const result = {
632
691
  host: proxyConfig.host,
633
692
  port: proxyConfig.port,
634
693
  protocol: proxyConfig.protocol,
635
- auth: proxyConfig.auth,
636
694
  };
695
+ // 只有当 auth 存在且包含有效值时才添加
696
+ if (((_c = proxyConfig.auth) === null || _c === void 0 ? void 0 : _c.username) && ((_d = proxyConfig.auth) === null || _d === void 0 ? void 0 : _d.password)) {
697
+ result.auth = {
698
+ username: proxyConfig.auth.username,
699
+ password: proxyConfig.auth.password,
700
+ };
701
+ }
702
+ return result;
637
703
  }
638
704
  this.logger.warn('Proxy is enabled but neither fromEnvironment nor manual configuration is provided');
639
705
  return undefined;
640
706
  }
707
+ /**
708
+ * 应用认证配置到请求配置
709
+ */
710
+ applyAuthToConfig(config) {
711
+ return __awaiter(this, void 0, void 0, function* () {
712
+ if (!this.authConfig) {
713
+ return config;
714
+ }
715
+ // Create a new config object to avoid type issues
716
+ const authConfig = Object.assign({}, config);
717
+ switch (this.authConfig.type) {
718
+ case api_client_config_interface_1.AuthType.NONE:
719
+ break;
720
+ case api_client_config_interface_1.AuthType.API_KEY:
721
+ const apiKeyConfig = this.authConfig.config;
722
+ if (apiKeyConfig.location === 'header') {
723
+ const headerName = apiKeyConfig.name || 'X-API-Key';
724
+ const value = apiKeyConfig.prefix
725
+ ? `${apiKeyConfig.prefix} ${apiKeyConfig.key}`
726
+ : apiKeyConfig.key;
727
+ authConfig.headers = Object.assign(Object.assign({}, authConfig.headers), { [headerName]: value });
728
+ }
729
+ else if (apiKeyConfig.location === 'query') {
730
+ const paramName = apiKeyConfig.name || 'api_key';
731
+ authConfig.params = Object.assign(Object.assign({}, authConfig.params), { [paramName]: apiKeyConfig.key });
732
+ }
733
+ break;
734
+ case api_client_config_interface_1.AuthType.BEARER_TOKEN:
735
+ const bearerConfig = this.authConfig.config;
736
+ const scheme = bearerConfig.scheme || 'Bearer';
737
+ authConfig.headers = Object.assign(Object.assign({}, authConfig.headers), { Authorization: `${scheme} ${bearerConfig.token}` });
738
+ break;
739
+ case api_client_config_interface_1.AuthType.BASIC_AUTH:
740
+ const basicConfig = this.authConfig.config;
741
+ const credentials = Buffer.from(`${basicConfig.username}:${basicConfig.password}`).toString('base64');
742
+ authConfig.headers = Object.assign(Object.assign({}, authConfig.headers), { Authorization: `Basic ${credentials}` });
743
+ break;
744
+ case api_client_config_interface_1.AuthType.OAUTH2:
745
+ const oauthConfig = this.authConfig.config;
746
+ // For now, using the access token directly if available
747
+ if (oauthConfig.accessToken) {
748
+ authConfig.headers = Object.assign(Object.assign({}, authConfig.headers), { Authorization: `Bearer ${oauthConfig.accessToken}` });
749
+ }
750
+ break;
751
+ case api_client_config_interface_1.AuthType.CUSTOM:
752
+ // Handle CUSTOM authentication which requires async processing
753
+ const customConfig = this.authConfig.config;
754
+ return yield customConfig.authenticator(authConfig);
755
+ }
756
+ return authConfig;
757
+ });
758
+ }
759
+ /**
760
+ * 更新请求统计信息
761
+ * 统一管理所有统计,避免在拦截器中重复统计
762
+ */
763
+ updateRequestStats(success, responseTime, method, status) {
764
+ this.requestStats.totalRequests++;
765
+ if (success) {
766
+ this.requestStats.successfulRequests++;
767
+ }
768
+ else {
769
+ this.requestStats.failedRequests++;
770
+ }
771
+ this.requestStats.totalResponseTime += responseTime;
772
+ this.requestStats.averageResponseTime =
773
+ this.requestStats.totalResponseTime / this.requestStats.totalRequests;
774
+ // 统计方法分布
775
+ if (method) {
776
+ const normalizedMethod = method.toUpperCase();
777
+ this.requestStats.requestsByMethod[normalizedMethod] =
778
+ (this.requestStats.requestsByMethod[normalizedMethod] || 0) + 1;
779
+ }
780
+ // 统计状态码分布
781
+ if (status) {
782
+ this.requestStats.requestsByStatus[status] =
783
+ (this.requestStats.requestsByStatus[status] || 0) + 1;
784
+ }
785
+ }
641
786
  };
642
787
  exports.HttpClientService = HttpClientService;
643
788
  exports.HttpClientService = HttpClientService = HttpClientService_1 = __decorate([
644
789
  (0, common_1.Injectable)(),
645
790
  __metadata("design:paramtypes", [circuit_breaker_service_1.HttpCircuitBreakerService,
646
791
  logging_service_1.HttpLoggingService,
647
- cache_service_1.HttpCacheService,
648
- redis_lock_service_1.RedisLockService, Object])
792
+ redis_lock_service_1.RedisLockService, Object, String, Object])
649
793
  ], HttpClientService);