网页端HTML使用MQTTJs订阅RabbitMQ数据

本文描述了一个开发者在构建日志系统时,如何使用RabbitMQ和WebSocket技术,通过JavaScript在网页端订阅和显示实时日志的过程,包括所需JS库的选择、配置和通信逻辑的实现。
摘要由CSDN通过智能技术生成

  最近在做一个公司的日志组件时有一个问题难住了我。今天问题终于解决了。由于在解决问题中,在网上也查了很多资料都没有一个完整的实例可以参考。所以本着无私分享的目的记录一下完整的解决过程和实例。

  需求:做一个统一日志系统可以查看日志列表和一个可以订阅最新日志的页面。通过提供一个封装好日志记录方法的sdk文件将日志统一收集。

  通过上面的需求进行我们使用RabbitMQ+Mongodb来实现系统。

  使用C#封装一个SDK大家都会这里就不说了。C#连接RabbitMQ示例代码也是一堆堆的也没什么好说的。下面重点说一下网页端如何使用JS去订阅RabbitMQ收到的最新日志信息。

  后端都是使用RabbitMQ的AMQP协议,而前端要求在网页HTML上显示数据。我们选择了使用MQTT协议从RabbitMQ中订阅数据。

  具体步骤:

1、先准备好相关JS库。MQTT有一个叫browserMqtt.js看名字就知道是为浏览器提供的JS库。还有一个封装了操作MQ的JS库 mqfactory.js。最后还要一个jquery.js文件。这样工具就准备好了。JS文件下载

2、HTML端代码。

<script type="text/javascript" src="~/js/MqJs/jquery.js"></script>
<script type="text/javascript" src="~/js/MqJs/browserMqtt.min.js"></script>
<script type="text/javascript" src="~/js/MqJs/mqfactory.js"></script>
<body>
    <div>
        <lable>Host: </lable><input id="txtHost" placeholder="192.168.1.88" value="10.1.0.7" /><br />
        <lable>Port: </lable><input id="txtPort" placeholder="15675" value="15675" /><br />
        <label>UserName: </label><input id="txtUserName" placeholder="username" value="admin" /><br />
        <label>Password: </label><input id="txtPassword" placeholder="password" value="admin" /><br />
        <label>Protocol: </label><input id="txtProtocol" placeholder="ws" value="ws" /><br />
        <input id="btnConnect" type="button" value="Connect RabbitMQ" />
    </div>
    <div>
        <input id="btnSubscribe" type="button" value="Subscribe" />
        <input id="btnPublish" type="button" value="Publish" /><br />
        <input id="btnSSHuanjing" type="button" value="Subscribe Huanjing" />
        <input id="hdnIsSubscribed" type="hidden" value="" />
        <input id="btnPubHuanjing" type="button" value="Publish Huanjing"><br />
        路由:<input id="btnRoutingKey" type="text" value="Dcon/Logs/Client"><br />
        <input id="txtMessage" type="text" placeholder="Please enter message" />
    </div>
    <div>
        <label>log:</label><br />
        <ul id="lstLog"></ul>
        <input id="btnClearLog" type="button" value="Clear Log" />
    </div>
