如何在NestJS中向客户端发送新消息通知(无第三方服务)
无第三方服务的新消息通知实现方案(NestJS + PWA/网页端)
针对你的场景,以下是几种不依赖第三方服务的实现方案,结合你已有的设备信息可以优化推送逻辑:
1. WebSocket实时推送(首选,前台在线场景)
NestJS原生支持WebSocket模块,适合实现实时消息推送,这是最可靠的前台通知方案。
核心逻辑
- 用户登录后,前端通过WebSocket与后端建立连接,握手时携带
userId和deviceId(从你存储的设备信息中获取)。 - 后端维护用户ID-设备ID-连接实例的映射表(单机用内存Map,集群环境建议用Redis)。
- 当有新消息产生时,后端根据接收者的
userId找到对应的所有活跃连接,直接推送通知数据。 - 前端收到推送后,调用浏览器
Notification API显示系统通知(需提前获取用户授权)。
NestJS网关示例代码
import { WebSocketGateway, WebSocketServer, OnGatewayConnection, OnGatewayDisconnect } from '@nestjs/websockets'; import { Server, Socket } from 'socket.io'; @WebSocketGateway({ cors: { origin: ['你的前端域名'] } }) export class NotificationGateway implements OnGatewayConnection, OnGatewayDisconnect { @WebSocketServer() server: Server; // 存储用户-设备-连接映射,集群环境替换为Redis private userDeviceConnections = new Map<string, Map<string, Socket>>(); handleConnection(client: Socket) { const { userId, deviceId } = client.handshake.query as { userId: string; deviceId: string }; if (!userId || !deviceId) return client.disconnect(); if (!this.userDeviceConnections.has(userId)) { this.userDeviceConnections.set(userId, new Map()); } this.userDeviceConnections.get(userId).set(deviceId, client); } handleDisconnect(client: Socket) { const { userId, deviceId } = client.handshake.query as { userId: string; deviceId: string }; if (!userId || !deviceId) return; const deviceMap = this.userDeviceConnections.get(userId); if (deviceMap) { deviceMap.delete(deviceId); if (deviceMap.size === 0) { this.userDeviceConnections.delete(userId); } } } // 供业务模块调用的推送方法 sendToUser(userId: string, notification: { title: string; content: string; data?: any }) { const deviceMap = this.userDeviceConnections.get(userId); if (deviceMap) { deviceMap.forEach(socket => socket.emit('new-notification', notification)); } } }
2. Service Worker Push API(后台离线场景)
如果需要用户在页面关闭时也能收到通知,PWA支持的Push API是最佳选择,无需第三方服务,仅依赖浏览器厂商的原生推送通道(全程由你控制)。
核心逻辑
- 前端注册Service Worker,请求用户通知权限,获取
Push Subscription(包含浏览器推送端点URL等信息),将其与用户、设备信息绑定后存入数据库(比IP更有用,这是推送的唯一标识)。 - 后端收到新消息时,根据用户的
Push Subscription向对应的端点发送加密请求,浏览器会在后台唤醒Service Worker,显示系统通知。
NestJS推送服务示例代码
import { Injectable } from '@nestjs/common'; import * as webpush from 'web-push'; // 提前生成VAPID密钥对(可用web-push工具生成) const VAPID_PUBLIC_KEY = '你的VAPID公钥'; const VAPID_PRIVATE_KEY = '你的VAPID私钥'; webpush.setVapidDetails( 'mailto:你的邮箱', VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY ); @Injectable() export class PushNotificationService { async sendNotification(subscription: any, payload: { title: string; content: string }) { try { await webpush.sendNotification(subscription, JSON.stringify({ title: payload.title, body: payload.content, icon: '/pwa-icon.png', data: { url: '/chat' } // 点击通知跳转的页面 })); } catch (error) { console.error('推送失败:', error); // 若推送失败,大概率是Subscription过期,需从数据库移除该记录 } } }
3. 轮询(备选,不推荐)
如果WebSocket和Push API无法适配你的场景,轮询是最简单的 fallback 方案,但实时性差且浪费资源。
核心逻辑
- 前端每隔固定时间(如10秒)向后端发送HTTP请求,查询该用户的未读新消息。
- 后端返回未读消息列表,前端判断是否有新消息并显示通知。
关于现有设备信息与IP的利用
- 设备ID:这是核心可用数据,可用来区分用户的不同设备,实现「精准推送」(比如只推送到用户当前活跃的设备,或全部设备),在WebSocket和Push方案中都能用来绑定连接/Subscription。
- IP:实用价值有限,因为公网环境下多为动态IP,且无法直接通过IP发起推送(NAT网络限制),仅可用于辅助安全验证(如判断设备是否在常用IP段),无法直接用于通知推送。
内容的提问来源于stack exchange,提问作者Toni Stark
相关产品推荐
相关产品推荐

