.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
相关推荐
波波0071 天前
每日一题:中间件是如何工作的?
中间件·.net·面试题
无风听海1 天前
.NET 10之可空引用类型
数据结构·.net
码云数智-园园1 天前
基于 JSON 配置的 .NET 桌面应用自动更新实现指南
.net
无风听海1 天前
.NET 10 之dotnet run的功能
.net
岩屿1 天前
Ubuntu下安装Docker并部署.NET API(二)
运维·docker·容器·.net
码云数智-大飞1 天前
.NET 中高效实现 List 集合去重的多种方法详解
.net
easyboot1 天前
使用tinyply.net保存ply格式点云
.net
张人玉1 天前
WPF 多语言实现完整笔记(.NET 4.7.2)
笔记·.net·wpf·多语言实现·多语言适配
波波0072 天前
Native AOT 能改变什么?.NET 预编译技术深度剖析
开发语言·.net
Crazy Struggle3 天前
.NET 中如何快速实现 List 集合去重?
c#·.net