@be-link/ecommerce-promotion-service-node-sdk 0.1.13 → 0.1.15
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/modules/BaseService.d.ts +1 -1
- package/modules/BaseService.js +1 -1
- package/package.json +1 -1
- package/utils/http.d.ts +6 -20
- package/utils/http.js +87 -99
package/modules/BaseService.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export default abstract class BaseService {
|
|
|
8
8
|
protected abstract prefixUrl: string;
|
|
9
9
|
/** 子网域名 */
|
|
10
10
|
protected readonly natDevHost = "http://192.168.3.168:8090/promotion";
|
|
11
|
-
protected readonly natProdHost = "http://
|
|
11
|
+
protected readonly natProdHost = "http://10.1.0.97:8090/promotion";
|
|
12
12
|
/** 公网域名 */
|
|
13
13
|
protected readonly publicDevHost = "https://ecommerce-dev.wejourney.top/promotion";
|
|
14
14
|
protected readonly publicProdHost = "";
|
package/modules/BaseService.js
CHANGED
|
@@ -13,7 +13,7 @@ class BaseService {
|
|
|
13
13
|
constructor() {
|
|
14
14
|
/** 子网域名 */
|
|
15
15
|
this.natDevHost = 'http://192.168.3.168:8090/promotion';
|
|
16
|
-
this.natProdHost = 'http://
|
|
16
|
+
this.natProdHost = 'http://10.1.0.97:8090/promotion';
|
|
17
17
|
/** 公网域名 */
|
|
18
18
|
this.publicDevHost = 'https://ecommerce-dev.wejourney.top/promotion';
|
|
19
19
|
this.publicProdHost = '';
|
package/package.json
CHANGED
package/utils/http.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
declare module '@fastify/request-context' {
|
|
2
2
|
interface RequestContextData {
|
|
3
|
-
requestId
|
|
3
|
+
requestId: string;
|
|
4
4
|
traceMessageId?: string;
|
|
5
5
|
pandoraUserId?: string;
|
|
6
6
|
beLinkUserId?: string;
|
|
@@ -9,25 +9,11 @@ declare module '@fastify/request-context' {
|
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
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
|
|
12
|
+
export declare function getHttpStats(): {
|
|
13
13
|
activeRequests: number;
|
|
14
14
|
totalRequests: number;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
details: NodeJS.ReadOnlyDict<import("net").Socket[]>;
|
|
20
|
-
};
|
|
21
|
-
httpsSockets: {
|
|
22
|
-
total: number;
|
|
23
|
-
hosts: number;
|
|
24
|
-
details: NodeJS.ReadOnlyDict<import("net").Socket[]>;
|
|
25
|
-
};
|
|
26
|
-
config: {
|
|
27
|
-
maxSockets: number;
|
|
28
|
-
maxTotalSockets: number;
|
|
29
|
-
requestTimeout: number;
|
|
30
|
-
note: string;
|
|
31
|
-
};
|
|
15
|
+
errorCount: number;
|
|
16
|
+
timeoutCount: number;
|
|
17
|
+
errorRate: string;
|
|
18
|
+
timeoutRate: string;
|
|
32
19
|
};
|
|
33
|
-
export declare function resetPeakStats(): void;
|
package/utils/http.js
CHANGED
|
@@ -4,27 +4,24 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.callApi = callApi;
|
|
7
|
-
exports.
|
|
8
|
-
exports.resetPeakStats = resetPeakStats;
|
|
7
|
+
exports.getHttpStats = getHttpStats;
|
|
9
8
|
const axios_1 = __importDefault(require("axios"));
|
|
10
9
|
const uuid_1 = require("uuid");
|
|
11
10
|
const axios_retry_1 = __importDefault(require("axios-retry"));
|
|
12
11
|
const request_context_1 = require("@fastify/request-context");
|
|
13
|
-
const safe_stable_stringify_1 = __importDefault(require("safe-stable-stringify"));
|
|
14
12
|
const http_1 = __importDefault(require("http"));
|
|
15
13
|
const https_1 = __importDefault(require("https"));
|
|
16
|
-
|
|
14
|
+
const safe_stable_stringify_1 = __importDefault(require("safe-stable-stringify"));
|
|
15
|
+
// 针对高并发优化配置 - 极限性能模式
|
|
17
16
|
const HTTP_CONFIG = {
|
|
18
|
-
maxSockets:
|
|
19
|
-
maxFreeSockets:
|
|
20
|
-
maxTotalSockets:
|
|
21
|
-
keepAliveMsecs:
|
|
22
|
-
timeout:
|
|
23
|
-
requestTimeout:
|
|
24
|
-
retries:
|
|
25
|
-
retryBaseDelay:
|
|
26
|
-
logThreshold: 1000, // 日志采样率(每1000个请求采样1次,降低日志开销)
|
|
27
|
-
monitorInterval: 5000, // 监控日志间隔(毫秒)
|
|
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
|
|
28
25
|
};
|
|
29
26
|
// 配置 HTTP/HTTPS Agent 以支持高并发连接池和 keepAlive
|
|
30
27
|
const httpAgent = new http_1.default.Agent({
|
|
@@ -34,7 +31,7 @@ const httpAgent = new http_1.default.Agent({
|
|
|
34
31
|
maxFreeSockets: HTTP_CONFIG.maxFreeSockets,
|
|
35
32
|
maxTotalSockets: HTTP_CONFIG.maxTotalSockets,
|
|
36
33
|
timeout: HTTP_CONFIG.timeout,
|
|
37
|
-
scheduling: '
|
|
34
|
+
scheduling: 'lifo', // 后进先出调度(优先复用热连接,提高连接复用效率)
|
|
38
35
|
});
|
|
39
36
|
const httpsAgent = new https_1.default.Agent({
|
|
40
37
|
keepAlive: true,
|
|
@@ -43,7 +40,7 @@ const httpsAgent = new https_1.default.Agent({
|
|
|
43
40
|
maxFreeSockets: HTTP_CONFIG.maxFreeSockets,
|
|
44
41
|
maxTotalSockets: HTTP_CONFIG.maxTotalSockets,
|
|
45
42
|
timeout: HTTP_CONFIG.timeout,
|
|
46
|
-
scheduling: '
|
|
43
|
+
scheduling: 'lifo', // 后进先出调度(优先复用热连接,提高连接复用效率)
|
|
47
44
|
});
|
|
48
45
|
// 配置 axios 默认使用这些 agent
|
|
49
46
|
axios_1.default.defaults.httpAgent = httpAgent;
|
|
@@ -57,10 +54,16 @@ axios_1.default.defaults.maxContentLength = 50 * 1024 * 1024; // 50MB 最大响
|
|
|
57
54
|
retryCondition(error) {
|
|
58
55
|
// 重试临时错误和特定的网络错误
|
|
59
56
|
const status = error.response?.status;
|
|
60
|
-
const isNetworkError = error.code === 'ECONNRESET' ||
|
|
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';
|
|
61
63
|
const isServerError = status === 502 || status === 503 || status === 504;
|
|
62
64
|
const isRateLimited = status === 429;
|
|
63
|
-
|
|
65
|
+
const isTimeout = error.code === 'ECONNABORTED' || error.message?.includes('timeout');
|
|
66
|
+
return isNetworkError || isServerError || isRateLimited || isTimeout;
|
|
64
67
|
},
|
|
65
68
|
retryDelay: (retryCount, error) => {
|
|
66
69
|
// 指数退避 + 抖动
|
|
@@ -73,47 +76,33 @@ axios_1.default.defaults.maxContentLength = 50 * 1024 * 1024; // 50MB 最大响
|
|
|
73
76
|
}
|
|
74
77
|
return 1000 + Math.random() * 1000; // 1-2秒
|
|
75
78
|
}
|
|
76
|
-
// 指数退避:
|
|
79
|
+
// 指数退避:200ms, 400ms, 800ms...
|
|
77
80
|
const exponentialDelay = HTTP_CONFIG.retryBaseDelay * Math.pow(2, retryCount - 1);
|
|
78
|
-
const jitter = Math.random() *
|
|
79
|
-
return Math.min(exponentialDelay + jitter,
|
|
81
|
+
const jitter = Math.random() * 100; // 0-100ms 抖动
|
|
82
|
+
return Math.min(exponentialDelay + jitter, 3000); // 最多延迟3秒
|
|
80
83
|
},
|
|
81
84
|
shouldResetTimeout: true, // 重试时重置超时
|
|
82
85
|
});
|
|
83
|
-
//
|
|
84
|
-
let requestCount = 0;
|
|
86
|
+
// 性能监控变量
|
|
85
87
|
let activeRequests = 0;
|
|
86
|
-
let
|
|
88
|
+
let totalRequests = 0;
|
|
89
|
+
let timeoutCount = 0;
|
|
90
|
+
let errorCount = 0;
|
|
87
91
|
async function callApi(url, request) {
|
|
88
|
-
//
|
|
89
|
-
const currentRequestId = ++requestCount;
|
|
90
|
-
activeRequests++;
|
|
91
|
-
// 记录峰值并发
|
|
92
|
-
if (activeRequests > peakActiveRequests) {
|
|
93
|
-
peakActiveRequests = activeRequests;
|
|
94
|
-
}
|
|
95
|
-
// 批量获取请求上下文,减少函数调用开销
|
|
92
|
+
// 获取请求上下文
|
|
96
93
|
const ctx = request_context_1.requestContext.get('requestId') ? request_context_1.requestContext : null;
|
|
97
94
|
const requestId = ctx?.get('requestId') || ctx?.get('traceMessageId') || (0, uuid_1.v4)();
|
|
98
95
|
const pandoraUserId = ctx?.get('pandoraUserId') || '';
|
|
99
96
|
const beLinkUserId = ctx?.get('beLinkUserId') || '';
|
|
100
97
|
const pandoraRoleId = ctx?.get('pandoraRoleId') || '';
|
|
101
98
|
const realIp = ctx?.get('realIp') || '';
|
|
102
|
-
//
|
|
103
|
-
const
|
|
104
|
-
|
|
99
|
+
// 性能监控
|
|
100
|
+
const startTime = Date.now();
|
|
101
|
+
activeRequests++;
|
|
102
|
+
totalRequests++;
|
|
103
|
+
const currentRequestId = totalRequests;
|
|
105
104
|
try {
|
|
106
|
-
|
|
107
|
-
console.log((0, safe_stable_stringify_1.default)({
|
|
108
|
-
message: '发起HTTP请求',
|
|
109
|
-
currentRequestId,
|
|
110
|
-
activeRequests,
|
|
111
|
-
requestId,
|
|
112
|
-
url,
|
|
113
|
-
type: 'http_request_start',
|
|
114
|
-
}));
|
|
115
|
-
}
|
|
116
|
-
const response = await axios_1.default.post(url, request || {}, {
|
|
105
|
+
const response = await axios_1.default.post(url, request, {
|
|
117
106
|
headers: {
|
|
118
107
|
'x-request-id': requestId,
|
|
119
108
|
'x-belink-pandora-userid': pandoraUserId,
|
|
@@ -126,38 +115,51 @@ async function callApi(url, request) {
|
|
|
126
115
|
httpAgent,
|
|
127
116
|
httpsAgent,
|
|
128
117
|
});
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
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
|
+
}));
|
|
140
129
|
}
|
|
141
130
|
const responseData = response.data;
|
|
142
131
|
return responseData.data;
|
|
143
132
|
}
|
|
144
133
|
catch (error) {
|
|
145
134
|
const axiosError = error;
|
|
146
|
-
const duration =
|
|
147
|
-
|
|
148
|
-
|
|
135
|
+
const duration = Date.now() - startTime;
|
|
136
|
+
errorCount++;
|
|
137
|
+
// 记录详细的错误信息用于诊断
|
|
138
|
+
const errorInfo = {
|
|
149
139
|
message: '请求失败',
|
|
150
|
-
currentRequestId,
|
|
151
140
|
url,
|
|
152
|
-
duration
|
|
141
|
+
duration,
|
|
153
142
|
activeRequests,
|
|
143
|
+
totalRequests: currentRequestId,
|
|
154
144
|
errorMessage: axiosError.message,
|
|
145
|
+
errorCode: axiosError.code,
|
|
155
146
|
type: 'http_request_failed',
|
|
156
|
-
}
|
|
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));
|
|
157
159
|
if (axiosError.response) {
|
|
158
160
|
const response = axiosError.response;
|
|
159
161
|
const data = response.data;
|
|
160
|
-
console.
|
|
162
|
+
console.log((0, safe_stable_stringify_1.default)({
|
|
161
163
|
message: '响应异常',
|
|
162
164
|
status: response.status,
|
|
163
165
|
errorMessage: data?.message || axiosError.message,
|
|
@@ -167,44 +169,30 @@ async function callApi(url, request) {
|
|
|
167
169
|
throw error;
|
|
168
170
|
}
|
|
169
171
|
finally {
|
|
170
|
-
// 记录请求完成,减少活跃计数
|
|
171
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
|
+
}
|
|
172
186
|
}
|
|
173
187
|
}
|
|
174
|
-
//
|
|
175
|
-
function
|
|
176
|
-
|
|
177
|
-
// 当前状态
|
|
188
|
+
// 获取当前统计信息
|
|
189
|
+
function getHttpStats() {
|
|
190
|
+
return {
|
|
178
191
|
activeRequests,
|
|
179
|
-
totalRequests
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
total: Object.keys(httpAgent.sockets).reduce((sum, host) => sum + (httpAgent.sockets[host]?.length || 0), 0),
|
|
185
|
-
hosts: Object.keys(httpAgent.sockets).length,
|
|
186
|
-
details: httpAgent.sockets,
|
|
187
|
-
},
|
|
188
|
-
httpsSockets: {
|
|
189
|
-
total: Object.keys(httpsAgent.sockets).reduce((sum, host) => sum + (httpsAgent.sockets[host]?.length || 0), 0),
|
|
190
|
-
hosts: Object.keys(httpsAgent.sockets).length,
|
|
191
|
-
details: httpsAgent.sockets,
|
|
192
|
-
},
|
|
193
|
-
// 配置信息
|
|
194
|
-
config: {
|
|
195
|
-
maxSockets: HTTP_CONFIG.maxSockets,
|
|
196
|
-
maxTotalSockets: HTTP_CONFIG.maxTotalSockets,
|
|
197
|
-
requestTimeout: HTTP_CONFIG.requestTimeout,
|
|
198
|
-
note: '已移除应用层并发队列限制,完全依赖socket连接池',
|
|
199
|
-
},
|
|
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%',
|
|
200
197
|
};
|
|
201
|
-
return stats;
|
|
202
|
-
}
|
|
203
|
-
// 重置峰值统计(可用于定期重置)
|
|
204
|
-
function resetPeakStats() {
|
|
205
|
-
peakActiveRequests = activeRequests;
|
|
206
|
-
console.log((0, safe_stable_stringify_1.default)({
|
|
207
|
-
message: '峰值统计已重置',
|
|
208
|
-
type: 'stats_reset',
|
|
209
|
-
}));
|
|
210
198
|
}
|