如何在C#.NET的SignalR项目中实现Angular推送通知
.NET + SignalR 聊天应用推送通知落地方案
核心逻辑拆分
推送不需要搞太复杂,按用户状态分两套通道走就行:
- 在线场景:用户当前正打开应用、SignalR连接保持正常,直接复用现有SignalR长连接推,延迟最低、实现成本极低
- 离线/后台场景:用户关了应用、或者把应用切到后台连接断开,走各端系统级推送通道,保证消息能触达
分步实现指引
1. 应用内在线推送(直接复用现有SignalR能力)
你已经搭完SignalR基础聊天能力,这部分只需要加少量代码:
- 先定义统一的通知传输模型,避免后续字段乱加:
public class ChatNotification { public string SenderNickname { get; set; } public string ContentPreview { get; set; } public Guid ConversationId { get; set; } public DateTimeOffset SendAt { get; set; } // 1=单聊消息 2=群聊@提醒 3=好友申请 public int NoticeType { get; set; } } - 在你现有的ChatHub里,在消息发送逻辑后追加通知推送逻辑,注意只定向推给接收方,别全局广播:
public class ChatHub : Hub { // 你原有的发消息、加群这些逻辑全部保留,只加通知段代码 public async Task SendPrivateMessage(Guid convId, string receiveUserId, string msgContent) { // 原有存库、推聊天消息到会话窗口的逻辑不变 // ... // 追加通知逻辑 var notice = new ChatNotification { SenderNickname = Context.User.Identity.Name, ContentPreview = msgContent.Length > 25 ? string.Concat(msgContent.AsSpan(0,25), "...") : msgContent, ConversationId = convId, SendAt = DateTimeOffset.Now, NoticeType = 1 }; // 按用户ID定向推送,你之前SignalR如果配了用ClaimTypes.NameIdentifier做用户标识,直接用Clients.User就行 await Clients.User(receiveUserId).SendAsync("GetNewNotice", notice); } } - 前端在现有SignalR连接上监听通知事件,避免重复打扰:
// 保留你之前初始化connection的代码,加事件监听 connection.on("GetNewNotice", (notice) => { // 核心判断:用户当前就在这个聊天会话页的话,不弹通知,只更新消息列表就行 if (currentOpenConvId !== notice.conversationId) { // 更新侧边栏会话的未读红点、角标数字 refreshUnreadCount(notice.conversationId); // 弹应用内小浮层提示 showAppToast(notice); // 如果用户给了浏览器通知权限,弹系统级通知(哪怕标签页在后台也能弹) if (window.Notification && Notification.permission === "granted") { new Notification(`来自${notice.senderNickname}的新消息`, { body: notice.contentPreview, icon: "/static/msg-icon.png" }); } } }); // 不要一进页面就弹权限申请,最好等用户发第一条消息之后再申请,用户同意率更高 if (window.Notification && Notification.permission === "default") { // 绑定到用户首次交互事件里调用 Notification.requestPermission() }
2. 离线场景推送(用户关了应用/连接断开)
这部分核心是先准确判断用户在线状态,别在线的时候还走离线通道推重复消息:
- 先做在线状态缓存:重写Hub的连接、断开事件,把用户在线状态存在缓存里(本地开发用内存缓存,生产换Redis就行)
// 构造函数注入IDistributedCache private readonly IDistributedCache _cache; public ChatHub(IDistributedCache cache) { _cache = cache; } public override async Task OnConnectedAsync() { var userId = Context.User.FindFirstValue(ClaimTypes.NameIdentifier); // 缓存2分钟,客户端每1分半发一次心跳保活,避免状态误判 await _cache.SetStringAsync($"online:{userId}", "1", new DistributedCacheEntryOptions{ SlidingExpiration = TimeSpan.FromMinutes(2) }); await base.OnConnectedAsync(); } public override async Task OnDisconnectedAsync(Exception? ex) { var userId = Context.User.FindFirstValue(ClaimTypes.NameIdentifier); await _cache.RemoveAsync($"online:{userId}"); await base.OnDisconnectedAsync(ex); } - 发消息的时候先查缓存,用户不在线就走对应端的离线推送通道:
- Web端:直接用WebPush类库,用户授权通知时把生成的PushSubscription存在数据库,离线时直接调用库方法推给浏览器官方推送服务,Chrome、Edge、Firefox都兼容,不需要自己搭推送服务器
- MAUI/原生移动端/桌面端:iOS对接APNs,安卓海外用FCM,国内安卓对接各厂商自有推送通道(华为、小米、OPPO、vivo都有官方.NET服务端SDK),嫌适配麻烦可以用聚合推送服务统一对接,省得每个厂商单独写逻辑
3. 必做的体验优化
- 提前做用户免打扰配置判断,开了免打扰的用户只存未读消息数,不弹强提醒
- 短时间内同一会话超过3条未读,就合并通知内容,比如显示“你有5条新消息”,别连续弹一堆通知
- 用户点通知跳转到对应会话后,立刻清掉该会话的未读状态,多端同步时避免出现已读了还弹通知的问题
内容的提问来源于stack exchange,提问作者Coding Enthusiast
相关产品推荐
相关产品推荐

