@nest-omni/core 4.1.3-27 → 4.1.3-29

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 (32) hide show
  1. package/audit/interceptors/audit-action.interceptor.d.ts +1 -0
  2. package/audit/interceptors/audit-action.interceptor.js +5 -3
  3. package/audit/services/audit-action.service.d.ts +1 -0
  4. package/audit/services/audit-action.service.js +5 -3
  5. package/audit/services/operation-description.service.d.ts +1 -0
  6. package/audit/services/operation-description.service.js +5 -3
  7. package/audit/services/transaction-audit.service.d.ts +1 -0
  8. package/audit/services/transaction-audit.service.js +6 -4
  9. package/common/helpers/validation-metadata-helper.d.ts +57 -0
  10. package/common/helpers/validation-metadata-helper.js +109 -5
  11. package/decorators/examples/field-i18n.example.d.ts +294 -0
  12. package/decorators/examples/field-i18n.example.js +478 -0
  13. package/decorators/field.decorators.d.ts +23 -0
  14. package/decorators/field.decorators.js +7 -2
  15. package/decorators/translate.decorator.d.ts +26 -0
  16. package/decorators/translate.decorator.js +26 -1
  17. package/http-client/decorators/http-client.decorators.d.ts +1 -0
  18. package/http-client/decorators/http-client.decorators.js +47 -30
  19. package/http-client/http-client.module.d.ts +8 -0
  20. package/http-client/http-client.module.js +24 -24
  21. package/http-client/services/http-client.service.js +56 -11
  22. package/http-client/utils/context-extractor.util.d.ts +2 -0
  23. package/http-client/utils/context-extractor.util.js +15 -3
  24. package/interceptors/index.d.ts +0 -1
  25. package/interceptors/index.js +0 -1
  26. package/interceptors/translation-interceptor.service.d.ts +7 -0
  27. package/interceptors/translation-interceptor.service.js +40 -8
  28. package/package.json +1 -1
  29. package/setup/bootstrap.setup.js +1 -1
  30. package/shared/services/api-config.service.js +18 -3
  31. package/interceptors/http-logging-interceptor.service.d.ts +0 -38
  32. package/interceptors/http-logging-interceptor.service.js +0 -167
@@ -28,21 +28,14 @@ exports.HttpClient = HttpClient;
28
28
  */
29
29
  const HttpRetry = (options = {}) => {
30
30
  return (target, propertyKey, descriptor) => {
31
- const retryOptions = {
32
- enabled: true,
33
- retries: options.retries || 3,
34
- retryDelay: options.retryDelay || ((retryCount) => Math.pow(2, retryCount) * 1000),
35
- retryCondition: options.retryCondition ||
31
+ const retryOptions = Object.assign(Object.assign({}, (options.enabled !== undefined && { enabled: options.enabled })), { retries: options.retries || 3, retryDelay: options.retryDelay || ((retryCount) => Math.pow(2, retryCount) * 1000), retryCondition: options.retryCondition ||
36
32
  ((error) => {
37
33
  // 默认重试条件:网络错误、超时、5xx错误、429错误
38
34
  if (!error.response)
39
35
  return true; // 网络错误
40
36
  const status = error.response.status;
41
37
  return status >= 500 || status === 429; // 5xx错误或429限流
42
- }),
43
- shouldResetTimeout: options.shouldResetTimeout !== false,
44
- onRetry: options.onRetry,
45
- };
38
+ }), shouldResetTimeout: options.shouldResetTimeout !== false, onRetry: options.onRetry });
46
39
  // Wrap the method to pass decorator context to HTTP client
47
40
  const originalMethod = descriptor.value;
48
41
  const wrappedMethod = function (...args) {
@@ -51,12 +44,17 @@ const HttpRetry = (options = {}) => {
51
44
  target,
52
45
  propertyKey,
53
46
  });
54
- try {
55
- return originalMethod.apply(this, args);
56
- }
57
- finally {
58
- call_stack_extractor_util_1.CallStackExtractor.clearDecoratorContext();
47
+ // 获取原始方法的返回值
48
+ const result = originalMethod.apply(this, args);
49
+ // 如果是 Promise,等待其完成后再清除上下文
50
+ if (result && typeof result.then === 'function') {
51
+ return result.finally(() => {
52
+ call_stack_extractor_util_1.CallStackExtractor.clearDecoratorContext();
53
+ });
59
54
  }
55
+ // 同步方法,立即清除上下文
56
+ call_stack_extractor_util_1.CallStackExtractor.clearDecoratorContext();
57
+ return result;
60
58
  };
61
59
  // Copy metadata from original to wrapped method
