@nest-omni/core 4.1.3-12 → 4.1.3-14
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/cache/dependencies/db.dependency.d.ts +55 -6
- package/cache/dependencies/db.dependency.js +64 -13
- package/common/boilerplate.polyfill.js +1 -1
- package/file-upload/decorators/column.decorator.d.ts +151 -0
- package/file-upload/decorators/column.decorator.js +273 -0
- package/file-upload/decorators/csv-data.decorator.d.ts +17 -31
- package/file-upload/decorators/csv-data.decorator.js +45 -91
- package/file-upload/decorators/csv-import.decorator.d.ts +34 -0
- package/file-upload/decorators/csv-import.decorator.js +24 -0
- package/file-upload/decorators/examples/column-mapping.example.d.ts +76 -0
- package/file-upload/decorators/examples/column-mapping.example.js +122 -0
- package/file-upload/decorators/excel-data.decorator.d.ts +15 -29
- package/file-upload/decorators/excel-data.decorator.js +42 -82
- package/file-upload/decorators/index.d.ts +3 -2
- package/file-upload/decorators/index.js +20 -2
- package/file-upload/decorators/validate-data.decorator.d.ts +91 -0
- package/file-upload/decorators/validate-data.decorator.js +39 -0
- package/file-upload/dto/update-file.dto.d.ts +0 -1
- package/file-upload/dto/update-file.dto.js +0 -4
- package/file-upload/entities/file-metadata.entity.d.ts +6 -3
- package/file-upload/entities/file-metadata.entity.js +2 -10
- package/file-upload/entities/file.entity.d.ts +3 -18
- package/file-upload/entities/file.entity.js +0 -34
- package/file-upload/file-upload.module.d.ts +1 -1
- package/file-upload/file-upload.module.js +44 -16
- package/file-upload/index.d.ts +13 -2
- package/file-upload/index.js +21 -3
- package/file-upload/interceptors/file-upload.interceptor.d.ts +61 -8
- package/file-upload/interceptors/file-upload.interceptor.js +417 -257
- package/file-upload/interfaces/file-processor.interface.d.ts +93 -0
- package/file-upload/interfaces/file-processor.interface.js +2 -0
- package/file-upload/interfaces/file-upload-options.interface.d.ts +3 -46
- package/file-upload/interfaces/file-upload-options.interface.js +3 -0
- package/file-upload/interfaces/processor-options.interface.d.ts +102 -0
- package/file-upload/interfaces/processor-options.interface.js +2 -0
- package/file-upload/processors/csv.processor.d.ts +98 -0
- package/file-upload/processors/csv.processor.js +391 -0
- package/file-upload/processors/excel.processor.d.ts +130 -0
- package/file-upload/processors/excel.processor.js +547 -0
- package/file-upload/processors/image.processor.d.ts +199 -0
- package/file-upload/processors/image.processor.js +377 -0
- package/file-upload/services/file.service.d.ts +3 -0
- package/file-upload/services/file.service.js +39 -10
- package/file-upload/services/malicious-file-detector.service.d.ts +29 -3
- package/file-upload/services/malicious-file-detector.service.js +256 -57
- package/file-upload/utils/dynamic-import.util.d.ts +6 -2
- package/file-upload/utils/dynamic-import.util.js +17 -5
- package/http-client/decorators/http-client.decorators.d.ts +4 -2
- package/http-client/decorators/http-client.decorators.js +2 -1
- package/http-client/entities/http-log.entity.js +1 -9
- package/http-client/examples/proxy-from-environment.example.d.ts +133 -0
- package/http-client/examples/proxy-from-environment.example.js +410 -0
- package/http-client/http-client.module.js +65 -6
- package/http-client/interfaces/http-client-config.interface.d.ts +6 -0
- package/http-client/services/http-client.service.d.ts +8 -0
- package/http-client/services/http-client.service.js +61 -17
- package/http-client/services/logging.service.d.ts +1 -1
- package/http-client/services/logging.service.js +74 -58
- package/http-client/utils/index.d.ts +1 -0
- package/http-client/utils/index.js +1 -0
- package/http-client/utils/proxy-environment.util.d.ts +42 -0
- package/http-client/utils/proxy-environment.util.js +148 -0
- package/package.json +9 -5
- package/shared/service-registry.module.js +18 -0
- package/transaction/data-source.util.d.ts +142 -0
- package/transaction/data-source.util.js +330 -0
- package/transaction/index.d.ts +1 -0
- package/transaction/index.js +12 -1
- package/validators/is-exists.validator.d.ts +19 -2
- package/validators/is-exists.validator.js +27 -2
- package/validators/is-unique.validator.d.ts +12 -1
- package/validators/is-unique.validator.js +26 -1
|
@@ -31,6 +31,7 @@ const context_extractor_util_1 = require("../utils/context-extractor.util");
|
|
|
31
31
|
const curl_generator_util_1 = require("../utils/curl-generator.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
|
+
const proxy_environment_util_1 = require("../utils/proxy-environment.util");
|
|
34
35
|
const redis_lock_service_1 = require("../../redis-lock/redis-lock.service");
|
|
35
36
|
/**
|
|
36
37
|
* HTTP客户端服务
|
|
@@ -105,12 +106,10 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
|
|
|
105
106
|
// 应用代理配置
|
|
106
107
|
const proxyConfig = decoratorConfigs.proxy || this.defaultConfig.proxy;
|
|
107
108
|
if (proxyConfig === null || proxyConfig === void 0 ? void 0 : proxyConfig.enabled) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
auth: proxyConfig.auth,
|
|
113
|
-
};
|
|
109
|
+
const resolvedProxy = this.resolveProxyConfig(proxyConfig, enhancedConfig.url);
|
|
110
|
+
if (resolvedProxy !== false) {
|
|
111
|
+
enhancedConfig.proxy = resolvedProxy;
|
|
112
|
+
}
|
|
114
113
|
}
|
|
115
114
|
// 配置axios-retry的onRetry回调以记录重试
|
|
116
115
|
if ((_c = this.defaultConfig.retry) === null || _c === void 0 ? void 0 : _c.enabled) {
|
|
@@ -136,7 +135,7 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
|
|
|
136
135
|
}
|
|
137
136
|
// 成功日志
|
|
138
137
|
if (loggingOptions === null || loggingOptions === void 0 ? void 0 : loggingOptions.enabled) {
|
|
139
|
-
const databaseLogging = (loggingOptions === null || loggingOptions === void 0 ? void 0 : loggingOptions.
|
|
138
|
+
const databaseLogging = (loggingOptions === null || loggingOptions === void 0 ? void 0 : loggingOptions.databaseLogging) ||
|
|
140
139
|
((_e = (_d = this.defaultConfig.logging) === null || _d === void 0 ? void 0 : _d.databaseLogging) === null || _e === void 0 ? void 0 : _e.enabled);
|
|
141
140
|
this.loggingService.logRequestSuccess(response, startTime, requestId, loggingOptions, databaseLogging, retryRecorder.getRecords(), cacheHit, circuitBreakerState, decoratorContext);
|
|
142
141
|
}
|
|
@@ -369,21 +368,14 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
|
|
|
369
368
|
* 创建Axios实例并配置axios-retry
|
|
370
369
|
*/
|
|
371
370
|
createAxiosInstance() {
|
|
372
|
-
var _a
|
|
371
|
+
var _a;
|
|
373
372
|
const instance = require('axios').create({
|
|
374
373
|
baseURL: this.defaultConfig.baseURL,
|
|
375
374
|
timeout: this.defaultConfig.timeout,
|
|
376
|
-
proxy: (
|
|
377
|
-
? {
|
|
378
|
-
host: this.defaultConfig.proxy.host,
|
|
379
|
-
port: this.defaultConfig.proxy.port,
|
|
380
|
-
protocol: this.defaultConfig.proxy.protocol,
|
|
381
|
-
auth: this.defaultConfig.proxy.auth,
|
|
382
|
-
}
|
|
383
|
-
: undefined,
|
|
375
|
+
proxy: this.resolveProxyConfig(this.defaultConfig.proxy),
|
|
384
376
|
});
|
|
385
377
|
// 配置axios-retry
|
|
386
|
-
if ((
|
|
378
|
+
if ((_a = this.defaultConfig.retry) === null || _a === void 0 ? void 0 : _a.enabled) {
|
|
387
379
|
(0, axios_retry_1.default)(instance, {
|
|
388
380
|
retries: this.defaultConfig.retry.retries || 3,
|
|
389
381
|
retryDelay: this.defaultConfig.retry.retryDelay ||
|
|
@@ -594,6 +586,58 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
|
|
|
594
586
|
}
|
|
595
587
|
return result;
|
|
596
588
|
}
|
|
589
|
+
/**
|
|
590
|
+
* 解析代理配置
|
|
591
|
+
* 支持从环境变量或手动配置中读取
|
|
592
|
+
* @param proxyConfig 代理配置
|
|
593
|
+
* @param targetUrl 目标 URL(用于 NO_PROXY 检查)
|
|
594
|
+
* @returns 解析后的代理配置,如果不应使用代理则返回 false 或 undefined
|
|
595
|
+
*/
|
|
596
|
+
resolveProxyConfig(proxyConfig, targetUrl) {
|
|
597
|
+
if (!(proxyConfig === null || proxyConfig === void 0 ? void 0 : proxyConfig.enabled)) {
|
|
598
|
+
return undefined;
|
|
599
|
+
}
|
|
600
|
+
// 如果启用从环境变量读取
|
|
601
|
+
if (proxyConfig.fromEnvironment) {
|
|
602
|
+
// 确定目标协议
|
|
603
|
+
let protocol = 'http';
|
|
604
|
+
if (targetUrl) {
|
|
605
|
+
try {
|
|
606
|
+
const url = new URL(targetUrl, this.defaultConfig.baseURL);
|
|
607
|
+
protocol = url.protocol === 'https:' ? 'https' : 'http';
|
|
608
|
+
}
|
|
609
|
+
catch (error) {
|
|
610
|
+
this.logger.warn(`Failed to parse target URL: ${error.message}`);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
// 从环境变量解析
|
|
614
|
+
const fullUrl = targetUrl
|
|
615
|
+
? new URL(targetUrl, this.defaultConfig.baseURL).href
|
|
616
|
+
: undefined;
|
|
617
|
+
const envProxy = proxy_environment_util_1.ProxyEnvironmentParser.parseFromEnvironment(protocol, fullUrl);
|
|
618
|
+
if (envProxy === false) {
|
|
619
|
+
this.logger.debug('Proxy bypassed based on environment configuration');
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
return {
|
|
623
|
+
host: envProxy.host,
|
|
624
|
+
port: envProxy.port,
|
|
625
|
+
protocol: envProxy.protocol,
|
|
626
|
+
auth: envProxy.auth,
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
// 使用手动配置
|
|
630
|
+
if (proxyConfig.host && proxyConfig.port) {
|
|
631
|
+
return {
|
|
632
|
+
host: proxyConfig.host,
|
|
633
|
+
port: proxyConfig.port,
|
|
634
|
+
protocol: proxyConfig.protocol,
|
|
635
|
+
auth: proxyConfig.auth,
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
this.logger.warn('Proxy is enabled but neither fromEnvironment nor manual configuration is provided');
|
|
639
|
+
return undefined;
|
|
640
|
+
}
|
|
597
641
|
};
|
|
598
642
|
exports.HttpClientService = HttpClientService;
|
|
599
643
|
exports.HttpClientService = HttpClientService = HttpClientService_1 = __decorate([
|
|
@@ -17,7 +17,7 @@ export declare class HttpLoggingService {
|
|
|
17
17
|
/**
|
|
18
18
|
* 记录请求成功响应
|
|
19
19
|
*/
|
|
20
|
-
logRequestSuccess(response: AxiosResponse, startTime: number, requestId: string, loggingOptions: any, databaseLogging?: boolean, retryRecords?: any[], cacheHit?: boolean, circuitBreakerState?: string, decoratorContext?: any): void
|
|
20
|
+
logRequestSuccess(response: AxiosResponse, startTime: number, requestId: string, loggingOptions: any, databaseLogging?: boolean, retryRecords?: any[], cacheHit?: boolean, circuitBreakerState?: string, decoratorContext?: any): Promise<void>;
|
|
21
21
|
/**
|
|
22
22
|
* 记录请求错误
|
|
23
23
|
*/
|
|
@@ -26,6 +26,7 @@ const http_log_entity_1 = require("../entities/http-log.entity");
|
|
|
26
26
|
const request_id_util_1 = require("../utils/request-id.util");
|
|
27
27
|
const context_extractor_util_1 = require("../utils/context-extractor.util");
|
|
28
28
|
const call_stack_extractor_util_1 = require("../utils/call-stack-extractor.util");
|
|
29
|
+
const transaction_1 = require("../../transaction");
|
|
29
30
|
/**
|
|
30
31
|
* HTTP日志服务
|
|
31
32
|
* 基于Spring Boot的请求日志记录机制,集成现有的ContextProvider
|
|
@@ -74,71 +75,77 @@ let HttpLoggingService = HttpLoggingService_1 = class HttpLoggingService {
|
|
|
74
75
|
/**
|
|
75
76
|
* 记录请求成功响应
|
|
76
77
|
*/
|
|
77
|
-
logRequestSuccess(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
userId: context.userId,
|
|
86
|
-
statusCode: response.status,
|
|
87
|
-
responseTime,
|
|
88
|
-
headers: logHeaders
|
|
89
|
-
? this.sanitizeHeaders(response.headers, sanitizeHeaders)
|
|
90
|
-
: undefined,
|
|
91
|
-
body: logBody ? this.sanitizeBody(response.data) : undefined,
|
|
92
|
-
responseSize: JSON.stringify(response.data).length,
|
|
93
|
-
cacheHit,
|
|
94
|
-
circuitBreakerState,
|
|
95
|
-
};
|
|
96
|
-
const logMessage = `HTTP Response [${requestId}]: ${response.status} (${responseTime}ms)${cacheHit ? ' [CACHE HIT]' : ''}`;
|
|
97
|
-
switch ((loggingOptions || {}).logLevel) {
|
|
98
|
-
case 'debug':
|
|
99
|
-
this.logger.debug(logMessage, logData);
|
|
100
|
-
break;
|
|
101
|
-
case 'info':
|
|
102
|
-
default:
|
|
103
|
-
this.logger.log(logMessage, logData);
|
|
104
|
-
break;
|
|
105
|
-
}
|
|
106
|
-
// 数据库日志记录
|
|
107
|
-
if (databaseLogging && this.logRepository) {
|
|
108
|
-
// 提取调用信息
|
|
109
|
-
const callInfo = this.extractCallInfo(decoratorContext, response.config);
|
|
110
|
-
this.saveToDatabase({
|
|
111
|
-
id: requestId,
|
|
78
|
+
logRequestSuccess(response_1, startTime_1, requestId_1, loggingOptions_1) {
|
|
79
|
+
return __awaiter(this, arguments, void 0, function* (response, startTime, requestId, loggingOptions, databaseLogging = false, retryRecords, cacheHit, circuitBreakerState, decoratorContext) {
|
|
80
|
+
var _a, _b;
|
|
81
|
+
const { logHeaders = true, logBody = true, sanitizeHeaders = [], } = loggingOptions || {};
|
|
82
|
+
const responseTime = Date.now() - startTime;
|
|
83
|
+
// 获取当前上下文信息
|
|
84
|
+
const context = context_extractor_util_1.ContextExtractor.getHttpContext();
|
|
85
|
+
const logData = {
|
|
112
86
|
requestId,
|
|
113
87
|
userId: context.userId,
|
|
114
|
-
method: ((_a = response.config.method) === null || _a === void 0 ? void 0 : _a.toUpperCase()) || 'UNKNOWN',
|
|
115
|
-
url: response.config.url || '',
|
|
116
|
-
headers: this.sanitizeHeaders(response.config.headers, sanitizeHeaders),
|
|
117
|
-
body: this.sanitizeBodyAsString(response.config.data),
|
|
118
|
-
params: response.config.params,
|
|
119
88
|
statusCode: response.status,
|
|
120
89
|
responseTime,
|
|
121
|
-
|
|
122
|
-
?
|
|
123
|
-
: 1,
|
|
124
|
-
success: true,
|
|
125
|
-
responseHeaders: this.sanitizeHeaders(response.headers, sanitizeHeaders),
|
|
126
|
-
responseBody: logBody
|
|
127
|
-
? this.sanitizeBodyAsString(response.data)
|
|
90
|
+
headers: logHeaders
|
|
91
|
+
? this.sanitizeHeaders(response.headers, sanitizeHeaders)
|
|
128
92
|
: undefined,
|
|
93
|
+
body: logBody ? this.sanitizeBody(response.data) : undefined,
|
|
129
94
|
responseSize: JSON.stringify(response.data).length,
|
|
130
|
-
requestSize: JSON.stringify(response.config.data).length,
|
|
131
|
-
serviceName: context.appId,
|
|
132
|
-
operationName: callInfo.operationName,
|
|
133
|
-
clientIp: context.clientIp,
|
|
134
|
-
source: (_b = context.metadata) === null || _b === void 0 ? void 0 : _b.source,
|
|
135
|
-
tags: context.tags,
|
|
136
|
-
metadata: logData,
|
|
137
|
-
retryRecords,
|
|
138
95
|
cacheHit,
|
|
139
96
|
circuitBreakerState,
|
|
140
|
-
}
|
|
141
|
-
|
|
97
|
+
};
|
|
98
|
+
const logMessage = `HTTP Response [${requestId}]: ${response.status} (${responseTime}ms)${cacheHit ? ' [CACHE HIT]' : ''}`;
|
|
99
|
+
switch ((loggingOptions || {}).logLevel) {
|
|
100
|
+
case 'debug':
|
|
101
|
+
this.logger.debug(logMessage, logData);
|
|
102
|
+
break;
|
|
103
|
+
``;
|
|
104
|
+
case 'info':
|
|
105
|
+
default:
|
|
106
|
+
this.logger.log(logMessage, logData);
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
if (!this.logRepository) {
|
|
110
|
+
this.logRepository = transaction_1.TransactionContextService.getDataSource().getRepository(loggingOptions.databaseLogging.tableName);
|
|
111
|
+
}
|
|
112
|
+
// 数据库日志记录
|
|
113
|
+
if (databaseLogging && this.logRepository) {
|
|
114
|
+
// 提取调用信息
|
|
115
|
+
const callInfo = this.extractCallInfo(decoratorContext, response.config);
|
|
116
|
+
this.saveToDatabase({
|
|
117
|
+
id: requestId,
|
|
118
|
+
requestId,
|
|
119
|
+
userId: context.userId,
|
|
120
|
+
method: ((_a = response.config.method) === null || _a === void 0 ? void 0 : _a.toUpperCase()) || 'UNKNOWN',
|
|
121
|
+
url: response.config.url || '',
|
|
122
|
+
headers: this.sanitizeHeaders(response.config.headers, sanitizeHeaders),
|
|
123
|
+
body: this.sanitizeBodyAsString(response.config.data),
|
|
124
|
+
params: response.config.params,
|
|
125
|
+
statusCode: response.status,
|
|
126
|
+
responseTime,
|
|
127
|
+
attemptCount: (retryRecords === null || retryRecords === void 0 ? void 0 : retryRecords.length)
|
|
128
|
+
? Math.max(...retryRecords.map((r) => r.attempt))
|
|
129
|
+
: 1,
|
|
130
|
+
success: true,
|
|
131
|
+
responseHeaders: this.sanitizeHeaders(response.headers, sanitizeHeaders),
|
|
132
|
+
responseBody: logBody
|
|
133
|
+
? this.sanitizeBodyAsString(response.data)
|
|
134
|
+
: undefined,
|
|
135
|
+
responseSize: JSON.stringify(response.data).length,
|
|
136
|
+
requestSize: JSON.stringify(response.config.data).length,
|
|
137
|
+
serviceName: context.appId,
|
|
138
|
+
operationName: callInfo.operationName,
|
|
139
|
+
clientIp: context.clientIp,
|
|
140
|
+
source: (_b = context.metadata) === null || _b === void 0 ? void 0 : _b.source,
|
|
141
|
+
tags: context.tags,
|
|
142
|
+
metadata: logData,
|
|
143
|
+
retryRecords,
|
|
144
|
+
cacheHit,
|
|
145
|
+
circuitBreakerState,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
});
|
|
142
149
|
}
|
|
143
150
|
/**
|
|
144
151
|
* 记录请求错误
|
|
@@ -389,13 +396,22 @@ let HttpLoggingService = HttpLoggingService_1 = class HttpLoggingService {
|
|
|
389
396
|
*/
|
|
390
397
|
saveToDatabase(logEntity) {
|
|
391
398
|
return __awaiter(this, void 0, void 0, function* () {
|
|
392
|
-
if (!this.logRepository)
|
|
399
|
+
if (!this.logRepository) {
|
|
400
|
+
this.logger.warn('Log repository not initialized, skipping database logging');
|
|
393
401
|
return;
|
|
402
|
+
}
|
|
394
403
|
try {
|
|
404
|
+
this.logger.debug('Saving HTTP log to database', {
|
|
405
|
+
requestId: logEntity.requestId,
|
|
406
|
+
});
|
|
395
407
|
yield this.logRepository.save(logEntity);
|
|
408
|
+
this.logger.debug('Successfully saved HTTP log to database', {
|
|
409
|
+
requestId: logEntity.requestId,
|
|
410
|
+
});
|
|
396
411
|
}
|
|
397
412
|
catch (error) {
|
|
398
413
|
this.logger.error('Failed to save HTTP log to database', error);
|
|
414
|
+
this.logger.error('Log entity details', logEntity);
|
|
399
415
|
}
|
|
400
416
|
});
|
|
401
417
|
}
|
|
@@ -18,3 +18,4 @@ __exportStar(require("./curl-generator.util"), exports);
|
|
|
18
18
|
__exportStar(require("./retry-recorder.util"), exports);
|
|
19
19
|
__exportStar(require("./context-extractor.util"), exports);
|
|
20
20
|
__exportStar(require("./call-stack-extractor.util"), exports);
|
|
21
|
+
__exportStar(require("./proxy-environment.util"), exports);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ProxyConfig } from '../interfaces/http-client-config.interface';
|
|
2
|
+
/**
|
|
3
|
+
* 代理环境变量解析工具
|
|
4
|
+
* 支持标准的代理环境变量:HTTP_PROXY, HTTPS_PROXY, NO_PROXY
|
|
5
|
+
*/
|
|
6
|
+
export declare class ProxyEnvironmentParser {
|
|
7
|
+
private static readonly logger;
|
|
8
|
+
/**
|
|
9
|
+
* 从环境变量解析代理配置
|
|
10
|
+
* @param protocol 请求协议 (http 或 https)
|
|
11
|
+
* @param targetUrl 目标 URL(用于检查 NO_PROXY)
|
|
12
|
+
* @returns 解析后的代理配置,如果不应使用代理则返回 false
|
|
13
|
+
*/
|
|
14
|
+
static parseFromEnvironment(protocol?: 'http' | 'https', targetUrl?: string): false | Required<Omit<ProxyConfig, 'enabled' | 'fromEnvironment'>>;
|
|
15
|
+
/**
|
|
16
|
+
* 解析代理 URL
|
|
17
|
+
* 支持格式: http://proxy:port, http://user:pass@proxy:port
|
|
18
|
+
*/
|
|
19
|
+
private static parseProxyUrl;
|
|
20
|
+
/**
|
|
21
|
+
* 检查是否应该绕过代理
|
|
22
|
+
* 基于 NO_PROXY 环境变量
|
|
23
|
+
*/
|
|
24
|
+
private static shouldBypassProxy;
|
|
25
|
+
/**
|
|
26
|
+
* 简单的 IP 地址匹配
|
|
27
|
+
* 支持精确匹配和 CIDR 表示法
|
|
28
|
+
*/
|
|
29
|
+
private static isIpMatch;
|
|
30
|
+
/**
|
|
31
|
+
* 获取所有代理相关的环境变量
|
|
32
|
+
*/
|
|
33
|
+
static getProxyEnvironmentVariables(): {
|
|
34
|
+
HTTP_PROXY?: string;
|
|
35
|
+
HTTPS_PROXY?: string;
|
|
36
|
+
NO_PROXY?: string;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* 检查是否设置了代理环境变量
|
|
40
|
+
*/
|
|
41
|
+
static hasProxyEnvironment(): boolean;
|
|
42
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ProxyEnvironmentParser = void 0;
|
|
4
|
+
const common_1 = require("@nestjs/common");
|
|
5
|
+
/**
|
|
6
|
+
* 代理环境变量解析工具
|
|
7
|
+
* 支持标准的代理环境变量:HTTP_PROXY, HTTPS_PROXY, NO_PROXY
|
|
8
|
+
*/
|
|
9
|
+
class ProxyEnvironmentParser {
|
|
10
|
+
/**
|
|
11
|
+
* 从环境变量解析代理配置
|
|
12
|
+
* @param protocol 请求协议 (http 或 https)
|
|
13
|
+
* @param targetUrl 目标 URL(用于检查 NO_PROXY)
|
|
14
|
+
* @returns 解析后的代理配置,如果不应使用代理则返回 false
|
|
15
|
+
*/
|
|
16
|
+
static parseFromEnvironment(protocol = 'http', targetUrl) {
|
|
17
|
+
// 获取代理环境变量 (大写和小写都支持)
|
|
18
|
+
const proxyEnv = protocol === 'https'
|
|
19
|
+
? process.env.HTTPS_PROXY ||
|
|
20
|
+
process.env.https_proxy ||
|
|
21
|
+
process.env.HTTP_PROXY ||
|
|
22
|
+
process.env.http_proxy
|
|
23
|
+
: process.env.HTTP_PROXY || process.env.http_proxy;
|
|
24
|
+
// 检查是否应该跳过代理
|
|
25
|
+
if (targetUrl && this.shouldBypassProxy(targetUrl)) {
|
|
26
|
+
this.logger.debug(`Bypassing proxy for ${targetUrl} due to NO_PROXY`);
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
if (!proxyEnv) {
|
|
30
|
+
this.logger.debug('No proxy environment variable found');
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const proxyConfig = this.parseProxyUrl(proxyEnv);
|
|
35
|
+
this.logger.debug(`Parsed proxy configuration from environment: ${JSON.stringify(proxyConfig)}`);
|
|
36
|
+
return proxyConfig;
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
this.logger.warn(`Failed to parse proxy URL from environment: ${error.message}`);
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 解析代理 URL
|
|
45
|
+
* 支持格式: http://proxy:port, http://user:pass@proxy:port
|
|
46
|
+
*/
|
|
47
|
+
static parseProxyUrl(proxyUrl) {
|
|
48
|
+
try {
|
|
49
|
+
const url = new URL(proxyUrl);
|
|
50
|
+
const config = {
|
|
51
|
+
protocol: url.protocol.replace(':', '') || 'http',
|
|
52
|
+
host: url.hostname,
|
|
53
|
+
port: url.port ? parseInt(url.port, 10) : url.protocol === 'https:' ? 443 : 80,
|
|
54
|
+
auth: undefined,
|
|
55
|
+
};
|
|
56
|
+
// 解析认证信息
|
|
57
|
+
if (url.username || url.password) {
|
|
58
|
+
config.auth = {
|
|
59
|
+
username: decodeURIComponent(url.username),
|
|
60
|
+
password: decodeURIComponent(url.password),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return config;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
throw new Error(`Invalid proxy URL format: ${proxyUrl}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 检查是否应该绕过代理
|
|
71
|
+
* 基于 NO_PROXY 环境变量
|
|
72
|
+
*/
|
|
73
|
+
static shouldBypassProxy(targetUrl) {
|
|
74
|
+
const noProxy = process.env.NO_PROXY || process.env.no_proxy;
|
|
75
|
+
if (!noProxy) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const url = new URL(targetUrl);
|
|
80
|
+
const hostname = url.hostname;
|
|
81
|
+
// NO_PROXY 可以是逗号分隔的列表
|
|
82
|
+
const bypassList = noProxy.split(',').map((item) => item.trim().toLowerCase());
|
|
83
|
+
for (const bypass of bypassList) {
|
|
84
|
+
if (!bypass)
|
|
85
|
+
continue;
|
|
86
|
+
// 精确匹配
|
|
87
|
+
if (hostname.toLowerCase() === bypass) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
// 通配符匹配 (例如 .example.com)
|
|
91
|
+
if (bypass.startsWith('.') && hostname.toLowerCase().endsWith(bypass)) {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
// 后缀匹配 (例如 example.com 匹配 api.example.com)
|
|
95
|
+
if (hostname.toLowerCase().endsWith('.' + bypass)) {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
// 特殊值 * 表示所有域名都绕过
|
|
99
|
+
if (bypass === '*') {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
// IP 地址范围匹配 (简单实现)
|
|
103
|
+
if (this.isIpMatch(hostname, bypass)) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
this.logger.warn(`Failed to parse target URL for NO_PROXY check: ${error.message}`);
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* 简单的 IP 地址匹配
|
|
115
|
+
* 支持精确匹配和 CIDR 表示法
|
|
116
|
+
*/
|
|
117
|
+
static isIpMatch(hostname, pattern) {
|
|
118
|
+
// 精确 IP 匹配
|
|
119
|
+
if (hostname === pattern) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
// CIDR 匹配 (简化版本,不做完整的 CIDR 计算)
|
|
123
|
+
if (pattern.includes('/')) {
|
|
124
|
+
const [baseIp] = pattern.split('/');
|
|
125
|
+
return hostname.startsWith(baseIp.substring(0, baseIp.lastIndexOf('.')));
|
|
126
|
+
}
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* 获取所有代理相关的环境变量
|
|
131
|
+
*/
|
|
132
|
+
static getProxyEnvironmentVariables() {
|
|
133
|
+
return {
|
|
134
|
+
HTTP_PROXY: process.env.HTTP_PROXY || process.env.http_proxy,
|
|
135
|
+
HTTPS_PROXY: process.env.HTTPS_PROXY || process.env.https_proxy,
|
|
136
|
+
NO_PROXY: process.env.NO_PROXY || process.env.no_proxy,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* 检查是否设置了代理环境变量
|
|
141
|
+
*/
|
|
142
|
+
static hasProxyEnvironment() {
|
|
143
|
+
const vars = this.getProxyEnvironmentVariables();
|
|
144
|
+
return !!(vars.HTTP_PROXY || vars.HTTPS_PROXY);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
exports.ProxyEnvironmentParser = ProxyEnvironmentParser;
|
|
148
|
+
ProxyEnvironmentParser.logger = new common_1.Logger(ProxyEnvironmentParser.name);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nest-omni/core",
|
|
3
|
-
"version": "4.1.3-
|
|
3
|
+
"version": "4.1.3-14",
|
|
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",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"author": "Jinpy",
|
|
37
37
|
"license": "Apache-2.0",
|
|
38
38
|
"engines": {
|
|
39
|
-
"node": ">=
|
|
39
|
+
"node": ">=22.0.0",
|
|
40
40
|
"npm": ">=9.0.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"@types/express-session": "^1.18.2",
|
|
46
46
|
"@types/lodash": "^4.17.20",
|
|
47
47
|
"@types/node": "^24.9.1",
|
|
48
|
+
"@types/sharp": "^0.31.1",
|
|
48
49
|
"@types/sprintf-js": "^1.1.4",
|
|
49
50
|
"@types/uuid": "^11.0.0",
|
|
50
51
|
"sqlite3": "^5.1.7",
|
|
@@ -66,7 +67,7 @@
|
|
|
66
67
|
"@sentry/profiling-node": "^10.22.0",
|
|
67
68
|
"@songkeys/nestjs-redis": "^11.0.0",
|
|
68
69
|
"@songkeys/nestjs-redis-health": "^11.0.0",
|
|
69
|
-
"axios": "^1.
|
|
70
|
+
"axios": "^1.13.2",
|
|
70
71
|
"axios-retry": "^4.5.0",
|
|
71
72
|
"bcrypt": "^6.0.0",
|
|
72
73
|
"body-parser": "^2.2.0",
|
|
@@ -96,6 +97,9 @@
|
|
|
96
97
|
"source-map-support": "^0.5.21",
|
|
97
98
|
"sprintf-js": "^1.1.3",
|
|
98
99
|
"typeorm": "^0.3.27",
|
|
99
|
-
"uuid": "^13.0.0"
|
|
100
|
-
|
|
100
|
+
"uuid": "^13.0.0",
|
|
101
|
+
"fast-csv": "^5.0.5",
|
|
102
|
+
"xlsx": "git+https://git.sheetjs.com/sheetjs/sheetjs.git#v0.20.3"
|
|
103
|
+
},
|
|
104
|
+
"peerDependencies": {}
|
|
101
105
|
}
|
|
@@ -36,6 +36,7 @@ const redis_lock_1 = require("../redis-lock");
|
|
|
36
36
|
const typeorm_2 = require("typeorm");
|
|
37
37
|
const vault_1 = require("../vault");
|
|
38
38
|
const validators_1 = require("../validators");
|
|
39
|
+
const transaction_1 = require("../transaction");
|
|
39
40
|
const providers = [
|
|
40
41
|
services_1.ApiConfigService,
|
|
41
42
|
services_1.GeneratorService,
|
|
@@ -85,10 +86,27 @@ const modules = [
|
|
|
85
86
|
}),
|
|
86
87
|
];
|
|
87
88
|
if (services_1.ApiConfigService.toBoolean(process.env.DB_ENABLED, true)) {
|
|
89
|
+
// 1. 首先导入 TransactionModule
|
|
90
|
+
modules.push(transaction_1.TransactionModule.forRoot({
|
|
91
|
+
defaultDataSource: 'default',
|
|
92
|
+
enableDynamicRegistration: true,
|
|
93
|
+
}));
|
|
94
|
+
// 2. 导入 TypeORM 模块,并在初始化后注册数据源
|
|
88
95
|
modules.push(typeorm_1.TypeOrmModule.forRootAsync({
|
|
89
96
|
inject: [services_1.ApiConfigService],
|
|
90
97
|
useFactory: (config) => config.typeormConfig,
|
|
91
98
|
}));
|
|
99
|
+
// 3. 添加数据源注册服务,在应用启动时注册 DataSource
|
|
100
|
+
providers.push({
|
|
101
|
+
provide: 'DATASOURCE_REGISTRAR',
|
|
102
|
+
useFactory: (dataSource) => __awaiter(void 0, void 0, void 0, function* () {
|
|
103
|
+
// 手动注册 default 数据源到 DataSourceUtil
|
|
104
|
+
(0, transaction_1.addDataSource)('default', dataSource, true);
|
|
105
|
+
return dataSource;
|
|
106
|
+
}),
|
|
107
|
+
inject: [typeorm_2.DataSource],
|
|
108
|
+
});
|
|
109
|
+
// 4. 配置 CLS 模块
|
|
92
110
|
modules.push(nestjs_cls_1.ClsModule.forRootAsync({
|
|
93
111
|
imports: [typeorm_1.TypeOrmModule],
|
|
94
112
|
useFactory(args) {
|