@be-link/ecommerce-trade-service-node-sdk 0.1.68 → 0.1.70
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.
|
@@ -445,6 +445,10 @@ export declare namespace PosOrderQueryService {
|
|
|
445
445
|
isFullRefund: boolean;
|
|
446
446
|
/** 退货时商品核销状态 */
|
|
447
447
|
verificationStatus?: ENUM.OrderVerificationStatus;
|
|
448
|
+
/** 已核销退货数量 */
|
|
449
|
+
verifiedRefundQuantity: number;
|
|
450
|
+
/** 未核销退货数量 */
|
|
451
|
+
unverifiedRefundQuantity: number;
|
|
448
452
|
/** 退款资源信息 */
|
|
449
453
|
resource?: {
|
|
450
454
|
/** 资源ID */
|
|
@@ -59,8 +59,10 @@ export declare namespace RosOrderQueryService {
|
|
|
59
59
|
reverseOrderIds?: string[];
|
|
60
60
|
/** 订单ID列表 */
|
|
61
61
|
orderIds?: string[];
|
|
62
|
-
/**
|
|
63
|
-
|
|
62
|
+
/** 是否核销券订单(true=核销券订单,false=非核销券订单) */
|
|
63
|
+
isVerifyCoupon?: boolean;
|
|
64
|
+
/** 订单来源列表 */
|
|
65
|
+
sourceList?: ENUM.OrderSource[];
|
|
64
66
|
/** 用户ID列表 */
|
|
65
67
|
userIds?: string[];
|
|
66
68
|
/** 门店ID列表 */
|
|
@@ -204,6 +206,10 @@ export declare namespace RosOrderQueryService {
|
|
|
204
206
|
operatorNickname?: string;
|
|
205
207
|
/** 上传的图片/视频附件 */
|
|
206
208
|
attachments?: string[];
|
|
209
|
+
/** 已核销已退货数量 */
|
|
210
|
+
verifiedRefundQuantity: number;
|
|
211
|
+
/** 未核销已退货数量 */
|
|
212
|
+
unverifiedRefundQuantity: number;
|
|
207
213
|
};
|
|
208
214
|
/** 正向订单信息 */
|
|
209
215
|
positiveOrder: {
|
package/package.json
CHANGED
package/utils/http.js
CHANGED
|
@@ -10,18 +10,16 @@ const axios_retry_1 = __importDefault(require("axios-retry"));
|
|
|
10
10
|
const request_context_1 = require("@fastify/request-context");
|
|
11
11
|
const http_1 = __importDefault(require("http"));
|
|
12
12
|
const https_1 = __importDefault(require("https"));
|
|
13
|
-
|
|
14
|
-
// 针对高并发优化配置 - 支持2000并发
|
|
13
|
+
// HTTP 连接池配置 - 支持 2000 QPS(响应时间 2秒)
|
|
15
14
|
const HTTP_CONFIG = {
|
|
16
|
-
maxSockets: 3000, //
|
|
17
|
-
maxFreeSockets: 1000, //
|
|
18
|
-
maxTotalSockets: 10000, //
|
|
19
|
-
keepAliveMsecs:
|
|
20
|
-
timeout:
|
|
21
|
-
requestTimeout:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
monitorInterval: 5000, // 监控日志间隔(毫秒)
|
|
15
|
+
maxSockets: 3000, // 每个 host 最大并发连接数
|
|
16
|
+
maxFreeSockets: 1000, // 保持空闲连接数(减少连接重建开销)
|
|
17
|
+
maxTotalSockets: 10000, // 所有 host 总连接数上限(支持多个服务同时调用)
|
|
18
|
+
keepAliveMsecs: 60000, // 保持连接60秒
|
|
19
|
+
timeout: 10000, // socket超时10秒
|
|
20
|
+
requestTimeout: 10000, // 请求超时10秒
|
|
21
|
+
retries: 0, // 不重试(失败直接返回)
|
|
22
|
+
retryBaseDelay: 200, // 基础重试延迟200ms
|
|
25
23
|
};
|
|
26
24
|
// 配置 HTTP/HTTPS Agent 以支持高并发连接池和 keepAlive
|
|
27
25
|
const httpAgent = new http_1.default.Agent({
|
|
@@ -31,7 +29,7 @@ const httpAgent = new http_1.default.Agent({
|
|
|
31
29
|
maxFreeSockets: HTTP_CONFIG.maxFreeSockets,
|
|
32
30
|
maxTotalSockets: HTTP_CONFIG.maxTotalSockets,
|
|
33
31
|
timeout: HTTP_CONFIG.timeout,
|
|
34
|
-
scheduling: '
|
|
32
|
+
scheduling: 'lifo', // 后进先出调度(优先复用热连接,提高连接复用效率)
|
|
35
33
|
});
|
|
36
34
|
const httpsAgent = new https_1.default.Agent({
|
|
37
35
|
keepAlive: true,
|
|
@@ -40,7 +38,7 @@ const httpsAgent = new https_1.default.Agent({
|
|
|
40
38
|
maxFreeSockets: HTTP_CONFIG.maxFreeSockets,
|
|
41
39
|
maxTotalSockets: HTTP_CONFIG.maxTotalSockets,
|
|
42
40
|
timeout: HTTP_CONFIG.timeout,
|
|
43
|
-
scheduling: '
|
|
41
|
+
scheduling: 'lifo', // 后进先出调度(优先复用热连接,提高连接复用效率)
|
|
44
42
|
});
|
|
45
43
|
// 配置 axios 默认使用这些 agent
|
|
46
44
|
axios_1.default.defaults.httpAgent = httpAgent;
|
|
@@ -50,13 +48,20 @@ axios_1.default.defaults.maxRedirects = 3; // 限制重定向次数
|
|
|
50
48
|
axios_1.default.defaults.maxContentLength = 50 * 1024 * 1024; // 50MB 最大响应大小
|
|
51
49
|
// 高并发场景下的智能重试配置
|
|
52
50
|
(0, axios_retry_1.default)(axios_1.default, {
|
|
51
|
+
retries: HTTP_CONFIG.retries,
|
|
53
52
|
retryCondition(error) {
|
|
54
53
|
// 重试临时错误和特定的网络错误
|
|
55
54
|
const status = error.response?.status;
|
|
56
|
-
const isNetworkError = error.code === 'ECONNRESET' ||
|
|
55
|
+
const isNetworkError = error.code === 'ECONNRESET' ||
|
|
56
|
+
error.code === 'ETIMEDOUT' ||
|
|
57
|
+
error.code === 'ECONNREFUSED' ||
|
|
58
|
+
error.code === 'ENOTFOUND' ||
|
|
59
|
+
error.code === 'ENETUNREACH' ||
|
|
60
|
+
error.code === 'EAI_AGAIN';
|
|
57
61
|
const isServerError = status === 502 || status === 503 || status === 504;
|
|
58
62
|
const isRateLimited = status === 429;
|
|
59
|
-
|
|
63
|
+
const isTimeout = error.code === 'ECONNABORTED' || error.message?.includes('timeout');
|
|
64
|
+
return isNetworkError || isServerError || isRateLimited || isTimeout;
|
|
60
65
|
},
|
|
61
66
|
retryDelay: (retryCount, error) => {
|
|
62
67
|
// 指数退避 + 抖动
|
|
@@ -69,230 +74,35 @@ axios_1.default.defaults.maxContentLength = 50 * 1024 * 1024; // 50MB 最大响
|
|
|
69
74
|
}
|
|
70
75
|
return 1000 + Math.random() * 1000; // 1-2秒
|
|
71
76
|
}
|
|
72
|
-
// 指数退避:
|
|
77
|
+
// 指数退避:200ms, 400ms, 800ms...
|
|
73
78
|
const exponentialDelay = HTTP_CONFIG.retryBaseDelay * Math.pow(2, retryCount - 1);
|
|
74
|
-
const jitter = Math.random() *
|
|
75
|
-
return Math.min(exponentialDelay + jitter,
|
|
79
|
+
const jitter = Math.random() * 100; // 0-100ms 抖动
|
|
80
|
+
return Math.min(exponentialDelay + jitter, 3000); // 最多延迟3秒
|
|
76
81
|
},
|
|
77
82
|
shouldResetTimeout: true, // 重试时重置超时
|
|
78
83
|
});
|
|
79
|
-
// 连接池状态监控
|
|
80
|
-
let requestCount = 0;
|
|
81
|
-
let activeRequests = 0;
|
|
82
|
-
let peakActiveRequests = 0; // 峰值并发
|
|
83
|
-
// 注意:已移除应用层并发队列限制,完全依赖底层socket连接池控制
|
|
84
|
-
// 这样可以避免应用层排队等待,充分利用socket连接池的并发能力
|
|
85
84
|
async function callApi(url, request) {
|
|
86
|
-
//
|
|
87
|
-
const currentRequestId = ++requestCount;
|
|
88
|
-
activeRequests++;
|
|
89
|
-
// 记录峰值并发
|
|
90
|
-
if (activeRequests > peakActiveRequests) {
|
|
91
|
-
peakActiveRequests = activeRequests;
|
|
92
|
-
}
|
|
93
|
-
// 批量获取请求上下文,减少函数调用开销
|
|
85
|
+
// 获取请求上下文
|
|
94
86
|
const ctx = request_context_1.requestContext.get('requestId') ? request_context_1.requestContext : null;
|
|
95
87
|
const requestId = ctx?.get('requestId') || ctx?.get('traceMessageId') || (0, uuid_1.v4)();
|
|
96
88
|
const pandoraUserId = ctx?.get('pandoraUserId') || '';
|
|
97
89
|
const beLinkUserId = ctx?.get('beLinkUserId') || '';
|
|
98
90
|
const pandoraRoleId = ctx?.get('pandoraRoleId') || '';
|
|
99
91
|
const realIp = ctx?.get('realIp') || '';
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
'x-request-id': requestId,
|
|
117
|
-
'x-belink-pandora-userid': pandoraUserId,
|
|
118
|
-
'x-belink-userid': beLinkUserId,
|
|
119
|
-
'x-belink-pandora-roleid': pandoraRoleId,
|
|
120
|
-
'x-real-ip': realIp,
|
|
121
|
-
Connection: 'keep-alive',
|
|
122
|
-
},
|
|
123
|
-
timeout: HTTP_CONFIG.requestTimeout,
|
|
124
|
-
httpAgent,
|
|
125
|
-
httpsAgent,
|
|
126
|
-
});
|
|
127
|
-
if (shouldLog) {
|
|
128
|
-
const duration = Date.now() - startTime;
|
|
129
|
-
if (duration > 3000) {
|
|
130
|
-
console.log((0, safe_stable_stringify_1.default)({
|
|
131
|
-
message: '请求完成',
|
|
132
|
-
currentRequestId,
|
|
133
|
-
duration: `${duration}ms`,
|
|
134
|
-
activeRequests,
|
|
135
|
-
type: 'http_request_complete',
|
|
136
|
-
}));
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
const responseData = response.data;
|
|
140
|
-
return responseData.data;
|
|
141
|
-
}
|
|
142
|
-
catch (error) {
|
|
143
|
-
const axiosError = error;
|
|
144
|
-
const duration = startTime ? Date.now() - startTime : 0;
|
|
145
|
-
// 错误始终记录(但简化输出)
|
|
146
|
-
console.error((0, safe_stable_stringify_1.default)({
|
|
147
|
-
message: '请求失败',
|
|
148
|
-
currentRequestId,
|
|
149
|
-
url,
|
|
150
|
-
duration: `${duration}ms`,
|
|
151
|
-
activeRequests,
|
|
152
|
-
errorMessage: axiosError.message,
|
|
153
|
-
type: 'http_request_failed',
|
|
154
|
-
}));
|
|
155
|
-
if (axiosError.response) {
|
|
156
|
-
const response = axiosError.response;
|
|
157
|
-
const data = response.data;
|
|
158
|
-
console.error((0, safe_stable_stringify_1.default)({
|
|
159
|
-
message: '响应异常',
|
|
160
|
-
status: response.status,
|
|
161
|
-
errorMessage: data?.message || axiosError.message,
|
|
162
|
-
type: 'http_response_error',
|
|
163
|
-
}));
|
|
164
|
-
}
|
|
165
|
-
throw error;
|
|
166
|
-
}
|
|
167
|
-
finally {
|
|
168
|
-
// 记录请求完成,减少活跃计数
|
|
169
|
-
activeRequests--;
|
|
170
|
-
}
|
|
92
|
+
const response = await axios_1.default.post(url, request || {}, {
|
|
93
|
+
headers: {
|
|
94
|
+
'x-request-id': requestId,
|
|
95
|
+
'x-belink-pandora-userid': pandoraUserId,
|
|
96
|
+
'x-belink-userid': beLinkUserId,
|
|
97
|
+
'x-belink-pandora-roleid': pandoraRoleId,
|
|
98
|
+
'x-real-ip': realIp,
|
|
99
|
+
Connection: 'keep-alive',
|
|
100
|
+
'Content-Type': 'application/json;charset=utf-8',
|
|
101
|
+
},
|
|
102
|
+
timeout: HTTP_CONFIG.requestTimeout,
|
|
103
|
+
httpAgent,
|
|
104
|
+
httpsAgent,
|
|
105
|
+
});
|
|
106
|
+
const responseData = response.data;
|
|
107
|
+
return responseData.data;
|
|
171
108
|
}
|
|
172
|
-
// 导出监控函数
|
|
173
|
-
// export function getConnectionStats() {
|
|
174
|
-
// const stats = {
|
|
175
|
-
// // 当前状态
|
|
176
|
-
// activeRequests,
|
|
177
|
-
// totalRequests: requestCount,
|
|
178
|
-
// // 峰值指标
|
|
179
|
-
// peakActiveRequests,
|
|
180
|
-
// // Socket连接池状态(这是真正的并发控制点)
|
|
181
|
-
// httpSockets: {
|
|
182
|
-
// total: Object.keys(httpAgent.sockets).reduce(
|
|
183
|
-
// (sum, host) => sum + (httpAgent.sockets[host]?.length || 0),
|
|
184
|
-
// 0,
|
|
185
|
-
// ),
|
|
186
|
-
// hosts: Object.keys(httpAgent.sockets).length,
|
|
187
|
-
// details: httpAgent.sockets,
|
|
188
|
-
// },
|
|
189
|
-
// httpsSockets: {
|
|
190
|
-
// total: Object.keys(httpsAgent.sockets).reduce(
|
|
191
|
-
// (sum, host) => sum + (httpsAgent.sockets[host]?.length || 0),
|
|
192
|
-
// 0,
|
|
193
|
-
// ),
|
|
194
|
-
// hosts: Object.keys(httpsAgent.sockets).length,
|
|
195
|
-
// details: httpsAgent.sockets,
|
|
196
|
-
// },
|
|
197
|
-
// // 配置信息
|
|
198
|
-
// config: {
|
|
199
|
-
// maxSockets: HTTP_CONFIG.maxSockets,
|
|
200
|
-
// maxTotalSockets: HTTP_CONFIG.maxTotalSockets,
|
|
201
|
-
// requestTimeout: HTTP_CONFIG.requestTimeout,
|
|
202
|
-
// note: '已移除应用层并发队列限制,完全依赖socket连接池',
|
|
203
|
-
// },
|
|
204
|
-
// }
|
|
205
|
-
// return stats
|
|
206
|
-
// }
|
|
207
|
-
// 重置峰值统计(可用于定期重置)
|
|
208
|
-
// export function resetPeakStats() {
|
|
209
|
-
// peakActiveRequests = activeRequests
|
|
210
|
-
// console.log(
|
|
211
|
-
// stringify({
|
|
212
|
-
// message: '峰值统计已重置',
|
|
213
|
-
// type: 'stats_reset',
|
|
214
|
-
// }),
|
|
215
|
-
// )
|
|
216
|
-
// }
|
|
217
|
-
// // 定期监控日志
|
|
218
|
-
// let monitorTimer: NodeJS.Timeout | null = null
|
|
219
|
-
// export function startMonitoring(interval: number = HTTP_CONFIG.monitorInterval) {
|
|
220
|
-
// if (monitorTimer) {
|
|
221
|
-
// console.log(
|
|
222
|
-
// stringify({
|
|
223
|
-
// message: '监控已在运行中',
|
|
224
|
-
// type: 'monitor_already_running',
|
|
225
|
-
// }),
|
|
226
|
-
// )
|
|
227
|
-
// return
|
|
228
|
-
// }
|
|
229
|
-
// console.log(
|
|
230
|
-
// stringify({
|
|
231
|
-
// message: '启动并发监控',
|
|
232
|
-
// interval: `${interval}ms`,
|
|
233
|
-
// type: 'monitor_start',
|
|
234
|
-
// }),
|
|
235
|
-
// )
|
|
236
|
-
// monitorTimer = setInterval(() => {
|
|
237
|
-
// const stats = getConnectionStats()
|
|
238
|
-
// // 只在有活动时打印
|
|
239
|
-
// if (stats.totalRequests === 0) {
|
|
240
|
-
// return
|
|
241
|
-
// }
|
|
242
|
-
// // 检测异常情况(基于socket连接池使用率)
|
|
243
|
-
// const httpSocketsUsage = stats.httpSockets.total / HTTP_CONFIG.maxSockets
|
|
244
|
-
// const httpsSocketsUsage = stats.httpsSockets.total / HTTP_CONFIG.maxSockets
|
|
245
|
-
// const maxSocketUsage = Math.max(httpSocketsUsage, httpsSocketsUsage)
|
|
246
|
-
// // Socket连接池使用率超过80%视为瓶颈
|
|
247
|
-
// const hasIssue = maxSocketUsage > 0.8
|
|
248
|
-
// if (hasIssue) {
|
|
249
|
-
// console.warn(
|
|
250
|
-
// stringify({
|
|
251
|
-
// message: '并发监控检测到瓶颈',
|
|
252
|
-
// 当前并发: stats.activeRequests,
|
|
253
|
-
// 峰值并发: stats.peakActiveRequests,
|
|
254
|
-
// 总请求数: stats.totalRequests,
|
|
255
|
-
// HTTP_Sockets: `${stats.httpSockets.total}/${HTTP_CONFIG.maxSockets} (${Math.round(
|
|
256
|
-
// httpSocketsUsage * 100,
|
|
257
|
-
// )}%)`,
|
|
258
|
-
// HTTPS_Sockets: `${stats.httpsSockets.total}/${HTTP_CONFIG.maxSockets} (${Math.round(
|
|
259
|
-
// httpsSocketsUsage * 100,
|
|
260
|
-
// )}%)`,
|
|
261
|
-
// 建议: 'Socket连接池接近饱和,考虑提高maxSockets配置',
|
|
262
|
-
// type: 'monitor_bottleneck_detected',
|
|
263
|
-
// }),
|
|
264
|
-
// )
|
|
265
|
-
// } else if (stats.totalRequests % 10000 === 0) {
|
|
266
|
-
// // 每1万次请求打印一次正常状态
|
|
267
|
-
// console.log(
|
|
268
|
-
// stringify({
|
|
269
|
-
// message: '并发监控运行正常',
|
|
270
|
-
// 总请求: stats.totalRequests,
|
|
271
|
-
// 当前并发: stats.activeRequests,
|
|
272
|
-
// 峰值并发: stats.peakActiveRequests,
|
|
273
|
-
// HTTP_Sockets使用率: `${Math.round(httpSocketsUsage * 100)}%`,
|
|
274
|
-
// HTTPS_Sockets使用率: `${Math.round(httpsSocketsUsage * 100)}%`,
|
|
275
|
-
// type: 'monitor_normal',
|
|
276
|
-
// }),
|
|
277
|
-
// )
|
|
278
|
-
// }
|
|
279
|
-
// }, interval)
|
|
280
|
-
// }
|
|
281
|
-
// export function stopMonitoring() {
|
|
282
|
-
// if (monitorTimer) {
|
|
283
|
-
// clearInterval(monitorTimer)
|
|
284
|
-
// monitorTimer = null
|
|
285
|
-
// console.log(
|
|
286
|
-
// stringify({
|
|
287
|
-
// message: '监控已停止',
|
|
288
|
-
// type: 'monitor_stop',
|
|
289
|
-
// }),
|
|
290
|
-
// )
|
|
291
|
-
// }
|
|
292
|
-
// }
|
|
293
|
-
// // 自动启动监控(可选,如果不想自动启动可以注释掉)
|
|
294
|
-
// if (process.env.NODE_ENV !== 'test') {
|
|
295
|
-
// setTimeout(() => {
|
|
296
|
-
// startMonitoring()
|
|
297
|
-
// }, 3000) // 延迟3秒启动,避免服务启动时的干扰
|
|
298
|
-
// }
|