62
60
  const metadataKeys = Reflect.getMetadataKeys(originalMethod);
@@ -87,6 +85,7 @@ const HttpCircuitBreaker = (options = {}) => {
87
85
  // Wrap the method to pass decorator context to HTTP client
88
86
  const originalMethod = descriptor.value;
89
87
  const wrappedMethod = function (...args) {
88
+ var _a;
90
89
  // Store decorator context in async local storage or call stack
91
90
  // Only set if not already set by another decorator
92
91
  if (!call_stack_extractor_util_1.CallStackExtractor.getDecoratorContext()) {
@@ -95,13 +94,21 @@ const HttpCircuitBreaker = (options = {}) => {
95
94
  propertyKey,
96
95
  });
97
96
  }
98
- try {
99
- return originalMethod.apply(this, args);
100
- }
101
- finally {
102
- // Clean up the context after execution
103
- call_stack_extractor_util_1.CallStackExtractor.clearDecoratorContext();
97
+ // 获取原始方法的返回值
98
+ const result = originalMethod.apply(this, args);
99
+ // 如果是 Promise,等待其完成后再清除上下文
100
+ if (result && typeof result.then === 'function') {
101
+ // 只有在最外层装饰器时才清除上下文
102
+ if (!call_stack_extractor_util_1.CallStackExtractor.getDecoratorContext() || ((_a = call_stack_extractor_util_1.CallStackExtractor.getDecoratorContext()) === null || _a === void 0 ? void 0 : _a.target) === target) {
103
+ return result.finally(() => {
104
+ call_stack_extractor_util_1.CallStackExtractor.clearDecoratorContext();
105
+ });
106
+ }
107
+ return result;
104
108
  }
109
+ // 同步方法,立即清除上下文
110
+ call_stack_extractor_util_1.CallStackExtractor.clearDecoratorContext();
111
+ return result;
105
112
  };
106
113
  // Copy metadata from original to wrapped method
107
114
  const metadataKeys = Reflect.getMetadataKeys(originalMethod);
@@ -152,12 +159,17 @@ const HttpLogRequest = (options = {}) => {
152
159
  target,
153
160
  propertyKey,
154
161
  });
155
- try {
156
- return originalMethod.apply(this, args);
157
- }
158
- finally {
159
- call_stack_extractor_util_1.CallStackExtractor.clearDecoratorContext();
162
+ // 获取原始方法的返回值
163
+ const result = originalMethod.apply(this, args);
164
+ // 如果是 Promise,等待其完成后再清除上下文
165
+ if (result && typeof result.then === 'function') {
166
+ return result.finally(() => {
167
+ call_stack_extractor_util_1.CallStackExtractor.clearDecoratorContext();
168
+ });
160
169
  }
170
+ // 同步方法,立即清除上下文
171
+ call_stack_extractor_util_1.CallStackExtractor.clearDecoratorContext();
172
+ return result;
161
173
  };
162
174
  // Copy metadata from original to wrapped method
163
175
  const metadataKeys = Reflect.getMetadataKeys(originalMethod);
@@ -186,12 +198,17 @@ const HttpTimeout = (timeoutMs) => {
186
198
  target,
187
199
  propertyKey,
188
200
  });
189
- try {
190
- return originalMethod.apply(this, args);
191
- }
192
- finally {
193
- call_stack_extractor_util_1.CallStackExtractor.clearDecoratorContext();
201
+ // 获取原始方法的返回值
202
+ const result = originalMethod.apply(this, args);
203
+ // 如果是 Promise,等待其完成后再清除上下文
204
+ if (result && typeof result.then === 'function') {
205
+ return result.finally(() => {
206
+ call_stack_extractor_util_1.CallStackExtractor.clearDecoratorContext();
207
+ });
194
208
  }
209
+ // 同步方法,立即清除上下文
210
+ call_stack_extractor_util_1.CallStackExtractor.clearDecoratorContext();
211
+ return result;
195
212
  };
196
213
  // Copy metadata from original to wrapped method
197
214
  const metadataKeys = Reflect.getMetadataKeys(originalMethod);
@@ -50,6 +50,14 @@ export declare class HttpClientModule {
50
50
  inject?: any[];
51
51
  imports?: any[];
52
52
  }): DynamicModule;
53
+ /**
54
+ * 安全地从环境变量解析整数
55
+ * @param configService NestJS ConfigService
56
+ * @param key 环境变量键
57
+ * @param defaultValue 默认值
58
+ * @returns 解析后的数字或默认值
59
+ */
60
+ private static parseEnvInt;
53
61
  /**
54
62
  * 从环境变量加载配置
55
63
  */