</body>
<script type="text/javascript">
    $(function () {
        var mqclient;
        //var routingKey = 'Dcon.Logs.ServerWebShow';
        var message;

        $('#btnSubscribe').attr('disabled', 'disabled');
        $('#btnPublish').attr('disabled', 'disabled');
        $('#btnSSHuanjing').attr('disabled', 'disabled');
        $('#btnPubHuanjing').attr('disabled', 'disabled');

        $('#btnConnect').click(function () {
            var mqttOpts = {
                host: (() => $('#txtHost').val())(),
                port: (() => $('#txtPort').val())(),
                username: (() => $('#txtUserName').val())(),
                password: (() => $('#txtPassword').val())(),
                //transformWsUrl方法用于在浏览器中使用MQTT的场景,默认情况下,MQTT自动生成的url为ws://ip:port形式,
                //然而服务器要求的格式是ws://ip:port/ws,所以MQTT提供了此接口用于在生成url时自定义url格式
                transformWsUrl: (url, opts, client) => { return opts.protocol && opts.protocol == 'ws' ? url + 'ws' : url; },
                clientId: (() => { return 'mqttjs_' + Math.random().toString(16).substr(2, 8); })()
            };
            var biz = {
                huanjing: function (handler, isOn) {
                    if (isOn !== false) {
                        this.ss(this.topics.huanjing, handler);
                    } else {
                        this.sus(this.topics.huanjing, handler);
                    }
                },
                topics: {
                    huanjing: '/hyj/huanjing/monitor'
                }
            };
            //系统初始化时注入连接选项
            mqfactory.inject(mqttOpts, biz);
            //创建mqclient单例 
            mqclient = mqfactory.create();
            //注册mqclient的连接成功事件
            mqclient.on('connect', mqconnected);
        });

        $('#btnSubscribe').click(function () {
            if ($(this).val() == 'Subscribe') {
                //订阅成功后,仅注册一次事件(要考虑每次注册事件时,事件处理器调用的次数,如果仅用一次,就用once方法)
                //routingKey = $("#btnRoutingKey").val();
                mqclient.once('onss', mqSubscribeSuccess);
                //简单订阅
                mqclient.ss($("#btnRoutingKey").val());
            } else {
                mqclient.once('onsus', mqUnsubscribeSuccess)
                mqclient.sus($("#btnRoutingKey").val());
            }
        });

        $('#btnPublish').click(function () {
            var msg = $('#txtMessage').val().length > 0 ? $('#txtMessage').val() : guid();
            if (message === msg) {
                msg = guid();
            }
            message = msg;
            $('#txtMessage').val(message);
            //发送消息
            mqclient.pub($("#btnRoutingKey").val(), message);
            $('#lstLog').append('<li>Send Message: ' + message + '</li>');
        });

        $('#btnSSHuanjing').click(function () {
            if ($(this).val() == 'Subscribe Huanjing') {
                mqclient.once('onss', mqHJSubscribeSuccess);
                mqclient.huanjing(onHuanjingMessageArrived);
            } else {
                mqclient.once('onsus', mqHJUnsubscribeSuccess);
                mqclient.huanjing(onHuanjingMessageArrived, false);
            }
        });

        $('#btnPubHuanjing').click(function () {
            var msg = $('#txtMessage').val().length > 0 ? $('#txtMessage').val() : guid();
            if (message === msg) {
                msg = guid();
            }
            message = msg;
            $('#txtMessage').val(message);
            //发送消息
            mqclient.pub(mqclient.topics.huanjing, message);
            $('#lstLog').append('<li>Send Huanjing Message: ' + message + '</li>');
        });

        $('#btnClearLog').click(function () {
            $('#lstLog').empty();
        });

        function mqconnected() {
            //alert("mqconnected");
            $('#btnSubscribe').removeAttr('disabled');
            $('#btnPublish').removeAttr('disabled');
            $('#btnSSHuanjing').removeAttr('disabled');
            $('#btnPubHuanjing').removeAttr('disabled');
            $('#lstLog').append('<li>mqclient connected</li>');
        }

        function mqSubscribeSuccess() {
            //订阅成功,就注册接受消息的方法,此处要接收多次,因此使用了on
            mqclient.on($("#btnRoutingKey").val(), onMessageArrived);
            $('#btnSubscribe').val('Unsubscribe');
            $('#lstLog').append('<li>Subscribe successful.' + $("#btnRoutingKey").val()+'</li>');
        }

        function mqUnsubscribeSuccess() {
            //注销订阅,所以将事件处理器解除绑定
            mqclient.off($("#btnRoutingKey").val(), onMessageArrived);
            $('#btnSubscribe').val('Subscribe');
            $('#lstLog').append('<li>Unsubscribe successful</li>');
        }

        function mqHJSubscribeSuccess() {
            $('#btnSSHuanjing').val('Unsubscribe Huanjing');
            $('#lstLog').append('<li>Hanjing Subscribe successful</li>');
        }

        function mqHJUnsubscribeSuccess() {
            $('#btnSSHuanjing').val('Subscribe Huanjing');
            $('#lstLog').append('<li>Huanjing Unsubscribe successful</li>');
        }

        function onMessageArrived(message) {
            $('#lstLog').append('<li>Receive message: ' + new Date().toString() + '    ' + message.toString() + '</li>');
        }

        function onHuanjingMessageArrived(message) {
            $('#lstLog').append('<li>Receive Huanjing message: ' + new Date().toString() + '    ' + message.toString() + '</li>');
        }

        function guid() {
            function s4() {
                return Math.floor((1 + Math.random()) * 0x10000)
                    .toString(16)
                    .substring(1);
            }
            return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
                s4() + '-' + s4() + s4() + s4();
        }
    });
