@azteam/rabbitmq-async 1.0.231 → 1.0.234

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.231",
3
+ "version": "1.0.234",
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
51
  this.client = await amqp.connect(`amqp://${this.host}`, opt);
43
- this.channel = await this.client.createChannel();
44
- this.channel.prefetch(1);
45
- this.connected = true;
46
- this._alert('connect', 'MQ connected');
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,133 +118,285 @@ 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
- async getInfo(queueName) {
175
+ async getInfo(queueName, options = {}) {
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,
181
+ ...options,
115
182
  });
116
183
  }
117
184
  } catch (err) {}
118
- return this.getInfo(queueName);
185
+
186
+ await timeout(2000);
187
+ return this.getInfo(queueName, options);
119
188
  }
120
189
 
121
- // eslint-disable-next-line consistent-return
122
- async consume(queueName, asyncFunction, callbackError = null) {
190
+ async consume(queueName, asyncFunction, callbackError = null, options = {}) {
123
191
  const messageQueue = this;
124
192
  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
- }
193
+ if (!this.connected || !this.channel) return;
194
+
195
+ const prefixQueueName = this.parsePrefix(queueName);
196
+ const {channel} = messageQueue;
197
+
198
+ await channel.assertQueue(prefixQueueName, {
199
+ durable: true,
200
+ ...options,
201
+ });
202
+
203
+ await channel.consume(prefixQueueName, async function (msg) {
204
+ if (!msg) return;
205
+
206
+ try {
207
+ const data = JSON.parse(msg.content.toString());
208
+ if (data) {
209
+ try {
210
+ if (msg.fields.redelivered) {
211
+ data.retry = (data.retry || 0) - 1;
212
+ if (data.retry > 0) {
213
+ await messageQueue.send(queueName, data);
214
+ } else if (!data.notCheck && messageQueue.retryKey) {
215
+ await messageQueue.send(messageQueue.retryKey, {
216
+ queueName,
217
+ data,
218
+ retry_time: new Date(),
219
+ ip: messageQueue.serverIp,
220
+ });
221
+ }
222
+ await channel.ack(msg);
223
+ } else {
224
+ try {
225
+ await asyncFunction(data);
226
+ await channel.ack(msg);
227
+ } catch (err1) {
228
+ const errString = err1.toString();
229
+ if (_.some(['Channel closed', 'Channel closing'], (el) => _.includes(errString, el))) {
230
+ // Ignore
231
+ } else {
232
+ if (callbackError && data.retry === 1) {
233
+ callbackError(prefixQueueName, errString);
234
+ }
235
+ if (data.retry <= 0) {
150
236
  await channel.ack(msg);
151
237
  } 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
- }
238
+ await channel.nack(msg);
170
239
  }
171
- } catch (err2) {
172
- reject(err2);
173
240
  }
174
- } else {
175
- channel.ack(msg);
176
241
  }
177
242
  }
178
- });
179
- });
243
+ } catch (err2) {
244
+ messageQueue._alert('error', err2);
245
+ }
246
+ } else {
247
+ channel.ack(msg);
248
+ }
249
+ } catch (parseError) {
250
+ messageQueue._alert('error', parseError);
251
+ channel.ack(msg);
180
252
  }
181
- }
253
+ });
182
254
  } catch (err3) {
183
- await timeout(5000);
184
- this._alert('err3', err3);
255
+ this._alert('error', err3);
185
256
  if (_.some(['Channel closed'], (el) => _.includes(err3.toString(), el))) {
186
- messageQueue.close();
257
+ messageQueue.handleDisconnect();
187
258
  }
188
259
  }
189
260
  }
190
261
 
