amqplib-init 1.1.11 → 1.2.1

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/README.md ADDED
@@ -0,0 +1 @@
1
+
package/index.js CHANGED
@@ -1,142 +1,153 @@
1
- const amqp = require('amqplib');
2
- const shelljs = require('shelljs');
3
- const log = require('chalk-style')
4
- const happy = require('happy-help');
5
- const axios = require('axios'); // 新增 axios 依赖
1
+ const ConfigResolver = require('./module/ConfigResolver');
2
+ const ConnectionManager = require('./module/ConnectionManager');
3
+ const MessageProcessor = require('./module/MessageProcessor');
4
+ const AutoReloader = require('./module/AutoReloader');
5
+ const log = require('chalk-style');
6
6
 
7
- module.exports = {
8
- // 初始化函数
9
- async init(option) { // 改为 async 函数
10
- const {
11
- channelName = 'node-test-channel', // 频道名称,默认为'node-test-channel'
12
- prefetch = 1, // 预取计数,默认为1
13
- pmId = 0, // PM2进程ID,默认为0
14
- callback = () => {}, // 消息处理回调,默认为空函数
15
- finish = () => {}, // 初始化完成回调,默认为空函数
16
- amqpLink = '', // RabbitMQ连接地址
17
- amqpAutoLink = '', // 自动获取连接信息的HTTPS链接
18
- heartbeat = 5, // 心跳间隔,单位为秒,默认为2秒
19
- timeout = 2000, // 连接超时时间,单位为毫秒,默认为10000毫秒
20
- delay = 0, // 消息处理完成后延迟ack的时间,单位为毫秒,默认为0毫秒
21
- autoReload = 0, // 自动重载间隔时间,单位为毫秒,默认为0毫秒(不自动重载)
22
- queryHook = () => {}, // 查询钩子,默认为空函数(用于判断是否有其他消息来源)
23
- initHook = () => {} // 初始化钩子,默认为空函数(在连接成功后执行)
24
- } = option;
25
- const durable = true; // 队列是否持久化,默认为true
26
- let connection = null; // RabbitMQ连接
27
- let channel = null; // RabbitMQ频道
7
+ /**
8
+ * AMQP初始化器主类
9
+ * 协调各个模块完成RabbitMQ的初始化和消息处理
10
+ */
11
+ class AMQPInitializer {
12
+ constructor() {
13
+ this.configResolver = new ConfigResolver();
14
+ this.connectionManager = null;
15
+ this.messageProcessor = null;
16
+ this.autoReloader = null;
17
+ this.config = null;
18
+ }
19
+
20
+ /**
21
+ * 初始化AMQP连接和消息处理
22
+ * @param {Object} options - 配置选项
23
+ * @returns {Promise<void>}
24
+ */
25
+ async init(options = {}) {
26
+ try {
27
+ // 1. 解析配置
28
+ this.config = this.configResolver.resolveConfig(options);
29
+ log.log('配置解析完成');
30
+
31
+ // 2. 解析连接地址
32
+ const amqpLink = await this.configResolver.resolveAmqpLink(
33
+ this.config.amqpAutoLink,
34
+ this.config.amqpLink
35
+ );
36
+
37
+ // 3. 初始化各个模块
38
+ this.connectionManager = new ConnectionManager(this.config);
39
+ this.messageProcessor = new MessageProcessor(this.config);
40
+ this.autoReloader = new AutoReloader(this.config);
41
+
42
+ // 4. 设置连接地址并初始化连接
43
+ this.connectionManager.setAmqpLink(amqpLink);
44
+ const channel = await this.connectionManager.initialize();
45
+
46
+ // 5. 重新定义重连逻辑,确保重连后重新启动消息处理和自动重载
47
+ this._setupReconnectHandler();
28
48
 
29
- // 处理 amqpAutoLink 自动获取连接信息
30
- let finalAmqpLink = amqpLink; // 最终使用的连接地址
31
- if (amqpAutoLink) {
49
+ // 6. 启动消息处理
50
+ await this.messageProcessor.startConsuming(channel);
51
+ log.log('消息处理器已启动');
52
+
53
+ // 7. 启动自动重载监控
54
+ this.autoReloader.start(channel);
55
+
56
+ // 8. 执行初始化完成回调
57
+ this.config.finish();
58
+
59
+ log.log('AMQP初始化完成');
60
+ } catch (error) {
61
+ log.error('AMQP初始化失败:', error.message);
62
+ throw error;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * 设置重连处理器
68
+ * 确保重连后各个模块能够正确重新启动
69
+ * @private
70
+ */
71
+ _setupReconnectHandler() {
72
+ const originalReconnect = this.connectionManager.reconnect.bind(this.connectionManager);
73
+
74
+ this.connectionManager.reconnect = async () => {
32
75
  try {
33
- log.log(`正在从 ${amqpAutoLink} 获取连接信息...`);
34
- const response = await axios.post(amqpAutoLink);
35
- const { info } = response.data;
76
+ // 执行原始重连逻辑
77
+ const newChannel = await originalReconnect();
78
+
79
+ // 重连后重新启动消息处理
80
+ this.messageProcessor.clearProcessingMessages();
81
+ await this.messageProcessor.startConsuming(newChannel);
82
+ log.log('重连后消息处理器已重新启动');
83
+
84
+ // 重连后重新启动自动重载监控
85
+ this.autoReloader.restart(newChannel);
86
+ log.log('重连后自动重载监控已重新启动');
36
87
 
37
- if (info && info.AMQPLIB_USER && info.AMQPLIB_PWD && info.AMQPLIB_PUB && info.AMQPLIB_PORT) {
38
- finalAmqpLink = `amqp://${info.AMQPLIB_USER}:${info.AMQPLIB_PWD}@${info.AMQPLIB_PUB}:${info.AMQPLIB_PORT}`;
39
- log.log(`✅ 自动获取连接信息成功: ${finalAmqpLink}`);
40
- } else {
41
- log.error('❌ 获取的连接信息不完整,使用默认连接地址');
42
- }
88
+ return newChannel;
43
89
  } catch (error) {
44
- log.error('❌ 获取连接信息失败:', error.message);
45
- log.log('使用默认连接地址继续...');
90
+ log.error('重连处理失败:', error.message);
91
+ throw error;
46
92
  }
47
- }
48
-
49
- // 重连函数
50
- const reconnect = async () => {
51
- log.error('连接丢失,正在尝试重连...');
52
- await happy.sleep(2); // 重连前等待2秒
53
- connection = await amqp.connect(finalAmqpLink, { heartbeat, timeout });
54
- channel = await connection.createChannel();
55
- await channel.assertQueue(channelName, { durable });
56
- await channel.prefetch(prefetch);
57
- await initHook({ channel, connection }); // 执行初始化钩子
58
- log.log(`已重新连接到RabbitMQ,频道: ${channelName}`);
59
- await consumeMessages(); // 开始消费消息
60
93
  };
94
+ }
61
95
 
62
- // 消费消息函数
63
- const consumeMessages = async () => {
64
- log.log(`开始消费: ${channelName}`);
65
- await channel.consume(channelName, async (msg) => {
66
- if (msg !== null) {
67
- try {
68
- const content = JSON.parse(msg.content.toString());
69
- log.log(`🪴 队列收到消息: ${JSON.stringify(content)}`);
70
- const startTime = Date.now();
71
- // 执行消息处理回调
72
- callback(content).then(() => {
73
- const endTime = Date.now() - startTime;
74
- log.log(`☘️ 消息处理完成,延迟: ${delay}ms,总时间: ${endTime}ms`);
75
- setTimeout(() => channel.ack(msg), delay); // 延迟ack
76
- }).catch(async (e) => {
77
- // 拒绝消息并重新排队
78
- log.log('‼️ 处理消息返回错误:', e);
79
- await happy.sleep(5);
80
- channel.reject(msg, true);
81
- });
82
- } catch (e) {
83
- // 拒绝消息并重新排队
84
- log.log('‼️ 处理消息时出错:', e.message);
85
- await happy.sleep(5);
86
- channel.reject(msg, true);
87
- }
88
- } else {
89
- // 直接消息此消息;
90
- channel.ack(msg);
91
- log.error('收到无效消息(自动消费):', msg);
92
- }
93
- }, { noAck: false });
96
+ /**
97
+ * 获取当前状态信息
98
+ * @returns {Object} 状态信息
99
+ */
100
+ getStatus() {
101
+ return {
102
+ isConnected: this.connectionManager ? this.connectionManager.isConnected() : false,
103
+ processingCount: this.messageProcessor ? this.messageProcessor.getProcessingCount() : 0,
104
+ autoReloaderActive: this.autoReloader ? this.autoReloader.isActive() : false,
105
+ channelName: this.config ? this.config.channelName : null
94
106
  };
107
+ }
95
108
 
96
- // 启动函数
97
- const start = async () => {
98
- try {
99
- connection = await amqp.connect(finalAmqpLink, { heartbeat, timeout });
100
- channel = await connection.createChannel();
101
- await channel.assertQueue(channelName, { durable });
102
- await channel.prefetch(prefetch);
103
- await initHook({ channel, connection }); // 执行初始化钩子
104
- log.log(`已连接到RabbitMQ,频道: ${channelName}`);
105
- await consumeMessages(); // 开始消费消息
106
- // 连接错误处理
107
- connection.on('error', (err) => {
108
- log.error('连接错误:', err.message);
109
- reconnect(); // 尝试重连
110
- });
111
-
112
- // 连接关闭处理
113
- connection.on('close', () => {
114
- log.error('与RabbitMQ的连接已关闭');
115
- reconnect(); // 尝试重连
116
- });
117
-
118
- // 自动重载逻辑
119
- if (autoReload > 0) {
120
- setInterval(async () => {
121
- try {
122
- const { messageCount } = await channel.checkQueue(channelName);
123
- const hookResult = await queryHook()
124
- log.log(`MQ队列中有 ${messageCount} 条消息,检测是否可以重启: ${hookResult?1:0}`);
125
- if (messageCount === 0 && hookResult) {
126
- log.log('队列中没有消息,正在重载服务...');
127
- shelljs?.exec(`pm2 reload ${pmId}`); // 使用PM2重载服务
128
- }
129
- } catch (e) {
130
- log.error('检查队列长度时出错:', e);
131
- }
132
- }, autoReload);
133
- }
134
- finish(); // 执行初始化完成回调
135
- } catch (e) {
136
- log.error('初始化RabbitMQ连接时出错:', e);
109
+ /**
110
+ * 优雅关闭
111
+ * @returns {Promise<void>}
112
+ */
113
+ async shutdown() {
114
+ try {
115
+ log.log('开始优雅关闭AMQP连接...');
116
+
117
+ // 停止自动重载监控
118
+ if (this.autoReloader) {
119
+ this.autoReloader.stop();
137
120
  }
138
- };
139
- // 返回启动函数并立即执行
140
- return start();
121
+
122
+ // 清理消息处理状态
123
+ if (this.messageProcessor) {
124
+ this.messageProcessor.clearProcessingMessages();
125
+ }
126
+
127
+ // 关闭连接
128
+ if (this.connectionManager) {
129
+ await this.connectionManager.close();
130
+ }
131
+
132
+ log.log('AMQP连接已优雅关闭');
133
+ } catch (error) {
134
+ log.error('关闭AMQP连接时出错:', error.message);
135
+ }
141
136
  }
137
+ }
138
+
139
+ // 导出兼容的API
140
+ module.exports = {
141
+ /**
142
+ * 初始化函数 - 保持向后兼容
143
+ * @param {Object} options - 配置选项
144
+ * @returns {Promise<void>}
145
+ */
146
+ async init(options) {
147
+ const initializer = new AMQPInitializer();
148
+ return await initializer.init(options);
149
+ },
150
+
151
+ // 导出类,供需要更多控制的用户使用
152
+ AMQPInitializer
142
153
  };
@@ -0,0 +1,139 @@
1
+ const shelljs = require('shelljs');
2
+ const log = require('chalk-style');
3
+
4
+ /**
5
+ * 自动重载器模块
6
+ * 负责监控队列状态并在满足条件时自动重载服务
7
+ */
8
+ class AutoReloader {
9
+ constructor(config) {
10
+ this.config = config;
11
+ this.intervalId = null;
12
+ this.isRunning = false;
13
+ }
14
+
15
+ /**
16
+ * 启动自动重载监控
17
+ * @param {Object} channel - RabbitMQ频道
18
+ */
19
+ start(channel) {
20
+ const { autoReload, channelName, pmId, queryHook } = this.config;
21
+
22
+ if (autoReload <= 0) {
23
+ log.log('自动重载功能未启用');
24
+ return;
25
+ }
26
+
27
+ if (this.isRunning) {
28
+ log.log('自动重载监控已在运行中');
29
+ return;
30
+ }
31
+
32
+ this.isRunning = true;
33
+ log.log(`启动自动重载监控,间隔: ${autoReload}ms`);
34
+
35
+ this.intervalId = setInterval(async () => {
36
+ try {
37
+ await this._checkAndReload(channel, channelName, pmId, queryHook);
38
+ } catch (error) {
39
+ log.error('自动重载检查时出错:', error.message);
40
+ }
41
+ }, autoReload);
42
+ }
43
+
44
+ /**
45
+ * 停止自动重载监控
46
+ */
47
+ stop() {
48
+ if (this.intervalId) {
49
+ clearInterval(this.intervalId);
50
+ this.intervalId = null;
51
+ this.isRunning = false;
52
+ log.log('自动重载监控已停止');
53
+ }
54
+ }
55
+
56
+ /**
57
+ * 重启自动重载监控(用于重连后)
58
+ * @param {Object} channel - 新的RabbitMQ频道
59
+ */
60
+ restart(channel) {
61
+ this.stop();
62
+ this.start(channel);
63
+ }
64
+
65
+ /**
66
+ * 获取监控状态
67
+ * @returns {boolean} 是否正在运行
68
+ */
69
+ isActive() {
70
+ return this.isRunning;
71
+ }
72
+
73
+ /**
74
+ * 检查并执行重载
75
+ * @param {Object} channel - RabbitMQ频道
76
+ * @param {string} channelName - 频道名称
77
+ * @param {number} pmId - PM2进程ID
78
+ * @param {Function} queryHook - 查询钩子函数
79
+ * @private
80
+ */
81
+ async _checkAndReload(channel, channelName, pmId, queryHook) {
82
+ try {
83
+ // 检查队列中的消息数量
84
+ const { messageCount } = await channel.checkQueue(channelName);
85
+
86
+ // 执行查询钩子,检查是否可以重启
87
+ const hookResult = await queryHook();
88
+
89
+ log.log(`MQ队列中有 ${messageCount} 条消息,检测是否可以重启: ${hookResult ? 1 : 0}`);
90
+
91
+ // 只有在队列为空且钩子函数返回true时才重载
92
+ if (messageCount === 0 && hookResult) {
93
+ await this._executeReload(pmId);
94
+ }
95
+ } catch (error) {
96
+ log.error('检查队列长度时出错:', error.message);
97
+ }
98
+ }
99
+
100
+ /**
101
+ * 执行服务重载
102
+ * @param {number} pmId - PM2进程ID
103
+ * @private
104
+ */
105
+ async _executeReload(pmId) {
106
+ try {
107
+ log.log('队列中没有消息,正在重载服务...');
108
+
109
+ if (shelljs && typeof shelljs.exec === 'function') {
110
+ const result = shelljs.exec(`pm2 reload ${pmId}`);
111
+
112
+ if (result.code === 0) {
113
+ log.log(`服务重载成功,PM2进程ID: ${pmId}`);
114
+ } else {
115
+ log.error(`服务重载失败,错误码: ${result.code}, 输出: ${result.stderr}`);
116
+ }
117
+ } else {
118
+ log.error('shelljs不可用,无法执行服务重载');
119
+ }
120
+ } catch (error) {
121
+ log.error('执行服务重载时出错:', error.message);
122
+ }
123
+ }
124
+
125
+ /**
126
+ * 获取当前配置信息
127
+ * @returns {Object} 配置信息
128
+ */
129
+ getConfig() {
130
+ return {
131
+ autoReload: this.config.autoReload,
132
+ pmId: this.config.pmId,
133
+ isRunning: this.isRunning,
134
+ intervalId: this.intervalId !== null
135
+ };
136
+ }
137
+ }
138
+
139
+ module.exports = AutoReloader;
@@ -0,0 +1,87 @@
1
+ const axios = require('axios');
2
+ const log = require('chalk-style');
3
+
4
+ /**
5
+ * 配置解析器模块
6
+ * 负责处理配置参数和自动获取RabbitMQ连接信息
7
+ */
8
+ class ConfigResolver {
9
+ constructor() {
10
+ this.defaultConfig = {
11
+ channelName: 'node-test-channel',
12
+ prefetch: 1,
13
+ pmId: 0,
14
+ callback: () => {},
15
+ finish: () => {},
16
+ amqpLink: '',
17
+ amqpAutoLink: '',
18
+ heartbeat: 60, // 增加心跳间隔到60秒,避免长时间处理时连接断开
19
+ timeout: 300000, // 增加连接超时到5分钟(300秒)
20
+ delay: 0,
21
+ autoReload: 0,
22
+ queryHook: () => {},
23
+ initHook: () => {},
24
+ durable: true,
25
+ // 新增配置项 - 支持15分钟长任务
26
+ messageTimeout: 900000, // 消息处理超时15分钟(900秒)
27
+ reconnectDelay: 5000, // 重连延迟5秒
28
+ maxReconnectAttempts: 10 // 最大重连次数
29
+ };
30
+ }
31
+
32
+ /**
33
+ * 解析和合并配置参数
34
+ * @param {Object} options - 用户传入的配置选项
35
+ * @returns {Object} 合并后的完整配置
36
+ */
37
+ resolveConfig(options = {}) {
38
+ return { ...this.defaultConfig, ...options };
39
+ }
40
+
41
+ /**
42
+ * 自动获取RabbitMQ连接信息
43
+ * @param {string} amqpAutoLink - 自动获取连接信息的HTTPS链接
44
+ * @param {string} fallbackAmqpLink - 备用连接地址
45
+ * @returns {Promise<string>} 最终使用的连接地址
46
+ */
47
+ async resolveAmqpLink(amqpAutoLink, fallbackAmqpLink) {
48
+ if (!amqpAutoLink) {
49
+ return fallbackAmqpLink;
50
+ }
51
+
52
+ try {
53
+ log.log(`正在从 ${amqpAutoLink} 获取连接信息...`);
54
+ const response = await axios.post(amqpAutoLink);
55
+ const { info } = response.data;
56
+
57
+ if (this._validateConnectionInfo(info)) {
58
+ const finalAmqpLink = `amqp://${info.AMQPLIB_USER}:${info.AMQPLIB_PWD}@${info.AMQPLIB_PUB}:${info.AMQPLIB_PORT}`;
59
+ log.log(`✅ 自动获取连接信息成功: ${finalAmqpLink}`);
60
+ return finalAmqpLink;
61
+ } else {
62
+ log.error('❌ 获取的连接信息不完整,使用默认连接地址');
63
+ return fallbackAmqpLink;
64
+ }
65
+ } catch (error) {
66
+ log.error('❌ 获取连接信息失败:', error.message);
67
+ log.log('使用默认连接地址继续...');
68
+ return fallbackAmqpLink;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * 验证连接信息的完整性
74
+ * @param {Object} info - 连接信息对象
75
+ * @returns {boolean} 是否包含所有必要字段
76
+ * @private
77
+ */
78
+ _validateConnectionInfo(info) {
79
+ return info &&
80
+ info.AMQPLIB_USER &&
81
+ info.AMQPLIB_PWD &&
82
+ info.AMQPLIB_PUB &&
83
+ info.AMQPLIB_PORT;
84
+ }
85
+ }
86
+
87
+ module.exports = ConfigResolver;
@@ -0,0 +1,183 @@
1
+ const amqp = require('amqplib');
2
+ const log = require('chalk-style');
3
+ const happy = require('happy-help');
4
+
5
+ /**
6
+ * 连接管理器模块
7
+ * 负责RabbitMQ连接管理、重连逻辑、频道管理
8
+ */
9
+ class ConnectionManager {
10
+ constructor(config) {
11
+ this.config = config;
12
+ this.connection = null;
13
+ this.channel = null;
14
+ this.amqpLink = '';
15
+ this.reconnectInProgress = false; // 防止重复重连
16
+ }
17
+
18
+ /**
19
+ * 设置AMQP连接地址
20
+ * @param {string} amqpLink - RabbitMQ连接地址
21
+ */
22
+ setAmqpLink(amqpLink) {
23
+ this.amqpLink = amqpLink;
24
+ }
25
+
26
+ /**
27
+ * 初始化连接和频道
28
+ * @returns {Promise<Object>} 返回频道对象
29
+ */
30
+ async initialize() {
31
+ const { channelName, durable, prefetch, heartbeat, timeout, initHook, reconnectDelay } = this.config;
32
+
33
+ try {
34
+ // 建立连接
35
+ this.connection = await amqp.connect(this.amqpLink, { heartbeat, timeout });
36
+
37
+ // 创建频道
38
+ this.channel = await this.connection.createChannel();
39
+
40
+ // 设置队列和预取
41
+ await this.channel.assertQueue(channelName, { durable });
42
+ await this.channel.prefetch(prefetch);
43
+
44
+ // 执行初始化钩子
45
+ await initHook({ channel: this.channel, connection: this.connection });
46
+
47
+ log.log(`已连接到RabbitMQ,频道: ${channelName}`);
48
+
49
+ // 设置连接事件监听
50
+ this._setupConnectionEvents();
51
+
52
+ return this.channel;
53
+ } catch (error) {
54
+ log.error('初始化RabbitMQ连接时出错:', error);
55
+ throw error;
56
+ }
57
+ }
58
+
59
+ /**
60
+ * 重连逻辑
61
+ * @returns {Promise<Object>} 返回新的频道对象
62
+ */
63
+ async reconnect() {
64
+ if (this.reconnectInProgress) {
65
+ log.log('重连已在进行中,跳过此次重连请求');
66
+ return this.channel;
67
+ }
68
+
69
+ this.reconnectInProgress = true;
70
+
71
+ try {
72
+ log.error('连接丢失,正在尝试重连...');
73
+
74
+ // 清理旧的channel状态
75
+ await this._cleanupOldChannel();
76
+
77
+ // 等待重连间隔,使用配置的延迟时间
78
+ const delaySeconds = (this.config.reconnectDelay || 5000) / 1000;
79
+ await happy.sleep(delaySeconds);
80
+
81
+ // 重新初始化连接
82
+ await this.initialize();
83
+
84
+ log.log(`已重新连接到RabbitMQ,频道: ${this.config.channelName}`);
85
+
86
+ return this.channel;
87
+ } catch (error) {
88
+ log.error('重连失败:', error.message);
89
+ // 如果重连失败,等待一段时间后重试
90
+ const retryDelay = this.config.reconnectDelay || 5000;
91
+ setTimeout(() => {
92
+ this.reconnectInProgress = false;
93
+ this.reconnect();
94
+ }, retryDelay);
95
+ throw error;
96
+ } finally {
97
+ this.reconnectInProgress = false;
98
+ }
99
+ }
100
+
101
+ /**
102
+ * 获取当前频道
103
+ * @returns {Object|null} 当前频道对象
104
+ */
105
+ getChannel() {
106
+ return this.channel;
107
+ }
108
+
109
+ /**
110
+ * 获取当前连接
111
+ * @returns {Object|null} 当前连接对象
112
+ */
113
+ getConnection() {
114
+ return this.connection;
115
+ }
116
+
117
+ /**
118
+ * 检查连接是否正常
119
+ * @returns {boolean} 连接状态
120
+ */
121
+ isConnected() {
122
+ return this.connection && this.channel && !this.connection.connection.destroyed;
123
+ }
124
+
125
+ /**
126
+ * 关闭连接
127
+ * @returns {Promise<void>}
128
+ */
129
+ async close() {
130
+ try {
131
+ if (this.channel) {
132
+ await this.channel.close();
133
+ this.channel = null;
134
+ }
135
+ if (this.connection) {
136
+ await this.connection.close();
137
+ this.connection = null;
138
+ }
139
+ log.log('RabbitMQ连接已关闭');
140
+ } catch (error) {
141
+ log.error('关闭连接时出错:', error.message);
142
+ }
143
+ }
144
+
145
+ /**
146
+ * 设置连接事件监听
147
+ * @private
148
+ */
149
+ _setupConnectionEvents() {
150
+ // 连接错误处理
151
+ this.connection.on('error', (err) => {
152
+ log.error('连接错误:', err.message);
153
+ this.reconnect().catch(error => {
154
+ log.error('重连失败:', error.message);
155
+ });
156
+ });
157
+
158
+ // 连接关闭处理
159
+ this.connection.on('close', () => {
160
+ log.error('与RabbitMQ的连接已关闭');
161
+ this.reconnect().catch(error => {
162
+ log.error('重连失败:', error.message);
163
+ });
164
+ });
165
+ }
166
+
167
+ /**
168
+ * 清理旧的频道状态
169
+ * @private
170
+ */
171
+ async _cleanupOldChannel() {
172
+ if (this.channel) {
173
+ try {
174
+ await this.channel.close();
175
+ } catch (e) {
176
+ log.log('关闭旧channel时出错:', e.message);
177
+ }
178
+ this.channel = null;
179
+ }
180
+ }
181
+ }
182
+
183
+ module.exports = ConnectionManager;
@@ -0,0 +1,145 @@
1
+ const log = require('chalk-style');
2
+ const happy = require('happy-help');
3
+
4
+ /**
5
+ * 消息处理器模块
6
+ * 负责消息处理、状态跟踪、ACK/REJECT逻辑
7
+ */
8
+ class MessageProcessor {
9
+ constructor(config) {
10
+ this.config = config;
11
+ this.processingMessages = new Set(); // 跟踪正在处理的消息
12
+ }
13
+
14
+ /**
15
+ * 开始消费消息
16
+ * @param {Object} channel - RabbitMQ频道
17
+ * @returns {Promise<Function>} 返回清理函数
18
+ */
19
+ async startConsuming(channel) {
20
+ const { channelName, callback, delay } = this.config;
21
+
22
+ log.log(`开始消费: ${channelName}`);
23
+
24
+ await channel.consume(channelName, async (msg) => {
25
+ if (msg !== null) {
26
+ await this._handleMessage(msg, channel, callback, delay);
27
+ } else {
28
+ // ✅ 修复关键bug:null消息不需要ACK操作
29
+ log.log('收到null消息,队列可能为空或连接中断');
30
+ // 移除错误的 channel.ack(msg) 调用
31
+ }
32
+ }, { noAck: false });
33
+
34
+ // 返回清理函数,用于重连时清理状态
35
+ return () => {
36
+ this.processingMessages.clear();
37
+ log.log('已清理消息处理状态');
38
+ };
39
+ }
40
+
41
+ /**
42
+ * 处理单条消息
43
+ * @param {Object} msg - RabbitMQ消息对象
44
+ * @param {Object} channel - RabbitMQ频道
45
+ * @param {Function} callback - 消息处理回调函数
46
+ * @param {number} delay - 延迟ACK时间
47
+ * @private
48
+ */
49
+ async _handleMessage(msg, channel, callback, delay) {
50
+ const deliveryTag = msg.fields.deliveryTag;
51
+
52
+ // 检查消息是否已在处理中
53
+ if (this.processingMessages.has(deliveryTag)) {
54
+ log.log(`消息 ${deliveryTag} 已在处理中,跳过`);
55
+ return;
56
+ }
57
+
58
+ this.processingMessages.add(deliveryTag);
59
+
60
+ try {
61
+ const content = JSON.parse(msg.content.toString());
62
+ log.log(`🪴 队列收到消息: ${JSON.stringify(content)}`);
63
+ const startTime = Date.now();
64
+
65
+ // 执行消息处理回调
66
+ callback(content)
67
+ .then(() => {
68
+ const endTime = Date.now() - startTime;
69
+ log.log(`☘️ 消息处理完成,延迟: ${delay}ms,总时间: ${endTime}ms`);
70
+ this._acknowledgeMessage(msg, channel, deliveryTag, delay);
71
+ })
72
+ .catch(async (e) => {
73
+ log.log('‼️ 处理消息返回错误:', e);
74
+ await this._rejectMessage(msg, channel, deliveryTag);
75
+ });
76
+ } catch (e) {
77
+ log.log('‼️ 处理消息时出错:', e.message);
78
+ await this._rejectMessage(msg, channel, deliveryTag);
79
+ }
80
+ }
81
+
82
+ /**
83
+ * 确认消息
84
+ * @param {Object} msg - RabbitMQ消息对象
85
+ * @param {Object} channel - RabbitMQ频道
86
+ * @param {string} deliveryTag - 消息标签
87
+ * @param {number} delay - 延迟时间
88
+ * @private
89
+ */
90
+ _acknowledgeMessage(msg, channel, deliveryTag, delay) {
91
+ // 确保消息还在处理集合中再进行ACK
92
+ if (this.processingMessages.has(deliveryTag)) {
93
+ setTimeout(() => {
94
+ try {
95
+ channel.ack(msg);
96
+ this.processingMessages.delete(deliveryTag);
97
+ log.log(`✅ 消息 ${deliveryTag} 已确认`);
98
+ } catch (ackError) {
99
+ log.error(`ACK消息 ${deliveryTag} 时出错:`, ackError.message);
100
+ this.processingMessages.delete(deliveryTag);
101
+ }
102
+ }, delay);
103
+ }
104
+ }
105
+
106
+ /**
107
+ * 拒绝消息并重新排队
108
+ * @param {Object} msg - RabbitMQ消息对象
109
+ * @param {Object} channel - RabbitMQ频道
110
+ * @param {string} deliveryTag - 消息标签
111
+ * @private
112
+ */
113
+ async _rejectMessage(msg, channel, deliveryTag) {
114
+ await happy.sleep(5);
115
+
116
+ if (this.processingMessages.has(deliveryTag)) {
117
+ try {
118
+ channel.reject(msg, true);
119
+ this.processingMessages.delete(deliveryTag);
120
+ log.log(`❌ 消息 ${deliveryTag} 已拒绝并重新排队`);
121
+ } catch (rejectError) {
122
+ log.error(`拒绝消息 ${deliveryTag} 时出错:`, rejectError.message);
123
+ this.processingMessages.delete(deliveryTag);
124
+ }
125
+ }
126
+ }
127
+
128
+ /**
129
+ * 清理所有处理中的消息状态
130
+ */
131
+ clearProcessingMessages() {
132
+ this.processingMessages.clear();
133
+ log.log('已清理所有消息处理状态');
134
+ }
135
+
136
+ /**
137
+ * 获取当前处理中的消息数量
138
+ * @returns {number} 处理中的消息数量
139
+ */
140
+ getProcessingCount() {
141
+ return this.processingMessages.size;
142
+ }
143
+ }
144
+
145
+ module.exports = MessageProcessor;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "amqplib-init",
3
- "version": "1.1.11",
4
- "description": "消息队列初始化",
3
+ "version": "1.2.1",
4
+ "description": "消息队列初始化 - 模块化架构版本 (支持15分钟长任务)",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "start": "node index.js"