@be-link/ecommerce-client-backend-service-node-sdk 0.1.29 → 0.1.30

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@be-link/ecommerce-client-backend-service-node-sdk",
3
- "version": "0.1.29",
3
+ "version": "0.1.30",
4
4
  "description": "EcommerceClientBackendService Node.js SDK",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
package/utils/http.d.ts CHANGED
@@ -1,7 +1,19 @@
1
1
  declare module '@fastify/request-context' {
2
2
  interface RequestContextData {
3
- requestId?: string;
3
+ requestId: string;
4
4
  traceMessageId?: string;
5
+ pandoraUserId?: string;
6
+ beLinkUserId?: string;
7
+ pandoraRoleId?: string;
8
+ realIp?: string;
5
9
  }
6
10
  }
7
11
  export declare function callApi<T extends (...args: any[]) => Promise<any>>(url: string, request?: Parameters<T>[0]): Promise<Awaited<ReturnType<T>>>;
12
+ export declare function getHttpStats(): {
13
+ activeRequests: number;
14
+ totalRequests: number;
15
+ errorCount: number;
16
+ timeoutCount: number;
17
+ errorRate: string;
18
+ timeoutRate: string;
19
+ };
package/utils/http.js CHANGED
@@ -1,100 +1,198 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
36
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
4
  };
38
5
  Object.defineProperty(exports, "__esModule", { value: true });
39
6
  exports.callApi = callApi;
40
- const safe_stable_stringify_1 = __importDefault(require("safe-stable-stringify"));
7
+ exports.getHttpStats = getHttpStats;
41
8
  const axios_1 = __importDefault(require("axios"));
42
9
  const uuid_1 = require("uuid");
43
10
  const axios_retry_1 = __importDefault(require("axios-retry"));
44
11
  const request_context_1 = require("@fastify/request-context");
