You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C#.NET的SignalR项目中实现Angular推送通知

.NET + SignalR 聊天应用推送通知落地方案

核心逻辑拆分

推送不需要搞太复杂,按用户状态分两套通道走就行:

  • 在线场景:用户当前正打开应用、SignalR连接保持正常,直接复用现有SignalR长连接推,延迟最低、实现成本极低
  • 离线/后台场景:用户关了应用、或者把应用切到后台连接断开,走各端系统级推送通道,保证消息能触达

分步实现指引

1. 应用内在线推送(直接复用现有SignalR能力)

你已经搭完SignalR基础聊天能力,这部分只需要加少量代码:

  1. 先定义统一的通知传输模型,避免后续字段乱加:
    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; }
    }
    
  2. 在你现有的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);
        }
    }
    
  3. 前端在现有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. 离线场景推送(用户关了应用/连接断开)

这部分核心是先准确判断用户在线状态,别在线的时候还走离线通道推重复消息:

  1. 先做在线状态缓存:重写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);
    }
    
  2. 发消息的时候先查缓存,用户不在线就走对应端的离线推送通道:
    • Web端:直接用WebPush类库,用户授权通知时把生成的PushSubscription存在数据库,离线时直接调用库方法推给浏览器官方推送服务,Chrome、Edge、Firefox都兼容,不需要自己搭推送服务器
    • MAUI/原生移动端/桌面端:iOS对接APNs,安卓海外用FCM,国内安卓对接各厂商自有推送通道(华为、小米、OPPO、vivo都有官方.NET服务端SDK),嫌适配麻烦可以用聚合推送服务统一对接,省得每个厂商单独写逻辑

3. 必做的体验优化

  • 提前做用户免打扰配置判断,开了免打扰的用户只存未读消息数,不弹强提醒
  • 短时间内同一会话超过3条未读,就合并通知内容,比如显示“你有5条新消息”,别连续弹一堆通知
  • 用户点通知跳转到对应会话后,立刻清掉该会话的未读状态,多端同步时避免出现已读了还弹通知的问题

内容的提问来源于stack exchange,提问作者Coding Enthusiast

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 17:39:42