這幾天在研究SignalR,網上大部分的例子都是聊天室,我的需求是把服務端的信息發送給前端展示。並且需要實現單個用戶推送。
用戶登錄我用的是ClaimsIdentity,這里就不多解釋,如果不是很了解,可以看這篇文章https://www.cnblogs.com/zhangjd/p/11332558.html
推薦https://www.cnblogs.com/laozhang-is-phi/p/netcore-vue-signalr.html#tbCommentBody這個博客,寫的很詳細,並且附有Dome
一、后端實現
1、引用SignalR包
Install-Package Microsoft.AspNetCore.SignalR
2、聲明一個類來記錄用戶的連接信息。
1 public class SignalRModel 2 { 3 public static Dictionary<string, SignalRStatus> StaticList = new Dictionary<string, SignalRStatus>(); 4 public static Dictionary<string, string> SignalRList { get; set; } = new Dictionary<string, string>(); 5 }
3、聲明Hub,這里我重寫了連接和斷開方法,用來綁定用戶和連接的ConnectionId。(這個比較復雜,是因為我程序中執行的第三方程序,需要實時輸出當前執行的程序的日志。但是調用的執行不可能直接寫在控制器里,這樣調用我沒辦法獲取當前用戶的登錄Id。然后我就在發起連接和斷開連接的方法處理了。)
1 public class ChatHub : Hub 2 { 3 /// <summary> 4 /// 連接成功 5 /// </summary> 6 /// <returns></returns> 7 public override Task OnConnectedAsync() 8 { 9 var id = this.Context.ConnectionId; 10 var claimNameIdentifier = this.Context.User.Claims.FirstOrDefault(s => s.Type == ClaimTypes.NameIdentifier)?.Value; 11 SignalRModel.SignalRList.Add(id, claimNameIdentifier); 12 if (SignalRModel.StaticList.Any(s => s.Key.Equals(claimNameIdentifier))) 13 { 14 SignalRModel.StaticList.Remove(claimNameIdentifier); 15 } 16 SignalRModel.StaticList.Add(claimNameIdentifier, SignalRStatus.Open); 17 return base.OnConnectedAsync(); 18 } 19 /// <summary> 20 /// 斷開連接 21 /// </summary> 22 public override Task OnDisconnectedAsync(Exception exception) 23 { 24 var id = this.Context.ConnectionId; 25 var claimNameIdentifier = this.Context.User.Claims.FirstOrDefault(s => s.Type == ClaimTypes.NameIdentifier)?.Value; 26 SignalRModel.SignalRList.Remove(id); 27 SignalRModel.StaticList.Remove(claimNameIdentifier); 28 return base.OnDisconnectedAsync(exception); 29 } 30 /// <summary> 31 /// 發送消息 32 /// </summary> 33 /// <param name="user"></param> 34 /// <param name="message"></param> 35 /// <returns></returns> 36 public async Task SendMessage(string user, string message) 37 { 38 await Clients.All.SendAsync("ReceiveMessage", user, message); 39 } 40 }
4、在程序啟動的時候,把記錄用戶連接信息的類,注入成單例,保存用戶和連接的對應關系,方便單個通信。
1 services.AddSingleton<SignalRModel>(provider => 2 { 3 return new SignalRModel(); 4 });
5、配置
1)、在ConfigureServices中加入
services.AddSignalR();//要寫在addmvc()前面
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
2)、在Configure中加入
app.UseMvc();
app.UseSignalR(routes => { routes.MapHub<ChatHub>("/api/chatHub"); });//要寫在UseMvc后面
6、這里我寫了后端兩個接口來發送消息,區別在於第一個是群發,第二個是針對一個連接發送的。
1 [HttpGet("SendAll")] 2 public IActionResult SendAll() 3 { 4 _hubContext.Clients.All.SendAsync("ReceiveUpdate", "推送全部人").Wait(); 5 return Ok("推送全部人"); 6 } 7 [HttpGet("SendOnly")] 8 public IActionResult SendOnly() 9 { 10 var claimNameIdentifier = User.Claims.FirstOrDefault(s => s.Type == ClaimTypes.NameIdentifier)?.Value; 11 if (string.IsNullOrEmpty(claimNameIdentifier)) 12 { 13 return Ok(new { code = ResultCode.NotLogin, message = "用戶未登陸!" }); 14 } 15 _hubContext.Clients.Clients(claimNameIdentifier).SendAsync("ReceiveUpdate", DateTime.Now).Wait(); 16 return Ok("推送當前登錄用戶"); 17 }
7、我項目實際用到的是這樣的,給當前登錄用戶發送日志消息,判斷連接是否斷開,如果斷開需要獲取前面寫的日志,發送給前端之后,把連接的狀態改成連接中,后面就正常發送。
1 foreach (var item in SignalRModel.SignalRList.Where(s => s.Value.Equals(userId.ToString())).ToList()) 2 { 3 if (SignalRModel.StaticList.Any(s => s.Key.Equals(userId.ToString()) && s.Value == SignalRStatus.Open)) 4 { 5 if (SignalRModel.StaticList.Any(s => s.Key.Equals(userId.ToString()))) 6 { 7 SignalRModel.StaticList.Remove(userId.ToString()); 8 } 9 SignalRModel.StaticList.Add(userId.ToString(), SignalRStatus.working); 10 _hubContext.Clients.Client(item.Key).SendAsync("ReceiveUpdate", FileHelper.ReadFile(Path.Combine(filePath, "tls_simplify.txt"), Encoding.UTF8)).Wait(); 11 } 12 _hubContext.Clients.Client(item.Key).SendAsync("ReceiveUpdate", args.Data).Wait(); 13 }
二、前端vue
1、安裝依賴包
npm install @aspnet/signalr
2、示例頁面
1 <template> 2 <section> 3 <div style="display: none1"> 4 <el-form ref="form" label-width="80px" @submit.prevent="onSubmit" 5 style="margin:20px;width:60%;min-width:600px;"> 6 <el-form-item label="用戶名"> 7 <el-input v-model="userName"></el-input> 8 </el-form-item> 9 <el-form-item label="密碼"> 10 <el-input v-model="userMessage"></el-input> 11 </el-form-item> 12 </el-form> 13 <ul v-for="(item, index) in messages" v-bind:key="index + 'itemMessage'"> 14 <li><b>Name: </b>{{item.user}}</li> 15 <li><b>Message: </b>{{item.message}}</li> 16 </ul> 17 <p> 18 <b>后台發送消息: </b>{{this.postMessage}} 19 </p> 20 <el-button type="primary" @click="submitCard">登錄</el-button> 21 <el-button type="primary" @click="getLogs">查詢</el-button> 22 </div> 23 </section> 24 </template> 25 26 <script> 27 28 import * as signalR from "@aspnet/signalr"; 29 30 export default { 31 name: 'Dashboard', 32 data() { 33 return { 34 filters: { 35 LinkUrl: '' 36 }, 37 listLoading: true, 38 postMessage: "", 39 userName: "Tom", 40 userMessage: "123", 41 connection: "", 42 messages: [], 43 t: "" 44 45 } 46 }, 47 methods: { 48 getRoles() { 49 let thisvue=this; 50 let para = { 51 page: this.page, 52 key: this.filters.LinkUrl 53 }; 54 this.listLoading = true; 55 thisvue.connection.start().then(() => { 56 thisvue.connection.invoke('GetLatestCount', 1).catch(function (err) { 57 return console.error(err); 58 }); 59 }); 60 }, 61 submitCard: function () { 62 if (this.userName && this.userMessage) { 63 this.connection.invoke('SendMessage', this.userName, this.userMessage).catch(function (err) { 64 return console.error(err); 65 }); 66 67 } 68 }, 69 getLogs: function () { 70 this.listLoading = true; 71 this.connection.invoke('GetLatestCount', 1).catch(function (err) { 72 return console.error(err); 73 }); 74 } 75 }, 76 created: function () { 77 let thisVue = this; 78 thisVue.connection = new signalR.HubConnectionBuilder() 79 .withUrl('http://localhost:5000/api/chatHub') 80 .configureLogging(signalR.LogLevel.Information) 81 .build(); 82 thisVue.connection.on('ReceiveMessage', function (user, message) { 83 thisVue.messages.push({user, message}); 84 }); 85 86 thisVue.connection.on('ReceiveUpdate', function (update) { 87 console.info('update success!') 88 thisVue.listLoading = false; 89 thisVue.postMessage = update; 90 window.clearInterval(this.t) 91 }) 92 }, 93 mounted() { 94 this.getRoles(); 95 }, 96 beforeDestroy() { 97 window.clearInterval(this.t) 98 this.connection.stop(); 99 } 100 } 101 </script> 102 103 <style scoped> 104 .demo-table-expand { 105 font-size: 0; 106 } 107 108 .demo-table-expand label { 109 width: 90px; 110 color: #99a9bf; 111 } 112 113 .demo-table-expand .el-form-item { 114 margin-right: 0; 115 margin-bottom: 0; 116 width: 30%; 117 } 118 119 .EXC { 120 color: red; 121 } 122 </style>