文章目錄
- 前言
- 一、消息發送的核心概念
- 1.客戶端標識
- 2.消息接收范圍
- 二、向特定用戶發送消息
- 管理員向指定用戶發送私信,或用戶之間一對一聊天。
- 三、向組發送消息
- 聊天室、工作群組、通知訂閱等。
- 四、廣播消息
- 系統公告、實時統計數據更新等。
- 五、向角色發送消息
- 向管理員組發送系統警報,或向特定權限用戶推送通知。
- 六、客戶端接收消息
- JavaScript 客戶端
- 總結
前言
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 的部分消息發送功能,滿足不同場景下的實時通信需求。