@@ -20,7 +20,6 @@ exports.HttpClientModule = void 0;
20
20
  const common_1 = require("@nestjs/common");
21
21
  const config_1 = require("@nestjs/config");
22
22
  const typeorm_1 = require("@nestjs/typeorm");
23
- const typeorm_2 = require("typeorm");
24
23
  const http_client_service_1 = require("./services/http-client.service");
25
24
  const circuit_breaker_service_1 = require("./services/circuit-breaker.service");
26
25
  const logging_service_1 = require("./services/logging.service");
@@ -86,7 +85,7 @@ let HttpClientModule = HttpClientModule_1 = class HttpClientModule {
86
85
  const databaseLoggingEnabled = (_b = (_a = httpConfig === null || httpConfig === void 0 ? void 0 : httpConfig.logging) === null || _a === void 0 ? void 0 : _a.databaseLogging) === null || _b === void 0 ? void 0 : _b.enabled;
87
86
  return new logging_service_1.HttpLoggingService();
88
87
  },
89
- inject: ['HTTP_CLIENT_CONFIG', typeorm_2.DataSource],
88
+ inject: ['HTTP_CLIENT_CONFIG'],
90
89
  },
91
90
  http_log_query_service_1.HttpLogQueryService,
92
91
  {
@@ -186,7 +185,7 @@ let HttpClientModule = HttpClientModule_1 = class HttpClientModule {
186
185
  const databaseLoggingEnabled = (_b = (_a = httpConfig === null || httpConfig === void 0 ? void 0 : httpConfig.logging) === null || _a === void 0 ? void 0 : _a.databaseLogging) === null || _b === void 0 ? void 0 : _b.enabled;
187
186
  return new logging_service_1.HttpLoggingService();
188
187
  },
189
- inject: ['HTTP_CLIENT_CONFIG', typeorm_2.DataSource],
188
+ inject: ['HTTP_CLIENT_CONFIG'],
190
189
  },
191
190
  http_log_query_service_1.HttpLogQueryService,
192
191
  {
@@ -242,6 +241,21 @@ let HttpClientModule = HttpClientModule_1 = class HttpClientModule {
242
241
  exports: baseExports,
243
242
  };
244
243
  }
244
+ /**
245
+ * 安全地从环境变量解析整数
246
+ * @param configService NestJS ConfigService
247
+ * @param key 环境变量键
248
+ * @param defaultValue 默认值
249
+ * @returns 解析后的数字或默认值
250
+ */
251
+ static parseEnvInt(configService, key, defaultValue) {
252
+ const value = configService.get(key);
253
+ if (value === undefined || value === null || value === '') {
254
+ return defaultValue;
255
+ }
256
+ const parsed = parseInt(value, 10);
257
+ return isNaN(parsed) ? defaultValue : parsed;
258
+ }
245
259
  /**
246
260
  * 从环境变量加载配置
247
261
  */
