@nest-omni/core 4.1.3-32 → 4.1.3-34

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.
@@ -24,7 +24,7 @@ export declare class EmailLogProvider {
24
24
  */
25
25
  verifyConnection(): Promise<boolean>;
26
26
  /**
27
- * Send log email
27
+ * Send log email with automatic retry on connection failure
28
28
  */
29
29
  sendLogEmail(params: {
30
30
  to: string[];
@@ -46,6 +46,14 @@ let EmailLogProvider = EmailLogProvider_1 = class EmailLogProvider {
46
46
  ? !this.options.smtpIgnoreTLSError
47
47
  : true,
48
48
  },
49
+ // 连接池配置
50
+ pool: true,
51
+ maxConnections: 5,
52
+ maxMessages: 10,
53
+ // 超时配置
54
+ connectionTimeout: 10000, // 10秒连接超时
55
+ greetingTimeout: 5000, // 5秒握手超时
56
+ socketTimeout: 10000, // 10秒socket超时
49
57
  };
50
58
  if (this.options.smtpUsername || this.options.smtpPassword) {
51
59
  config.auth = {
@@ -76,35 +84,67 @@ let EmailLogProvider = EmailLogProvider_1 = class EmailLogProvider {
76
84
  });
77
85
  }
78
86
  /**
79
- * Send log email
87
+ * Send log email with automatic retry on connection failure
80
88
  */
81
89
  sendLogEmail(params) {
82
90
  return __awaiter(this, void 0, void 0, function* () {
83
- if (!this.transporter) {
84
- throw new Error('Email transporter not initialized');
85
- }
86
- try {
87
- const mailOptions = {
88
- from: params.from,
89
- to: params.to.join(', '),
90
- subject: params.subject,
91
- };
92
- if (params.cc && params.cc.length > 0) {
93
- mailOptions.cc = params.cc.join(', ');
91
+ var _a, _b, _c;
92
+ const maxRetries = 2;
93
+ let lastError = null;
94
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
95
+ try {
96
+ if (!this.transporter) {
97
+ this.initializeTransporter();
98
+ }
99
+ const mailOptions = {
100
+ from: params.from,
101
+ to: params.to.join(', '),
102
+ subject: params.subject,
103
+ };
104
+ if (params.cc && params.cc.length > 0) {
105
+ mailOptions.cc = params.cc.join(', ');
106
+ }
107
+ if (params.html) {
108
+ mailOptions.html = params.html;
109
+ }
110
+ else if (params.text) {
111
+ mailOptions.text = params.text;
112
+ }
113
+ const info = yield this.transporter.sendMail(mailOptions);
114
+ this.logger.debug(`Log email sent to ${params.to.join(', ')} - Message ID: ${info.messageId}`);
115
+ return;
94
116
  }
95
- if (params.html) {
96
- mailOptions.html = params.html;
117
+ catch (error) {
118
+ lastError = error;
119
+ // Check if this is a connection error that can be retried
120
+ const isConnectionError = error.code === 'ECONNECTION' ||
121
+ error.code === 'ETIMEDOUT' ||
122
+ error.code === 'ECONNRESET' ||
123
+ ((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes('Connection closed')) ||
124
+ ((_b = error.message) === null || _b === void 0 ? void 0 : _b.includes('socket hang up')) ||
125
+ ((_c = error.message) === null || _c === void 0 ? void 0 : _c.includes('Unexpected socket close'));
126
+ if (isConnectionError && attempt < maxRetries) {
127
+ this.logger.warn(`Email send failed (attempt ${attempt}/${maxRetries}): ${error.message}. Reinitializing connection...`);
128
+ // Close existing transporter and create a new one
129
+ if (this.transporter) {
130
+ try {
131
+ this.transporter.close();
132
+ }
133
+ catch (e) {
134
+ // Ignore close errors
135
+ }
136
+ }
137
+ this.transporter = null;
138
+ // Wait a bit before retrying
139
+ yield new Promise((resolve) => setTimeout(resolve, 500));
140
+ }
141
+ else {
142
+ this.logger.error(`Failed to send log email after ${attempt} attempt(s): ${error.message}`, error.stack);
143
+ throw error;
144
+ }
97
145
  }
98
- else if (params.text) {
99
- mailOptions.text = params.text;
100
- }
101
- const info = yield this.transporter.sendMail(mailOptions);
102
- this.logger.debug(`Log email sent to ${params.to.join(', ')} - Message ID: ${info.messageId}`);
103
- }
104
- catch (error) {
105
- this.logger.error(`Failed to send log email: ${error.message}`, error.stack);
106
- throw error;
107
146
  }
147
+ throw lastError;
108
148
  });
109
149
  }
110
150
  /**
@@ -24,7 +24,6 @@ function getFullResponseExample(httpClient) {
24
24
  return __awaiter(this, void 0, void 0, function* () {
25
25
  // 返回完整的AxiosResponse对象
26
26
  const fullResponse = yield httpClient.get('https://api.example.com/data', {}, // config
27
- 'my-client', // clientName
28
27
  { returnType: 'full' });
29
28
  // 现在可以访问headers、status等信息
30
29
  console.log('Status:', fullResponse.status);
@@ -40,7 +39,7 @@ function getFullResponseExample(httpClient) {
40
39
  function getCustomResponseExample(httpClient) {
41
40
  return __awaiter(this, void 0, void 0, function* () {
42
41
  // 自定义转换函数 - 只返回需要的字段
43
- const customResponse = yield httpClient.get('https://api.example.com/users', {}, 'my-client', {
42
+ const customResponse = yield httpClient.get('https://api.example.com/users', {}, {
44
43
  returnType: 'custom',
45
44
  transform: (response) => {
46
45
  var _a;
@@ -61,7 +60,7 @@ function getCustomResponseExample(httpClient) {
61
60
  function getPagedResponseExample(httpClient) {
62
61
  return __awaiter(this, void 0, void 0, function* () {
63
62
  var _a, _b;
64
- const response = yield httpClient.get('https://api.example.com/items?page=1&size=10', {}, 'my-client', { returnType: 'full' });
63
+ const response = yield httpClient.get('https://api.example.com/items?page=1&size=10', {}, { returnType: 'full' });
65
64
  // 提取分页信息
66
65
  const paginationInfo = {
67
66
  data: response.data,
@@ -79,7 +78,7 @@ function getPagedResponseExample(httpClient) {
79
78
  function getRedirectUrlExample(httpClient) {
80
79
  return __awaiter(this, void 0, void 0, function* () {
81
80
  const response = yield httpClient.get('https://api.example.com/redirect-me', { maxRedirects: 0 }, // 禁止自动重定向以便捕获Location header
82
- 'my-client', { returnType: 'full' });
81
+ { returnType: 'full' });
83
82
  // 对于3xx重定向状态,获取重定向URL
84
83
  if (response.status >= 300 && response.status < 400) {
85
84
  const redirectUrl = response.headers['location'];
@@ -92,7 +91,7 @@ function getRedirectUrlExample(httpClient) {
92
91
  // 示例5: 向后兼容 - 默认行为(只返回data)
93
92
  function getDefaultBehaviorExample(httpClient) {
94
93
  return __awaiter(this, void 0, void 0, function* () {
95
- // 不传递returnOptions参数,保持原有行为
94
+ // 不传递options参数,保持原有行为
96
95
  const data = yield httpClient.get('https://api.example.com/simple-data');
97
96
  // 直接得到data,无需.response.data
98
97
  console.log('Simple data:', data);
@@ -105,7 +104,8 @@ function conditionalReturnExample(httpClient, needHeaders) {
105
104
  const returnOptions = needHeaders
106
105
  ? { returnType: 'full' }
107
106
  : { returnType: 'data' }; // 默认
108
- const result = yield httpClient.post('https://api.example.com/process', { action: 'calculate' }, {}, 'my-client', returnOptions);
107
+ const result = yield httpClient.post('https://api.example.com/process', { action: 'calculate' }, {}, // config
108
+ returnOptions);
109
109
  if (needHeaders) {
110
110
  // 处理完整响应
111
111
  const fullResponse = result;
@@ -3,3 +3,4 @@ export * from './advanced-usage.example';
3
3
  export * from './multi-api-configuration.example';
4
4
  export * from './axios-config-extended.example';
5
5
  export * from './ssl-certificate.example';
6
+ export * from './request-options.example';
@@ -19,3 +19,4 @@ __exportStar(require("./advanced-usage.example"), exports);
19
19
  __exportStar(require("./multi-api-configuration.example"), exports);
20
20
  __exportStar(require("./axios-config-extended.example"), exports);
21
21
  __exportStar(require("./ssl-certificate.example"), exports);
22
+ __exportStar(require("./request-options.example"), exports);
@@ -0,0 +1,58 @@
1
+ /**
2
+ * HTTP 请求级别配置示例
3
+ * 演示 RequestOptions 的使用方式
4
+ */
5
+ import { HttpClientService } from '../services/http-client.service';
6
+ export declare class RequestOptionsExample {
7
+ private readonly httpClient;
8
+ private readonly logger;
9
+ constructor(httpClient: HttpClientService);
10
+ /**
11
+ * 示例1:最简单 - 禁用重试
12
+ */
13
+ disableRetry(): Promise<any>;
14
+ /**
15
+ * 示例2:启用重试
16
+ */
17
+ enableRetry(): Promise<any>;
18
+ /**
19
+ * 示例3:自定义重试条件
20
+ */
21
+ customRetryCondition(): Promise<any>;
22
+ /**
23
+ * 示例4:配置超时和返回类型
24
+ */
25
+ withTimeoutAndReturnType(): Promise<any>;
26
+ /**
27
+ * 示例5:使用客户端名称
28
+ */
29
+ withClientName(): Promise<any>;
30
+ /**
31
+ * 示例6:自定义重试延迟
32
+ */
33
+ customRetryDelay(): Promise<any>;
34
+ /**
35
+ * 示例7:组合多个配置
36
+ */
37
+ combinedOptions(): Promise<any>;
38
+ /**
39
+ * 示例8:在 PRTG 服务中使用
40
+ */
41
+ prtgCreateDevice(): Promise<any>;
42
+ /**
43
+ * 示例9:简洁写法 - 多个配置
44
+ */
45
+ multipleSettings(): Promise<any>;
46
+ /**
47
+ * 示例10:添加自定义 headers
48
+ */
49
+ withHeaders(): Promise<any>;
50
+ /**
51
+ * 示例11:添加查询参数
52
+ */
53
+ withParams(): Promise<any>;
54
+ /**
55
+ * 示例12:使用自定义转换函数
56
+ */
57
+ withTransform(): Promise<any>;
58
+ }
@@ -0,0 +1,197 @@
1
+ "use strict";
2
+ /**
3
+ * HTTP 请求级别配置示例
4
+ * 演示 RequestOptions 的使用方式
5
+ */
6
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
7
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
8
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
9
+ 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;
10
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
11
+ };
12
+ var __metadata = (this && this.__metadata) || function (k, v) {
13
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
14
+ };
15
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
16
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
17
+ return new (P || (P = Promise))(function (resolve, reject) {
18
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
19
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
20
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
21
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
22
+ });
23
+ };
24
+ var RequestOptionsExample_1;
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.RequestOptionsExample = void 0;
27
+ const common_1 = require("@nestjs/common");
28
+ const http_client_service_1 = require("../services/http-client.service");
29
+ let RequestOptionsExample = RequestOptionsExample_1 = class RequestOptionsExample {
30
+ constructor(httpClient) {
31
+ this.httpClient = httpClient;
32
+ this.logger = new common_1.Logger(RequestOptionsExample_1.name);
33
+ }
34
+ /**
35
+ * 示例1:最简单 - 禁用重试
36
+ */
37
+ disableRetry() {
38
+ return __awaiter(this, void 0, void 0, function* () {
39
+ return yield this.httpClient.get('/api/data', undefined, {
40
+ retry: { enabled: false },
41
+ });
42
+ });
43
+ }
44
+ /**
45
+ * 示例2:启用重试
46
+ */
47
+ enableRetry() {
48
+ return __awaiter(this, void 0, void 0, function* () {
49
+ return yield this.httpClient.post('/api/data', { key: 'value' }, undefined, {
50
+ retry: { enabled: true, retries: 3 },
51
+ });
52
+ });
53
+ }
54
+ /**
55
+ * 示例3:自定义重试条件
56
+ */
57
+ customRetryCondition() {
58
+ return __awaiter(this, void 0, void 0, function* () {
59
+ return yield this.httpClient.get('/api/service', undefined, {
60
+ retry: {
61
+ enabled: true,
62
+ retries: 2,
63
+ retryCondition: (error) => {
64
+ if (!error.response)
65
+ return true;
66
+ return error.response.status === 503;
67
+ },
68
+ },
69
+ });
70
+ });
71
+ }
72
+ /**
73
+ * 示例4:配置超时和返回类型
74
+ */
75
+ withTimeoutAndReturnType() {
76
+ return __awaiter(this, void 0, void 0, function* () {
77
+ const response = yield this.httpClient.request({ url: '/api/data', method: 'GET' }, {
78
+ retry: { enabled: false },
79
+ timeout: 5000,
80
+ returnType: 'full',
81
+ });
82
+ this.logger.log(`Status: ${response.status}`);
83
+ return response.data;
84
+ });
85
+ }
86
+ /**
87
+ * 示例5:使用客户端名称
88
+ */
89
+ withClientName() {
90
+ return __awaiter(this, void 0, void 0, function* () {
91
+ return yield this.httpClient.get('/api/data', undefined, {
92
+ clientName: 'my-api-client',
93
+ retry: { enabled: false },
94
+ });
95
+ });
96
+ }
97
+ /**
98
+ * 示例6:自定义重试延迟
99
+ */
100
+ customRetryDelay() {
101
+ return __awaiter(this, void 0, void 0, function* () {
102
+ return yield this.httpClient.request({ url: '/api/unstable', method: 'GET' }, {
103
+ retry: {
104
+ enabled: true,
105
+ retries: 3,
106
+ retryDelay: (retryCount) => retryCount * 1000,
107
+ },
108
+ });
109
+ });
110
+ }
111
+ /**
112
+ * 示例7:组合多个配置
113
+ */
114
+ combinedOptions() {
115
+ return __awaiter(this, void 0, void 0, function* () {
116
+ const response = yield this.httpClient.put('/api/resource/123', { status: 'active' }, undefined, {
117
+ clientName: 'api-client',
118
+ retry: {
119
+ enabled: true,
120
+ retries: 2,
121
+ retryCondition: (error) => { var _a; return ((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 503; },
122
+ retryDelay: (retryCount) => Math.pow(3, retryCount) * 1000,
123
+ },
124
+ timeout: 10000,
125
+ returnType: 'full',
126
+ });
127
+ return response.data;
128
+ });
129
+ }
130
+ /**
131
+ * 示例8:在 PRTG 服务中使用
132
+ */
133
+ prtgCreateDevice() {
134
+ return __awaiter(this, void 0, void 0, function* () {
135
+ return yield this.httpClient.post('experimental/groups/123/device', {
136
+ basic: {
137
+ name: 'MyDevice',
138
+ host: '192.168.1.1',
139
+ tags: ['sdwan'],
140
+ },
141
+ }, undefined, {
142
+ retry: { enabled: false },
143
+ });
144
+ });
145
+ }
146
+ /**
147
+ * 示例9:简洁写法 - 多个配置
148
+ */
149
+ multipleSettings() {
150
+ return __awaiter(this, void 0, void 0, function* () {
151
+ return yield this.httpClient.get('/api/data', undefined, {
152
+ clientName: 'prtg-v2-apac',
153
+ retry: { enabled: false },
154
+ timeout: 10000,
155
+ returnType: 'full',
156
+ });
157
+ });
158
+ }
159
+ /**
160
+ * 示例10:添加自定义 headers
161
+ */
162
+ withHeaders() {
163
+ return __awaiter(this, void 0, void 0, function* () {
164
+ return yield this.httpClient.get('/api/data', { headers: { 'X-Custom-Header': 'value' } }, {
165
+ retry: { enabled: false },
166
+ });
167
+ });
168
+ }
169
+ /**
170
+ * 示例11:添加查询参数
171
+ */
172
+ withParams() {
173
+ return __awaiter(this, void 0, void 0, function* () {
174
+ return yield this.httpClient.get('/api/data', { params: { page: 1, limit: 10 } });
175
+ });
176
+ }
177
+ /**
178
+ * 示例12:使用自定义转换函数
179
+ */
180
+ withTransform() {
181
+ return __awaiter(this, void 0, void 0, function* () {
182
+ return yield this.httpClient.get('/api/data', undefined, {
183
+ returnType: 'custom',
184
+ transform: (response) => ({
185
+ id: response.data.id,
186
+ name: response.data.name.toUpperCase(),
187
+ timestamp: new Date().toISOString(),
188
+ }),
189
+ });
190
+ });
191
+ }
192
+ };
193
+ exports.RequestOptionsExample = RequestOptionsExample;
194
+ exports.RequestOptionsExample = RequestOptionsExample = RequestOptionsExample_1 = __decorate([
195
+ (0, common_1.Injectable)(),
196
+ __metadata("design:paramtypes", [http_client_service_1.HttpClientService])
197
+ ], RequestOptionsExample);
@@ -12,9 +12,22 @@ interface ExtendedAxiosRequestConfig extends AxiosRequestConfig {
12
12
  };
13
13
  }
14
14
  /**
15
- * 请求返回选项
15
+ * 请求级别配置选项
16
+ * 用于在单次请求中覆盖默认配置
16
17
  */
17
- interface RequestReturnOptions {
18
+ export interface RequestOptions {
19
+ /** 客户端名称 */
20
+ clientName?: string;
21
+ /** 重试配置(请求级别,覆盖装饰器和全局配置) */
22
+ retry?: {
23
+ enabled?: boolean;
24
+ retries?: number;
25
+ retryCondition?: (error: any) => boolean;
26
+ retryDelay?: (retryCount: number) => number;
27
+ shouldResetTimeout?: boolean;
28
+ };
29
+ /** 超时配置(请求级别) */
30
+ timeout?: number;
18
31
  /** 返回类型: 'data'(默认) | 'full' | 'custom' */
19
32
  returnType?: 'data' | 'full' | 'custom';
20
33
  /** 自定义返回转换函数 */
@@ -65,15 +78,23 @@ export declare class HttpClientService {
65
78
  /**
66
79
  * 执行HTTP请求
67
80
  * @param config 请求配置
68
- * @param decoratorContext 装饰器上下文
69
- * @param clientName 客户端名称
70
- * @param returnOptions 返回选项
71
- * - returnType: 'data'(默认)只返回response.data
72
- * - returnType: 'full'返回完整AxiosResponse对象
73
- * - returnType: 'custom'使用自定义转换函数
74
- * - transform: 自定义转换函数,接收完整response返回任意数据
81
+ * @param options 请求级别配置选项
82
+ * @returns 响应数据
83
+ *
84
+ * @example
85
+ * // 禁用重试
86
+ * await this.request({ url: '/api/data' }, { retry: { enabled: false } })
87
+ *
88
+ * @example
89
+ * // 完整配置
90
+ * await this.request({ url: '/api/data' }, {
91
+ * clientName: 'api-client',
92
+ * retry: { enabled: true, retries: 2 },
93
+ * timeout: 5000,
94
+ * returnType: 'full'
95
+ * })
75
96
  */
76
- request<T = any>(config: ExtendedAxiosRequestConfig, decoratorContext?: any, clientName?: string, returnOptions?: RequestReturnOptions): Promise<T>;
97
+ request<T = any>(config: ExtendedAxiosRequestConfig, options?: RequestOptions): Promise<T>;
77
98
  /**
78
99
  * 生成curl命令(动态生成,不存储)
79
100
  */
@@ -94,26 +115,27 @@ export declare class HttpClientService {
94
115
  * 获取客户端名称
95
116
  */
96
117
  getName(): string | undefined;
97
- get<T = any>(url: string, config?: AxiosRequestConfig, clientName?: string, returnOptions?: RequestReturnOptions): Promise<T>;
98
- post<T = any>(url: string, data?: any, config?: AxiosRequestConfig, clientName?: string, returnOptions?: RequestReturnOptions): Promise<T>;
99
- put<T = any>(url: string, data?: any, config?: AxiosRequestConfig, clientName?: string, returnOptions?: RequestReturnOptions): Promise<T>;
100
- patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig, clientName?: string, returnOptions?: RequestReturnOptions): Promise<T>;
101
- delete<T = any>(url: string, config?: AxiosRequestConfig, clientName?: string, returnOptions?: RequestReturnOptions): Promise<T>;
118
+ get<T = any>(url: string, config?: AxiosRequestConfig, options?: RequestOptions): Promise<T>;
119
+ post<T = any>(url: string, data?: any, config?: AxiosRequestConfig, options?: RequestOptions): Promise<T>;
120
+ put<T = any>(url: string, data?: any, config?: AxiosRequestConfig, options?: RequestOptions): Promise<T>;
121
+ patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig, options?: RequestOptions): Promise<T>;
122
+ delete<T = any>(url: string, config?: AxiosRequestConfig, options?: RequestOptions): Promise<T>;
102
123
  /**
103
124
  * 带等待锁的认证请求方法
104
125
  * 用于需要确保只有一个进程执行认证相关操作的场景
105
126
  *
106
127
  * @param config 请求配置
107
128
  * @param tokenProvider token提供函数
108
- * @param options 等待锁选项
129
+ * @param lockOptions 等待锁选项
130
+ * @param requestOptions HTTP请求选项
109
131
  * @returns 响应数据
110
132
  */
111
- authRequest<T = any>(config: AxiosRequestConfig, tokenProvider: () => Promise<string>, options?: {
133
+ authRequest<T = any>(config: AxiosRequestConfig, tokenProvider: () => Promise<string>, lockOptions?: {
112
134
  lockKey?: string;
113
135
  lockTimeout?: number;
114
136
  waitTimeout?: number;
115
137
  enableRetry?: boolean;
116
- }, clientName?: string, returnOptions?: RequestReturnOptions): Promise<T>;
138
+ }, requestOptions?: RequestOptions): Promise<T>;
117
139
  /**
118
140
  * 带等待锁的批量认证请求
119
141
  * 用于需要批量执行认证相关操作但避免重复认证的场景
@@ -126,12 +148,12 @@ export declare class HttpClientService {
126
148
  authBatchRequest<T = any>(requests: Array<{
127
149
  config: AxiosRequestConfig;
128
150
  key: string;
129
- }>, tokenProvider: () => Promise<string>, options?: {
151
+ }>, tokenProvider: () => Promise<string>, lockOptions?: {
130
152
  lockKey?: string;
131
153
  lockTimeout?: number;
132
154
  waitTimeout?: number;
133
155
  maxConcurrency?: number;
134
- }, clientName?: string): Promise<Array<T | null>>;
156
+ }, requestOptions?: RequestOptions): Promise<Array<T | null>>;
135
157
  /**
136
158
  * Capture calling context information
137
159
  * @returns Calling context with service class and method name
@@ -96,22 +96,44 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
96
96
  /**
97
97
  * 执行HTTP请求
98
98
  * @param config 请求配置
99
- * @param decoratorContext 装饰器上下文
100
- * @param clientName 客户端名称
101
- * @param returnOptions 返回选项
102
- * - returnType: 'data'(默认)只返回response.data
103
- * - returnType: 'full'返回完整AxiosResponse对象
104
- * - returnType: 'custom'使用自定义转换函数
105
- * - transform: 自定义转换函数,接收完整response返回任意数据
99
+ * @param options 请求级别配置选项
100
+ * @returns 响应数据
101
+ *
102
+ * @example
103
+ * // 禁用重试
104
+ * await this.request({ url: '/api/data' }, { retry: { enabled: false } })
105
+ *
106
+ * @example
107
+ * // 完整配置
108
+ * await this.request({ url: '/api/data' }, {
109
+ * clientName: 'api-client',
110
+ * retry: { enabled: true, retries: 2 },
111
+ * timeout: 5000,
112
+ * returnType: 'full'
113
+ * })
106
114
  */
107
- request(config, decoratorContext, clientName, returnOptions) {
115
+ request(config, options) {
108
116
  return __awaiter(this, void 0, void 0, function* () {
109
117
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
110
- // Use the instance's clientName as fallback if not provided
111
- const effectiveClientName = clientName || this.clientName;
112
- // If no decorator context provided, try to get it from CallStackExtractor
118
+ // 解析参数
119
+ let effectiveClientName;
120
+ let requestRetryConfig;
121
+ let requestReturnType;
122
+ let requestTransform;
123
+ let requestTimeout;
124
+ if (options) {
125
+ effectiveClientName = options.clientName || this.clientName || '';
126
+ requestRetryConfig = options.retry;
127
+ requestReturnType = options.returnType;
128
+ requestTransform = options.transform;
129
+ requestTimeout = options.timeout;
130
+ }
131
+ else {
132
+ effectiveClientName = this.clientName || '';
133
+ }
134
+ // Get decorator context from CallStackExtractor
113
135
  // This allows decorators on service methods to pass context to HTTP client
114
- const effectiveDecoratorContext = decoratorContext || call_stack_extractor_util_1.CallStackExtractor.getDecoratorContext();
136
+ const effectiveDecoratorContext = call_stack_extractor_util_1.CallStackExtractor.getDecoratorContext();
115
137
  // ========== 安全验证开始 ==========
116
138
  // 构造完整URL进行验证
117
139
  const requestURL = config.url || '';
@@ -186,7 +208,7 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
186
208
  // 应用认证配置
187
209
  const authConfig = yield this.applyAuthToConfig(config);
188
210
  // 应用装饰器配置
189
- const enhancedConfig = this.applyDecoratorConfig(authConfig, decoratorContext);
211
+ const enhancedConfig = this.applyDecoratorConfig(authConfig, effectiveDecoratorContext);
190
212
  // 将 retryRecorder 存储到 config metadata 中,供 onRetry 回调使用
191
213
  enhancedConfig.metadata = enhancedConfig.metadata || {};
192
214
  enhancedConfig.metadata.retryRecorder = retryRecorder;
@@ -194,9 +216,17 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
194
216
  const decoratorConfigs = effectiveDecoratorContext
195
217
  ? decorators_1.HttpDecoratorUtils.getAllDecoratorConfigs(effectiveDecoratorContext.target, effectiveDecoratorContext.propertyKey)
196
218
  : {};
197
- // 存储装饰器的重试配置到 metadata,供 retryCondition 使用
198
- if (((_c = decoratorConfigs.retry) === null || _c === void 0 ? void 0 : _c.enabled) !== undefined) {
199
- enhancedConfig.metadata.retryEnabled = decoratorConfigs.retry.enabled;
219
+ // 存储重试配置到 metadata,供 retryCondition 使用
220
+ // 优先级:请求级别配置 > 装饰器配置 > 全局配置
221
+ const retryEnabled = (requestRetryConfig === null || requestRetryConfig === void 0 ? void 0 : requestRetryConfig.enabled) !== undefined
222
+ ? requestRetryConfig.enabled
223
+ : (_c = decoratorConfigs.retry) === null || _c === void 0 ? void 0 : _c.enabled;
224
+ if (retryEnabled !== undefined) {
225
+ enhancedConfig.metadata.retryEnabled = retryEnabled;
226
+ }
227
+ // 如果请求级别提供了重试配置,存储到 metadata
228
+ if (requestRetryConfig) {
229
+ enhancedConfig.metadata.requestRetryConfig = requestRetryConfig;
200
230
  }
201
231
  // 日志记录开始
202
232
  const decoratorLogging = decoratorConfigs.logging || {};
@@ -209,8 +239,8 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
209
239
  enhancedConfig.metadata = enhancedConfig.metadata || {};
210
240
  enhancedConfig.metadata.requestId = requestId;
211
241
  }
212
- // 应用超时配置
213
- const timeout = decoratorConfigs.timeout || this.defaultConfig.timeout;
242
+ // 应用超时配置(优先级:请求级别 > 装饰器 > 全局)
243
+ const timeout = requestTimeout || decoratorConfigs.timeout || this.defaultConfig.timeout;
214
244
  if (timeout) {
215
245
  enhancedConfig.timeout = timeout;
216
246
  }
@@ -232,20 +262,21 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
232
262
  const databaseLogging = (loggingOptions === null || loggingOptions === void 0 ? void 0 : loggingOptions.databaseLogging) ||
233
263
  ((_f = (_e = this.defaultConfig.logging) === null || _e === void 0 ? void 0 : _e.databaseLogging) === null || _f === void 0 ? void 0 : _f.enabled);
234
264
  this.logger.debug(`Logging request success with circuitBreakerState: ${circuitBreakerState || 'undefined'}`);
235
- this.loggingService.logRequestSuccess(response, startTime, requestId, loggingOptions, databaseLogging, retryRecorder.getRecords(), circuitBreakerState, decoratorContext, effectiveClientName, callingContext);
265
+ this.loggingService.logRequestSuccess(response, startTime, requestId, loggingOptions, databaseLogging, retryRecorder.getRecords(), circuitBreakerState, effectiveDecoratorContext, effectiveClientName, callingContext);
236
266
  }
237
267
  // 更新统计信息
238
268
  this.updateRequestStats(true, Date.now() - startTime, ((_g = response.config) === null || _g === void 0 ? void 0 : _g.method) || 'GET', response.status);
239
269
  // 根据返回选项处理响应
240
- const options = returnOptions || { returnType: 'data' };
241
- switch (options.returnType) {
270
+ const returnType = requestReturnType || 'data';
271
+ const transform = requestTransform;
272
+ switch (returnType) {
242
273
  case 'full':
243
274
  // 返回完整响应对象
244
275
  return response;
245
276
  case 'custom':
246
277
  // 使用自定义转换函数
247
- if (options.transform) {
248
- return options.transform(response);
278
+ if (transform) {
279
+ return transform(response);
249
280
  }
250
281
  // 如果没有提供转换函数,回退到返回data
251
282
  return response.data;
@@ -328,29 +359,29 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
328
359
  return this.clientName;
329
360
  }
330
361
  // 便捷方法
331
- get(url, config, clientName, returnOptions) {
362
+ get(url, config, options) {
332
363
  return __awaiter(this, void 0, void 0, function* () {
333
- return this.request(Object.assign(Object.assign({}, config), { method: 'GET', url }), undefined, clientName, returnOptions);
364
+ return this.request(Object.assign(Object.assign({}, config), { method: 'GET', url }), options);
334
365
  });
335
366
  }
336
- post(url, data, config, clientName, returnOptions) {
367
+ post(url, data, config, options) {
337
368
  return __awaiter(this, void 0, void 0, function* () {
338
- return this.request(Object.assign(Object.assign({}, config), { method: 'POST', url, data }), undefined, clientName, returnOptions);
369
+ return this.request(Object.assign(Object.assign({}, config), { method: 'POST', url, data }), options);
339
370
  });
340
371
  }
341
- put(url, data, config, clientName, returnOptions) {
372
+ put(url, data, config, options) {
342
373
  return __awaiter(this, void 0, void 0, function* () {
343
- return this.request(Object.assign(Object.assign({}, config), { method: 'PUT', url, data }), undefined, clientName, returnOptions);
374
+ return this.request(Object.assign(Object.assign({}, config), { method: 'PUT', url, data }), options);
344
375
  });
345
376
  }
346
- patch(url, data, config, clientName, returnOptions) {
377
+ patch(url, data, config, options) {
347
378
  return __awaiter(this, void 0, void 0, function* () {
348
- return this.request(Object.assign(Object.assign({}, config), { method: 'PATCH', url, data }), undefined, clientName, returnOptions);
379
+ return this.request(Object.assign(Object.assign({}, config), { method: 'PATCH', url, data }), options);
349
380
  });
350
381
  }
351
- delete(url, config, clientName, returnOptions) {
382
+ delete(url, config, options) {
352
383
  return __awaiter(this, void 0, void 0, function* () {
353
- return this.request(Object.assign(Object.assign({}, config), { method: 'DELETE', url }), undefined, clientName, returnOptions);
384
+ return this.request(Object.assign(Object.assign({}, config), { method: 'DELETE', url }), options);
354
385
  });
355
386
  }
356
387
  /**
@@ -359,17 +390,17 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
359
390
  *
360
391
  * @param config 请求配置
361
392
  * @param tokenProvider token提供函数
362
- * @param options 等待锁选项
393
+ * @param lockOptions 等待锁选项
394
+ * @param requestOptions HTTP请求选项
363
395
  * @returns 响应数据
364
396
  */
365
- authRequest(config, tokenProvider, options, clientName, returnOptions) {
397
+ authRequest(config, tokenProvider, lockOptions, requestOptions) {
366
398
  return __awaiter(this, void 0, void 0, function* () {
367
- const effectiveClientName = clientName || this.clientName;
368
399
  if (!this.redisLockService) {
369
400
  this.logger.warn('RedisLockService not available, proceeding without lock');
370
- return this.request(config, undefined, effectiveClientName, returnOptions);
401
+ return this.request(config, requestOptions);
371
402
  }
372
- const { lockKey = `auth-request:${(0, request_id_util_1.generateRequestId)()}`, lockTimeout = 30000, waitTimeout = 60000, enableRetry = true, } = options || {};
403
+ const { lockKey = `auth-request:${(0, request_id_util_1.generateRequestId)()}`, lockTimeout = 30000, waitTimeout = 60000, enableRetry = true, } = lockOptions || {};
373
404
  try {
374
405
  // 尝试获取锁并执行请求
375
406
  const result = yield this.redisLockService.withLock(lockKey, () => __awaiter(this, void 0, void 0, function* () {
@@ -378,7 +409,7 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
378
409
  // 添加认证头
379
410
  const authConfig = Object.assign(Object.assign({}, config), { headers: Object.assign(Object.assign({}, config.headers), { Authorization: `Bearer ${token}` }) });
380
411
  // 执行请求
381
- return this.request(authConfig, undefined, effectiveClientName, returnOptions);
412
+ return this.request(authConfig, requestOptions);
382
413
  }), {
383
414
  ttl: lockTimeout,
384
415
  retryCount: enableRetry ? 3 : 0,
@@ -397,7 +428,7 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
397
428
  this.logger.warn('Retrying auth request without lock');
398
429
  const token = yield tokenProvider();
399
430
  const authConfig = Object.assign(Object.assign({}, config), { headers: Object.assign(Object.assign({}, config.headers), { Authorization: `Bearer ${token}` }) });
400
- return this.request(authConfig, undefined, effectiveClientName);
431
+ return this.request(authConfig, requestOptions);
401
432
  }
402
433
  throw error;
403
434
  }
@@ -412,9 +443,8 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
412
443
  * @param options 等待锁选项
413
444
  * @returns 响应数组
414
445
  */
415
- authBatchRequest(requests, tokenProvider, options, clientName) {
446
+ authBatchRequest(requests, tokenProvider, lockOptions, requestOptions) {
416
447
  return __awaiter(this, void 0, void 0, function* () {
417
- const effectiveClientName = clientName || this.clientName;
418
448
  if (!this.redisLockService) {
419
449
  this.logger.warn('RedisLockService not available, proceeding batch auth requests without lock');
420
450
  // 无锁批量执行
@@ -422,7 +452,7 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
422
452
  const promises = requests.map((request) => __awaiter(this, void 0, void 0, function* () {
423
453
  try {
424
454
  const authConfig = Object.assign(Object.assign({}, request.config), { headers: Object.assign(Object.assign({}, request.config.headers), { Authorization: `Bearer ${token}` }) });
425
- return yield this.request(authConfig, undefined, effectiveClientName);
455
+ return yield this.request(authConfig, requestOptions);
426
456
  }
427
457
  catch (error) {
428
458
  this.logger.error(`Batch auth request failed for key: ${request.key}`, error);
@@ -431,7 +461,7 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
431
461
  }));
432
462
  return Promise.all(promises);
433
463
  }
434
- const { lockKey = `batch-auth-request:${(0, request_id_util_1.generateRequestId)()}`, lockTimeout = 60000, waitTimeout = 120000, maxConcurrency = 5, } = options || {};
464
+ const { lockKey = `batch-auth-request:${(0, request_id_util_1.generateRequestId)()}`, lockTimeout = 60000, waitTimeout = 120000, maxConcurrency = 5, } = lockOptions || {};
435
465
  try {
436
466
  // 使用锁获取token并执行批量请求
437
467
  const results = yield this.redisLockService.withLock(lockKey, () => __awaiter(this, void 0, void 0, function* () {
@@ -444,7 +474,7 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
444
474
  const executeRequest = (requestConfig, key, index) => __awaiter(this, void 0, void 0, function* () {
445
475
  try {
446
476
  const authConfig = Object.assign(Object.assign({}, requestConfig), { headers: Object.assign(Object.assign({}, requestConfig.headers), { Authorization: `Bearer ${token}` }) });
447
- const result = yield this.request(authConfig, undefined, effectiveClientName);
477
+ const result = yield this.request(authConfig, requestOptions);
448
478
  results[index] = result;
449
479
  return result;
450
480
  }
@@ -585,38 +615,67 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
585
615
  ((retryCount) => Math.pow(2, retryCount) * 1000),
586
616
  retryCondition: this.defaultConfig.retry.retryCondition ||
587
617
  ((error) => {
588
- var _a;
589
- // 检查装饰器配置是否禁用了重试
618
+ var _a, _b, _c;
590
619
  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) {
620
+ // 1. 检查请求级别的重试配置(最高优先级)
621
+ const requestRetryConfig = (_a = requestConfig === null || requestConfig === void 0 ? void 0 : requestConfig.metadata) === null || _a === void 0 ? void 0 : _a.requestRetryConfig;
622
+ if (requestRetryConfig) {
623
+ // 如果请求级别明确设置了 enabled
624
+ if (requestRetryConfig.enabled === false) {
625
+ return false;
626
+ }
627
+ // 如果请求级别提供了自定义 retryCondition
628
+ if (requestRetryConfig.retryCondition) {
629
+ return requestRetryConfig.retryCondition(error);
630
+ }
631
+ // enabled: true 但没有自定义条件,使用默认逻辑
632
+ if (requestRetryConfig.enabled === true) {
633
+ if (!error.response)
634
+ return true;
635
+ const status = error.response.status;
636
+ return status >= 500 || status === 429;
637
+ }
638
+ }
639
+ // 2. 检查装饰器配置
640
+ if (((_b = requestConfig === null || requestConfig === void 0 ? void 0 : requestConfig.metadata) === null || _b === void 0 ? void 0 : _b.retryEnabled) === false) {
592
641
  return false; // 装饰器明确禁用了重试
593
642
  }
594
- // 默认重试条件:网络错误、超时、5xx错误、429错误
595
- if (!error.response)
596
- return true; // 网络错误
597
- const status = error.response.status;
598
- return status >= 500 || status === 429; // 5xx错误或429限流
643
+ // 3. 检查装饰器是否启用了重试
644
+ if (((_c = requestConfig === null || requestConfig === void 0 ? void 0 : requestConfig.metadata) === null || _c === void 0 ? void 0 : _c.retryEnabled) === true) {
645
+ // 默认重试条件:网络错误、超时、5xx错误、429错误
646
+ if (!error.response)
647
+ return true;
648
+ const status = error.response.status;
649
+ return status >= 500 || status === 429;
650
+ }
651
+ // 4. 默认不重试
652
+ return false;
599
653
  }),
600
654
  shouldResetTimeout: this.defaultConfig.retry.shouldResetTimeout !== false,
601
655
  onRetry: (retryCount, error, requestConfig) => {
602
- var _a, _b, _c, _d;
656
+ var _a, _b, _c, _d, _e, _f, _g;
603
657
  // 从 request config metadata 中获取 retryRecorder
604
658
  const retryRecorder = (_a = requestConfig.metadata) === null || _a === void 0 ? void 0 : _a.retryRecorder;
659
+ const requestRetryConfig = (_b = requestConfig.metadata) === null || _b === void 0 ? void 0 : _b.requestRetryConfig;
605
660
  if (retryRecorder) {
606
- this.logger.debug(`Recording retry attempt ${retryCount} for ${(_b = requestConfig.method) === null || _b === void 0 ? void 0 : _b.toUpperCase()} ${requestConfig.url}`);
607
- const retryRecord = retry_recorder_util_1.RetryRecorder.recordRetry(retryCount, error, requestConfig, this.calculateRetryDelay(retryCount, this.defaultConfig.retry));
661
+ this.logger.debug(`Recording retry attempt ${retryCount} for ${(_c = requestConfig.method) === null || _c === void 0 ? void 0 : _c.toUpperCase()} ${requestConfig.url}`);
662
+ // 使用请求级别的 retryDelay 或默认配置
663
+ const retryDelay = (requestRetryConfig === null || requestRetryConfig === void 0 ? void 0 : requestRetryConfig.retryDelay) || ((_d = this.defaultConfig.retry) === null || _d === void 0 ? void 0 : _d.retryDelay);
664
+ const retryConfig = retryDelay ? { retryDelay } : this.defaultConfig.retry;
665
+ const retryRecord = retry_recorder_util_1.RetryRecorder.recordRetry(retryCount, error, requestConfig, this.calculateRetryDelay(retryCount, retryConfig));
608
666
  retryRecorder.addRecord(retryRecord);
609
667
  }
610
668
  else {
611
- this.logger.warn(`RetryRecorder not found in request config metadata for ${(_c = requestConfig.method) === null || _c === void 0 ? void 0 : _c.toUpperCase()} ${requestConfig.url}. Retry records will not be saved.`);
669
+ this.logger.warn(`RetryRecorder not found in request config metadata for ${(_e = requestConfig.method) === null || _e === void 0 ? void 0 : _e.toUpperCase()} ${requestConfig.url}. Retry records will not be saved.`);
612
670
  }
613
- // 调用用户自定义的 onRetry 回调
614
- if (this.defaultConfig.retry.onRetry) {
615
- this.defaultConfig.retry.onRetry(retryCount, error, requestConfig);
671
+ // 调用用户自定义的 onRetry 回调(请求级别 > 全局)
672
+ const onRetryCallback = (requestRetryConfig === null || requestRetryConfig === void 0 ? void 0 : requestRetryConfig.onRetry) || ((_f = this.defaultConfig.retry) === null || _f === void 0 ? void 0 : _f.onRetry);
673
+ if (onRetryCallback) {
674
+ onRetryCallback(retryCount, error, requestConfig);
616
675
  }
617
676
  else {
618
677
  // 默认日志
619
- this.logger.warn(`Retrying request (attempt ${retryCount}): ${(_d = requestConfig.method) === null || _d === void 0 ? void 0 : _d.toUpperCase()} ${requestConfig.url}`);
678
+ this.logger.warn(`Retrying request (attempt ${retryCount}): ${(_g = requestConfig.method) === null || _g === void 0 ? void 0 : _g.toUpperCase()} ${requestConfig.url}`);
620
679
  }
621
680
  },
622
681
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nest-omni/core",
3
- "version": "4.1.3-32",
3
+ "version": "4.1.3-34",
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",