使用微信掃描登陸相信你們都不會陌生吧,二維碼與手機結合產生了不一樣應用場景,基於二維碼的應用更是比較普遍。爲了知足ios、android客戶端與web短信平臺的結合,特開發了基於SinglarR消息推送機制的掃描登陸。本系統涉及到如下知識點:javascript
SignalR:http://signalr.net/ 這官網,ASP.NET SignalR 是爲 ASP.NET 開發人員提供的一個庫,能夠簡化開發人員將實時 Web 功能添加到應用程序的過程。實時 Web 功能是指這樣一種功能:當所鏈接的客戶端變得可用時服務器代碼能夠當即向其推送內容,而不是讓服務器等待客戶端請求新的數據。html
二維碼:使用的QRCode類庫,https://github.com/jeromeetienne/jquery-qrcodejava
MVC5:開發環境是基於MVC5jquery
在實現本功能前,有點不是太肯定可否拿下。android
所謂萬事開頭難,經過查詢想資料及本身概括分析:系統涉及到手機客戶端、瀏覽者、服務端,實現掃描登陸也就是三者之間是如何協調工做的。經過axure畫出以下關係圖:ios
移動客戶端、瀏覽者、服務端三者協做關係圖git
【M】:表示移動端 【B】:表示瀏覽者(瀏覽器客戶端) 【S】:服務端,消息推送者及掃描認證接口發佈者github
步驟說明:web
Step(步驟)1 ,【B】瀏覽登陸頁面,Step2【S】產生一個標識符UUID,並推送給B,生成登陸二維碼;json
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】跳轉到首頁。
因爲本人用的是VS15Preview4,能夠直接使用Nuget可視化管理工具進行安裝:Tools—>Nuget Package Manager—>Manage Nuget Packages for Solution…,打開如下界面:
在Browser 標籤下輸入SignalR,查詢到Microsoft.AspNet.SignalR
找到對應的項目,點擊「Install」安裝按鈕便可引用相關類庫,同時應用下載相關js庫。
關於SignalR的知識點,能夠到官網 http://www.asp.net/signalr 進行深刻學習。
服務端要向客戶端推送UUID,對於UUID惟一標識符,具備重要特性:(1)有時間限制,120秒以內掃碼有效;(2)具備必定的狀態。對應的聲明週期就是:生成—>推送—>狀態判斷—>手機端掃描—>驗證UUID—>狀態判斷—>銷燬等系列過程。
服務端的核心代碼將單獨創建一個項目去實現:
本類將鏈接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); } } }
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); } } }
對【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; } } } }
知足時鐘、獲取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; } } }
在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); } } }
QRCodeAction.cs:維護UUID,建立、保存、狀態更改、刪除等。
QRModel.cs:UUID實體
全部文件,可在《七、總結與下載》中下載。
添加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()); }); });
以上代碼包括相關二維碼的生成。
二維碼類庫選擇https://github.com/jeromeetienne/jquery-qrcode 一個QRCode原生態js類庫,jquery對其進行了擴展。
添加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,可實現具備180秒生命週期二維碼的生成,對於不一樣的瀏覽者,生成的二維碼是不一樣的,效果以下:
二維碼生成了,可是存儲的是什麼呢?首先咱們看下如下的二維:
![]() |
![]() |
hbuilder官網 |
千牛電腦客戶端二維碼登陸界面 |
顯然,掃描這兩個圖片上的二維碼會獲得不一樣的結果。對某些二維碼的解碼要對應配套的客戶端才能起到做用,不然用其餘工具解析出來也就是字符串。
在本系統中,二維碼存儲的是一個json對象,格式爲:
{"connectionid":"19c12e95-26d7-410c-8292-2a3afdd1a4da","uuid":"a04702df-6a52-4e1c-be8b-9b3dbeef4d72","islogin":0,"isvalid":1}
connectionid:客戶端與SignalR聯繫的id,其格式爲Guid
手機客戶端掃描以後,可根據這些參數狀況進行判斷,是否向服務端發送請求。在作掃描應用(好比掃描登陸)時,要依據業務場景進行消息傳遞,生成對應二維碼,並不侷限於json對象、url地址等。
總結下來,二維碼應用場景,以下圖:
爲了知足【M】端掃描以後,提交UUID+用戶信息進行認證,創建QRCode API接口。接口任務比較簡單,就是對UUID合法性進行判斷,而後判斷用戶信息登陸狀況,更改UUID的登陸狀態。
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; } } }
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; } } }
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; } } }
二維碼中能夠加入圖片嗎?
文中二維碼 有個圖片上面有 M 字母是怎麼處理的?
第一個問題:是把存儲圖片信息存儲到二維碼中,手機掃碼能夠識別吧?這個問題涉及到二維碼的存儲容量,理論上若是二維碼的存儲容量足夠大,可把圖片序列化成01的字符進行存儲,掃描就能夠識別。但二維碼有不一樣的標準,不一樣標準下數據容量是不一樣的。建議不要存儲圖片,詳情可查看知乎,瞭解一下:http://www.zhihu.com/question/20387257
M字母是一個圖片,來自http://www.dcloud.io/,只須要把想放的圖放到已生成的二維碼中間便可,但圖片不宜過大,調試一下,用手機識別一下。有興趣的朋友能夠查看草榴二維碼:http://cli.im/
疑問: 輸入參數有 用戶名和密碼,那個是每次都須要用戶輸入的?仍是經過掃描二維碼得到的? 仍是哪一種方式來給 輸入參數的用戶名和密碼賦值的。
我想了解樓主是按哪一種方式實現的呢?
首先要理解一下掃描登陸的流程,【M】掃描二維碼只獲取相關【B】的惟一標識符信息,掃碼以後,【M】(前提是【M】必須已經登陸成功)發送用戶名\密碼\UUID到【S】進行一系列的驗證;爲了提升安全性,在【M】提交數據時,對密碼進行md5時間戳加密。
能夠這樣不 在手機端隨機生成碼 加密存在手機上並上傳服務器 後端生成帶有該碼加時間的二維碼 網頁掃的時候對比登錄
要實現掃描登陸,弄懂一個問題:爲何掃描二維碼以後,提交給服務器的數據就是當前頁面所需的呢?在本項目中,是經過SignalR的固有通訊connectionid來確認的。你所說的流程應該以下:
在本流程圖中,比方案中的步驟延長了;在Step2中,會出現問題,如何將【M】推送過來的UUID推送到你看到的【B】端?顯然缺乏紐帶。本方法是不可行的。
二維碼應用比較普遍,記得去北京的故宮旁邊的中山公園,裏面的古樹也有二維碼,掃描可查看相關聯信息。牢牢對於二維碼而言就是存儲有限信息,但就是這有限的信息,能夠將龐大的信息系統鏈接一塊兒,所用的應用不是前沿技術的突破,而是咱們思考問題方式的轉變、思惟角度的變化。因爲二維碼具備信息存儲的獨特性,可在如下方面應用:
因爲最近在作短信業務平臺,將二維碼應用到營銷管理中,每一個業務人員具備獨立的推廣二維碼,客戶掃碼可進行短信測試,若註冊成爲會員則就是本業務人員的直屬客戶,可查看《二維碼在短信業務應用的初步構思》。
最後,上傳《基於SignalR的消息推送與二維碼描登陸實現》主要文件下載:http://files.cnblogs.com/files/zsy/signalr%E4%B8%8Eqrcode.rar