alemonjs 2.1.0-alpha.1 → 2.1.0-alpha.10
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/lib/app/event-processor-event.js +1 -1
- package/lib/app/event-processor-middleware.js +1 -1
- package/lib/app/event-processor.js +1 -1
- package/lib/app/hook-use-api.d.ts +9 -2
- package/lib/app/hook-use-api.js +34 -18
- package/lib/app/load.js +10 -14
- package/lib/app/message-api.d.ts +4 -4
- package/lib/app/message-format.d.ts +1 -1
- package/lib/app/message-format.js +1 -1
- package/lib/app/store.js +4 -3
- package/lib/cbp/actions.js +9 -21
- package/lib/cbp/api.js +36 -0
- package/lib/cbp/config.js +8 -2
- package/lib/cbp/connect.d.ts +6 -2
- package/lib/cbp/connect.js +208 -26
- package/lib/cbp/index.js +50 -4
- package/lib/core/utils.d.ts +77 -0
- package/lib/core/utils.js +162 -0
- package/lib/global.d.ts +8 -0
- package/lib/index.d.ts +2 -2
- package/lib/index.js +2 -2
- package/lib/main.js +1 -1
- package/lib/typing/actions.d.ts +1 -1
- package/lib/typing/apis.d.ts +18 -0
- package/lib/typing/client/index.d.ts +1 -1
- package/lib/typing/event/base/user.d.ts +4 -0
- package/lib/typing/message/button.d.ts +1 -1
- package/lib/typing/message/image.d.ts +1 -1
- package/lib/utils.d.ts +43 -0
- package/lib/utils.js +108 -0
- package/package.json +5 -1
package/lib/cbp/connect.js
CHANGED
|
@@ -1,8 +1,66 @@
|
|
|
1
1
|
import { WebSocket } from 'ws';
|
|
2
2
|
import { onProcessor } from '../app/event-processor.js';
|
|
3
3
|
import { ResultCode } from '../core/code.js';
|
|
4
|
-
import { deviceId, FULL_RECEIVE_HEADER, DEVICE_ID_HEADER, USER_AGENT_HEADER, actionResolves, actionTimeouts, reconnectInterval } from './config.js';
|
|
4
|
+
import { deviceId, FULL_RECEIVE_HEADER, DEVICE_ID_HEADER, USER_AGENT_HEADER, apiResolves, apiTimeouts, actionResolves, actionTimeouts, reconnectInterval, HEARTBEAT_INTERVAL } from './config.js';
|
|
5
|
+
import { createResult } from '../core/utils.js';
|
|
5
6
|
|
|
7
|
+
// 心跳
|
|
8
|
+
const useHeartbeat = ({ ping, isConnected, terminate }) => {
|
|
9
|
+
let heartbeatTimer = null;
|
|
10
|
+
let lastPong = Date.now();
|
|
11
|
+
const stopHeartbeat = () => {
|
|
12
|
+
if (heartbeatTimer) {
|
|
13
|
+
clearInterval(heartbeatTimer);
|
|
14
|
+
heartbeatTimer = null;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const callback = () => {
|
|
18
|
+
if (isConnected()) {
|
|
19
|
+
const diff = Date.now() - lastPong;
|
|
20
|
+
const max = HEARTBEAT_INTERVAL * 2; // 最大心跳间隔
|
|
21
|
+
// 检查上次 pong 是否超时
|
|
22
|
+
if (diff > max) {
|
|
23
|
+
logger.debug({
|
|
24
|
+
code: ResultCode.Fail,
|
|
25
|
+
message: '心跳超时,断开重连',
|
|
26
|
+
data: null
|
|
27
|
+
});
|
|
28
|
+
terminate(); // 强制断开
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
ping();
|
|
32
|
+
logger.debug({
|
|
33
|
+
code: ResultCode.Ok,
|
|
34
|
+
message: `发送 ping`,
|
|
35
|
+
data: null
|
|
36
|
+
});
|
|
37
|
+
heartbeatTimer = setTimeout(callback, HEARTBEAT_INTERVAL);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
stopHeartbeat(); // 如果连接已关闭,停止心跳
|
|
41
|
+
terminate(); // 强制断开
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
const startHeartbeat = () => {
|
|
45
|
+
lastPong = Date.now();
|
|
46
|
+
stopHeartbeat();
|
|
47
|
+
callback();
|
|
48
|
+
};
|
|
49
|
+
const control = {
|
|
50
|
+
start: startHeartbeat,
|
|
51
|
+
stop: stopHeartbeat,
|
|
52
|
+
pong: () => {
|
|
53
|
+
// 收到 pong,说明连接正常
|
|
54
|
+
lastPong = Date.now();
|
|
55
|
+
logger.debug({
|
|
56
|
+
code: ResultCode.Ok,
|
|
57
|
+
message: `收到 pong`,
|
|
58
|
+
data: null
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
return [control];
|
|
63
|
+
};
|
|
6
64
|
/**
|
|
7
65
|
* CBP 客户端
|
|
8
66
|
* @param url
|
|
@@ -17,6 +75,27 @@ const cbpClient = (url, options = {}) => {
|
|
|
17
75
|
delete global.chatbotClient;
|
|
18
76
|
}
|
|
19
77
|
const { open = () => { }, isFullReceive = true } = options;
|
|
78
|
+
const [heartbeatControl] = useHeartbeat({
|
|
79
|
+
ping: () => {
|
|
80
|
+
global?.chatbotClient?.ping?.();
|
|
81
|
+
},
|
|
82
|
+
isConnected: () => {
|
|
83
|
+
return global?.chatbotClient && global?.chatbotClient?.readyState === WebSocket.OPEN;
|
|
84
|
+
},
|
|
85
|
+
terminate: () => {
|
|
86
|
+
try {
|
|
87
|
+
// 强制断开连接
|
|
88
|
+
global?.chatbotClient?.terminate?.();
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
logger.debug({
|
|
92
|
+
code: ResultCode.Fail,
|
|
93
|
+
message: '强制断开连接失败',
|
|
94
|
+
data: error
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
});
|
|
20
99
|
const start = () => {
|
|
21
100
|
global.chatbotClient = new WebSocket(url, {
|
|
22
101
|
headers: {
|
|
@@ -25,7 +104,13 @@ const cbpClient = (url, options = {}) => {
|
|
|
25
104
|
[FULL_RECEIVE_HEADER]: isFullReceive ? '1' : '0'
|
|
26
105
|
}
|
|
27
106
|
});
|
|
28
|
-
global.chatbotClient.on('open',
|
|
107
|
+
global.chatbotClient.on('open', () => {
|
|
108
|
+
open();
|
|
109
|
+
heartbeatControl.start(); // 启动心跳
|
|
110
|
+
});
|
|
111
|
+
global.chatbotClient.on('pong', () => {
|
|
112
|
+
heartbeatControl.pong(); // 更新 pong 时间
|
|
113
|
+
});
|
|
29
114
|
// 客户端接收,被标准化的平台消息
|
|
30
115
|
global.chatbotClient.on('message', message => {
|
|
31
116
|
try {
|
|
@@ -33,7 +118,7 @@ const cbpClient = (url, options = {}) => {
|
|
|
33
118
|
const parsedMessage = JSON.parse(message.toString());
|
|
34
119
|
logger.debug({
|
|
35
120
|
code: ResultCode.Ok,
|
|
36
|
-
message: '
|
|
121
|
+
message: '客户端接收到消息',
|
|
37
122
|
data: parsedMessage
|
|
38
123
|
});
|
|
39
124
|
if (parsedMessage?.activeId) {
|
|
@@ -47,19 +132,46 @@ const cbpClient = (url, options = {}) => {
|
|
|
47
132
|
}
|
|
48
133
|
}
|
|
49
134
|
}
|
|
50
|
-
else if (parsedMessage?.
|
|
51
|
-
// 如果有
|
|
52
|
-
const resolve =
|
|
135
|
+
else if (parsedMessage?.apiId) {
|
|
136
|
+
// 如果有 apiId,说明是一个接口请求。要进行处理
|
|
137
|
+
const resolve = apiResolves.get(parsedMessage.apiId);
|
|
138
|
+
if (resolve) {
|
|
139
|
+
apiResolves.delete(parsedMessage.apiId);
|
|
140
|
+
// 清除超时器
|
|
141
|
+
const timeout = apiTimeouts.get(parsedMessage.apiId);
|
|
142
|
+
if (timeout) {
|
|
143
|
+
apiTimeouts.delete(parsedMessage.apiId);
|
|
144
|
+
clearTimeout(timeout);
|
|
145
|
+
}
|
|
146
|
+
// 调用回调函数
|
|
147
|
+
if (Array.isArray(parsedMessage.payload)) {
|
|
148
|
+
resolve(parsedMessage.payload);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
// 错误处理
|
|
152
|
+
resolve([createResult(ResultCode.Fail, '接口处理错误', null)]);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else if (parsedMessage?.actionId) {
|
|
157
|
+
// 如果有 actionId
|
|
158
|
+
const resolve = actionResolves.get(parsedMessage.actionId);
|
|
53
159
|
if (resolve) {
|
|
160
|
+
actionResolves.delete(parsedMessage.actionId);
|
|
54
161
|
// 清除超时器
|
|
55
|
-
const timeout = actionTimeouts.get(parsedMessage.
|
|
162
|
+
const timeout = actionTimeouts.get(parsedMessage.actionId);
|
|
56
163
|
if (timeout) {
|
|
164
|
+
actionTimeouts.delete(parsedMessage.actionId);
|
|
57
165
|
clearTimeout(timeout);
|
|
58
|
-
actionTimeouts.delete(parsedMessage.actionID);
|
|
59
166
|
}
|
|
60
167
|
// 调用回调函数
|
|
61
|
-
|
|
62
|
-
|
|
168
|
+
if (Array.isArray(parsedMessage.payload)) {
|
|
169
|
+
resolve(parsedMessage.payload);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
// 错误处理
|
|
173
|
+
resolve([createResult(ResultCode.Fail, '消费处理错误', null)]);
|
|
174
|
+
}
|
|
63
175
|
}
|
|
64
176
|
}
|
|
65
177
|
else if (parsedMessage.name) {
|
|
@@ -70,13 +182,14 @@ const cbpClient = (url, options = {}) => {
|
|
|
70
182
|
catch (error) {
|
|
71
183
|
logger.error({
|
|
72
184
|
code: ResultCode.Fail,
|
|
73
|
-
message: '
|
|
185
|
+
message: '客户端解析消息失败',
|
|
74
186
|
data: error
|
|
75
187
|
});
|
|
76
188
|
}
|
|
77
189
|
});
|
|
78
190
|
global.chatbotClient.on('close', () => {
|
|
79
|
-
|
|
191
|
+
heartbeatControl.stop(); // 停止心跳
|
|
192
|
+
logger.warn({
|
|
80
193
|
code: ResultCode.Fail,
|
|
81
194
|
message: '连接关闭,尝试重新连接...',
|
|
82
195
|
data: null
|
|
@@ -87,6 +200,13 @@ const cbpClient = (url, options = {}) => {
|
|
|
87
200
|
start(); // 重新连接
|
|
88
201
|
}, reconnectInterval); // 6秒后重连
|
|
89
202
|
});
|
|
203
|
+
global.chatbotClient.on('error', err => {
|
|
204
|
+
logger.error({
|
|
205
|
+
code: ResultCode.Fail,
|
|
206
|
+
message: '客户端错误',
|
|
207
|
+
data: err
|
|
208
|
+
});
|
|
209
|
+
});
|
|
90
210
|
};
|
|
91
211
|
start();
|
|
92
212
|
};
|
|
@@ -97,6 +217,26 @@ const cbpPlatform = (url, options = {
|
|
|
97
217
|
delete global.chatbotPlatform;
|
|
98
218
|
}
|
|
99
219
|
const { open = () => { } } = options;
|
|
220
|
+
const [heartbeatControl] = useHeartbeat({
|
|
221
|
+
ping: () => {
|
|
222
|
+
global?.chatbotPlatform?.ping?.();
|
|
223
|
+
},
|
|
224
|
+
isConnected: () => {
|
|
225
|
+
return global?.chatbotPlatform && global?.chatbotPlatform?.readyState === WebSocket.OPEN;
|
|
226
|
+
},
|
|
227
|
+
terminate: () => {
|
|
228
|
+
try {
|
|
229
|
+
global?.chatbotPlatform?.terminate?.();
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
logger.debug({
|
|
233
|
+
code: ResultCode.Fail,
|
|
234
|
+
message: '强制断开连接失败',
|
|
235
|
+
data: error
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
});
|
|
100
240
|
/**
|
|
101
241
|
* 发送数据
|
|
102
242
|
* @param data
|
|
@@ -107,29 +247,47 @@ const cbpPlatform = (url, options = {
|
|
|
107
247
|
global.chatbotPlatform.send(JSON.stringify(data));
|
|
108
248
|
}
|
|
109
249
|
};
|
|
110
|
-
const
|
|
250
|
+
const actionReplys = [];
|
|
251
|
+
const apiReplys = [];
|
|
111
252
|
/**
|
|
112
253
|
* 消费数据
|
|
113
254
|
* @param data
|
|
114
255
|
* @param payload
|
|
115
256
|
*/
|
|
116
|
-
const
|
|
257
|
+
const replyAction = (data, payload) => {
|
|
117
258
|
if (global.chatbotPlatform && global.chatbotPlatform.readyState === WebSocket.OPEN) {
|
|
259
|
+
// 透传消费。也就是对应的设备进行处理消费。
|
|
118
260
|
global.chatbotPlatform.send(JSON.stringify({
|
|
119
261
|
action: data.action,
|
|
120
262
|
payload: payload,
|
|
121
|
-
|
|
122
|
-
// 透传消费。也就是对应的设备进行处理消费。
|
|
263
|
+
actionId: data.actionId,
|
|
123
264
|
DeviceId: data.DeviceId
|
|
124
265
|
}));
|
|
125
266
|
}
|
|
126
267
|
};
|
|
268
|
+
const replyApi = (data, payload) => {
|
|
269
|
+
if (global.chatbotPlatform && global.chatbotPlatform.readyState === WebSocket.OPEN) {
|
|
270
|
+
// 透传消费。也就是对应的设备进行处理消费。
|
|
271
|
+
global.chatbotPlatform.send(JSON.stringify({
|
|
272
|
+
action: data.action,
|
|
273
|
+
apiId: data.apiId,
|
|
274
|
+
DeviceId: data.DeviceId,
|
|
275
|
+
payload: payload
|
|
276
|
+
}));
|
|
277
|
+
}
|
|
278
|
+
};
|
|
127
279
|
/**
|
|
128
280
|
* 接收行为
|
|
129
281
|
* @param reply
|
|
130
282
|
*/
|
|
131
283
|
const onactions = (reply) => {
|
|
132
|
-
|
|
284
|
+
actionReplys.push(reply);
|
|
285
|
+
};
|
|
286
|
+
/**
|
|
287
|
+
* 接收接口
|
|
288
|
+
*/
|
|
289
|
+
const onapis = (reply) => {
|
|
290
|
+
apiReplys.push(reply);
|
|
133
291
|
};
|
|
134
292
|
/**
|
|
135
293
|
* 启动 WebSocket 连接
|
|
@@ -141,19 +299,34 @@ const cbpPlatform = (url, options = {
|
|
|
141
299
|
[DEVICE_ID_HEADER]: deviceId
|
|
142
300
|
}
|
|
143
301
|
});
|
|
144
|
-
global.chatbotPlatform.on('open',
|
|
302
|
+
global.chatbotPlatform.on('open', () => {
|
|
303
|
+
open();
|
|
304
|
+
heartbeatControl.start(); // 启动心跳
|
|
305
|
+
});
|
|
306
|
+
global.chatbotPlatform.on('pong', () => {
|
|
307
|
+
heartbeatControl.pong(); // 更新 pong 时间
|
|
308
|
+
});
|
|
145
309
|
global.chatbotPlatform.on('message', message => {
|
|
146
310
|
try {
|
|
147
311
|
const data = JSON.parse(message.toString());
|
|
148
312
|
logger.debug({
|
|
149
313
|
code: ResultCode.Ok,
|
|
150
|
-
message: '
|
|
314
|
+
message: '平台端接收消息',
|
|
151
315
|
data: data
|
|
152
316
|
});
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
317
|
+
if (data.apiId) {
|
|
318
|
+
for (const cb of apiReplys) {
|
|
319
|
+
cb(data,
|
|
320
|
+
// 传入一个消费函数
|
|
321
|
+
val => replyApi(data, val));
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
else if (data.actionId) {
|
|
325
|
+
for (const cb of actionReplys) {
|
|
326
|
+
cb(data,
|
|
327
|
+
// 传入一个消费函数
|
|
328
|
+
val => replyAction(data, val));
|
|
329
|
+
}
|
|
157
330
|
}
|
|
158
331
|
}
|
|
159
332
|
catch (error) {
|
|
@@ -165,9 +338,10 @@ const cbpPlatform = (url, options = {
|
|
|
165
338
|
}
|
|
166
339
|
});
|
|
167
340
|
global.chatbotPlatform.on('close', err => {
|
|
168
|
-
|
|
341
|
+
heartbeatControl.stop(); // 停止心跳
|
|
342
|
+
logger.warn({
|
|
169
343
|
code: ResultCode.Fail,
|
|
170
|
-
message: '
|
|
344
|
+
message: '平台端连接关闭,尝试重新连接...',
|
|
171
345
|
data: err
|
|
172
346
|
});
|
|
173
347
|
delete global.chatbotPlatform;
|
|
@@ -176,11 +350,19 @@ const cbpPlatform = (url, options = {
|
|
|
176
350
|
start(); // 重新连接
|
|
177
351
|
}, reconnectInterval); // 6秒后重连
|
|
178
352
|
});
|
|
353
|
+
global.chatbotPlatform.on('error', err => {
|
|
354
|
+
logger.error({
|
|
355
|
+
code: ResultCode.Fail,
|
|
356
|
+
message: '平台端错误',
|
|
357
|
+
data: err
|
|
358
|
+
});
|
|
359
|
+
});
|
|
179
360
|
};
|
|
180
361
|
start();
|
|
181
362
|
const client = {
|
|
182
363
|
send,
|
|
183
|
-
onactions
|
|
364
|
+
onactions,
|
|
365
|
+
onapis
|
|
184
366
|
};
|
|
185
367
|
return client;
|
|
186
368
|
};
|
package/lib/cbp/index.js
CHANGED
|
@@ -47,9 +47,41 @@ const cbpServer = (port, listeningListener) => {
|
|
|
47
47
|
try {
|
|
48
48
|
// 解析消息
|
|
49
49
|
const parsedMessage = JSON.parse(message.toString());
|
|
50
|
-
// 1. 解析得到
|
|
50
|
+
// 1. 解析得到 actionId ,说明是消费行为请求。要广播告诉所有客户端。
|
|
51
51
|
// 2. 解析得到 name ,说明是一个事件请求。
|
|
52
|
-
|
|
52
|
+
// 3. 解析得到 apiId ,说明是一个接口请求。
|
|
53
|
+
logger.debug({
|
|
54
|
+
code: ResultCode.Ok,
|
|
55
|
+
message: '服务端接收到消息',
|
|
56
|
+
data: parsedMessage
|
|
57
|
+
});
|
|
58
|
+
if (parsedMessage.apiId) {
|
|
59
|
+
// 指定的设备 处理消费。终端有记录每个客户端是谁
|
|
60
|
+
const DeviceId = parsedMessage.DeviceId;
|
|
61
|
+
if (childrenClient.has(DeviceId)) {
|
|
62
|
+
const clientWs = childrenClient.get(DeviceId);
|
|
63
|
+
if (clientWs && clientWs.readyState === WebSocket.OPEN) {
|
|
64
|
+
// 发送消息到指定的子客户端
|
|
65
|
+
clientWs.send(message);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
// 如果连接已关闭,删除该客户端
|
|
69
|
+
childrenClient.delete(DeviceId);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else if (fullClient.has(DeviceId)) {
|
|
73
|
+
const clientWs = fullClient.get(DeviceId);
|
|
74
|
+
if (clientWs && clientWs.readyState === WebSocket.OPEN) {
|
|
75
|
+
// 发送消息到指定的全量客户端
|
|
76
|
+
clientWs.send(message);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
// 如果连接已关闭,删除该客户端
|
|
80
|
+
fullClient.delete(DeviceId);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else if (parsedMessage?.actionId) {
|
|
53
85
|
// 指定的设备 处理消费。终端有记录每个客户端是谁
|
|
54
86
|
const DeviceId = parsedMessage.DeviceId;
|
|
55
87
|
if (childrenClient.has(DeviceId)) {
|
|
@@ -139,7 +171,7 @@ const cbpServer = (port, listeningListener) => {
|
|
|
139
171
|
else {
|
|
140
172
|
logger.error({
|
|
141
173
|
code: ResultCode.Fail,
|
|
142
|
-
message: '
|
|
174
|
+
message: '服务端出现意外,无法绑定客户端',
|
|
143
175
|
data: null
|
|
144
176
|
});
|
|
145
177
|
}
|
|
@@ -171,7 +203,7 @@ const cbpServer = (port, listeningListener) => {
|
|
|
171
203
|
catch (error) {
|
|
172
204
|
logger.error({
|
|
173
205
|
code: ResultCode.Fail,
|
|
174
|
-
message: '
|
|
206
|
+
message: '服务端解析平台消息失败',
|
|
175
207
|
data: error
|
|
176
208
|
});
|
|
177
209
|
return;
|
|
@@ -186,6 +218,13 @@ const cbpServer = (port, listeningListener) => {
|
|
|
186
218
|
data: null
|
|
187
219
|
});
|
|
188
220
|
});
|
|
221
|
+
ws.on('error', err => {
|
|
222
|
+
logger.error({
|
|
223
|
+
code: ResultCode.Fail,
|
|
224
|
+
message: `Client ${originId} error`,
|
|
225
|
+
data: err
|
|
226
|
+
});
|
|
227
|
+
});
|
|
189
228
|
};
|
|
190
229
|
// 设置子客户端
|
|
191
230
|
const setChildrenClient = (originId, ws) => {
|
|
@@ -217,6 +256,13 @@ const cbpServer = (port, listeningListener) => {
|
|
|
217
256
|
data: null
|
|
218
257
|
});
|
|
219
258
|
});
|
|
259
|
+
ws.on('error', err => {
|
|
260
|
+
logger.error({
|
|
261
|
+
code: ResultCode.Fail,
|
|
262
|
+
message: `Client ${originId} error`,
|
|
263
|
+
data: err
|
|
264
|
+
});
|
|
265
|
+
});
|
|
220
266
|
};
|
|
221
267
|
// 全量客户端
|
|
222
268
|
const setFullClient = (originId, ws) => {
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Dirent } from 'fs';
|
|
2
|
+
import { ResultCode } from './code.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 将字符串转为定长字符串
|
|
6
|
+
* @param str 输入字符串
|
|
7
|
+
* @param options 可选项
|
|
8
|
+
* @returns 固定长度的哈希值
|
|
9
|
+
*/
|
|
10
|
+
declare const createHash: (str: string, options?: {
|
|
11
|
+
length?: number;
|
|
12
|
+
algorithm?: string;
|
|
13
|
+
}) => string;
|
|
14
|
+
/**
|
|
15
|
+
* 使用用户的哈希键
|
|
16
|
+
* @param e
|
|
17
|
+
* @returns
|
|
18
|
+
*/
|
|
19
|
+
declare const useUserHashKey: (event: {
|
|
20
|
+
UserId: string;
|
|
21
|
+
Platform: string;
|
|
22
|
+
}) => string;
|
|
23
|
+
/**
|
|
24
|
+
* 创建app名称
|
|
25
|
+
* @param url
|
|
26
|
+
* @param app 模块名
|
|
27
|
+
* @param select 选择事件类型,默认 res
|
|
28
|
+
* @returns
|
|
29
|
+
*/
|
|
30
|
+
declare const createEventName: (url: string, appKey: string) => string;
|
|
31
|
+
/**
|
|
32
|
+
* 将字符串转为数字
|
|
33
|
+
* @param str
|
|
34
|
+
* @returns
|
|
35
|
+
*/
|
|
36
|
+
declare const stringToNumber: (str: string, size?: number) => number;
|
|
37
|
+
/**
|
|
38
|
+
* 递归获取所有文件
|
|
39
|
+
* @param dir
|
|
40
|
+
* @param condition
|
|
41
|
+
* @returns
|
|
42
|
+
*/
|
|
43
|
+
declare const getRecursiveDirFiles: (dir: string, condition?: (func: Dirent) => boolean) => {
|
|
44
|
+
path: string;
|
|
45
|
+
name: string;
|
|
46
|
+
}[];
|
|
47
|
+
/**
|
|
48
|
+
* 解析错误并提示缺失的模块
|
|
49
|
+
* @param e
|
|
50
|
+
* @returns
|
|
51
|
+
*/
|
|
52
|
+
declare const showErrorModule: (e: Error) => void;
|
|
53
|
+
declare const getInputExportPath: (input?: string) => any;
|
|
54
|
+
/**
|
|
55
|
+
* 属于直接调用的函数
|
|
56
|
+
* 参数错误直接抛出错误
|
|
57
|
+
* 如果函数内部有错误,则使用 Result 进行返回
|
|
58
|
+
*/
|
|
59
|
+
/**
|
|
60
|
+
* 异步方法。且需要读取返回值的进行判断的,
|
|
61
|
+
* 都要以 Result 作为返回值
|
|
62
|
+
*/
|
|
63
|
+
type Result = {
|
|
64
|
+
code: ResultCode;
|
|
65
|
+
message: string | Object;
|
|
66
|
+
data: any;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* 创建结果
|
|
70
|
+
* @param code
|
|
71
|
+
* @param message
|
|
72
|
+
* @returns
|
|
73
|
+
*/
|
|
74
|
+
declare const createResult: (code: ResultCode, message: string | Object, data?: any) => Result;
|
|
75
|
+
|
|
76
|
+
export { createEventName, createHash, createResult, getInputExportPath, getRecursiveDirFiles, showErrorModule, stringToNumber, useUserHashKey };
|
|
77
|
+
export type { Result };
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import fs, { existsSync, readdirSync } from 'fs';
|
|
3
|
+
import path, { join } from 'path';
|
|
4
|
+
import { createRequire } from 'module';
|
|
5
|
+
import { ResultCode } from './code.js';
|
|
6
|
+
import { file_suffix_response } from './variable.js';
|
|
7
|
+
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
/**
|
|
10
|
+
* 将字符串转为定长字符串
|
|
11
|
+
* @param str 输入字符串
|
|
12
|
+
* @param options 可选项
|
|
13
|
+
* @returns 固定长度的哈希值
|
|
14
|
+
*/
|
|
15
|
+
const createHash = (str, options = {}) => {
|
|
16
|
+
const { length = 11, algorithm = 'sha256' } = options;
|
|
17
|
+
// 使用 crypto 生成哈希
|
|
18
|
+
const hash = crypto.createHash(algorithm).update(str).digest('hex');
|
|
19
|
+
// 截取指定长度
|
|
20
|
+
return hash.slice(0, length);
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* 使用用户的哈希键
|
|
24
|
+
* @param e
|
|
25
|
+
* @returns
|
|
26
|
+
*/
|
|
27
|
+
const useUserHashKey = (event) => {
|
|
28
|
+
return createHash(`${event.Platform}:${event.UserId}`);
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* 创建app名称
|
|
32
|
+
* @param url
|
|
33
|
+
* @param app 模块名
|
|
34
|
+
* @param select 选择事件类型,默认 res
|
|
35
|
+
* @returns
|
|
36
|
+
*/
|
|
37
|
+
const createEventName = (url, appKey) => {
|
|
38
|
+
let uri = url;
|
|
39
|
+
if (process.platform === 'win32') {
|
|
40
|
+
uri = uri.replace(/\\/g, '/');
|
|
41
|
+
}
|
|
42
|
+
// 去掉空字符串
|
|
43
|
+
const names = uri.split('/').filter(item => item !== '');
|
|
44
|
+
// 去掉最后一个文件名
|
|
45
|
+
names.pop();
|
|
46
|
+
const name = `${appKey}:${names.join(':')}`;
|
|
47
|
+
return name;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* 将字符串转为数字
|
|
51
|
+
* @param str
|
|
52
|
+
* @returns
|
|
53
|
+
*/
|
|
54
|
+
const stringToNumber = (str, size = 33) => {
|
|
55
|
+
let hash = 5381;
|
|
56
|
+
let i = str.length;
|
|
57
|
+
while (i) {
|
|
58
|
+
hash = (hash * size) ^ str.charCodeAt(--i);
|
|
59
|
+
}
|
|
60
|
+
/*JavaScript对32位签名执行逐位操作(如上面的XOR)
|
|
61
|
+
*整数。由于我们希望结果始终为正,因此转换
|
|
62
|
+
*通过执行无符号位移,将带符号的int转换为无符号*/
|
|
63
|
+
return hash >>> 0;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* 递归获取所有文件
|
|
67
|
+
* @param dir
|
|
68
|
+
* @param condition
|
|
69
|
+
* @returns
|
|
70
|
+
*/
|
|
71
|
+
const getRecursiveDirFiles = (dir, condition = item => file_suffix_response.test(item.name)) => {
|
|
72
|
+
//
|
|
73
|
+
let results = [];
|
|
74
|
+
if (!existsSync(dir))
|
|
75
|
+
return results;
|
|
76
|
+
const list = readdirSync(dir, { withFileTypes: true });
|
|
77
|
+
list.forEach(item => {
|
|
78
|
+
const fullPath = join(dir, item.name);
|
|
79
|
+
if (item.isDirectory()) {
|
|
80
|
+
results = results.concat(getRecursiveDirFiles(fullPath, condition));
|
|
81
|
+
}
|
|
82
|
+
else if (item.isFile() && condition(item)) {
|
|
83
|
+
results.push({
|
|
84
|
+
path: fullPath,
|
|
85
|
+
name: item.name
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
return results;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* 解析错误并提示缺失的模块
|
|
93
|
+
* @param e
|
|
94
|
+
* @returns
|
|
95
|
+
*/
|
|
96
|
+
const showErrorModule = (e) => {
|
|
97
|
+
if (!e)
|
|
98
|
+
return;
|
|
99
|
+
const moduleNotFoundRegex = /Cannot find (module|package)/;
|
|
100
|
+
// 处理模块未找到的错误
|
|
101
|
+
if (moduleNotFoundRegex.test(e?.message)) {
|
|
102
|
+
const match = e.stack?.match(/'(.+?)'/);
|
|
103
|
+
if (match) {
|
|
104
|
+
const pack = match[1];
|
|
105
|
+
logger.error({
|
|
106
|
+
code: ResultCode.FailInternal,
|
|
107
|
+
message: `缺少模块或依赖 ${pack},请安装`,
|
|
108
|
+
data: null
|
|
109
|
+
});
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// 处理其他错误
|
|
114
|
+
logger.error({
|
|
115
|
+
code: ResultCode.FailInternal,
|
|
116
|
+
message: e?.message,
|
|
117
|
+
data: e
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
const createExports = (packageJson) => {
|
|
121
|
+
if (packageJson?.exports) {
|
|
122
|
+
if (typeof packageJson.exports === 'string') {
|
|
123
|
+
return packageJson.exports;
|
|
124
|
+
}
|
|
125
|
+
else if (typeof packageJson.exports === 'object') {
|
|
126
|
+
return packageJson.exports['.'] || packageJson.exports['./index.js'];
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
const getInputExportPath = (input) => {
|
|
131
|
+
const packageJsonPath = path.join(input ?? process.cwd(), 'package.json');
|
|
132
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
133
|
+
const packageJson = require(packageJsonPath);
|
|
134
|
+
const main = packageJson?.main || createExports(packageJson);
|
|
135
|
+
if (main) {
|
|
136
|
+
return main;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* 创建结果
|
|
142
|
+
* @param code
|
|
143
|
+
* @param message
|
|
144
|
+
* @returns
|
|
145
|
+
*/
|
|
146
|
+
const createResult = (code, message, data) => {
|
|
147
|
+
// 如果不是 2000。则logger
|
|
148
|
+
if (code !== ResultCode.Ok) {
|
|
149
|
+
logger.error({
|
|
150
|
+
code,
|
|
151
|
+
message,
|
|
152
|
+
data: data
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
code,
|
|
157
|
+
message,
|
|
158
|
+
data: data
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export { createEventName, createHash, createResult, getInputExportPath, getRecursiveDirFiles, showErrorModule, stringToNumber, useUserHashKey };
|
package/lib/global.d.ts
CHANGED
|
@@ -60,6 +60,14 @@ declare global {
|
|
|
60
60
|
* 定义数据格式
|
|
61
61
|
*/
|
|
62
62
|
var format: OnDataFormatFunc;
|
|
63
|
+
namespace NodeJS {
|
|
64
|
+
interface ProcessEnv {
|
|
65
|
+
login?: string;
|
|
66
|
+
platform?: string;
|
|
67
|
+
port?: string;
|
|
68
|
+
NODE_ENV?: 'development' | 'production';
|
|
69
|
+
}
|
|
70
|
+
}
|
|
63
71
|
}
|
|
64
72
|
declare const logger: any;
|
|
65
73
|
declare const core: {
|