.net 6 signalr
1)Startup.cs
//添加 SignalR 服务services.AddSignalR();// 添加 CORS 策略(允许前端访问)services.AddCors(options =>{options.AddPolicy("AllowAll", builder =>{builder.WithOrigins(AppsettingsEntity.allowCors) // 前端地址.AllowAnyHeader().AllowAnyMethod().AllowCredentials(); // SignalR 必须允许凭证});});// 启用 CORS(一定要放在 UseRouting 之后,UseEndpoints 之前)app.UseCors("AllowAll");app.UseEndpoints(endpoints =>
{endpoints.MapControllers();// 注册 ChatHubendpoints.MapHub<ChatHub>("/chathub").RequireCors("AllowAll"); // Hub 也要应用 CORS 策略
});
2)ChatHub.cs
using Common.PlatformEnum;
using Microsoft.AspNetCore.SignalR;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;namespace Platform.Api.Hubs
{public class ChatHub : Hub{// 存储用户与连接ID的映射(一个用户可能有多个连接,比如多个浏览器标签页)private static readonly ConcurrentDictionary<string, HashSet<string>> _userConnections = new();// 当用户连接上时触发public override Task OnConnectedAsync(){var user = Context.GetHttpContext()?.Request.Query["user"];if (!string.IsNullOrEmpty(user)){_userConnections.AddOrUpdate(user!,_ => new HashSet<string> { Context.ConnectionId },(_, connections) =>{connections.Add(Context.ConnectionId);return connections;});}return base.OnConnectedAsync();}// 当用户断开连接时触发public override Task OnDisconnectedAsync(Exception? exception){foreach (var kvp in _userConnections){if (kvp.Value.Contains(Context.ConnectionId)){Logout(kvp.Key);kvp.Value.Remove(Context.ConnectionId);if (kvp.Value.Count == 0)_userConnections.TryRemove(kvp.Key, out _);break;}}return base.OnDisconnectedAsync(exception);}// 向所有人发送(原始示例)public async Task SendMessage(string user, string message){await Clients.All.SendAsync("ReceiveMessage", user, message);}// 向指定用户发送消息public async Task SendMessageToUser(string toUser, string formUser, SignalrEnum signalrEnum, string message){if (_userConnections.TryGetValue(toUser, out var connections)){foreach (var connectionId in connections){await Clients.Client(connectionId).SendAsync("ReceiveMessage", formUser,(int)signalrEnum, message);}}}/// <summary>/// 学生进入竞赛场次/// </summary>/// <param name="formUser">学生signalr用户标识</param>/// <returns></returns>public async Task StudentLogin(string formUser){string[] userSplit = formUser.Split('_');string session = userSplit[0];var _userConnection = _userConnections.FirstOrDefault(a => a.Key.StartsWith($"{session}_t"));if (_userConnection.Key != null){string toUser = _userConnection.Key;await SendMessageToUser(toUser, formUser, SignalrEnum.学生进入竞赛场次, $"{formUser}进入竞赛场次");}}/// <summary>/// 老师进入竞赛场次/// </summary>/// <param name="formUser">老师signalr用户标识</param>/// <returns></returns>public async Task TeacherLogin(string formUser){string[] userSplit = formUser.Split('_');string session = userSplit[0];var _tempUserConnections = _userConnections.Where(a => a.Key.StartsWith($"{session}_s"));string studentids = string.Join(",", _tempUserConnections.Select(a => a.Key.Split('_')[2]));await SendMessageToUser(formUser, formUser, SignalrEnum.老师进入竞赛场次, studentids);}/// <summary>/// 学生离开竞赛场次/// </summary>/// <param name="formUser">学生signalr用户标识</param>/// <returns></returns>public async Task Logout(string formUser){string[] userSplit = formUser.Split('_');string session = userSplit[0];string type = userSplit[1];if (type == "s"){var _userConnection = _userConnections.FirstOrDefault(a => a.Key.StartsWith($"{session}_t"));if (_userConnection.Key != null){string toUser = _userConnection.Key;await SendMessageToUser(toUser, formUser, SignalrEnum.学生离开竞赛场次, $"{formUser}离开竞赛场次");}}}/// <summary>/// 收卷/// </summary>/// <param name="formUser">老师signalr用户标识</param>/// <param name="toUserId">学生数据库主键,如果是多个用英文逗号隔开</param>/// <returns></returns>public async Task Finish(string formUser, string toUserId){string[] userSplit = formUser.Split('_');string session = userSplit[0];if (string.IsNullOrEmpty(toUserId)){//全场收卷var _tempUserConnections = _userConnections.Where(a => a.Key.StartsWith($"{session}_s"));foreach (var _userConnection in _tempUserConnections){string toUser = _userConnection.Key;await SendMessageToUser(toUser, formUser, SignalrEnum.收卷, $"{formUser}收卷");}}else{//单个收卷foreach (string touserid in toUserId.Split(',')){var _userConnection = _userConnections.FirstOrDefault(a => a.Key.StartsWith($"{session}_s_{touserid}"));if (_userConnection.Key != null){string toUser = _userConnection.Key;await SendMessageToUser(toUser, formUser, SignalrEnum.收卷, $"{formUser}收卷");}}}}/// <summary>/// 延迟/// </summary>/// <param name="formUser">老师signalr用户标识</param>/// <param name="toUserId">学生数据库主键,如果是多个用英文逗号隔开</param>/// <returns></returns>public async Task Delay(string formUser, string toUserId){string[] userSplit = formUser.Split('_');string session = userSplit[0];if (string.IsNullOrEmpty(toUserId)){//全场延迟var _tempUserConnections = _userConnections.Where(a => a.Key.StartsWith($"{session}_s"));foreach (var _userConnection in _tempUserConnections){string toUser = _userConnection.Key;await SendMessageToUser(toUser, formUser, SignalrEnum.延迟, $"{formUser}延迟");}}else{//单个延迟foreach (string touserid in toUserId.Split(',')){var _userConnection = _userConnections.FirstOrDefault(a => a.Key.StartsWith($"{session}_s_{touserid}"));if (_userConnection.Key != null){string toUser = _userConnection.Key;await SendMessageToUser(toUser, formUser, SignalrEnum.延迟, $"{formUser}延迟");}}}}}
}
3)老师端
<script src="signalr.min.js"></script>
<script>// 当前用户标识(CompetitionSessionId_t_UserId)// 场次1_老师_老师1const currentUser = "p1_t_t1";// 建立连接时,把 user 作为参数传给后端const connection = new signalR.HubConnectionBuilder()// .withUrl(`http://47.111.227.47:8082/chathub?user=${currentUser}`).withUrl(`http://localhost:8082/chathub?user=${currentUser}`).build();// 接收消息事件connection.on("ReceiveMessage", (user, type, message) => {console.log(`收到来自 ${user} 的消息: signalr操作类型:${type},内容:${message}`);});// 启动连接connection.start().then(() => {console.log("已连接 SignalR Hub 作为用户:", currentUser);// 老师进入竞赛场次connection.invoke("TeacherLogin", currentUser).catch(console.error);}).catch(console.error);// 全场收卷function finish() {connection.invoke("Finish", currentUser, '').catch(console.error);}// 单个收卷(如果是多个用英文逗号隔开)function singleFinish() {connection.invoke("Finish", currentUser, 's1,s2').catch(console.error);}// 全场延迟function delay() {connection.invoke("Delay", currentUser, '').catch(console.error);}// 单个延迟(如果是多个用英文逗号隔开)function singlDelay() {connection.invoke("Delay", currentUser, 's1').catch(console.error);}
</script>
场次1_老师_老师1
<button onclick="finish()">全场收卷</button>
<button onclick="singleFinish()">单收卷</button>
<button onclick="delay()">全场延时</button>
<button onclick="singlDelay()">单个延时</button>
4)学生端
<script src="signalr.min.js"></script>
<script>// 当前用户标识(CompetitionSessionId_s_CompetitionUserId)// 场次1_学生_学生1const currentUser = "p1_s_s1";// 建立连接时,把 user 作为参数传给后端const connection = new signalR.HubConnectionBuilder().withUrl(`http://localhost:8082/chathub?user=${currentUser}`).build();// 接收消息事件connection.on("ReceiveMessage", (user, type, message) => {console.log(`收到来自 ${user} 的消息: signalr操作类型:${type},内容:${message}`);});// 启动连接connection.start().then(() => {console.log("已连接 SignalR Hub 作为用户:", currentUser);// 学生进入竞赛场次connection.invoke("StudentLogin", currentUser).catch(console.error);}).catch(console.error);
</script>
场次1_学生_学生1