@azteam/rabbitmq-async 1.0.230 → 1.0.233

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": "@azteam/rabbitmq-async",
3
- "version": "1.0.230",
3
+ "version": "1.0.233",
4
4
  "description": "N/A",
5
5
  "keywords": [
6
6
  "toda",
@@ -16,7 +16,7 @@
16
16
  "prepublishOnly": "babel src --delete-dir-on-start -d lib"
17
17
  },
18
18
  "dependencies": {
19
- "@azteam/util": "1.0.64",
19
+ "@azteam/util": "1.0.65",
20
20
  "amqplib": "0.8.0",
21
21
  "fs-extra": "10.1.0",
22
22
  "lodash": "4.17.23"
package/src/Provider.js CHANGED
@@ -6,9 +6,8 @@ class Provider {
6
6
  this.configs = {};
7
7
 
8
8
  if (Array.isArray(configs)) {
9
- configs.map((config) => {
9
+ configs.forEach((config) => {
10
10
  this.configs[config.name] = config;
11
- return true;
12
11
  });
13
12
  } else {
14
13
  this.configs.main = configs;
@@ -34,6 +33,18 @@ class Provider {
34
33
  })
35
34
  );
36
35
  }
36
+
37
+ async closeAll() {
38
+ await Promise.all(
39
+ Object.keys(this.connections).map(async (keyConfig) => {
40
+ const connection = this.connections[keyConfig];
41
+ if (connection) {
42
+ await connection.close();
43
+ }
44
+ })
45
+ );
46
+ this.connections = {};
47
+ }
37
48
  }
38
49
 
39
50
  export default Provider;
@@ -14,20 +14,17 @@ class RabbitMQAsync {
14
14
  this.retryKey = config.retry_key;
15
15
 
16
16
  this.listConsume = [];
17
+ this.listSub = [];
17
18
 
18
19
  this.client = null;
19
20
  this.channel = null;
21
+ this.reconnecting = false;
20
22
 
21
- setInterval(async () => {
22
- if (!this.connected) {
23
- this.connect();
24
- }
25
- this.startListConsume();
26
- }, 5000);
23
+ this.connect();
27
24
  }
28
25
 
29
- async waitConnection(n = 10) {
30
- for (let i = 0; !this.connected || i < n; i += 1) {
26
+ async waitConnection(n = 60) {
27
+ for (let i = 0; !this.connected && i < n; i += 1) {
31
28
  await timeout(1000);
32
29
  }
33
30
  if (!this.connected) {
@@ -35,43 +32,82 @@ class RabbitMQAsync {
35
32
  }
36
33
  }
37
34
 
35
+ scheduleReconnect() {
36
+ if (this.reconnecting) return;
37
+ this.reconnecting = true;
38
+ setTimeout(() => {
39
+ this.reconnecting = false;
40
+ if (!this.connected) {
41
+ this.connect();
42
+ }
43
+ }, 5000).unref();
44
+ }
45
+
38
46
  async connect() {
47
+ if (this.connected) return;
39
48
  this._alert('connecting', 'MQ connecting...');
40
49
  const opt = {credentials: amqp.credentials.plain(this.username, this.password)};
41
50
  try {
42
- this.client = await amqp.connect(`amqp://${this.host}?heartbeat=180`, opt);
43
- this.channel = await this.client.createChannel();
44
- this.channel.prefetch(1);
45
- this.connected = true;
46
- this._alert('connect', 'MQ connected');
51
+ this.client = await amqp.connect(`amqp://${this.host}`, opt);
52
+
47
53
  this.client.on('error', (err) => {
48
- this.close();
49
54
  this._alert('error', `MQ Error ${err}`);
55
+ this.handleDisconnect();
50
56
  });
51
57
 
52
58
  this.client.on('close', () => {
53
- this.close();
54
59
  this._alert('close', 'MQ closed');
60
+ this.handleDisconnect();
55
61
  });
62
+
63
+ this.channel = await this.client.createChannel();
64
+ this.channel.prefetch(1);
65
+ this.connected = true;
66
+ this._alert('connect', 'MQ connected');
67
+
68
+ this.startListConsume();
69
+ this.startListSub();
56
70
  } catch (err) {
57
- this.close();
58
- this._alert('error', `MQ connect ${err}`);
71
+ this._alert('error', `MQ connect error: ${err}`);
72
+ this.handleDisconnect();
59
73
  }
60
74
  }
61
75
 
76
+ handleDisconnect() {
77
+ if (this.connected) {
78
+ this.connected = false;
79
+ this.listConsume.forEach((c) => {
80
+ c.listened = false;
81
+ });
82
+ this.listSub.forEach((s) => {
83
+ s.listened = false;
84
+ });
85
+ }
86
+ this.client = null;
87
+ this.channel = null;
88
+ this.scheduleReconnect();
89
+ }
90
+
62
91
  async close() {
63
92
  this.connected = false;
64
- // eslint-disable-next-line array-callback-return
65
- this.listConsume.map((c) => {
66
- // eslint-disable-next-line no-param-reassign
93
+ this.listConsume.forEach((c) => {
67
94
  c.listened = false;
68
95
  });
69
- try {
70
- await this.client.close();
71
- } catch (err) {}
72
- try {
73
- await this.channel.close();
74
- } catch (err) {}
96
+ this.listSub.forEach((s) => {
97
+ s.listened = false;
98
+ });
99
+ if (this.channel) {
100
+ try {
101
+ await this.channel.close();
102
+ } catch (err) {}
103
+ this.channel = null;
104
+ }
105
+ if (this.client) {
106
+ try {
107
+ await this.client.close();
108
+ } catch (err) {}
109
+ this.client = null;
110
+ }
75
111
  }
76
112
 
77
113
  parsePrefix(name) {
@@ -82,119 +118,150 @@ class RabbitMQAsync {
82
118
  return newName;
83
119
  }
84
120
 
85
- async send(queueName, msg = {}, limit = 0) {
121
+ async send(queueName, msg = {}, limit = 0, retryCount = 0) {
122
+ if (retryCount >= 10) {
123
+ this._alert('error', `Failed to send to queue ${queueName} after 10 retries`);
124
+ return false;
125
+ }
86
126
  try {
87
127
  const prefixQueueName = this.parsePrefix(queueName);
88
- if (this.connected) {
89
- const {channel} = this,
90
- queueInfo = await this.channel.assertQueue(prefixQueueName, {
91
- durable: true,
92
- });
93
-
94
- if (limit === 0 || queueInfo.messageCount < limit) {
95
- await channel.sendToQueue(prefixQueueName, Buffer.from(JSON.stringify({retry: 5, ...msg})), {
96
- persistent: true,
97
- });
98
- }
128
+ if (!this.connected || !this.channel) {
129
+ throw new Error('Not connected');
130
+ }
131
+
132
+ const queueInfo = await this.channel.assertQueue(prefixQueueName, {
133
+ durable: true,
134
+ });
135
+
136
+ if (limit === 0 || queueInfo.messageCount < limit) {
137
+ await this.channel.sendToQueue(prefixQueueName, Buffer.from(JSON.stringify({retry: 5, ...msg})), {
138
+ persistent: true,
139
+ });
140
+ }
141
+
142
+ return true;
143
+ } catch (err) {
144
+ await timeout(2000);
145
+ return this.send(queueName, msg, limit, retryCount + 1);
146
+ }
147
+ }
148
+
149
+ async pub(exchangeName, routingKey = '', msg = {}, type = 'fanout', retryCount = 0) {
150
+ if (retryCount >= 10) {
151
+ this._alert('error', `Failed to publish to exchange ${exchangeName} after 10 retries`);
152
+ return false;
153
+ }
154
+ try {
155
+ const prefixExchangeName = this.parsePrefix(exchangeName);
156
+ if (!this.connected || !this.channel) {
157
+ throw new Error('Not connected');
99
158
  }
100
159
 
160
+ await this.channel.assertExchange(prefixExchangeName, type, {
161
+ durable: true,
162
+ });
163
+
164
+ await this.channel.publish(prefixExchangeName, routingKey, Buffer.from(JSON.stringify({retry: 5, ...msg})), {
165
+ persistent: true,
166
+ });
167
+
101
168
  return true;
102
169
  } catch (err) {
103
170
  await timeout(2000);
104
- return this.send(queueName, msg);
171
+ return this.pub(exchangeName, routingKey, msg, type, retryCount + 1);
105
172
  }
106
173
  }
107
174
 
108
175
  async getInfo(queueName) {
109
176
  try {
110
177
  const prefixQueueName = this.parsePrefix(queueName);
111
- if (this.connected) {
112
- const {channel} = this;
113
- return await channel.assertQueue(prefixQueueName, {
178
+ if (this.connected && this.channel) {
179
+ return await this.channel.assertQueue(prefixQueueName, {
114
180
  durable: true,
115
181
  });
116
182
  }
117
183
  } catch (err) {}
184
+
185
+ await timeout(2000);
118
186
  return this.getInfo(queueName);
119
187
  }
120
188
 
121
- // eslint-disable-next-line consistent-return
122
189
  async consume(queueName, asyncFunction, callbackError = null) {
123
190
  const messageQueue = this;
124
191
  try {
125
- if (this.connected) {
126
- const prefixQueueName = this.parsePrefix(queueName),
127
- {channel} = messageQueue;
128
- if (channel) {
129
- await channel.assertQueue(prefixQueueName, {
130
- durable: true,
131
- });
132
- await new Promise((resolve, reject) => {
133
- channel.consume(prefixQueueName, async function (msg) {
134
- if (msg) {
135
- const data = JSON.parse(msg.content.toString());
136
- if (data) {
137
- try {
138
- if (msg.fields.redelivered) {
139
- data.retry -= 1;
140
- if (data.retry > 0) {
141
- await messageQueue.send(queueName, data);
142
- } else if (!data.notCheck) {
143
- await messageQueue.send(messageQueue.retryKey, {
144
- queueName,
145
- data,
146
- retry_time: new Date(),
147
- ip: messageQueue.serverIp,
148
- });
149
- }
192
+ if (!this.connected || !this.channel) return;
193
+
194
+ const prefixQueueName = this.parsePrefix(queueName);
195
+ const {channel} = messageQueue;
196
+
197
+ await channel.assertQueue(prefixQueueName, {
198
+ durable: true,
199
+ });
200
+
201
+ await channel.consume(prefixQueueName, async function (msg) {
202
+ if (!msg) return;
203
+
204
+ try {
205
+ const data = JSON.parse(msg.content.toString());
206
+ if (data) {
207
+ try {
208
+ if (msg.fields.redelivered) {
209
+ data.retry = (data.retry || 0) - 1;
210
+ if (data.retry > 0) {
211
+ await messageQueue.send(queueName, data);
212
+ } else if (!data.notCheck && messageQueue.retryKey) {
213
+ await messageQueue.send(messageQueue.retryKey, {
214
+ queueName,
215
+ data,
216
+ retry_time: new Date(),
217
+ ip: messageQueue.serverIp,
218
+ });
219
+ }
220
+ await channel.ack(msg);
221
+ } else {
222
+ try {
223
+ await asyncFunction(data);
224
+ await channel.ack(msg);
225
+ } catch (err1) {
226
+ const errString = err1.toString();
227
+ if (_.some(['Channel closed', 'Channel closing'], (el) => _.includes(errString, el))) {
228
+ // Ignore
229
+ } else {
230
+ if (callbackError && data.retry === 1) {
231
+ callbackError(prefixQueueName, errString);
232
+ }
233
+ if (data.retry <= 0) {
150
234
  await channel.ack(msg);
151
235
  } else {
152
- try {
153
- await asyncFunction(data);
154
- await channel.ack(msg);
155
- } catch (err1) {
156
- const errString = err1.toString();
157
- if (_.some(['Channel closed', 'Channel closing'], (el) => _.includes(errString, el))) {
158
- reject(err1);
159
- } else {
160
- if (callbackError && data.retry === 1) {
161
- callbackError(prefixQueueName, errString);
162
- }
163
- if (data.retry <= 0) {
164
- await channel.ack(msg);
165
- } else {
166
- await channel.nack(msg);
167
- }
168
- }
169
- }
236
+ await channel.nack(msg);
170
237
  }
171
- } catch (err2) {
172
- reject(err2);
173
238
  }
174
- } else {
175
- channel.ack(msg);
176
239
  }
177
240
  }
178
- });
179
- });
241
+ } catch (err2) {
242
+ messageQueue._alert('error', err2);
243
+ }
244
+ } else {
245
+ channel.ack(msg);
246
+ }
247
+ } catch (parseError) {
248
+ messageQueue._alert('error', parseError);
249
+ channel.ack(msg);
180
250
  }
181
- }
251
+ });
182
252
  } catch (err3) {
183
- await timeout(5000);
184
- this._alert('err3', err3);
253
+ this._alert('error', err3);
185
254
  if (_.some(['Channel closed'], (el) => _.includes(err3.toString(), el))) {
186
- messageQueue.close();
255
+ messageQueue.handleDisconnect();
187
256
  }
188
257
  }
189
258
  }
190
259
 
191
260
  startListConsume() {
192
- if (this.connected) {
193
- // eslint-disable-next-line array-callback-return
194
- this.listConsume.map((c) => {
261
+ if (this.connected && this.channel) {
262
+ this.listConsume.forEach((c) => {
195
263
  if (!c.listened) {
196
264
  this.consume(c.queueName, c.asyncFunction, c.callbackError);
197
- // eslint-disable-next-line no-param-reassign
198
265
  c.listened = true;
199
266
  }
200
267
  });
@@ -209,6 +276,124 @@ class RabbitMQAsync {
209
276
  callbackError,
210
277
  listened: false,
211
278
  });
279
+ if (this.connected) {
280
+ this.startListConsume();
281
+ }
282
+ }
283
+ }
284
+
285
+ async subscribe(exchangeName, routingKey, queueName, asyncFunction, callbackError = null, type = 'fanout') {
286
+ const messageQueue = this;
287
+ try {
288
+ if (!this.connected || !this.channel) return;
289
+
290
+ const prefixExchangeName = this.parsePrefix(exchangeName);
291
+ const prefixQueueName = queueName ? this.parsePrefix(queueName) : '';
292
+ const {channel} = messageQueue;
293
+
294
+ await channel.assertExchange(prefixExchangeName, type, {
295
+ durable: true,
296
+ });
297
+
298
+ const q = await channel.assertQueue(prefixQueueName, {
299
+ exclusive: !queueName,
300
+ durable: !!queueName,
301
+ });
302
+
303
+ await channel.bindQueue(q.queue, prefixExchangeName, routingKey);
304
+
305
+ await channel.consume(q.queue, async function (msg) {
306
+ if (!msg) return;
307
+
308
+ try {
309
+ const data = JSON.parse(msg.content.toString());
310
+ if (data) {
311
+ try {
312
+ if (msg.fields.redelivered && queueName) {
313
+ // named queue error retry flow
314
+ data.retry = (data.retry || 0) - 1;
315
+ if (data.retry > 0) {
316
+ await messageQueue.send(queueName, data);
317
+ } else if (!data.notCheck && messageQueue.retryKey) {
318
+ await messageQueue.send(messageQueue.retryKey, {
319
+ queueName: q.queue,
320
+ data,
321
+ retry_time: new Date(),
322
+ ip: messageQueue.serverIp,
323
+ });
324
+ }
325
+ await channel.ack(msg);
326
+ } else {
327
+ try {
328
+ await asyncFunction(data);
329
+ await channel.ack(msg);
330
+ } catch (err1) {
331
+ const errString = err1.toString();
332
+ if (_.some(['Channel closed', 'Channel closing'], (el) => _.includes(errString, el))) {
333
+ // Ignore
334
+ } else {
335
+ if (callbackError && data.retry === 1) {
336
+ callbackError(q.queue, errString);
337
+ }
338
+ if (queueName) {
339
+ if (data.retry <= 0) {
340
+ await channel.ack(msg);
341
+ } else {
342
+ await channel.nack(msg);
343
+ }
344
+ } else {
345
+ await channel.nack(msg, false, false);
346
+ }
347
+ }
348
+ }
349
+ }
350
+ } catch (err2) {
351
+ messageQueue._alert('error', err2);
352
+ }
353
+ } else {
354
+ channel.ack(msg);
355
+ }
356
+ } catch (parseError) {
357
+ messageQueue._alert('error', parseError);
358
+ channel.ack(msg);
359
+ }
360
+ });
361
+ } catch (err3) {
362
+ this._alert('error', err3);
363
+ if (_.some(['Channel closed'], (el) => _.includes(err3.toString(), el))) {
364
+ messageQueue.handleDisconnect();
365
+ }
366
+ }
367
+ }
368
+
369
+ startListSub() {
370
+ if (this.connected && this.channel) {
371
+ this.listSub.forEach((s) => {
372
+ if (!s.listened) {
373
+ this.subscribe(s.exchangeName, s.routingKey, s.queueName, s.asyncFunction, s.callbackError, s.type);
374
+ s.listened = true;
375
+ }
376
+ });
377
+ }
378
+ }
379
+
380
+ async sub(exchangeName, routingKey, queueName, asyncFunction, callbackError = null, type = 'fanout') {
381
+ const existing = queueName
382
+ ? _.find(this.listSub, (s) => s.exchangeName === exchangeName && s.routingKey === routingKey && s.queueName === queueName)
383
+ : false;
384
+ if (!existing) {
385
+ this.listSub.push({
386
+ exchangeName,
387
+ routingKey,
388
+ queueName,
389
+ asyncFunction,
390
+ callbackError,
391
+ type,
392
+ listened: false,
393
+ });
394
+ if (this.connected) {
395
+ this.startListSub();
396
+ }
212
397
  }
213
398
  }
214
399
 
@@ -220,7 +405,7 @@ class RabbitMQAsync {
220
405
  if (typeof this.alertCallback === 'function') {
221
406
  this.alertCallback(status, msg);
222
407
  } else {
223
- console.error(status, msg);
408
+ console.error(`[RabbitMQAsync][${status}]`, msg);
224
409
  }
225
410
  }
226
411
  }