zoukankan      html  css  js  c++  java
  • ASP.NET Core SignalR :学习消息通讯,实现一个消息通知

     什么是 SignalR

           目前我用业余时间正在做一个博客系统,其中有个功能就是评论通知,就是假如A用户评论B用户的时候,如果B用户首页处于打开状态,那么就会提示B用户有未读消息。暂时用SignalR来实现这个功能。我也是看了两天的资料才明白怎么去使用。

          关于SignalR的理论知识可以去官网或者百度,我这里只是结合自己的功能来分享下,如果有错,请原谅指出。

    下载js

          SignalR是需要微软提供的js,因为我的项目是前后端分离的,所以我是单独下载到一个文件夹,然后复制js到我的前端项目里。只需要signalr.js

    页面加载创建连接

         

     //创建连接  
    var connection = new signalR.HubConnectionBuilder().withUrl('http://127.0.0.1:5000/chatHub').build();
    //ajax执行成功执行
      $.ajax({
                success: function (response) {              
                         connection.start().then(function () {
                                connection.invoke('SetConnectionMaps', response.data.account);
                        }
                    },
                });
         

            首先你要了解到SignalR基本运行的原理,官网:https://docs.microsoft.com/zh-cn/aspnet/core/tutorials/signalr?view=aspnetcore-3.1&tabs=visual-studio

            你可以直接继承Hub这个类,我这里用的是强类型Hub<T>,我就是为了让前端和后端统一下。刚开始Hub<T>我纠结了好久,不知道怎么用,最后我手动做了下,认为它只是为了方便前端和后端统一。

     如果你只是简单的继承Hub类,你就必须调用SendAsync方法,并且指定前端接收触发的方法名称“InvokeMessage”,如果你后端和前端名字对应不上,就会有问题。

    public class SingalrService : Hub
        {
            private ISingalrSvc _singalrSvc;
            public SingalrService(ISingalrSvc singalrSvc)
            {
                _singalrSvc = singalrSvc;
            }
          
            public async Task SendMessageAsync(Message sendMessage)
            {
                await Clients.All.SendAsync("InvokeMessage",sendMessage);
            }
    
            public void SetConnectionMaps(string account)
            {
                string connectionid = Context.ConnectionId;
                _singalrSvc.SetConnectionMaps(connectionid, account);
            }
            public override Task OnDisconnectedAsync(Exception exception)
            {
                _singalrSvc.Remove(Context.ConnectionId);           
                return base.OnDisconnectedAsync(exception);
            }
        }

     所以有了强类型Hub<T>,自己定义一个接口,提过方法InvokeMessage供前前端调用。

        /// <summary>
        /// 客户端js调用方法
        /// </summary>
        public interface ISingalrClient
        {
            Task InvokeMessage(Message sendMessage);
        }
        public class SingalrService : Hub<ISingalrClient>
        {
            private ISingalrSvc _singalrSvc;
            public SingalrService(ISingalrSvc singalrSvc)
            {
                _singalrSvc = singalrSvc;
            }
            public void SetConnectionMaps(string account)
            {
                string connectionid = Context.ConnectionId;
                _singalrSvc.SetConnectionMaps(connectionid, account);
            }
    //连接中断时执行,微软这样描述的:
    //重写 OnDisconnectedAsync 虚方法,以便在客户端断开连接时执行操作。 如果客户端故意断开连接(例如,通过调用 connection.stop()),exception 参数将 null
    //但是,如果客户端由于错误(例如网络故障)而断开连接,则 exception 参数将包含描述失败的异常
    public override Task OnDisconnectedAsync(Exception exception) { _singalrSvc.Remove(Context.ConnectionId);
    return base.OnDisconnectedAsync(exception); } }

           这个时候一个用户打开了首页,然后首页有个js方法来初始化连接,同一个页面内的connectionid是一样的,每次刷新或新打开一个窗口的新页面的connectionid是不一样的,并且你刷新页面或者关掉会认为是连接中断,会执行OnDisconnectedAsync方法,这个方法时SingalR自带的,它是个虚方法,你也可以重写,就像我一样。我这里的代码逻辑是将连接id和当前登录人作为键值对存入内存,然后用户关掉页面就会执行OnDisconnectedAsync方法,将相关的coonectionid从内存删掉:  

    layui.use(['element', 'layer'], function () {          
    var
    element = layui.element; element.render('nav'); initLoad();
    //初始化连接,每个页面的connection的connectionid是一样的,但是每次创建的不一样
    var connection = new signalR.HubConnectionBuilder().withUrl('http://127.0.0.1:5000/chatHub').build();
    //绑定后台触发的方法,前面已经讲过了,具体业务还没实现, connection.on(
    'InvokeMessage', (reviceMessage) => { var v = reviceMessage; }); $.ajax({ url: url + 'user/userInfo', type: 'get', dataType: 'json', beforeSend: function (xhr) { doBeforeSend(xhr); }, success: function (response) { if (response.code == '1') { $("#nologin").show(); $("#user").hide(); } else { $("#nologin").hide(); $("#user").show(); $("#photo").attr('src', response.data.headPhoto);
    //连接开始 connection.start().then(
    function () {
    //调用后台方法,不是api接口,将当前登录人账号传过去 connection.invoke(
    'SetConnectionMaps', response.data.account); }) } }, complete: function (xhr) { doComplete(xhr); }, }); });

      这个时候连接已经创建完成,并且用户并没有关闭首页,连接一直处于连接状态。这个时候另一个用户打开了一篇文章详情,并且对它评论提交内容后,我让它触发了一个连接SingalR的事件,

        form.on('submit(review)', function (data) {
            loading = layer.load(2);
            var commentModel = {
                'Content': data.field.desc,
            }
            $.ajax({
                url: url + 'article/review/' + id,
                contentType: 'application/json; charset=utf-8',
                type: 'post',
                datatype: 'json',
                data: JSON.stringify(commentModel),
                beforeSend: function (xhr) {
                    doBeforeSend(xhr);
                },
                success: function (response) {
                    if (response.code == 0) {
                        //另一个用户创建了连接
                        var connection = new signalR.HubConnectionBuilder().withUrl('http://127.0.0.1:5000/chatHub').build();
                        connection.start().then(function () {
                            var apiRoute=url+'Singalr/admin';//admin是我设置死的,实际应该是自己判断,会调用下面的api,[Route("api/[controller]")],SingalR也是支持api调用的
                            var token=localStorage.getItem('token');
                            fetch(apiRoute,{
                                method:'get',
                                headers:{
                                    'Authorization':'Bearer ' + token                               
                                }
                            })
                            event.preventDefault();
                        })
                        layer.close(loading);
                    } else {
                        layer.close(loading);
                        layer.msg("评论失败", {
                            icon: 5
                        });
                    }
                },
                complete: function (xhr) {
                    doComplete(xhr);
                },
            })
        [ApiController]
        public class SingalrController : ControllerBase
        {
            private IHubContext<SingalrService, ISingalrClient> _hubContext;
            private ISingalrSvc _singalrSvc;
            public SingalrController(IHubContext<SingalrService, ISingalrClient> hubContext, ISingalrSvc singalrSvc)
            {
                _hubContext = hubContext;
                _singalrSvc = singalrSvc;
            }
            /// <summary>
            /// 查询未处理数量
            /// </summary>
            /// <param name="account"></param>
            /// <returns></returns>
            [HttpGet("{account}")]
            public async Task NewsCount(string account)
            {
                Message sendMessage = new Message();
                sendMessage.Data = "11";
    //刚已经讲了,用户加载首页的时候已经把connectionid和account存入到了内存里面,现在再取用户相关的connectionID,如果直接调用Clinets.ALL就是给所有客户端发送消息 IReadOnlyList
    <string> connectionIds = (IReadOnlyList<string>)_singalrSvc.GetConnectionIds(account); await _hubContext.Clients.Clients(connectionIds).InvokeMessage(sendMessage); } }

    这个时候调用了这个api执行了里面的_hubContext.Clients.Clients(connectionIds).InvokeMessage(sendMessage),connectionIds是根据业务逻辑所判断的触发的那些客户端;然后前端会根据方法名响应对应的js代码,如下

    layui.use(['element', 'layer'], function () {
                var element = layui.element;
                element.render('nav');
                initLoad();
                var connection = new signalR.HubConnectionBuilder().withUrl('http://127.0.0.1:5000/chatHub').build();

    //我认为是一个用来侦听服务端方法的js connection.on('InvokeMessage', (reviceMessage) => { var v = reviceMessage;
    //响应后端方法成功后,就开始自己的业务逻辑 }); $.ajax({ url: url + 'user/userInfo', type: 'get', dataType: 'json', beforeSend: function (xhr) { doBeforeSend(xhr); }, success: function (response) { if (response.code == '1') { $("#nologin").show(); $("#user").hide(); } else { $("#nologin").hide(); $("#user").show(); $("#photo").attr('src', response.data.headPhoto); connection.start().then(function () { connection.invoke('SetConnectionMaps', response.data.account); }) } }, complete: function (xhr) { doComplete(xhr); }, }); });

      

           

          

    游戏也能赚钱?如果你热爱游戏,并且想通过游戏赢得零花钱,5173是个不错的选择  http://www.5173.com/?recommenduserid=US15061749098191-04F6

  • 相关阅读:
    玩游戏 学Flex布局
    解决:父级元素不能被子元素内容撑开的解决办法,父级元素没有高度的解决办法
    Flex 项目属性:flex 布局示例
    display: flex; 布局
    box-sizing 的作用
    <a>标签里面直接嵌套图片,<img>下面出现一小段空白的原因
    ucore操作系统实验学习笔记2
    ucore 操作系统学习笔记1 -- 环境搭建
    LeetCode题解之 Assign Cookies
    LeetCode题解之Lemonade Change
  • 原文地址:https://www.cnblogs.com/MrHanBlog/p/11996689.html
Copyright © 2011-2022 走看看