191
262
  startListConsume() {
192
- if (this.connected) {
193
- // eslint-disable-next-line array-callback-return
194
- this.listConsume.map((c) => {
263
+ if (this.connected && this.channel) {
264
+ this.listConsume.forEach((c) => {
195
265
  if (!c.listened) {
196
- this.consume(c.queueName, c.asyncFunction, c.callbackError);
197
- // eslint-disable-next-line no-param-reassign
266
+ this.consume(c.queueName, c.asyncFunction, c.callbackError, c.options);
198
267
  c.listened = true;
199
268
  }
200
269
  });
201
270
  }
202
271
  }
203
272
 
204
- async receiving(queueName, asyncFunction, callbackError = null) {
273
+ async receiving(queueName, asyncFunction, callbackError = null, options = {}) {
205
274
  if (!_.find(this.listConsume, (c) => c.queueName === queueName)) {
206
275
  this.listConsume.push({
207
276
  queueName,
208
277
  asyncFunction,
209
278
  callbackError,
279
+ options,
210
280
  listened: false,
211
281
  });
282
+ if (this.connected) {
283
+ this.startListConsume();
284
+ }
285
+ }
286
+ }
287
+
288
+ async subscribe(exchangeName, routingKey, queueName, asyncFunction, callbackError = null, type = 'fanout') {
289
+ const messageQueue = this;
290
+ try {
291
+ if (!this.connected || !this.channel) return;
292
+
293
+ const prefixExchangeName = this.parsePrefix(exchangeName);
294
+ const prefixQueueName = queueName ? this.parsePrefix(queueName) : '';
295
+ const {channel} = messageQueue;
296
+
297
+ await channel.assertExchange(prefixExchangeName, type, {
298
+ durable: true,
299
+ });
300
+
301
+ const q = await channel.assertQueue(prefixQueueName, {
302
+ exclusive: !queueName,
303
+ durable: !!queueName,
304
+ });
305
+
306
+ await channel.bindQueue(q.queue, prefixExchangeName, routingKey);
307
+
308
+ await channel.consume(q.queue, async function (msg) {
309
+ if (!msg) return;
310
+
311
+ try {
312
+ const data = JSON.parse(msg.content.toString());
313
+ if (data) {
314
+ try {
315
+ if (msg.fields.redelivered && queueName) {
316
+ // named queue error retry flow
317
+ data.retry = (data.retry || 0) - 1;
318
+ if (data.retry > 0) {
319
+ await messageQueue.send(queueName, data);
320
+ } else if (!data.notCheck && messageQueue.retryKey) {
321
+ await messageQueue.send(messageQueue.retryKey, {
322
+ queueName: q.queue,
323
+ data,
324
+ retry_time: new Date(),
325
+ ip: messageQueue.serverIp,
326
+ });
327
+ }
328
+ await channel.ack(msg);
329
+ } else {
330
+ try {
331
+ await asyncFunction(data);
332
+ await channel.ack(msg);
333
+ } catch (err1) {
334
+ const errString = err1.toString();
335
+ if (_.some(['Channel closed', 'Channel closing'], (el) => _.includes(errString, el))) {
336
+ // Ignore
337
+ } else {
338
+ if (callbackError && data.retry === 1) {
339
+ callbackError(q.queue, errString);
340
+ }
341
+ if (queueName) {
342
+ if (data.retry <= 0) {
343
+ await channel.ack(msg);
344
+ } else {
345
+ await channel.nack(msg);
346
+ }
347
+ } else {
348
+ await channel.nack(msg, false, false);
349
+ }
350
+ }
351
+ }
352
+ }
353
+ } catch (err2) {
354
+ messageQueue._alert('error', err2);
355
+ }
356
+ } else {
357
+ channel.ack(msg);
358
+ }
359
+ } catch (parseError) {
360
+ messageQueue._alert('error', parseError);
361
+ channel.ack(msg);
362
+ }
363
+ });
364
+ } catch (err3) {
365
+ this._alert('error', err3);
366
+ if (_.some(['Channel closed'], (el) => _.includes(err3.toString(), el))) {
367
+ messageQueue.handleDisconnect();
368
+ }
369
+ }
370
+ }
371
+
372
+ startListSub() {
373
+ if (this.connected && this.channel) {
374
+ this.listSub.forEach((s) => {
375
+ if (!s.listened) {
376
+ this.subscribe(s.exchangeName, s.routingKey, s.queueName, s.asyncFunction, s.callbackError, s.type);
377
+ s.listened = true;
378
+ }
379
+ });
380
+ }
381
+ }
382
+
383
+ async sub(exchangeName, routingKey, queueName, asyncFunction, callbackError = null, type = 'fanout') {
384
+ const existing = queueName
385
+ ? _.find(this.listSub, (s) => s.exchangeName === exchangeName && s.routingKey === routingKey && s.queueName === queueName)
386
+ : false;
387
+ if (!existing) {
388
+ this.listSub.push({
389
+ exchangeName,
390
+ routingKey,
391
+ queueName,
392
+ asyncFunction,
393
+ callbackError,
394
+ type,
395
+ listened: false,
396
+ });
397
+ if (this.connected) {
398
+ this.startListSub();
399
+ }
212
400
  }
213
401
  }
214
402
 
@@ -220,7 +408,7 @@ class RabbitMQAsync {
220
408
  if (typeof this.alertCallback === 'function') {
221
409
  this.alertCallback(status, msg);
222
410
  } else {
223
- console.error(status, msg);
411
+ console.error(`[RabbitMQAsync][${status}]`, msg);
224
412
  }
225
413
  }
226
414
  }