这几天在研究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;60%;min-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 90px; 110 color: #99a9bf; 111 } 112 113 .demo-table-expand .el-form-item { 114 margin-right: 0; 115 margin-bottom: 0; 116 30%; 117 } 118 119 .EXC { 120 color: red; 121 } 122 </style>