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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 15:54:57