- SinglR理解:它就相当于路由器的作用;收集客户端的请求,将相应请求对应消息发,根据不同需求发回客户端
基本使用
- 安装包:Microsoft.AspNetCore.SignalR.StackExchangeRedis
- 在程序中注入服务:builder.Services.AddSignalR();添加hub类:app.MapHub<ChatRoomHub>("/ChatRoomHub");
- 创建一个继承hub的类,代码如下
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using System.Security.Claims;
using static signalRTest.UserManeger;
namespace signalRTest
{
// [Authorize]用于身份验证
[Authorize]
public class ChatRoomHub:Hub
{
public Task SendPublicMessage(string message)
{
string connId = this.Context.ConnectionId;
string msg = $"{connId} {DateTime.Now}:{message}";
return Clients.All.SendAsync("ReceivePublicMessage", msg);
}
public async Task<string> SendPrivateMessage(string name, string message)
{
User? user=UserManeger.FindByName(name);
if (user == null) { return "DestUserNotDefind"; }
string destUserId=user.id.ToString();
string srcSendUser = this.Context.User!.FindFirst(ClaimTypes.Name)!.Value;
string time = DateTime.Now.ToString();
await this.Clients.User(destUserId).SendAsync("ReceivePriveteMessage", srcSendUser, time, message);
return "ok";
}
}
}
(后端工作完成)
- 前端
<template>
<input type="text" v-model="state.userMessage" v-on:keypress="txtMsgOnkeypress"/>
<div><ul>
<li v-for="(msg,index) in state.messages" :key="index">{{msg}}</li>
</ul></div>
</template>
<script>
import { reactive, onMounted } from 'vue';
import * as signalR from '@microsoft/signalr';
let connection;
export default {name: 'Login',
setup() {
const state = reactive({ userMessage: "", messages: [] });
const txtMsgOnkeypress = async function (e) {
if (e.keyCode != 13) return;
//hub类中发消息的方法
await connection.invoke("SendPublicMessage", state.userMessage);
state.userMessage = "";
};
onMounted(async function () {
connection = new signalR.HubConnectionBuilder()
.withUrl('https://localhost:44337/Hubs/ChatRoomHub')//自己的hub类的路径
.withAutomaticReconnect().build();
await connection.start();
//发送消息的key
connection.on('ReceivePublicMessage', msg => {
state.messages.push(msg);
});
});
return { state, txtMsgOnkeypress };
},
}
</script>
- 前后端交互属于跨域,所以要启用中间件UseCors();
- program.cs配置如下
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using signalRTest; using System.Text; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); //注入jwt; builder.Services.Configure<JWTOption>(builder.Configuration.GetSection("JWT")); //将jwt引入到swagger builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(x => { var jwtOpt = builder.Configuration.GetSection("JWT").Get<JWTOption>(); byte[] keyBytes = Encoding.UTF8.GetBytes(jwtOpt.SigningKey); var secKey = new SymmetricSecurityKey(keyBytes); x.TokenValidationParameters = new() { ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = true, ValidateIssuerSigningKey = true, IssuerSigningKey = secKey }; //从请求头中拿到Token,用于身份验证 x.Events = new JwtBearerEvents { OnMessageReceived = context => { var accessToken = context.Request.Query["access_token"]; var path = context.HttpContext.Request.Path; if (!string.IsNullOrEmpty(accessToken) && (path.StartsWithSegments("/ChatRoomHub"))) { context.Token = accessToken; } return Task.CompletedTask; } }; }); //注入SignalR服务 builder.Services.AddSignalR(); //配置跨域交互 string[] urls = new[] { "http://localhost:3000" }; builder.Services.AddCors(options => options.AddDefaultPolicy(builder => builder.WithOrigins(urls) .AllowAnyMethod().AllowAnyHeader().AllowCredentials()) ); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseCors(); app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); //添加我们的hub类 app.MapHub<ChatRoomHub>("/ChatRoomHub"); app.MapControllers(); app.Run();
(基本使用结束)
SignalR的身份认证(我做的时候不方便连数据库,所以自己写usermanerg类代替)
- 安装Microsoft.AspNetCore.Authentication.JwtBearer(jwt)、Microsoft.AspNetCore.Identity.EntityFrameworkCore
- 在hub类加上[Authorize]
- 在appsettings.json配置jwt密钥、超时,添加jwoption类
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "JWT": { "SigningKey": "fasdfad&9045dafz222#fadpio@02328hdahihiasuighcaui", "ExpireSeconds": "86400" } }
namespace signalRTest { public class JWTOption { public string SigningKey { get; set; } public int ExpireSeconds { get; set; } } }
- 我的usermanager类
using System.Runtime.CompilerServices; namespace signalRTest { public class UserManeger { public static User? FindByName(string name) { return GetAll().SingleOrDefault(u => u.name == name); } public static User? FindById(int id) { return GetAll().SingleOrDefault(u => u.id == id); } public static List<User> GetAll() { List<User> users = new List<User>(); users.Add(new User(1, "yzk", "123456")); users.Add(new User(2, "siruis", "123456")); users.Add(new User(3, "jack", "123456")); users.Add(new User(4, "lili", "123456")); return users; } public record class User(int id, string name, string password) { } } }
- 前端代码:
<template> <fieldset> <legend>登录</legend> <div> <div> 用户名:<input type="text" v-model="state.loginData.UserName" /> </div> <div> 密 码:<input type="password" v-model="state.loginData.PassWord" /> </div> <div> <input type="button" value="登录" v-on:click="loginClick" /> </div> </div> </fieldset> 公屏:<input type="text" v-model="state.userMessage" v-on:keypress="txtMsgOnkeypress" /> <div> 私聊给:<input type="text" v-model="state.privateMsg.destUserName" /> 信息:<input type="text" v-model="state.privateMsg.message" v-on:keypress="txtPrivareMsgOnkeypress" /> </div> <div> <ul> <li v-for="(msg,index) in state.messages" :key="index">{{msg}}</li> </ul> </div> </template> <script> import { reactive, onMounted } from 'vue'; import * as signalR from '@microsoft/signalr'; import axios from 'axios'; let connection; export default { name: 'SignalR', setup() { const state = reactive({ userMessage: "", messages: [], accessTocken:"", loginData:{UserName:"",PassWord:""}, privateMsg:{destUserName:"",message:""} }); const startConn = async function () { const transport = signalR.HttpTransportType.WebSockets; const options = { skipNegotiation: true, transport: transport }; options.accessTokenFactory = () => state.accessTocken; connection = new signalR.HubConnectionBuilder() .withUrl('https://localhost:7229/ChatRoomHub', options) .withAutomaticReconnect().build(); try { await connection.start(); } catch (err) { alert(err); return; } connection.on('ReceivePublicMessage', msg => { state.messages.push(msg); }); connection.on('ReceivePriveteMessage', (srcUser,time,msg) => { state.messages.push(srcUser+"在"+time+"发来私信:"+msg); }); connection.on('UserAdded', userName => { state.messages.push("系统消息:欢迎" + userName+"加入我们!"); }); alert("登陆成功可以聊天了"); }; const loginClick = async function(){ const req=await axios.post('https://localhost:7229/api/UserLong/Longin',state.loginData); state.accessTocken=req.data; startConn(); }; const txtMsgOnkeypress = async function(e) { if (e.keyCode != 13) return; await connection.invoke("SendPublicMessage", state.userMessage); state.userMessage = ""; }; const txtPrivareMsgOnkeypress =async function (e) { if(e.keyCode!=13)return; var destUserName=state.privateMsg.destUserName; var msg=state.privateMsg.message; try{ const ret=await connection.invoke("SendPrivateMessage",destUserName,msg); if(ret!="ok"){alert(ret)}; }catch(err){ alert(err); return; } state.privateMsg.message="" } return { state, txtMsgOnkeypress, loginClick, txtPrivareMsgOnkeypress }; }, } </script>