基于.NET 6 SignalR的离线通知内存存储重发方案咨询
.NET 6 SignalR 离线通知内存存储与重连补发实现方案
针对你的需求,我们可以通过内存存储服务实现离线通知暂存与重连补发,以下是具体实现步骤:
1. 创建离线通知内存存储服务
定义接口和线程安全的内存实现类,用ConcurrentDictionary处理多线程环境下的并发问题:
public interface IOfflineNotificationStore { // 添加离线通知 void AddNotification(string userId, NotificationModel notification); // 获取并清除用户的所有离线通知 List<NotificationModel> GetAndClearNotifications(string userId); } public class InMemoryOfflineNotificationStore : IOfflineNotificationStore { private readonly ConcurrentDictionary<string, List<NotificationModel>> _offlineNotifications = new(); public void AddNotification(string userId, NotificationModel notification) { _offlineNotifications.AddOrUpdate( userId, new List<NotificationModel> { notification }, (key, existingList) => { existingList.Add(notification); return existingList; }); } public List<NotificationModel> GetAndClearNotifications(string userId) { if (_offlineNotifications.TryRemove(userId, out var notifications)) { return notifications; } return new List<NotificationModel>(); } }
2. 注册服务到依赖注入容器
在Program.cs中将存储服务注册为单例(确保全局共享内存数据):
builder.Services.AddSingleton<IOfflineNotificationStore, InMemoryOfflineNotificationStore>(); // 确保IUserConnectionManager已正确注册 builder.Services.AddSingleton<IUserConnectionManager, UserConnectionManager>();
3. 修改NotificationHub逻辑
注入存储服务
在Hub构造函数中新增IOfflineNotificationStore注入,同时移除原代码中未使用的_userConnections冗余字段:
public class NotificationHub : Hub<INotificationHub> { private readonly IUserConnectionManager _userConnectionManager; private readonly IOfflineNotificationStore _offlineNotificationStore; public NotificationHub(IUserConnectionManager userConnectionManager, IOfflineNotificationStore offlineNotificationStore) { _userConnectionManager = userConnectionManager; _offlineNotificationStore = offlineNotificationStore; } // ... 其他原有代码 }
重写OnConnectedAsync,补发离线通知
用户连接成功后,立即取出并发送该用户的所有离线通知:
public override async Task OnConnectedAsync() { var token = Context.GetHttpContext().Request.Headers["access_token"]; if (!string.IsNullOrEmpty(token)) { var handler = new JwtSecurityTokenHandler(); var jsonToken = handler.ReadToken(token) as JwtSecurityToken; if (jsonToken != null) { string userId = jsonToken.Claims.FirstOrDefault(claim => claim.Type == "email")?.Value; if (!string.IsNullOrEmpty(userId)) { // 保存用户连接 _userConnectionManager.KeppUserConnection(userId, Context.ConnectionId); // 补发离线通知 var offlineNotifications = _offlineNotificationStore.GetAndClearNotifications(userId); foreach (var notification in offlineNotifications) { await Clients.Client(Context.ConnectionId).SendNotification(notification); } var response = new ServerResponse { status = "Accepted", }; await Clients.Client(Context.ConnectionId).ConnectionResponse(response); } else { var response = new ServerResponse { status = "Error", message = "无法获取用户信息" }; await Clients.Client(Context.ConnectionId).ConnectionResponse(response); Context.Abort(); } } else { var response = new ServerResponse { status = "Error", message = "Token无效" }; await Clients.Client(Context.ConnectionId).ConnectionResponse(response); Context.Abort(); } } else { var response = new ServerResponse { status = "Error", message = "Token no proporcionado" }; await Clients.Client(Context.ConnectionId).ConnectionResponse(response); Context.Abort(); } }
修改SendNotificationToSpecificUser,存储离线通知
发送通知时,若用户无在线连接,则将通知存入离线存储:
public async Task SendNotificationToSpecificUser(NotificationModel message, List<string> target) { foreach (var userId in target) { var connectionIds = _userConnectionManager.GetUserConnections(userId); if (connectionIds.Any()) { // 用户在线,直接发送 foreach (var connectionId in connectionIds) { await Clients.Client(connectionId).SendNotification(message); } } else { // 用户离线,存储通知 _offlineNotificationStore.AddNotification(userId, message); } } }
注意事项
- 内存存储局限性:服务重启后离线通知会丢失,若需持久化可后续扩展为数据库存储,当前方案满足内存存储需求。
- 线程安全:使用
ConcurrentDictionary确保多线程环境下的操作安全,Hub实例为每次请求创建,但存储服务为单例。 - 原代码中
UserConnection方法可整合到OnConnectedAsync中,避免逻辑分散。
内容的提问来源于stack exchange,提问作者Elias
相关产品推荐
相关产品推荐