12
+ const http_1 = __importDefault(require("http"));
13
+ const https_1 = __importDefault(require("https"));
14
+ const safe_stable_stringify_1 = __importDefault(require("safe-stable-stringify"));
15
+ // 针对高并发优化配置 - 极限性能模式
16
+ const HTTP_CONFIG = {
17
+ maxSockets: Infinity, // 不限制每个主机的socket连接数(极限并发模式)
18
+ maxFreeSockets: 2000, // 保留2000个空闲连接(减少连接重建开销)
19
+ maxTotalSockets: Infinity, // 不限制总socket数(避免连接池成为瓶颈)
20
+ keepAliveMsecs: 60000, // 保持连接60秒
21
+ timeout: 6000, // socket超时6秒(给予充足时间)
22
+ requestTimeout: 6000, // 请求超时6秒(避免正常请求超时)
23
+ retries: 0, // 不重试(失败直接返回)
24
+ retryBaseDelay: 200, // 基础重试延迟200ms
25
+ };
26
+ // 配置 HTTP/HTTPS Agent 以支持高并发连接池和 keepAlive
27
+ const httpAgent = new http_1.default.Agent({
28
+ keepAlive: true,
29
+ keepAliveMsecs: HTTP_CONFIG.keepAliveMsecs,
30
+ maxSockets: HTTP_CONFIG.maxSockets,
31
+ maxFreeSockets: HTTP_CONFIG.maxFreeSockets,
32
+ maxTotalSockets: HTTP_CONFIG.maxTotalSockets,
33
+ timeout: HTTP_CONFIG.timeout,
34
+ scheduling: 'lifo', // 后进先出调度(优先复用热连接,提高连接复用效率)
35
+ });
36
+ const httpsAgent = new https_1.default.Agent({
37
+ keepAlive: true,
38
+ keepAliveMsecs: HTTP_CONFIG.keepAliveMsecs,
39
+ maxSockets: HTTP_CONFIG.maxSockets,
40
+ maxFreeSockets: HTTP_CONFIG.maxFreeSockets,
41
+ maxTotalSockets: HTTP_CONFIG.maxTotalSockets,
42
+ timeout: HTTP_CONFIG.timeout,
43
+ scheduling: 'lifo', // 后进先出调度(优先复用热连接,提高连接复用效率)
44
+ });
45
+ // 配置 axios 默认使用这些 agent
46
+ axios_1.default.defaults.httpAgent = httpAgent;
47
+ axios_1.default.defaults.httpsAgent = httpsAgent;
48
+ axios_1.default.defaults.timeout = HTTP_CONFIG.requestTimeout;
49
+ axios_1.default.defaults.maxRedirects = 3; // 限制重定向次数
50
+ axios_1.default.defaults.maxContentLength = 50 * 1024 * 1024; // 50MB 最大响应大小
51
+ // 高并发场景下的智能重试配置
45
52
  (0, axios_retry_1.default)(axios_1.default, {
46
- retries: 1,
53
+ retries: HTTP_CONFIG.retries,
47
54
  retryCondition(error) {
48
- return error.response?.status === 502;
49
- },
50
- retryDelay: (retryCount) => {
51
- console.info(`retryCount: ${retryCount}, retryDelay: ${retryCount * 500}`);
52
- return retryCount * 500;
55
+ // 重试临时错误和特定的网络错误
56
+ const status = error.response?.status;
57
+ const isNetworkError = error.code === 'ECONNRESET' ||
58
+ error.code === 'ETIMEDOUT' ||
59
+ error.code === 'ECONNREFUSED' ||
60
+ error.code === 'ENOTFOUND' ||
61
+ error.code === 'ENETUNREACH' ||
62
+ error.code === 'EAI_AGAIN';
63
+ const isServerError = status === 502 || status === 503 || status === 504;
64
+ const isRateLimited = status === 429;
65
+ const isTimeout = error.code === 'ECONNABORTED' || error.message?.includes('timeout');
66
+ return isNetworkError || isServerError || isRateLimited || isTimeout;
53
67
  },
54
- onRetry(retryCount, error, requestConfig) {
55
- console.info(`retryCount: ${retryCount}, onRetry: ${error.message}, requestHeader: ${(0, safe_stable_stringify_1.default)(requestConfig.headers)}`);
68
+ retryDelay: (retryCount, error) => {
69
+ // 指数退避 + 抖动
70
+ const status = error.response?.status;
71
+ // 如果是限流错误,延迟更长
72
+ if (status === 429) {
73
+ const retryAfter = error.response?.headers['retry-after'];
74
+ if (retryAfter) {
75
+ return parseInt(retryAfter) * 1000;
76
+ }
77
+ return 1000 + Math.random() * 1000; // 1-2秒
78
+ }
79
+ // 指数退避:200ms, 400ms, 800ms...
80
+ const exponentialDelay = HTTP_CONFIG.retryBaseDelay * Math.pow(2, retryCount - 1);
81
+ const jitter = Math.random() * 100; // 0-100ms 抖动
82
+ return Math.min(exponentialDelay + jitter, 3000); // 最多延迟3秒
56
83
  },
84
+ shouldResetTimeout: true, // 重试时重置超时
57
85
  });
86
+ // 性能监控变量
87
+ let activeRequests = 0;
88
+ let totalRequests = 0;
89
+ let timeoutCount = 0;
90
+ let errorCount = 0;
58
91
  async function callApi(url, request) {
59
- const requestId = request_context_1.requestContext.get('requestId') || request_context_1.requestContext.get('traceMessageId') || (0, uuid_1.v4)();
92
+ // 获取请求上下文
93
+ const ctx = request_context_1.requestContext.get('requestId') ? request_context_1.requestContext : null;
94
+ const requestId = ctx?.get('requestId') || ctx?.get('traceMessageId') || (0, uuid_1.v4)();
95
+ const pandoraUserId = ctx?.get('pandoraUserId') || '';
96
+ const beLinkUserId = ctx?.get('beLinkUserId') || '';
97
+ const pandoraRoleId = ctx?.get('pandoraRoleId') || '';
98
+ const realIp = ctx?.get('realIp') || '';
99
+ // 性能监控
100
+ const startTime = Date.now();
101
+ activeRequests++;
102
+ totalRequests++;
103
+ const currentRequestId = totalRequests;
60
104
  try {
61
- console.info(`准备发起ecommerce-client-backend-service请求[${requestId}]: ${url}, 参数: ${(0, safe_stable_stringify_1.default)(request)}`);
62
- const response = await axios_1.default.post(url, request || {}, {
63
- headers: { 'x-request-id': requestId },
105
+ const response = await axios_1.default.post(url, request, {
106
+ headers: {
107
+ 'x-request-id': requestId,
108
+ 'x-belink-pandora-userid': pandoraUserId,
109
+ 'x-belink-userid': beLinkUserId,
110
+ 'x-belink-pandora-roleid': pandoraRoleId,
111
+ 'x-real-ip': realIp,
112
+ Connection: 'keep-alive',
113
+ },
114
+ timeout: HTTP_CONFIG.requestTimeout,
115
+ httpAgent,
116
+ httpsAgent,
64
117
  });
118
+ const duration = Date.now() - startTime;
119
+ // 记录慢请求(超过3秒)
120
+ if (duration > 3000) {
121
+ console.log((0, safe_stable_stringify_1.default)({
122
+ message: '慢请求警告',
123
+ url,
124
+ duration,
125
+ activeRequests,
126
+ requestId: currentRequestId,
127
+ type: 'slow_request',
128
+ }));
129
+ }
65
130
  const responseData = response.data;
66
131
  return responseData.data;
67
132
  }
68
133
  catch (error) {
69
134
  const axiosError = error;
135
+ const duration = Date.now() - startTime;
136
+ errorCount++;
137
+ // 记录详细的错误信息用于诊断
138
+ const errorInfo = {
139
+ message: '请求失败',
140
+ url,
141
+ duration,
142
+ activeRequests,
143
+ totalRequests: currentRequestId,
144
+ errorMessage: axiosError.message,
145
+ errorCode: axiosError.code,
146
+ type: 'http_request_failed',
147
+ };
148
+ // 添加超时相关信息
149
+ if (axiosError.code === 'ECONNABORTED' || axiosError.message?.includes('timeout')) {
150
+ errorInfo.isTimeout = true;
151
+ errorInfo.timeoutValue = HTTP_CONFIG.requestTimeout;
152
+ timeoutCount++;
153
+ }
154
+ // 添加重试信息
155
+ if (axiosError.config && 'axios-retry' in axiosError.config) {
156
+ errorInfo.retryCount = axiosError.config['axios-retry']?.retryCount || 0;
157
+ }
158
+ console.log((0, safe_stable_stringify_1.default)(errorInfo));
70
159
  if (axiosError.response) {
71
160
  const response = axiosError.response;
72
161
  const data = response.data;
73
- console.error(`ecommerce-client-backend-service 异常: ${axiosError.message},requestId: ${requestId}`);
74
- console.info('响应信息', data.message);
75
- console.error('异常堆栈', (0, safe_stable_stringify_1.default)(error.stack));
76
- throw error;
77
- }
78
- // 调用dns模块解析url
79
- const dns = await Promise.resolve().then(() => __importStar(require('dns')));
80
- const dnsPromise = new Promise((resolve, reject) => {
81
- const lookupRes = dns.lookup(url, (err, address) => {
82
- if (err) {
83
- console.error(err.message);
84
- reject(err);
85
- }
86
- console.info(`lookup: ${(0, safe_stable_stringify_1.default)(lookupRes)}`);
87
- resolve(address);
88
- });
89
- });
90
- try {
91
- const address = await dnsPromise;
92
- console.info(`address: ${(0, safe_stable_stringify_1.default)(address)}`);
162
+ console.log((0, safe_stable_stringify_1.default)({
163
+ message: '响应异常',
164
+ status: response.status,
165
+ errorMessage: data?.message || axiosError.message,
166
+ type: 'http_response_error',
167
+ }));
93
168
  }
94
- catch (error) {
95
- console.info(`error: ${(0, safe_stable_stringify_1.default)(error)}`);
96
- }
97
- console.error(`ecommerce-client-backend-service 未知异常: ${axiosError.message}`, error.stack);
98
169
  throw error;
99
170
  }
171
+ finally {
172
+ activeRequests--;
173
+ // 每1000个请求输出一次统计信息
174
+ if (totalRequests % 1000 === 0) {
175
+ console.log((0, safe_stable_stringify_1.default)({
176
+ message: 'HTTP客户端统计',
177
+ totalRequests,
178
+ activeRequests,
179
+ errorCount,
180
+ timeoutCount,
181
+ errorRate: ((errorCount / totalRequests) * 100).toFixed(2) + '%',
182
+ timeoutRate: ((timeoutCount / totalRequests) * 100).toFixed(2) + '%',
183
+ type: 'http_stats',
184
+ }));
185
+ }
186
+ }
187
+ }
188
+ // 获取当前统计信息
189
+ function getHttpStats() {
190
+ return {
191
+ activeRequests,
192
+ totalRequests,
193
+ errorCount,
194
+ timeoutCount,
195
+ errorRate: totalRequests > 0 ? ((errorCount / totalRequests) * 100).toFixed(2) + '%' : '0%',
196
+ timeoutRate: totalRequests > 0 ? ((timeoutCount / totalRequests) * 100).toFixed(2) + '%' : '0%',
197
+ };
100
198
  }