文章目录
前言
SignalR 提供了强大的消息发送机制,支持向特定用户、组或所有客户端广播消息。
一、消息发送的核心概念
1.客户端标识
- 连接 ID (Context.ConnectionId):每次客户端连接时生成的唯一标识符,用于精确识别某个连接。
- 用户标识 (Context.UserIdentifier):与身份验证关联的用户唯一标识(如数据库 ID),可关联多个连接(同一用户多设备登录)。
2.消息接收范围
- 所有客户端:Clients.All
- 特定用户:Clients.User(userId)
- 特定连接:Clients.Client(connectionId)
- 特定组:Clients.Group(groupName)
- 除发送者外的客户端:Clients.Others
二、向特定用户发送消息
管理员向指定用户发送私信,或用户之间一对一聊天。
- 代码如下(示例):
/// <summary> /// 向特定用户发送消息 /// </summary> /// <param name="toUserName">接收者</param> /// <param name="content">发送的消息</param> /// <returns></returns> public async Task SendPrivateMsgAsync(string toUserName, string content) { try { var senderUserID = Context.UserIdentifier; var senderUser= await userManager.FindByIdAsync(senderUserID); var toUser = await userManager.FindByNameAsync(toUserName); await Clients.User(toUser.Id.ToString()).SendAsync("ReceivePrivateMsg", senderUser.UserName, content); } catch (Exception ex) { throw; } }
- 关键点
- 用户标识:需通过身份验证系统获取(如 JWT 的 sub 声明)。
- 多设备支持:同一用户的多个连接都会收到消息。
三、向组发送消息
聊天室、工作群组、通知订阅等。
代码如下(示例):
// 在内存中缓存组信息以提高性能 private static readonly ConcurrentDictionary<string, GroupInfo> _groups = new ConcurrentDictionary<string, GroupInfo>(); /// <summary> /// 创建自定义组 /// </summary> /// <param name="groupName"></param> /// <returns></returns> public async Task CreateGroup(string groupName) { long userId = Convert.ToInt64(Context.UserIdentifier); if (_groups.ContainsKey(groupName)) { await Clients.Caller.SendAsync("GroupCreationFailed", "组已存在"); return; } // 创建新组并保存到数据库 var group = new Group { GroupName = groupName, CreatedAt = DateTime.UtcNow, CreatorId = userId }; myDbContext.Groups.Add(group); await myDbContext.SaveChangesAsync(); // 添加到内存缓存 var groupInfo = new GroupInfo { GroupId = group.GroupId, GroupName = groupName, MemberIds = new HashSet<long> { userId } }; _groups.TryAdd(groupName, groupInfo); // 创建者自动加入组 await AddUserToGroup(groupName, userId); await Clients.All.SendAsync("GroupCreated", groupName); } private async Task AddUserToGroup(string groupName, long userId) { try { var groupInfo = _groups[groupName]; // 添加到数据库 var groupMember = new GroupMember { GroupId = groupInfo.GroupId, UserId = userId, JoinedAt = DateTime.UtcNow }; myDbContext.GroupMembers.Add(groupMember); await myDbContext.SaveChangesAsync(); } catch (Exception) { throw; } } /// <summary> /// 加入自定义组 /// </summary> /// <param name="groupName"></param> /// <returns></returns> public async Task JoinGroup(string groupName) { var userId = Convert.ToInt64(Context.UserIdentifier); if (!_groups.TryGetValue(groupName, out var groupInfo)) { await Clients.Caller.SendAsync("JoinGroupFailed", "组不存在"); return; } if (groupInfo.MemberIds.Contains(userId)) { await Clients.Caller.SendAsync("JoinGroupFailed", "您已在该组中"); return; } // 添加用户到组 await AddUserToGroup(groupName, userId); // 更新内存缓存 groupInfo.MemberIds.Add(userId); // 将用户加入 SignalR 组 await Groups.AddToGroupAsync(Context.ConnectionId, groupName); await Clients.Group(groupName).SendAsync("UserJoinedGroup", Context.User.Identity.Name, groupName); //try //{ // if (_groups.ContainsKey(groupName)) // { // await Groups.AddToGroupAsync(Context.ConnectionId, groupName); // _groups[groupName].Add(Context.ConnectionId); // await Clients.Group(groupName).SendAsync("UserJoinGroup", Context.UserIdentifier, groupName); ; // } //} //catch (Exception ex) //{ // throw; //} } /// <summary> /// 用户离开自定义组 /// </summary> /// <param name="groupName"></param> /// <returns></returns> public async Task LeaveGroup(string groupName) { var userId = Convert.ToInt64(Context.UserIdentifier); if (!_groups.TryGetValue(groupName, out var groupInfo) || !groupInfo.MemberIds.Contains(userId)) { await Clients.Caller.SendAsync("LeaveGroupFailed", "您不在该组中"); return; } // 从组中移除用户 await RemoveUserFromGroup(groupName, userId); // 更新内存缓存 groupInfo.MemberIds.Remove(userId); // 如果组为空,删除组 if (groupInfo.MemberIds.Count == 0) { await DeleteGroup(groupName); } else { // 将用户移出 SignalR 组 await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName); await Clients.Group(groupName).SendAsync("UserLeftGroup", Context.User.Identity.Name, groupName); } //if (_groups.ContainsKey(groupName) && _groups[groupName].Contains(Context.ConnectionId)) //{ // await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName); // _groups[groupName].Remove(Context.ConnectionId); // await Clients.Group(groupName).SendAsync("UserLeaveGroup",Context.UserIdentifier, groupName); //} } private async Task RemoveUserFromGroup(string groupName, long userId) { var groupInfo = _groups[groupName]; // 从数据库移除 var groupMember = await myDbContext.GroupMembers .FirstOrDefaultAsync(gm => gm.GroupId == groupInfo.GroupId && gm.UserId == userId); if (groupMember != null) { myDbContext.GroupMembers.Remove(groupMember); await myDbContext.SaveChangesAsync(); } } private async Task DeleteGroup(string groupName) { if (_groups.TryRemove(groupName, out var groupInfo)) { // 从数据库删除组 var group = await myDbContext.Groups.FindAsync(groupInfo.GroupId); if (group != null) { myDbContext.Groups.Remove(group); await myDbContext.SaveChangesAsync(); } await Clients.All.SendAsync("GroupDeleted", groupName); } }
关键点
- 组管理:需手动维护用户与组的关系(如 JoinGroup 和 LeaveGroup)。
- 持久化:组信息不持久化,服务器重启后需重新加入。
四、广播消息
系统公告、实时统计数据更新等。
- 代码示例:
/// <summary> /// 向所有用户发送消息 /// </summary> /// <param name="user"></param> /// <param name="content"></param> /// <returns></returns> [Authorize(Roles = "admin")] public async Task SendMessageAsync(string user, string content) { //var connectionId = this.Context.ConnectionId; //string msg = $"{connectionId},{DateTime.Now.ToString()}:{user}"; await Clients.All.SendAsync("ReceiveMsg", user, content); } /// <summary> /// 向除发送者外的所有客户端发送消息 /// </summary> /// <param name="sender"></param> /// <param name="content"></param> /// <returns></returns> public async Task SendOthersMsg(string sender, string content) { await Clients.Others.SendAsync("ReceiveMsg",sender, content); }
五、向角色发送消息
向管理员组发送系统警报,或向特定权限用户推送通知。
- 代码示例:
/// <summary> /// 向管理员组AdminUsers发送消息 /// </summary> /// <param name="sender"></param> /// <param name="content"></param> /// <returns></returns> public async Task SendAdminMsgAsync(string sender, string content) { await Clients.Group("AdminUsers").SendAsync("ReceiveAdminMsg", sender, content); }
六、客户端接收消息
JavaScript 客户端
代码示例:
// 创建新连接 state.connection = new signalR.HubConnectionBuilder() .withUrl(state.serverUrl, { accessTokenFactory: () => token, skipNegotiation: true, transport: signalR.HttpTransportType.WebSockets }) .withAutomaticReconnect() .configureLogging(signalR.LogLevel.Information) .build(); // 注册消息处理程序 state.connection.on("ReceiveMsg", (user, message) => { state.messages.push({ type: 'broadcast', sender: `${user}(广播消息)`, content: message, timestamp: new Date() }); }); state.connection.on("ReceivePrivateMsg", (sender, message) => { if (!sender || !message) return; state.messages.push({ type: 'private', sender: `${sender} (私信)`, content: message, timestamp: new Date() }); }); state.connection.on("ReceiveGroupMsg", (sender, group, message) => { state.messages.push({ type: 'group', sender: `${sender} (${group})`, content: message, group: group, timestamp: new Date() }); }); ...... // 启动连接 await state.connection.start();
总结
通过以上方法,你可以灵活实现 SignalR 的部分消息发送功能,满足不同场景下的实时通信需求。