最近在做一个公司的日志组件时有一个问题难住了我。今天问题终于解决了。由于在解决问题中,在网上也查了很多资料都没有一个完整的实例可以参考。所以本着无私分享的目的记录一下完整的解决过程和实例。
  需求:做一个统一日志系统可以查看日志列表和一个可以订阅最新日志的页面。通过提供一个封装好日志记录方法的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 == )
{
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
 
运行效果图:
 

网页端HTML使用MQTTJs订阅RabbitMQ数据的更多相关文章

  1. 吉特仓库管理系统(开源)-如何在网页端启动WinForm 程序

    在逛淘宝或者使用QQ相关的产品的时候,比如淘宝我要联系店家点击旺旺图标的时候能够自动启动阿里旺旺进行聊天.之前很奇怪为什么网页端能够自动启动客户端程序,最近在开发吉特仓储管理系统的时候也遇到一个类似的 ...

  2. 如何在网页端启动WinForm 程序

    在逛淘宝或者使用QQ相关的产品的时候,比如淘宝我要联系店家点击旺旺图标的时候能够自动启动阿里旺旺进行聊天.之前很奇怪为什么网页端能够自动启动客户端程序,最近在开发吉特仓储管理系统的时候也遇到一个类似的 ...

  3. 应用市场高速下载以及网页端调起APP页面研究与实现

    Github博文地址,此处更新可能不是很及时. 好久没写博客了,好大一个坑.正好,最近刚做完应用市场的高速下载功能,便拿来填了这个坑. 话说产品为了增加用户量,提升用户活跃度以及配合推广,更坑爹的是看 ...

  4. 网页端启动WinForm

    网页端启动WinForm 程序 在逛淘宝或者使用QQ相关的产品的时候,比如淘宝我要联系店家点击旺旺图标的时候能够自动启动阿里旺旺进行聊天.之前很奇怪为什么网页端能够自动启动客户端程序,最近在开发吉特仓 ...

  5. 分享基于 websocket 网页端聊天室

    博客地址:https://ainyi.com/67 有一个月没有写博客了,也是因为年前需求多.回家过春节的原因,现在返回北京的第二天,想想,应该也要分享技术专题的博客了!! 主题 基于 websock ...

  6. 应用市场快速下载以及网页端调起APP页面研究与实现

    Github博文地址,此处更新可能不是非常及时. 好久没写博客了,好大一个坑. 正好,近期刚做完应用市场的快速下载功能,便拿来填了这个坑. 话说产品为了添加用户量,提升用户活跃度以及配合推广,更坑爹的 ...

  7. 【Beta】“北航社团帮”测试报告——小程序v2.0与网页端v1.0

    目录 测试计划.过程和结果 后端测试--单元测试与覆盖率 后端测试--压力测试 展示部分数据 平均数据 前端测试--小程序v2.0 授权登录与权限检查 新功能的测试 兼容性测试 性能测试 前端测试-- ...

  8. 【Beta】“北航社团帮”发布声明——小程序v2.0与网页端v1.0

    目录 Beta版本新功能 小程序v2.0新功能 新功能列表 功能详情图 新功能动图展示 网页端v1.0功能 登录方式 社团信息的修改 新闻的录入和修改 活动的录入和修改 这一版修复的缺陷 Beta版本 ...

  9. 转载:微信开放平台开发第三方授权登陆(二):PC网页端

    微信开放平台开发第三方授权登陆(二):PC网页端 2018年07月24日 15:13:32 晋文子上 阅读数 12644更多 分类专栏: 微信开发 第三方授权登录   版权声明:本文为博主原创文章,遵 ...

随机推荐

  1. C#和NewSQL更配 —— TiDB入门(可能是C#下的全网首发)

    阅读目录 背景 TiDB是什么 环境部署 实战 性能测试 结语 一.背景 在上一篇尝试CockroachDB(传送门在此:http://www.cnblogs.com/Zachary-Fan/p/co ...

  2. c# 后台get post请求

    //get请求 public static TResult Get<TResult>(string host, string url) { var httpClient = new Htt ...

  3. php之数组

    数组分类: 1.索引数组. 索引值从0开始,依次递增. 2.使用array()函数声明数组 <?php // 1.直接为数组元素赋值即可声明数组 $contact_index[0] = 1; $ ...

  4. Atomic类和CAS

    说Atomic类之前,先聊一聊volatile. 对volatile的第一印象就是可见性.所谓可见性,就是一个线程对共享变量的修改,别的线程能够感知到. 但是对于原子性,volatile是不能保证的. ...

  5. Tomcat闪退的问题

    问题:双击tomcat bin下的startup.bat,tomcat的窗口一闪而过,未成功启动: 原因是:在启动tomcat是,需要读取环境变量和配置信息,缺少了这些信息就会导致了tomcat的闪退 ...

  6. JavaPOI处理Excel

     java处理excel,心得分享如下,如有不妥或者需要补充的地方,敬请指出,欢迎随时交流 1.加载excel,获得workbook对象 fileTemp = new File(pathOfMExce ...

  7. NPOI office 组件资料汇总 (excel, word)

    POI 是一套用Java写成的库,能够帮助开发者在没有安装微软Office的情况下读写Office 的文件,支持的文件格式包括xls, doc, ppt等. NPOI 是POI的.net 版本. 最新 ...

  8. java TreeSet应用

    这篇是紧接着上一篇而写的,具体的实现TreeSet中有序的第二中方法 首先新建一个类,此类就是用于集合中存放的对象 然后定义一个类,实现Comparator中的CompareTo()方法 最后一个测试 ...

  9. bugly cocos 接入和 符号表使用

    bugly cocos 接入和 符号表使用 在bugly网站下载 BuglyCocosPlugin 的sdk ios 1. 在 项目的 classes 里面新建 文件夹  BuglyCocosPlug ...

  10. C++中关于重载默认构造函数与默认全部参数的构造函数的使用注意

    # include<iostream>using namespace std;class Time{public:                            //公用成员函数  ...