基于SignalR的消息推送与二维码描登录实现
1 概要說明
??? 使用微信掃描登錄相信大家都不會陌生吧,二維碼與手機結合產生了不同應用場景,基于二維碼的應用更是比較廣泛。為了滿足ios、android客戶端與web短信平臺的結合,特開發了基于SinglarR消息推送機制的掃描登錄。本系統涉及到以下知識點:
??? SignalR:http://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){//啟動SignalRapp.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
轉載于:https://www.cnblogs.com/dxqNet/p/10276214.html
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的基于SignalR的消息推送与二维码描登录实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: .net中的SelectList在Htm
- 下一篇: java判断时间为上午,中午,下午,晚上