@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.
- package/audit/interceptors/audit-action.interceptor.d.ts +1 -0
- package/audit/interceptors/audit-action.interceptor.js +5 -3
- package/audit/services/audit-action.service.d.ts +1 -0
- package/audit/services/audit-action.service.js +5 -3
- package/audit/services/operation-description.service.d.ts +1 -0
- package/audit/services/operation-description.service.js +5 -3
- package/audit/services/transaction-audit.service.d.ts +1 -0
- package/audit/services/transaction-audit.service.js +6 -4
- package/common/helpers/validation-metadata-helper.d.ts +57 -0
- package/common/helpers/validation-metadata-helper.js +109 -5
- package/decorators/examples/field-i18n.example.d.ts +294 -0
- package/decorators/examples/field-i18n.example.js +478 -0
- package/decorators/field.decorators.d.ts +23 -0
- package/decorators/field.decorators.js +7 -2
- package/decorators/translate.decorator.d.ts +26 -0
- package/decorators/translate.decorator.js +26 -1
- package/http-client/decorators/http-client.decorators.d.ts +1 -0
- package/http-client/decorators/http-client.decorators.js +47 -30
- package/http-client/http-client.module.d.ts +8 -0
- package/http-client/http-client.module.js +24 -24
- package/http-client/services/http-client.service.js +56 -11
- package/http-client/utils/context-extractor.util.d.ts +2 -0
- package/http-client/utils/context-extractor.util.js +15 -3
- package/interceptors/index.d.ts +0 -1
- package/interceptors/index.js +0 -1
- package/interceptors/translation-interceptor.service.d.ts +7 -0
- package/interceptors/translation-interceptor.service.js +40 -8
- package/package.json +1 -1
- package/setup/bootstrap.setup.js +1 -1
- package/shared/services/api-config.service.js +18 -3
- package/interceptors/http-logging-interceptor.service.d.ts +0 -38
- 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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
//
|
|
103
|
-
call_stack_extractor_util_1.CallStackExtractor.
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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'
|
|
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'
|
|
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:
|
|
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:
|
|
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:
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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:
|
|
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 (
|
|
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
|
-
//
|
|
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: (
|
|
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
|
-
((
|
|
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, ((
|
|
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: (
|
|
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
|
-
((
|
|
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 = (
|
|
239
|
-
const errorStatus = (
|
|
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
|
-
|
|
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])
|
|
@@ -13,7 +13,15 @@ class ContextExtractor {
|
|
|
13
13
|
static getHttpContext() {
|
|
14
14
|
try {
|
|
15
15
|
const requestId = providers_1.ContextProvider.getRequestId();
|
|
16
|
-
|
|
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 (
|
|
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
|
-
|
|
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;
|
package/interceptors/index.d.ts
CHANGED
package/interceptors/index.js
CHANGED
|
@@ -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
|
-
|
|
13
|
-
|
|
27
|
+
const translation_service_1 = require("../shared/services/translation.service");
|
|
28
|
+
/**
|
|
29
|
+
* 翻译拦截器
|
|
30
|
+
* 自动翻译响应数据中标记为需要翻译的字段
|
|
31
|
+
*/
|
|
14
32
|
let TranslationInterceptor = class TranslationInterceptor {
|
|
15
|
-
|
|
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
|
-
//
|
|
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
package/setup/bootstrap.setup.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
218
|
-
|
|
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
|
-
}
|