</script>

3.后端代码:

3.1客户端sdk代码

/// <summary>
        /// 写日志
        /// </summary>
        /// <param name="model"></param>
        public static void Write(LogModel model)
        {
            //判断写入的日志级别
            if (model != null && model.LogLevel >= LogLevel)
            {
                try
                {
                    var mqMsg = new MqMessage()
                    {
                        MessageBody = JSON.Serialize(model),
                        MessageRouter = SystemConst.RoutingKeyTopic.LogTopic_Producer
                    };
                    //MQHelper.Instance.ProducerMessage_Fanout(mqMsg);
                    MQHelper.Instance.ProducerMessage_Topic(mqMsg);
                }
                catch (Exception ex)
                {
                    var errorLog = string.Format("Ip:{0},LogHelper.Write方法异常,{1}", IpHelper.LocalHostIp, ex.Message);
                    //MQHelper.Instance.ProducerMessage_Fanout(new MqMessage() { MessageBody = errorLog });
                    MQHelper.Instance.ProducerMessage_Topic(new MqMessage() { MessageBody = errorLog });
                }
            }
        }

3.2后端MQ代码:

#region 主题 交换机
        /// <summary>
        /// 生产者 客户端调用
        /// </summary>
        /// <param name="msg"></param>
        public void ProducerMessage_Topic(MqMessage msg)
        {
            try
            {
                using (var connection = factory.CreateConnection())
                {
                    using (var channel = connection.CreateModel())
                    {
                        var body = Encoding.UTF8.GetBytes(msg.MessageBody);
                        channel.BasicPublish(exchange: SystemConst.MqName_LogMq_TopicDefault,
                                             routingKey: msg.MessageRouter,
                                             basicProperties: null,
                                             body: body);
                        Console.WriteLine(" [x] Sent {0}", msg.MessageBody);
                    }
                }
            }
            catch (Exception ex)
            {
                var exMsg = ex.Message;
            }
        }

        /// <summary>
        /// 消费者 服务器接收并写入数据库
        /// 消费方法无法通过参数传入
        /// EventHandler<BasicDeliverEventArgs> received
        /// </summary>
       public void ConsumeMessage_Topic(params string[] routingKeys)
        {
            if (routingKeys == null || routingKeys.Length == 0)
            {
                throw new Exception("请指定接收路由");
            }
            using (var connection = factory.CreateConnection())
            {
                using (var channel = connection.CreateModel())
                {
                    var queueName = channel.QueueDeclare().QueueName;//获得已经生成的随机队列名
                    //对列与交换机绑定
                    foreach (var rKey in routingKeys)
                    {
                        channel.QueueBind(queue: queueName,
                                      exchange: SystemConst.MqName_LogMq_TopicDefault,
                                      routingKey: rKey);
                    }

                    var consumer = new EventingBasicConsumer(channel);
                    //绑定消费方法
                    consumer.Received += consomer_Received_Topic;
                    //绑定消费者
                    channel.BasicConsume(queue: queueName,
                                         autoAck: true,
                                         consumer: consumer);
                    Console.WriteLine("日志订阅服务启动成功.");
                    Console.WriteLine(" Press [enter] to exit.");
                    Console.ReadLine();
                }
            }
        }

        /// <summary>
        /// 接收通知服务异步的推送
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void consomer_Received_Topic(object sender, BasicDeliverEventArgs e)
        {
            var body = e.Body;
            var message = Encoding.UTF8.GetString(body);
            Console.WriteLine(" [x] {0}", message);
//这里可以增加写入数据库的代码
        }
        #endregion

