.net 6 signalr

1)Startup.cs

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();
    // 注册 ChatHub
    endpoints.MapHub<ChatHub>("/chathub")
             .RequireCors("AllowAll"); // Hub 也要应用 CORS 策略
});

2)ChatHub.cs

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)老师端

javascript 复制代码
<script src="signalr.min.js"></script>
<script>
	// 当前用户标识(CompetitionSessionId_t_UserId)
	// 场次1_老师_老师1
	const 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)学生端

javascript 复制代码
<script src="signalr.min.js"></script>
<script>
	// 当前用户标识(CompetitionSessionId_s_CompetitionUserId)
	// 场次1_学生_学生1
	const 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
相关推荐
许泽宇的技术分享5 小时前
从零到一:基于.NET 9.0构建企业级AI智能体对话平台的实战之旅
人工智能·.net·ai智能体·a2a协议·agent framework
IDOlaoluo5 小时前
Microsoft.NET安装步骤详解(.NET Framework/.NET 6/7/8安装教程)
microsoft·.net
贪嘴6 小时前
.NET MCP Server 开发教程
.net
L X..11 小时前
Unity反射调用 ReactiveProperty<T>(泛型类型)内部方法时崩溃
unity·c#·游戏引擎·.net
CodeCraft Studio12 小时前
如何从 FastReport .NET 将报表导出为 JPEG / PNG / BMP / GIF / TIFF / EMF
windows·.net·报表开发·报表工具·fastreport·报表转图片
缺点内向14 小时前
C# 中 Excel 工作表打印前页面边距的设置方法
c#·.net·excel
喵叔哟15 小时前
6. 从0到上线:.NET 8 + ML.NET LTR 智能类目匹配实战--渐进式学习闭环:从反馈到再训练
学习·机器学习·.net
喵叔哟1 天前
4. 从0到上线:.NET 8 + ML.NET LTR 智能类目匹配实战--从业务到方案:数据与特征工程:从 CSV 到可训练的 LTR 样本
.net