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

    1.new . delete . malloc . free 关系 delete 会调用对象的析构函数 , 和 new 对应, free 只会释放内存, new 调用构造函数. malloc 与 fr ...

  2. My Favorite Color

    我喜欢的颜色收藏.. <H4>标签的颜色: Html中行内样式的设置.. Html中行内样式的设置.. Html中行内样式的设置.. Html中行内样式的设置.. <html> ...

  3. Visual Studio 编译使用FLTK库

    FLTK介绍 FLTK (Fast Light Tool Kit 发音为fulltick) 是一种使用C++开发的GUI工具包,它可以应用于Unix,Linux,MS-Windows95/98/NT/ ...

  4. sql primary key 约束

    PRIMARY KEY 约束唯一标识数据库表中的每条记录. 主键必须包含唯一的值. 主键列不能包含 NULL 值. 每个表都应该有一个主键,并且每个表只能有一个主键. SQL PRIMARY KEY ...

  5. ASM的备份集在文件系统上恢复测试

    背景:最近时常有客户咨询这类问题,其实很简单一个操作,但由于每个人的理解差异,也容易出现各种问题或者误解,本文主要总结下这个过程以及常遇到的问题处理. 环境:Site A(Oracle RAC 11. ...

  6. C# 反射、与dynamic最佳组合

    在 C# 中反射技术应用广泛,至于什么是反射.........你如果不了解的话,请看下段说明,否则请跳过下段.广告一下:希望我文章的朋友请关注一下我的blog,这也有助于提高本人写作的动力. 反射:当 ...

  7. Linux下检测进程是否存在

    这个问题看起来好像很简单,"ps -ef | grep xx"一下就行啦!这样做当然可以,但是如果我们考究起性能来,这恐怕不是个好办法. 假设我们现在要监测某进程是否存活,每分钟检 ...

  8. pycharm远程linux开发和调试代码

    pycharm是一个非常强大的python开发工具,现在很多代码最终在线上跑的环境都是linux,而开发环境可能还是windows下开发,这就需要经常在linux上进行调试,或者在linux对代码进行 ...

  9. C++学习日记(二)————初始字符串类型

    使用频率高,但操作复杂的数据有哪些? 做下总结: int; double;float;char;bool这些类型用的比较频繁,但并不复杂.但对于字符串来说(char数组)用的频繁但操作又复杂,只能用一 ...

  10. JavaScript事件循环(Event Loop)机制

    JavaScript 是单线程单并发语言 什么是单线程 主程序只有一个线程,即同一时间片断内其只能执行单个任务. 为什么选择单线程? JavaScript的主要用途是与用户互动,以及操作DOM.这决定 ...