3.3路由

/// <summary>
        /// 主题路由
        /// </summary>
        public class RoutingKeyTopic
        {
            /// <summary>
            /// 生产者
            /// </summary>
            public const string LogTopic_Producer = "Dcon.Logs.Client";

            /// <summary>
            /// 消息者_日志服务_保存日志
            /// </summary>
            public const string LogTopic_Consume_Server_SaveDB = "Dcon.Logs.*";

            /// <summary>
            /// 消息者_日志服务_Web显示日志
            /// </summary>
            public const string LogTopic_Consume_Server_WebShow = "Dcon.Logs#";//".Logs.Client";

            /// <summary>
            /// 消息者_日志服务_Web显示日志
            /// </summary>
            public const string LogTopic_Consume_Server_WebShow_T = "*.Logs.Client";//".Logs.Client";

            /// <summary>
            /// 消息者_日志服务_ # 接收所有
            /// </summary>
            public const string LogTopic_Consume_Server_All = "#";//".Logs.Client";
        }
    }

注意点:

1、MQTT的路由是以 / 来分割的。在RabbitMQ中会被转义成 . 如示例中的路由Dcon/Logs/Client会被转换成 Dcon.Logs.Client

2、网页端接收时的路由要和发送端的路由一至。也就是说 后端用 Dcon.Logs.Client 来推数据前端就要使用 Dcon/Logs/Client来接收数据。

3、MQTT路由不支持通配符.

4、由于MQTT的JS库没有提供Topic交换机与路由绑定功能。所以前端接收时 不能设置订阅主题交换机名称。如果要和amqp交互只能使用amqp的默认主题交换机名称 amq.topic

运行效果图:

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在 JavaScript 中订阅 RabbitMQ,你可以使用 RabbitMQ 的 JavaScript 客户库,例如 amqplib。以下是一个简单的示例代码: 首先,确保你已经安装了 amqplib 包。你可以使用 npm 进行安装: ```bash npm install amqplib ``` 然后,你可以使用以下代码实现 RabbitMQ订阅功能: ```javascript const amqp = require('amqplib'); async function main() { try { // 连接到 RabbitMQ const connection = await amqp.connect('amqp://localhost'); // 创建一个信道 const channel = await connection.createChannel(); // 声明一个交换机 const exchangeName = 'my_exchange'; await channel.assertExchange(exchangeName, 'fanout', { durable: false }); // 声明一个临时队列 const queue = await channel.assertQueue('', { exclusive: true }); // 绑定队列到交换机 await channel.bindQueue(queue.queue, exchangeName, ''); // 接收消息 channel.consume(queue.queue, (msg) => { console.log('收到消息:', msg.content.toString()); }, { noAck: true }); console.log('等待接收消息...'); } catch (error) { console.error('出现错误:', error); } } main(); ``` 在上面的代码中,我们首先连接到 RabbitMQ,然后创建一个信道。接下来,我们声明一个交换机,并创建一个临时队列。然后,我们将队列绑定到交换机上。最后,我们通过 `channel.consume` 方法来接收消息,并将收到的消息输出到控制台。 请注意,上述代码假设 RabbitMQ 运行在本地的默认口上(5672)。你可能需要根据实际情况修改连接字符串。 希望能对你有所帮助!如果你有任何其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

StevenChen85

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值