一、前端
<script src="js/jquery-3.3.1.min.js"></script>
<script src="lib/SingnlR/signalr.js"></script>
<script type="text/javascript">
var url = "https://localhost:44314/chat"; //本地站点可以直接写“/chat”
var token = "token"; // JWT验证码。不带Bearer
const connection = new signalR.HubConnectionBuilder().withUrl(url, {
accessTokenFactory: () => token
}).build();
connection.on("ReceiveMessage", (message) => {
console.log(message);//后台返回的数据,可以是json格式
});
//发送给所有人
document.getElementById("sendAllButton").addEventListener("click", event => {
const message = “发给后台的数据”;
//发送消息
connection.invoke("SendToAll", message).catch(err => console.error(err.toString()));
event.preventDefault();
});
//发送给单个人
document.getElementById("sendOneButton").addEventListener("click", event => {
const user = “接收者唯一ID”;
const message = "发送给后台的数据";
//发送消息;参数(“后台的函数名”,“后台函数参数1”,“后台函数参数2”)
connection.invoke("Send", user, message).catch(err => console.error(err.toString()));
event.preventDefault();
});
connection.start().catch(err => console.error(err.toString()));
二、后台
[Authorize] //jwt权限验证特性
public class SignalrHub : Hub, ITransientDependency //ABP的框架依赖,不使用abp的可忽略
{
public IAbpSession AbpSession { private get; set; }
public ILogger Logger { private get; set; }
//系统中的UserId与SignalR生成的ConnectionId的对应关系
private static ConcurrentDictionary<long, string> userIds = new ConcurrentDictionary<long, string>();
public SignalrHub()
{
AbpSession = NullAbpSession.Instance;
Logger = NullLogger.Instance;
}
#region 建立连接时触发 + override async Task OnConnectedAsync()
/// <summary>
/// 建立连接时触发
/// </summary>
/// <returns></returns>
public override async Task OnConnectedAsync()
{
var userInfo = AbpSession.GetUserInfo();
if (userInfo == null || userInfo.ShopId == 0)
{
Logger.Error("链接SignalR未获取到用户信息");
return;
}
userIds.AddOrUpdate(userInfo.UserId, Context.ConnectionId, (uid, _) => Context.ConnectionId);
//await Clients.All.SendAsync("ReceiveMessage", new { user = Context.ConnectionId, message = "我进来了" });
}
#endregion
#region 离开连接时触发 + override async Task OnDisconnectedAsync(Exception ex)
/// <summary>
/// 离开连接时触发
/// </summary>
/// <param name="ex"></param>
/// <returns></returns>
public override async Task OnDisconnectedAsync(Exception ex)
{
var userInfo = AbpSession.GetUserInfo();
if (userInfo == null || userInfo.ShopId == 0)
{
Logger.Error("退出SignalR未获取到用户信息");
return;
}
userIds.Remove(userInfo.UserId, out string connectionId);
//await Clients.All.SendAsync("ReceiveMessage", new { user = Context.ConnectionId, message = "我离开了" });
}
#endregion
#region 向所有人推送消息 + Task SendToAll(string message)
/// <summary>
/// 向所有人推送消息
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
public Task SendToAll(string message)
{
return Clients.All.SendAsync("ReceiveMessage", new { user = Context.ConnectionId, message = message });
}
#endregion
#region 向指定组推送消息 + Task SendToGroup(string groupName, string message)
/// <summary>
/// 向指定组推送消息
/// </summary>
/// <param name="groupName"></param>
/// <param name="message"></param>
/// <returns></returns>
public Task SendToGroup(string groupName, string message)
{
return Clients.Group(groupName).SendAsync("ReceiveMessage", new { user = Context.ConnectionId, message = message });
}
#endregion
#region 加入指定组 + async Task JoinToGroup(string groupName)
/// <summary>
/// 加入指定组
/// </summary>
/// <param name="groupName"></param>
/// <returns></returns>
public async Task JoinToGroup(string groupName)
{
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
}
#endregion
#region 推出指定组 + async Task LeaveGroup(string groupName)
/// <summary>
/// 推出指定组
/// </summary>
/// <param name="groupName"></param>
/// <returns></returns>
public async Task LeaveGroup(string groupName)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
}
#endregion
#region 向指定Id推送消息 + async Task Echo(string userid, string message)
/// <summary>
/// 向指定Id推送消息
/// </summary>
/// <param name="userid">要推送消息的对象</param>
/// <param name="message"></param>
/// <returns></returns>
public async Task Send(string userid, string message)
{
await Clients.Client(userid).SendAsync("ReceiveMessage", new { user = Context.ConnectionId, message = message });
}
#endregion
三、Cors跨域
与平常的跨域配置没啥区别
//允许跨域(Startup.ConfigureServices中)
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddSignalR();
services.AddCors(options =>
{
options.AddPolicy("CorsName", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader().AllowCredentials());
});
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseCors("CorsName"); // 设置全局跨域
app.UseSignalR(routes =>
{
//SignalrHub为后台代码中继承Hub的类,“/chat”为请求路由地址;
routes.MapHub<SignalrHub>("/chat");
//可以配置多个地址
routes.MapHub<。。。省略号>("。。。");
});
}
四、Jwt权限验证
与平常的Jwt配置多了几行代码, 看代码中的“重点”位置
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "JwtBearer";
options.DefaultChallengeScheme = "JwtBearer";
}).AddJwtBearer("JwtBearer", options =>
{
options.Audience = "Audience";
options.TokenValidationParameters = new TokenValidationParameters
{
// The signing key must match!
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("SecurityKey")),
// Validate the JWT Issuer (iss) claim
ValidateIssuer = true,
ValidIssuer = "Issuer",
// Validate the JWT Audience (aud) claim
ValidateAudience = true,
ValidAudience = "Audience",
// Validate the token expiry
ValidateLifetime = true,
// If you want to allow a certain Account of clock drift, set that here
ClockSkew = TimeSpan.Zero
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = (context) =>{
if (!context.HttpContext.Request.Path.HasValue)
{
return Task.CompletedTask;
}
//重点在于这里;判断是Signalr的路径
var accessToken = context.HttpContext.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!(string.IsNullOrWhiteSpace(accessToken)) && path.StartsWithSegments("/chat"))
{
context.Token = accessToken;
return Task.CompletedTask;
}
return Task.CompletedTask;
}
};
});
五、Nginx配置参考
里面包含了Nginx的SignalR配置:https://blog.csdn.net/qq_26900081/article/details/102912807