基于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({ width: 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({ width: 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({ width: 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

时间: 2024-10-06 18:46:13

基于SignalR的消息推送与二维码描登录实现的相关文章

Android 基于Netty的消息推送方案之概念和工作原理(二)

上一篇文章中我讲述了关于消息推送的方案以及一个基于Netty实现的一个简单的Hello World.为了更好的理解Hello World中的代码,今天我来解说一下关于Netty中一些概念和工作原理的内容,假设你认为本篇文章有些枯燥.请先去阅读<Android 基于Netty的消息推送方案之Hello World(一)> ChannelEvent Netty是基于事件驱动的,就是我们上文提到的.发生什么事.就通知"有关部门". 所以.不难理解.我们自己的业务代码中,一定有跟这

Signalr实现消息推送

一.前言 大多数系统里面好像都有获取消息的功能,但这些消息来源都不是实时的,比如你开两个浏览器,用两个不同的账号登录,用一个账号给另外一个账号发送消息,然而并不会实时收到消息,必须要自己手动F5刷新一下页面才会显示自己的消息,这样感觉用户体验不太好.之前看了Learning hard关于Signalr的文章,刚好自己项目中有用到获取实时消息的功能,然而我们项目中就是用js代码setinterval方法进行1秒刷新读取数据的,这样严重给服务器端添加负担,影响系统性能!所以自己稍微研究了一下,下面是

Android 基于Netty的消息推送方案之字符串的接收和发送(三)

在上一篇文章中<Android 基于Netty的消息推送方案之概念和工作原理(二)> ,我们介绍过一些关于Netty的概念和工作原理的内容,今天我们先来介绍一个叫做ChannelBuffer的东东. ChannelBuffer Netty中的消息传递,都必须以字节的形式,以ChannelBuffer为载体传递.简单的说,就是你想直接写个字符串过去,对不起,抛异常.虽然,Netty定义的writer的接口参数是Object的,这可能也是会给新上手的朋友容易造成误会的地方.Netty源码中,是这样

Android 基于Netty的消息推送方案之对象的传递(四)

在上一篇文章中<Android 基于Netty的消息推送方案之字符串的接收和发送(三)>我们介绍了Netty的字符串传递,我们知道了Netty的消息传递都是基于流,通过ChannelBuffer传递的,那么自然,Object也需要转换成ChannelBuffer来传递.好在Netty本身已经给我们写好了这样的转换工具.ObjectEncoder和ObjectDecoder,下面我们介绍一个案例. 1. 我们构造一个用来传输的对象(JavaBean) [java] view plaincopy

在云平台上基于Go语言+Google图表API提供二维码生成应用

二维码能够说已经深深的融入了我们的生活其中.到处可见它的身影:但通常我们都是去扫二维码, 曾经我们分享给朋友一个网址直接把Url发过去,如今我们能够把自己的信息生成二维码再分享给他人. 这里就分享一下基于Go语言+Google图表API提供二维码生成功能的小应用,并演示怎样把它公布到云平台上, 让每一个人都能够通过网络訪问使用它. Google图表API Google在http://chart.apis.google.com 上提供了一个将表单数据自己主动转换为图表的服务. 只是,该服务非常难交

实现网站二维码扫描登录

实现网站二维码扫描登录 分类: 架构设计2014-03-31 10:33 14613人阅读 评论(6) 收藏 举报 在尝试使用网页版微信时,发现微信的登录方式比较酷.区别与常用的用户名和密码的登录方式,网页微信登录只需要轻轻一扫,即可方便的实现登录功能. 下面尝试根据个人的理解对其可能的架构猜测一番.总体来看,扫描二维码实现网站的登录并不是太困难的事情.首先来看一下二维码登录的整体架构: 在整个架构中,主要包含了几个模块:手机App.浏览器.Web服务器以及存储服务(session服务).整个方

实现手机扫描二维码页面登录,类似web微信-第一篇,业务分析

转自:http://www.cnblogs.com/fengyun99/p/3541249.html 关于XMPP组件的文章,先休息两天,好歹已经完整的写了一份. 这两天,先实现一套关于web微信扫描二维码页面登录的试验,因为这种模式在我们的很多业务场景里大有前途. 首先介绍一下web微信登录的过程 手机必须运行微信,并且合法登录 打开web微信的页面,展示一个二维码 用手机微信的扫描功能扫描该二维码 页面立即显示手机已扫描 手机显示是否确认登录,点击确认 页面登录 这个过程将传统的web登录转

聊一聊二维码扫描登录原理

扫二维码登录现在比较常见,比如微信.支付宝等 PC 端登录,并且好像每款 APP 都支持扫码登录,不搞个扫码登录都不好意思.作为技术人员,不知道您对这背后的实现逻辑是否敢兴趣,反正我是一直都对这背后实现好奇.最近刚好看到一个关于扫码登录原理的视频,于是就整理出来了这篇文章,希望对您有所帮助. 本文共三个主题: 什么是二维码. 移动端基于 token 的认证机制. 二维码扫码登录的原理. 1.什么是二维码 二维码又称二维条码,常见的二维码为QR Code,QR全称Quick Response,是一

php 实现 二维码 扫描登录

本人简单实现的示例,使用任意二维码工具打开二维码对应链接 http://www.vincentguo.cn/demo/scan 原理介绍: 第一步:访问登录页面,生成唯一key,例如MkhjDFL=,并且将此key 存入cache,对应值为-1 ,-1表示未登录 ,key有效期我设置的为5分钟,过期就会重新生成二维码图片 第二步:生成二维码,本人使用库(https://github.com/2amigos/yii2-qrcode-helper),二维码对应的链接 http://www.vince