• 基于SignalR的消息推送与二维码描登录实现


    1 概要说明

        使用微信扫描登录相信大家都不会陌生吧,二维码与手机结合产生了不同应用场景,基于二维码的应用更是比较广泛。为了满足ios、android客户端与web短信平台的结合,特开发了基于SinglarR消息推送机制的扫描登录。本系统涉及到以下知识点:

        SignalRhttp://signalr.net/ 这官网,ASP.NET SignalR 是为 ASP.NET 开发人员提供的一个库,可以简化开发人员将实时 Web 功能添加到应用程序的过程。实时 Web 功能是指这样一种功能:当所连接的客户端变得可用时服务器代码可以立即向其推送内容,而不是让服务器等待客户端请求新的数据。

        二维码:使用的QRCode类库,https://github.com/jeromeetienne/jquery-qrcode

        MVC5:开发环境是基于MVC5

    2、系统关系图

        在实现本功能前,有点不是太确定能否拿下。

        所谓万事开头难,通过查询想资料及自己归纳分析:系统涉及到手机客户端、浏览者、服务端,实现扫描登录也就是三者之间是如何协调工作的。通过axure画出如下关系图:

    移动客户端、浏览者、服务端三者协作关系图

        【M】:表示移动端   【B】:表示浏览者(浏览器客户端)  【S】:服务端,消息推送者及扫描认证接口发布者

        步骤说明:

        Step(步骤)1  ,【B】浏览登录页面,Step2【S】产生一个标识符UUID,并推送给B,生成登录二维码;

        Step3,【M】扫描二维码,前提条件是【M】已登录,Step4【M】解析二维码信息获取UUID;

        Step5,【M】向服务端发送UUID+登录信息,Step6【S】对UUID+登录信息进行相关解析认证,Step6 UUID认证,不通过认证,则到Step6-1 重新生成UUID循环Step 2与并Step6-2 返回给【M】UUID认证失败原因,Step6 通过认证,Step6-2转到登录信息认证,Step 7登录信息认证,失败Step7-3重新生成UUID循环Step 2,成功则Step7-1推送给【B】跳转到首页。

    3、SignalR循环消息推送

    3.1 引用SignalR

        由于本人用的是VS15Preview4,可以直接使用Nuget可视化管理工具进行安装:Tools—>Nuget Package Manager—>Manage Nuget Packages for Solution…,打开以下界面:

    在Browser 标签下输入SignalR,查询到Microsoft.AspNet.SignalR

    找到对应的项目,点击“Install”安装按钮即可引用相关类库,同时应用下载相关js库。

        关于SignalR的知识点,可以到官网 http://www.asp.net/signalr 进行深入学习。

    3.2 服务端SignalR实现

        服务端要向客户端推送UUID,对于UUID唯一标识符,具有重要特性:(1)有时间限制,120秒之内扫码有效;(2)具有一定的状态。对应的声明周期就是:生成—>推送—>状态判断—>手机端扫描—>验证UUID—>状态判断—>销毁等系列过程。

        服务端的核心代码将单独建立一个项目去实现:

    3.2.1 Nofifier.cs通知类

        本类将连接QRCodeHub与SessionTimer

    复制代码
    using Microsoft.AspNet.SignalR;
    
    namespace TxSms.SingalR
    {
        public static class Notifier
        {
            private static readonly IHubContext Context = GlobalHost.ConnectionManager.GetHubContext<QRCodeHub>();
    
            public static void SessionTimeOut(string connectionId, int time)
            {
                Context.Clients.Client(connectionId).alertClient(time);
            }
    
            public static void SendElapsedTime(string connectionId, int time)
            {
                Context.Clients.Client(connectionId).sendElapsedTime(time);
            }
    
            public static void SendQRCodeUUID(string connectionId, string uuid)
            {
                Context.Clients.Client(connectionId).sendQRCodeUUID(uuid);
            }
        }
    }
    复制代码

    3.2.2 QRCodeHub.cs SignalR核心实现

        SignalR的核心代码:

    复制代码
    using Microsoft.AspNet.SignalR;
    using System.Threading.Tasks;
    
    namespace TxSms.SingalR
    {
        /// <summary>
        /// 二维码推送
        /// </summary>
        //[HubName("qrcode")]
        public class QRCodeHub : Hub
        {
            /// <summary>
            /// 给客户端发送时间间隔
            /// </summary>
            /// <param name="time"></param>
            public void SendTimeOutNotice(int time)
            {
                Clients.Client(Context.ConnectionId).alertClient(time);
            }
    
            public void CheckElapsedTime(int time)
            {
                Clients.Client(Context.ConnectionId).sendElapsedTime(time);
            }
    
            /// <summary>
            /// 发送二维码UUID内容
            /// </summary>
            /// <param name="uuid"></param>
            public void SendQRCodeUUID(string uuid)
            {
                Clients.Client(Context.ConnectionId).sendQRCodeUUID(uuid);
            }
    
            /// <summary>
            /// Called when the connection connects to this hub instance.
            /// </summary>
            /// <returns>A <see cref="T:System.Threading.Tasks.Task" /></returns>
            public override Task OnConnected()
            {
                SessionTimer.StartTimer(Context.ConnectionId);
                return base.OnConnected();
            }
    
            /// <summary>
            /// Called when a connection disconnects from this hub gracefully or due to a timeout.
            /// </summary>
            /// <param name="stopCalled">
            /// true, if stop was called on the client closing the connection gracefully;
            /// false, if the connection has been lost for longer than the
            /// <see cref="P:Microsoft.AspNet.SignalR.Configuration.IConfigurationManager.DisconnectTimeout" />.
            /// Timeouts can be caused by clients reconnecting to another SignalR server in scaleout.
            /// </param>
            /// <returns>A <see cref="T:System.Threading.Tasks.Task" /></returns>
            public override Task OnDisconnected(bool stopCalled)
            {
                SessionTimer.StopTimer(Context.ConnectionId);
                return base.OnDisconnected(stopCalled);
            }
    
            /// <summary>
            /// Called when the connection reconnects to this hub instance.
            /// </summary>
            /// <returns>A <see cref="T:System.Threading.Tasks.Task" /></returns>
            public override Task OnReconnected()
            {
                if (!SessionTimer.Timers.ContainsKey(Context.ConnectionId))
                {
                    SessionTimer.StartTimer(Context.ConnectionId);
                }
                return base.OnReconnected();
            }
    
            /// <summary>
            /// 重置时钟
            /// </summary>
            public void ResetTimer()
            {
                SessionTimer timer;
                if (SessionTimer.Timers.TryGetValue(Context.ConnectionId, out timer))
                {
                    timer.ResetTimer();
                }
                else
                {
                    SessionTimer.StartTimer(Context.ConnectionId);
                }
            }
    
            /// <summary>
            /// 发送普通消息
            /// </summary>
            /// <param name="name"></param>
            /// <param name="message"></param>
            public void Send(string name, string message)
            {
                Clients.All.addNewMessageToPage(name, message);
            }
        }
    }
    复制代码

    3.2.3 SessionTimer.cs 对应客户端时钟

        对【B】来说,每个都产生一个timer,进行按1s间隔发送消息。

    复制代码
    using System;
    using System.Collections.Concurrent;
    using System.Timers;
    
    namespace TxSms.SingalR
    {
        public class SessionTimer : IDisposable
        {
            /// <summary>
            /// 存储客户端对应的Timer
            /// </summary>
            public static readonly ConcurrentDictionary<string, SessionTimer> Timers;
    
            private readonly Timer _timer;
    
            static SessionTimer()
            {
                Timers = new ConcurrentDictionary<string, SessionTimer>();
            }
    
            /// <summary>
            /// 构造函数
            /// </summary>
            /// <param name="connectionId"></param>
            private SessionTimer(string connectionId)
            {
                ConnectionId = connectionId;
                _timer = new Timer
                {
                    Interval = Utility.ActivityTimerInterval()
                };
                _timer.Elapsed += (s, e) => MonitorElapsedTime();
                _timer.Start();
            }
    
            public int TimeCount { get; set; }
    
            /// <summary>
            /// 客户端连接Id
            /// </summary>
            public string ConnectionId { get; set; }
    
            /// <summary>
            /// 启动Timer
            /// </summary>
            /// <param name="connectionId"></param>
            public static void StartTimer(string connectionId)
            {
                var newTimer = new SessionTimer(connectionId);
                if (!Timers.TryAdd(connectionId, newTimer))
                {
                    newTimer.Dispose();
                }
            }
    
            /// <summary>
            /// 停止Timer
            /// </summary>
            /// <param name="connectionId"></param>
            public static void StopTimer(string connectionId)
            {
                SessionTimer oldTimer;
                if (Timers.TryRemove(connectionId, out oldTimer))
                {
                    oldTimer.Dispose();
                }
            }
    
            /// <summary>
            /// 重置Timer
            /// </summary>
            public void ResetTimer()
            {
                TimeCount = 0;
                _timer.Stop();
                _timer.Start();
            }
    
            public void Dispose()
            {
                // Stop might not be necessary since we call Dispose
                _timer.Stop();
                _timer.Dispose();
            }
    
            /// <summary>
            /// 给客户端发送消息
            /// </summary>
            private void MonitorElapsedTime()
            {
                Utility.ClearExpiredUUID();
                var uuid = Utility.GetUUID(ConnectionId);
                //if (TimeCount >= Utility.TimerValue())
                //{
                //    StopTimer(ConnectionId);
                //    Notifier.SendQRCodeUUID(ConnectionId, uuid);
                //    Notifier.SessionTimeOut(ConnectionId, TimeCount);
                //}
                //else
                //{
                Notifier.SendQRCodeUUID(ConnectionId, uuid);
                Notifier.SendElapsedTime(ConnectionId, TimeCount);
                //}
                TimeCount++;
                if (TimeCount > 1000)
                {
                    TimeCount = 0;
                }
            }
        }
    }
    复制代码

    3.2.4 Utility.cs 基础配置

        满足时钟、获取QRCode等

    复制代码
    using TxSms.Actions;
    
    namespace TxSms.SingalR
    {
        internal class Utility
        {
            public static int IntNum = 0;
    
            /// <summary>
            /// 时间间隔
            /// </summary>
            /// <returns></returns>
            public static int TimerValue()
            {
                return 1000;
            }
    
            public static double ActivityTimerInterval()
            {
                return 1000.0;
            }
    
            /// <summary>
            /// 获取当前UUID
            /// </summary>
            /// <returns></returns>
            public static string GetUUID(string connectionId)
            {
                try
                {
                    var model = new QRCodeAction().GetValidModel(connectionId);
                    return model.ToJson(connectionId);
                }
                catch
                {
                    return "ERROR";
                }
            }
    
            /// <summary>
            /// 删除过期UUID
            /// </summary>
            public static void ClearExpiredUUID()
            {
                IntNum++;
                if (IntNum <= 1000) return;
                new QRCodeAction().ClearExpiredUUID();
                IntNum = 0;
            }
        }
    }
    复制代码

    3.2.5 SignalR在MVC中启动配置

        在MVC中,启动项目进行如下配置:

    复制代码
    using Microsoft.Owin;
    using Owin;
    
    [assembly: OwinStartup(typeof(TxSms.Web.Startup))]
    
    namespace TxSms.Web
    {
        public partial class Startup
        {
            public void Configuration(IAppBuilder app)
            {
                //启动SignalR
                app.MapSignalR();
                ConfigureAuth(app);
            }
        }
    }
    复制代码

    3.2.6 其他类库说明

        QRCodeAction.cs:维护UUID,创建、保存、状态更改、删除等。

        QRModel.cs:UUID实体

        所有文件,可在《6、相关文件》中下载。

    3.3 客户端SignalR实现

        添加SignalR js库:

        <script type="text/javascript" src="~/Scripts/jquery.signalR-2.2.1.min.js"></script>
        <script type="text/javascript" src="~/signalr/hubs"></script

        两者必须都引用。

        调用接口如下:

    复制代码
    var codeUUID = "";
            $(function () {
                // Reference the auto-generated proxy for the hub.
                var qrcode = $.connection.qRCodeHub;
                // Create a function that the hub can call back to display messages.
                qrcode.client.addNewMessageToPage = function (name, message) {
                    // Add the message to the page.
                    console.log(message);
                    //jQuery('#divQRCode').qrcode({  180, height: 180, correctLevel: 0, text: message });
                };
                qrcode.client.sendElapsedTime = function (time) {
                    console.log(time);
                };
                qrcode.client.sendQRCodeUUID = function (uuid) {
                    console.log("sendQRCodeUUID");
                    console.log(codeUUID);
                    if (codeUUID === uuid) {
                        return;
                    }
                    codeUUID = uuid;
                    if (codeUUID !== "ERROR") {
                        var jsonUUID = $.parseJSON(codeUUID);
                        if (jsonUUID.islogin === 1) { //判断是否登录
                            window.location.href = "/Home/Index/@Model.Name";
                        }
                    }
                    $("#divQRCode").html("");
                    $('#divQRCode').qrcode({  180, height: 180, correctLevel: 0, text: codeUUID });
                };
                // Start the connection.
                $.connection.hub.start().done(function () {
                    //qrcode.server.updateConnectionId($.connection.hub.id);
                    qrcode.server.send("qrcode", Math.random());
                });
            });
    复制代码

     以上代码包括相关二维码的生成。

    4、二维码生成

        二维码类库选择https://github.com/jeromeetienne/jquery-qrcode

        添加script标签:

        <script type="text/javascript" src="~/Scripts/qrcode.min.js"></script>
        <script type="text/javascript" src="~/Scripts/jquery.qrcode.min.js"></script>

        定义div标签,用来呈现二维码:

    复制代码
    <!--二维码登录开始-->
                                            <div class="ewmcode_login" id="ewmcode_login">
                                                <div class="codeText">安全登录 防止被盗</div>
                                                <div id="divQRCode" class="codebox" style="background:none;"></div>
                                                <div class="coderemindText">扫一扫登录</div>
                                            </div>
                            <!--二维码登录结束-->
    复制代码

      呈现二维码:

                    $("#divQRCode").html("");
                    $('#divQRCode').qrcode({  180, height: 180, correctLevel: 0, text: codeUUID });

        通过3与4,可实现具有120秒生命周期二维码的生成,对于不同的浏览者,生成的二维码是不同的,效果如下:

    5、扫描认证接口

        为了满足【M】端扫描之后,提交UUID+用户信息进行认证,建立QRCode API接口。接口任务比较简单,就是对UUID合法性进行判断,然后判断用户信息登录情况,更改UUID的登录状态。

    5.1 输入参数

    复制代码
    using Abp.Application.Services.Dto;
    using System;
    using System.ComponentModel.DataAnnotations;
    
    namespace TxSms.Inputs
    {
        /// <summary>
        /// 二维码登录认证
        /// </summary>
        [Serializable]
        public class QRCodeVerifyInput : IInputDto
        {
            /// <summary>
            /// 构造函数
            /// </summary>
            public QRCodeVerifyInput()
            {
                ConnectionId = Guid.Empty.ToString();
                UUID = Guid.Empty;
                UserName = Password = "";
            }
    
            /// <summary>
            /// 当前回话ID
            /// </summary>
            [DisplayFormat(ConvertEmptyStringToNull = false)]
            public string ConnectionId { get; set; }
    
            /// <summary>
            /// 唯一标识符号
            /// </summary>
            public Guid UUID { get; set; }
    
            /// <summary>
            /// 用户账号
            /// </summary>
            [DisplayFormat(ConvertEmptyStringToNull = false)]
            public string UserName { get; set; }
    
            /// <summary>
            /// 登录密码
            /// </summary>
            [DisplayFormat(ConvertEmptyStringToNull = false)]
            public string Password { get; set; }
    
            /// <summary>
            /// 平台
            /// </summary>
            [DisplayFormat(ConvertEmptyStringToNull = false)]
            public string Platform { get; set; }
        }
    }
    复制代码

    5.2 输出参数

    复制代码
    using Abp.Application.Services.Dto;
    using Newtonsoft.Json;
    using System.ComponentModel.DataAnnotations;
    using System.Web.Mvc;
    using TxSms.MVC;
    
    namespace TxSms.Outputs
    {
        /// <summary>
        /// 输出基类
        /// </summary>
        [ModelBinder(typeof(EmptyStringModelBinder))]
        public class TxSmsOutputDto : IOutputDto
        {
            /// <summary>
            /// 构造函数
            /// </summary>
            public TxSmsOutputDto()
            {
                Result = 0; //默认为0,表示初始值或正确
                Message = "";
            }
    
            /// <summary>
            /// 错误代码
            /// </summary>
            [JsonProperty("Result")]
            public int Result { get; set; }
    
            /// <summary>
            /// 错误信息
            /// </summary>
            [DisplayFormat(ConvertEmptyStringToNull = false)]
            [JsonProperty("Message")]
            public string Message { get; set; }
        }
    }
    复制代码

    5.3 API接口

    复制代码
    using System;
    using System.Threading.Tasks;
    using System.Web.Http;
    using TxSms.Actions;
    using TxSms.Inputs;
    using TxSms.Outputs;
    
    namespace TxSms
    {
        /// <summary>
        /// 二维码接口
        /// </summary>
        public class QRCodeController : TxSmsApiController
        {
            /// <summary>
            /// 二维码登录认证
            /// </summary>
            /// <returns>
            /// 0:登录成功;-1:参数错误 -2:ConnectionId、UUID、UserName、Password不允许为空-3:ConnectionId回话id不存在-4:UUID输入错误-5:UUID已过期
            /// -6:本UUID已登录-7:登录账号已停用-8:登录账号已删除-9:登录密码输入错误-10:登录账号不存在
            /// </returns>
            [AllowAnonymous]
            [HttpPost]
            public async Task<TxSmsOutputDto> QRCodeVerify([FromBody]QRCodeVerifyInput model)
            {
                TxSmsOutputDto result = new TxSmsOutputDto();
    
                #region 参数验证
    
                if (model.IsNull())
                {
                    result.Result = -1;
                    result.Message = "参数错误";
                    return result;
                }
                if (model.ConnectionId.IsNullOrEmpty() || model.UUID.Equals(Guid.Empty) || model.UserName.IsNullOrEmpty() || model.Password.IsNullOrEmpty())
                {
                    result.Result = -2;
                    result.Message = "ConnectionId、UUID、UserName、Password不允许为空";
                    return result;
                }
    
                #endregion 参数验证
    
                #region 有效性判断
    
                //验证ConnectionId合法性
                if (QRCodeAction.QRCodeLists.ContainsKey(model.ConnectionId))
                {
                    result.Result = -3;
                    result.Message = "ConnectionId回话id不存在";
                    return result;
                }
                //验证UUID有效性
                var findCode = QRCodeAction.QRCodeLists[model.ConnectionId];
                if (!model.UUID.Equals(findCode.UUID))
                {
                    result.Result = -4;
                    result.Message = "UUID输入错误";
                    return result;
                }
                if (!findCode.IsValid())
                {
                    result.Result = -5;
                    result.Message = "UUID已过期";
                    return result;
                }
                if (findCode.IsLogin)
                {
                    result.Result = -6;
                    result.Message = "本UUID已登录";
                    return result;
                }
    
                #endregion 有效性判断
    
                LoginUserNameInput loginParam = new LoginUserNameInput
                {
                    UserName = model.UserName,
                    Password = model.Password,
                    Platform = model.Platform
                };
                LoginOutput loginResult = await new SessionController().LoginUserName(loginParam);
                switch (loginResult.Result)
                {
                    case -1:
                        result.Result = -7;
                        result.Message = "登录账号已停用";
                        break;
    
                    case -2:
                        result.Result = -8;
                        result.Message = "登录账号已删除";
                        break;
    
                    case -3:
                        result.Result = -9;
                        result.Message = "登录密码输入错误";
                        break;
    
                    case -4:
                        result.Result = -10;
                        result.Message = "登录账号不存在";
                        break;
                }
                if (loginResult.Result > 0) //登录成功,值为AccId
                {
                    result.Result = 0;
                    findCode.IsLogin = true; //更改登录状态
                    result.Message = "成功登录";
                }
                return result;
            }
        }
    }
    复制代码

    6、总结与下载

        二维码应用比较广泛,记得去北京的故宫旁边的中山公园,里面的古树也有二维码,扫描可查看相关联信息。紧紧对于二维码而言就是存储有限信息,但就是这有限的信息,可以将庞大的信息系统连接一起,所用的应用不是前沿技术的突破,而是我们思考问题方式的转变、思维角度的变化。

        主要文件下载:http://files.cnblogs.com/files/zsy/signalr%E4%B8%8Eqrcode.rar

    文章转自:http://www.cnblogs.com/zsy/p/5882034.html

  • 相关阅读:
    总账数据访问安全性控制(5)
    XML输出中文时,无法用xsl查看(XML文件不能正常显示、中文显示乱码)
    设计抗混叠滤波器的三大指导原则(转载)
    Verilog中变量位宽注意
    学习cordic算法所得(流水线结构、Verilog标准)
    傅里叶分析的理解
    转载:Allegro实用技巧之模块复用
    c语言学习之 辗转相除法求最大公约数
    c语言学习之 正序分解整数
    新博客开张
  • 原文地址:https://www.cnblogs.com/dxqNet/p/10276214.html
Copyright © 2020-2023  润新知