@@ -249,14 +263,10 @@ let HttpClientModule = HttpClientModule_1 = class HttpClientModule {
249
263
  var _a;
250
264
  return {
251
265
  baseURL: configService.get('HTTP_CLIENT_BASE_URL'),
252
- timeout: configService.get('HTTP_CLIENT_TIMEOUT')
253
- ? parseInt(configService.get('HTTP_CLIENT_TIMEOUT'))
254
- : undefined,
266
+ timeout: this.parseEnvInt(configService, 'HTTP_CLIENT_TIMEOUT'),
255
267
  retry: {
256
268
  enabled: configService.get('HTTP_CLIENT_RETRY_ENABLED') === 'true',
257
- retries: configService.get('HTTP_CLIENT_RETRY_RETRIES')
258
- ? parseInt(configService.get('HTTP_CLIENT_RETRY_RETRIES'))
259
- : undefined,
269
+ retries: this.parseEnvInt(configService, 'HTTP_CLIENT_RETRY_RETRIES'),
260
270
  retryDelay: configService.get('HTTP_CLIENT_RETRY_DELAY_STRATEGY') ===
261
271
  'exponential'
262
272
  ? (retryCount) => Math.pow(2, retryCount) * 1000
@@ -271,18 +281,10 @@ let HttpClientModule = HttpClientModule_1 = class HttpClientModule {
271
281
  circuitBreaker: {
272
282
  enabled: configService.get('HTTP_CLIENT_CIRCUIT_BREAKER_ENABLED') ===
273
283
  'true',
274
- failureThreshold: configService.get('HTTP_CLIENT_CIRCUIT_BREAKER_FAILURE_THRESHOLD')
275
- ? parseInt(configService.get('HTTP_CLIENT_CIRCUIT_BREAKER_FAILURE_THRESHOLD'))
276
- : 5,
277
- recoveryTimeoutMs: configService.get('HTTP_CLIENT_CIRCUIT_BREAKER_RECOVERY_TIMEOUT')
278
- ? parseInt(configService.get('HTTP_CLIENT_CIRCUIT_BREAKER_RECOVERY_TIMEOUT'))
279
- : 60000,
280
- monitoringPeriodMs: configService.get('HTTP_CLIENT_CIRCUIT_BREAKER_MONITORING_PERIOD')
281
- ? parseInt(configService.get('HTTP_CLIENT_CIRCUIT_BREAKER_MONITORING_PERIOD'))
282
- : 10000,
283
- minimumThroughputThreshold: configService.get('HTTP_CLIENT_CIRCUIT_BREAKER_MINIMUM_THROUGHPUT')
284
- ? parseInt(configService.get('HTTP_CLIENT_CIRCUIT_BREAKER_MINIMUM_THROUGHPUT'))
285
- : 10,
284
+ failureThreshold: this.parseEnvInt(configService, 'HTTP_CLIENT_CIRCUIT_BREAKER_FAILURE_THRESHOLD', 5),
285
+ recoveryTimeoutMs: this.parseEnvInt(configService, 'HTTP_CLIENT_CIRCUIT_BREAKER_RECOVERY_TIMEOUT', 60000),
286
+ monitoringPeriodMs: this.parseEnvInt(configService, 'HTTP_CLIENT_CIRCUIT_BREAKER_MONITORING_PERIOD', 10000),
287
+ minimumThroughputThreshold: this.parseEnvInt(configService, 'HTTP_CLIENT_CIRCUIT_BREAKER_MINIMUM_THROUGHPUT', 10),
286
288
  countHalfOpenCalls: configService.get('HTTP_CLIENT_CIRCUIT_BREAKER_COUNT_HALF_OPEN') === 'true',
287
289
  },
288
290
  logging: {
@@ -292,9 +294,7 @@ let HttpClientModule = HttpClientModule_1 = class HttpClientModule {
292
294
  logErrors: configService.get('HTTP_CLIENT_LOG_ERRORS') !== 'false',
293
295
  logHeaders: configService.get('HTTP_CLIENT_LOG_HEADERS') !== 'false',
294
296
  logBody: configService.get('HTTP_CLIENT_LOG_BODY') !== 'false',
295
- maxBodyLength: configService.get('HTTP_CLIENT_LOG_MAX_BODY_LENGTH')
296
- ? parseInt(configService.get('HTTP_CLIENT_LOG_MAX_BODY_LENGTH'))
297
- : undefined,
297
+ maxBodyLength: this.parseEnvInt(configService, 'HTTP_CLIENT_LOG_MAX_BODY_LENGTH'),
298
298
  sanitize: (_a = configService
299
299
  .get('HTTP_CLIENT_LOG_SANITIZE')) === null || _a === void 0 ? void 0 : _a.split(','),
300
300
  logLevel: configService.get('HTTP_CLIENT_LOG_LEVEL'),
@@ -8,6 +8,9 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
8
8
  var __metadata = (this && this.__metadata) || function (k, v) {
9
9
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
10
  };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
11
14
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
12
15
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
13
16
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -103,7 +106,7 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
103
106
  */
104
107
  request(config, decoratorContext, clientName, returnOptions) {
105
108
  return __awaiter(this, void 0, void 0, function* () {
106
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
109
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
107
110
  // Use the instance's clientName as fallback if not provided
108
111
  const effectiveClientName = clientName || this.clientName;
109
112
  // If no decorator context provided, try to get it from CallStackExtractor
@@ -123,7 +126,7 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
123
126
  try {
124
127
  fullURL = new URL(requestURL, baseURL).href;
125
128
  }
126
- catch (_m) {
129
+ catch (_o) {
127
130
  // 如果URL构建失败,使用原始URL
128
131
  fullURL = requestURL;
129
132
  }
@@ -142,8 +145,34 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
142
145
  }
143
146
  // 如果URL被修改,更新配置
144
147
  if (sanitizeResult.url !== fullURL) {
145
- // 仅更新完整URL,保留原始的相对路径
148
+ // 更新URL为清洗后的安全URL
146
149
  this.logger.debug(`URL sanitized from "${fullURL}" to "${sanitizeResult.url}"`);
150
+ // 如果原始URL是相对路径,保持相对路径;否则更新为清洗后的完整URL
151
+ if (requestURL && !requestURL.startsWith('http://') && !requestURL.startsWith('https://')) {
152
+ // 相对路径情况:从清洗后的完整URL中提取相对路径部分
153
+ try {
154
+ const sanitizedURL = new URL(sanitizeResult.url);
155
+ const baseURLParts = baseURL.split('/');
156
+ const sanitizedPathParts = sanitizedURL.pathname.split('/');
157
+ // 计算相对路径
158
+ let relativePath = sanitizedURL.pathname;
159
+ if (baseURLParts.length > 3) {
160
+ const basePath = baseURLParts.slice(3).join('/');
161
+ if (relativePath.startsWith(basePath)) {
162
+ relativePath = relativePath.substring(basePath.length);
163
+ }
164
+ }
165
+ config.url = relativePath || requestURL;
166
+ }
167
+ catch (_p) {
168
+ // 如果提取失败,保持原始URL
169
+ config.url = requestURL;
170
+ }
171
+ }
172
+ else {
173
+ // 绝对路径情况:直接使用清洗后的URL
174
+ config.url = sanitizeResult.url;
175
+ }
147
176
  }
148
177
  }
149
178
  // ========== 安全验证结束 ==========
@@ -165,10 +194,14 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
165
194
  const decoratorConfigs = effectiveDecoratorContext
166
195
  ? decorators_1.HttpDecoratorUtils.getAllDecoratorConfigs(effectiveDecoratorContext.target, effectiveDecoratorContext.propertyKey)
167
196
  : {};
197
+ // 存储装饰器的重试配置到 metadata,供 retryCondition 使用
198
+ if (((_c = decoratorConfigs.retry) === null || _c === void 0 ? void 0 : _c.enabled) !== undefined) {
199
+ enhancedConfig.metadata.retryEnabled = decoratorConfigs.retry.enabled;
200
+ }
168
201
  // 日志记录开始
169
202
  const decoratorLogging = decoratorConfigs.logging || {};
170
203
  this.logger.debug(`Logging config merge: decoratorLogging=${JSON.stringify(decoratorLogging)}, defaultLogging=${JSON.stringify(this.defaultConfig.logging)}`);
171
- 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 });
204
+ const loggingOptions = Object.assign(Object.assign(Object.assign({}, this.defaultConfig.logging), decoratorLogging), { databaseLogging: (_d = this.defaultConfig.logging) === null || _d === void 0 ? void 0 : _d.databaseLogging });
172
205
  this.logger.debug(`Merged loggingOptions: databaseLog=${loggingOptions.databaseLog}, hasDatabaseLogging=${!!loggingOptions.databaseLogging}`);
173
206
  if (loggingOptions === null || loggingOptions === void 0 ? void 0 : loggingOptions.enabled) {
174
207
  requestId = this.loggingService.logRequestStart(enhancedConfig, loggingOptions);
@@ -197,12 +230,12 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
197
230
  // 成功日志
198
231
  if (loggingOptions === null || loggingOptions === void 0 ? void 0 : loggingOptions.enabled) {
199
232
  const databaseLogging = (loggingOptions === null || loggingOptions === void 0 ? void 0 : loggingOptions.databaseLogging) ||
200
- ((_e = (_d = this.defaultConfig.logging) === null || _d === void 0 ? void 0 : _d.databaseLogging) === null || _e === void 0 ? void 0 : _e.enabled);
233
+ ((_f = (_e = this.defaultConfig.logging) === null || _e === void 0 ? void 0 : _e.databaseLogging) === null || _f === void 0 ? void 0 : _f.enabled);
201
234
  this.logger.debug(`Logging request success with circuitBreakerState: ${circuitBreakerState || 'undefined'}`);
202
235
  this.loggingService.logRequestSuccess(response, startTime, requestId, loggingOptions, databaseLogging, retryRecorder.getRecords(), circuitBreakerState, decoratorContext, effectiveClientName, callingContext);
203
236
  }
204
237
  // 更新统计信息
205
- this.updateRequestStats(true, Date.now() - startTime, ((_f = response.config) === null || _f === void 0 ? void 0 : _f.method) || 'GET', response.status);
238
+ this.updateRequestStats(true, Date.now() - startTime, ((_g = response.config) === null || _g === void 0 ? void 0 : _g.method) || 'GET', response.status);
206
239
  // 根据返回选项处理响应
207
240
  const options = returnOptions || { returnType: 'data' };
208
241
  switch (options.returnType) {
@@ -227,16 +260,16 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
227
260
  const decoratorLogging = effectiveDecoratorContext
228
261
  ? decorators_1.HttpDecoratorUtils.getLoggingOptions(effectiveDecoratorContext.target, effectiveDecoratorContext.propertyKey)
229
262
  : {};
230
- 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 });
263
+ const loggingOptions = Object.assign(Object.assign(Object.assign({}, this.defaultConfig.logging), decoratorLogging), { databaseLogging: (_h = this.defaultConfig.logging) === null || _h === void 0 ? void 0 : _h.databaseLogging });
231
264
  if (loggingOptions === null || loggingOptions === void 0 ? void 0 : loggingOptions.enabled) {
232
265
  const databaseLogging = (loggingOptions === null || loggingOptions === void 0 ? void 0 : loggingOptions.databaseLog) ||
233
- ((_j = (_h = this.defaultConfig.logging) === null || _h === void 0 ? void 0 : _h.databaseLogging) === null || _j === void 0 ? void 0 : _j.enabled);
266
+ ((_k = (_j = this.defaultConfig.logging) === null || _j === void 0 ? void 0 : _j.databaseLogging) === null || _k === void 0 ? void 0 : _k.enabled);
234
267
  const records = retryRecorder.getRecords();
235
268
  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);
236
269
  }
237
270
  // 更新统计信息
238
- const errorMethod = (_k = error.config) === null || _k === void 0 ? void 0 : _k.method;
239
- const errorStatus = (_l = error.response) === null || _l === void 0 ? void 0 : _l.status;
271
+ const errorMethod = (_l = error.config) === null || _l === void 0 ? void 0 : _l.method;
272
+ const errorStatus = (_m = error.response) === null || _m === void 0 ? void 0 : _m.status;
240
273
  this.updateRequestStats(false, Date.now() - startTime, errorMethod, errorStatus);
241
274
  throw error;
242
275
  }
@@ -525,7 +558,11 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
525
558
  const axiosCreateConfig = {
526
559
  baseURL: this.defaultConfig.baseURL,
527
560
  timeout: this.defaultConfig.timeout,
528
- proxy: resolvedProxy !== undefined ? resolvedProxy : undefined,
561
+ // 只有当 resolvedProxy 是有效的配置对象时才设置代理
562
+ // false 和 undefined 都表示不使用代理
563
+ proxy: resolvedProxy && typeof resolvedProxy === 'object'
564
+ ? resolvedProxy
565
+ : undefined,
529
566
  // 应用处理后的 httpsAgent
530
567
  httpsAgent: httpsAgent,
531
568
  };
@@ -548,6 +585,13 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
548
585
  ((retryCount) => Math.pow(2, retryCount) * 1000),
549
586
  retryCondition: this.defaultConfig.retry.retryCondition ||
550
587
  ((error) => {
588
+ var _a;
589
+ // 检查装饰器配置是否禁用了重试
590
+ const requestConfig = error.config;
591
+ if (((_a = requestConfig === null || requestConfig === void 0 ? void 0 : requestConfig.metadata) === null || _a === void 0 ? void 0 : _a.retryEnabled) === false) {
592
+ return false; // 装饰器明确禁用了重试
593
+ }
594
+ // 默认重试条件:网络错误、超时、5xx错误、429错误
551
595
  if (!error.response)
552
596
  return true; // 网络错误
553
597
  const status = error.response.status;
@@ -898,6 +942,7 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
898
942
  exports.HttpClientService = HttpClientService;
899
943
  exports.HttpClientService = HttpClientService = HttpClientService_1 = __decorate([
900
944
  (0, common_1.Injectable)(),
945
+ __param(2, (0, common_1.Optional)()),
901
946
  __metadata("design:paramtypes", [circuit_breaker_service_1.HttpCircuitBreakerService,
902
947
  logging_service_1.HttpLoggingService,
903
948
  redis_lock_service_1.RedisLockService, Object, String, Object])
@@ -29,6 +29,8 @@ export declare class ContextExtractor {
29
29
  static getClientIp(): string | undefined;
30
30
  /**
31
31
  * 获取用户代理
32
+ * 注意:当前 ContextProvider 不存储 userAgent 信息
33
+ * 如需使用此功能,需要扩展 ContextProvider 或从请求头中获取
32
34
  */
33
35
  static getUserAgent(): string | undefined;
34
36
  /**
@@ -13,7 +13,15 @@ class ContextExtractor {
13
13
  static getHttpContext() {
14
14
  try {
15
15
  const requestId = providers_1.ContextProvider.getRequestId();
16
- const authUser = providers_1.ContextProvider.getAuthUser();
16
+ // 使用安全的方式获取 authUser,避免抛出异常
17
+ let authUser = undefined;
18
+ try {
19
+ authUser = providers_1.ContextProvider.getAuthUser();
20
+ }
21
+ catch (_a) {
22
+ // 在测试/CLI环境中可能没有用户上下文
23
+ authUser = undefined;
24
+ }
17
25
  const router = providers_1.ContextProvider.getRouter();
18
26
  const source = providers_1.ContextProvider.getSource();
19
27
  return {
@@ -29,7 +37,7 @@ class ContextExtractor {
29
37
  tags: [],
30
38
  };
31
39
  }
32
- catch (_a) {
40
+ catch (_b) {
33
41
  // If no context is available, return empty context
34
42
  return {
35
43
  tags: [],
@@ -73,11 +81,15 @@ class ContextExtractor {
73
81
  }
74
82
  /**
75
83
  * 获取用户代理
84
+ * 注意:当前 ContextProvider 不存储 userAgent 信息
85
+ * 如需使用此功能,需要扩展 ContextProvider 或从请求头中获取
76
86
  */
77
87
  static getUserAgent() {
78
88
  try {
79
89
  const router = providers_1.ContextProvider.getRouter();
80
- return undefined;
90
+ // TODO: userAgent 需要从请求头中获取,当前 router 对象不包含此字段
91
+ // 可以考虑扩展 ContextProvider 来存储完整的请求信息
92
+ return router === null || router === void 0 ? void 0 : router.userAgent;
81
93
  }
82
94
  catch (_a) {
83
95
  return undefined;
@@ -1,3 +1,2 @@
1
1
  export * from './language-interceptor.service';
2
2
  export * from './translation-interceptor.service';
3
- export * from './http-logging-interceptor.service';
@@ -16,4 +16,3 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./language-interceptor.service"), exports);
18
18
  __exportStar(require("./translation-interceptor.service"), exports);
19
- __exportStar(require("./http-logging-interceptor.service"), exports);
@@ -1,5 +1,12 @@
1
1
  import type { CallHandler, ExecutionContext, NestInterceptor } from '@nestjs/common';
2
2
  import type { Observable } from 'rxjs';
3
+ import { TranslationService } from '../shared/services/translation.service';
4
+ /**
5
+ * 翻译拦截器
6
+ * 自动翻译响应数据中标记为需要翻译的字段
7
+ */
3
8
  export declare class TranslationInterceptor implements NestInterceptor {
9
+ private readonly translationService?;
10
+ constructor(translationService?: TranslationService);
4
11
  intercept(context: ExecutionContext, next: CallHandler): Observable<any>;
5
12
  }
@@ -5,35 +5,67 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
6
  return c > 3 && r && Object.defineProperty(target, key, r), r;
7
7
  };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
15
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
16
+ return new (P || (P = Promise))(function (resolve, reject) {
17
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
18
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
19
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
20
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
21
+ });
22
+ };
8
23
  Object.defineProperty(exports, "__esModule", { value: true });
9
24
  exports.TranslationInterceptor = void 0;
10
25
  const common_1 = require("@nestjs/common");
11
26
  const operators_1 = require("rxjs/operators");
12
- // import { TranslationService } from '../shared/services/translation.service';
13
- // FIXME: add implementation
27
+ const translation_service_1 = require("../shared/services/translation.service");
28
+ /**
29
+ * 翻译拦截器
30
+ * 自动翻译响应数据中标记为需要翻译的字段
31
+ */
14
32
  let TranslationInterceptor = class TranslationInterceptor {
15
- // constructor(private readonly translationService: TranslationService) {}
33
+ constructor(translationService) {
34
+ this.translationService = translationService;
35
+ }
16
36
  intercept(context, next) {
17
37
  const ctx = context.switchToHttp();
18
38
  const req = ctx.getRequest();
19
39
  const res = ctx.getResponse();
20
- return next.handle().pipe((0, operators_1.map)((data) => {
21
- // const newData = this.translationService.translateNecessaryKeys(data);
40
+ return next.handle().pipe((0, operators_1.map)((data) => __awaiter(this, void 0, void 0, function* () {
41
+ // 如果 TranslationService 可用,翻译响应数据
42
+ let translatedData = data;
43
+ if (this.translationService) {
44
+ try {
45
+ translatedData = yield this.translationService.translateNecessaryKeys(data);
46
+ }
47
+ catch (error) {
48
+ // 翻译失败时返回原始数据,不中断请求
49
+ translatedData = data;
50
+ }
51
+ }
22
52
  // status 201 => 200
23
53
  if (res.statusCode === common_1.HttpStatus.CREATED) {
24
54
  res.status(common_1.HttpStatus.OK);
25
55
  }
26
56
  return {
27
57
  code: 200,
28
- data,
58
+ data: translatedData,
29
59
  msg: 'success',
30
60
  requestId: req.id,
31
61
  timestamp: new Date().toISOString(),
32
62
  };
33
- }));
63
+ })));
34
64
  }
35
65
  };
36
66
  exports.TranslationInterceptor = TranslationInterceptor;
37
67
  exports.TranslationInterceptor = TranslationInterceptor = __decorate([
38
- (0, common_1.Injectable)()
68
+ (0, common_1.Injectable)(),
69
+ __param(0, (0, common_1.Optional)()),
70
+ __metadata("design:paramtypes", [translation_service_1.TranslationService])
39
71
  ], TranslationInterceptor);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nest-omni/core",
3
- "version": "4.1.3-27",
3
+ "version": "4.1.3-29",
4
4
  "description": "A comprehensive NestJS framework for building enterprise-grade applications with best practices",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -166,7 +166,7 @@ function bootstrapSetup(AppModule, SetupSwagger) {
166
166
  const reflector = app.get(core_1.Reflector);
167
167
  app.useGlobalFilters(new setup_1.SentryGlobalFilter(), new __1.HttpExceptionFilter(), new __1.QueryFailedFilter(reflector));
168
168
  // 全局拦截器
169
- app.useGlobalInterceptors(new __1.HttpLoggingInterceptor(configService), new __1.LanguageInterceptor(), new __1.TranslationInterceptor());
169
+ app.useGlobalInterceptors(new __1.LanguageInterceptor(), new __1.TranslationInterceptor(), new nestjs_pino_1.LoggerErrorInterceptor());
170
170
  // 全局管道
171
171
  app.useGlobalPipes(new nestjs_i18n_1.I18nValidationPipe({
172
172
  whitelist: true,
@@ -57,7 +57,7 @@ let ApiConfigService = ApiConfigService_1 = class ApiConfigService {
57
57
  password: this.getString('DB_PASSWORD'),
58
58
  database: this.getString('DB_DATABASE'),
59
59
  subscribers: [],
60
- synchronize: this.isDev,
60
+ synchronize: this.getBoolean('DB_SYNCHRONIZE', this.isDev),
61
61
  migrationsRun: true,
62
62
  logging: this.getBoolean('DB_LOG_ENABLED'),
63
63
  logger: this.isDev ? 'formatted-console' : 'simple-console',
@@ -214,8 +214,23 @@ let ApiConfigService = ApiConfigService_1 = class ApiConfigService {
214
214
  genReqId: (req) => req.id,
215
215
  transport,
216
216
  level: this.isDev ? 'debug' : 'info',
217
- // 禁用 pinoHttp 的自动日志,由 HttpLoggingInterceptor 处理
218
- autoLogging: false,
217
+ customLogLevel: function (res) {
218
+ if (res.statusCode >= 500)
219
+ return 'error';
220
+ if (res.statusCode >= 400)
221
+ return 'warn';
222
+ return 'info';
223
+ },
224
+ wrapSerializers: true,
225
+ customProps: (req, res) => {
226
+ return {
227
+ env: this.nodeEnv,
228
+ appName: this.getString('NAME'),
229
+ user: req === null || req === void 0 ? void 0 : req.user,
230
+ 'req.body': req === null || req === void 0 ? void 0 : req.body,
231
+ 'res.body': res === null || res === void 0 ? void 0 : res.body,
232
+ };
233
+ },
219
234
  redact: {
220
235
  paths: ['req.headers.authorization', 'req.headers.apikey', 'req.headers.cookie'],
221
236
  remove: true,
@@ -1,38 +0,0 @@
1
- import { NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
2
- import { Observable } from 'rxjs';
3
- import { ApiConfigService } from '../shared/services/api-config.service';
4
- /**
5
- * HTTP 日志拦截器
6
- * 参考 Tomcat AccessLog 的实现,每个请求只记录一条日志
7
- * 在请求完成时同时记录请求和响应的完整信息
8
- */
9
- export declare class HttpLoggingInterceptor implements NestInterceptor {
10
- private readonly configService;
11
- private readonly logger;
12
- constructor(configService: ApiConfigService);
13
- intercept(context: ExecutionContext, next: CallHandler): Observable<any>;
14
- /**
15
- * 生成请求 ID
16
- */
17
- private generateRequestId;
18
- /**
19
- * 记录请求和响应(一条日志)
20
- */
21
- private logRequestResponse;
22
- /**
23
- * 记录请求和错误(一条日志)
24
- */
25
- private logRequestError;
26
- /**
27
- * 清理敏感的 header 信息
28
- */
29
- private sanitizeHeaders;
30
- /**
31
- * 清理敏感的 body 信息
32
- */
33
- private sanitizeBody;
34
- /**
35
- * 从 headers 提取用户信息
36
- */
37
- private extractUser;
38
- }