Nuxt 3 + PWA 通知完整实现指南(Web Push)

📌 目标

为 Nuxt 3 项目添加以下功能:

  • 用户启用/禁用 PWA 通知(前端开关)
  • 浏览器请求推送权限
  • 用户同意后,将订阅信息(PushSubscription JSON)保存到后端数据库
  • 后端使用 web-push 发送通知

🧩 技术栈

  • Nuxt 3
  • @vite-pwa/nuxt 插件
  • 浏览器 Push API
  • Web Push 协议
  • Node.js + web-push 库
  • Prisma + MySQL(或 PostgreSQL)

📁 项目结构预览

swift 复制代码
/public/sw.js               // 自定义 Service Worker
/server/api/push-subscribe // 接收客户端订阅 JSON
/server/utils/push.ts      // 使用 web-push 发送通知
/composables/usePushSubscribe.ts
/prisma/schema.prisma

1️⃣ 安装 PWA 支持

bash 复制代码
npm install @vite-pwa/nuxt

修改 nuxt.config.ts

ts 复制代码
export default defineNuxtConfig({
  modules: ['@vite-pwa/nuxt'],
  runtimeConfig: {
    public: {
      vapidPublicKey: process.env.VAPID_PUBLIC_KEY
    },
    vapidPrivateKey: process.env.VAPID_PRIVATE_KEY,
    vapidSubject: process.env.VAPID_SUBJECT
  },
  pwa: {
    strategies: 'injectManifest',
    registerType: 'autoUpdate',
    srcDir: 'public',
    filename: 'sw.js',
    manifest: {
      name: '过期提醒',
      short_name: '提醒',
      start_url: '/',
      display: 'standalone',
      theme_color: '#ffffff',
    },
    devOptions: {
      enabled: process.env.NODE_ENV === 'development',
    },
  },
})

2️⃣ 设置环境变量 .env

env 复制代码
VAPID_PUBLIC_KEY=你的VAPID公钥
VAPID_PRIVATE_KEY=你的VAPID私钥
VAPID_SUBJECT=mailto:admin@example.com

使用 web-push 生成 VAPID 密钥:

perl 复制代码
bash
复制编辑
npx web-push generate-vapid-keys

3️⃣ 前端逻辑:订阅并发送给后端

composables/usePushSubscribe.ts

ts 复制代码
function urlBase64ToUint8Array(base64String: string): Uint8Array { ... }

export async function usePushSubscribe(data: { id: number, value: boolean }) {
  const config = useRuntimeConfig()
  const publicKey = config.public.vapidPublicKey

  // 请求权限
  const permission = await Notification.requestPermission()
  if (permission !== 'granted') {
    throw new Error('通知权限被拒绝')
  }

  const registration = await navigator.serviceWorker.ready

  const subscription = await registration.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: urlBase64ToUint8Array(publicKey),
  })

  // 发送给后端保存
  await $fetch('/api/push-subscribe', {
    method: 'POST',
    body: {
      id: data.id,
      pwaEnabled: data.value,
      subscription,
    }
  })
}

4️⃣ 后端接口:保存订阅信息

/server/api/push-subscribe.ts

ts 复制代码
export default defineEventHandler(async (event) => {
  const body = await readBody(event)
  const { id, pwaEnabled, subscription } = body

  // 保存到数据库
  await prisma.notification.upsert({
    where: { id },
    update: {
      pwaEnabled,
      subscription: subscription,
    },
    create: {
      id,
      pwaEnabled,
      subscription: subscription,
    },
  })

  return { success: true }
})

5️⃣ 数据模型

prisma/schema.prisma

prisma 复制代码
model Notification {
  id            Int      @id @default(autoincrement())
  pwaEnabled    Boolean  @default(false)
  subscription  Json?
  updatedAt     DateTime @updatedAt
}

6️⃣ 后端发送通知

/server/utils/push.ts

ts 复制代码
import webPush from 'web-push'
import { useRuntimeConfig } from '#imports'

export async function sendNotification(subscription: any, payload: object) {
  const config = useRuntimeConfig()

  webPush.setVapidDetails(
    config.vapidSubject,
    config.vapidPublicKey,
    config.vapidPrivateKey
  )

  await webPush.sendNotification(subscription, JSON.stringify(payload))
}

调用示例(在 API 或定时任务中):

ts 复制代码
await sendNotification(user.subscription, {
  title: '任务提醒',
  body: '你有一条未完成的任务',
})

7️⃣ 自定义 public/sw.js

js 复制代码
self.addEventListener('push', (event) => {
  const data = event.data.json()
  event.waitUntil(
    self.registration.showNotification(data.title, {
      body: data.body,
      icon: '/icon.png',
    })
  )
})

✅ 结果

  • 用户点击 UI 切换开关触发通知订阅
  • 浏览器会请求权限
  • 后端记录订阅信息
  • 后台可通过 web-push 发送通知到用户浏览器

🔒 安全提示

  • 不要将 VAPID_PRIVATE_KEY 注入到前端,只用于服务端
  • 每个订阅对象是唯一的,按用户 ID 存储或哈希索引
  • 注意兼容性问题(Safari 不支持 Web Push)
相关推荐
java1234_小锋9 小时前
【技术专题】HTML5 网页技术 - HTML5简介 & HTML5 文档结构
前端·html·html5
woniu_maggie10 小时前
SAP MDG主数据治理实战:从架构原理到项目实施全解析
后端
xiaoqiMikko10 小时前
Redis 实战(一):内存满了 —— 拒写、淘汰、被系统杀掉,以及删了 key 内存为什么不降(6.2.4 实测)
redis·后端
鶴哥只手遮天10 小时前
Vue 3 核心源码深度剖析系列(v3.5.44)-第 1 章 项目全景:monorepo 架构与工程化体系
前端·vue.js·开源
流星。。方10 小时前
小团队官网SEO落地:前端语义化与静态生成的工程实践
前端·状态模式
用户5304297657910 小时前
盘点危化品搬运诊断的6类工具与自处理边界判断
前端
curtain10 小时前
从古法编程到 Agent:我入行这两年,AI 改变了什么
前端·openai
工程师小爱10 小时前
电芯尺寸合格,不等于电池包尺寸合格
后端
zyfts10 小时前
10分钟自制Codex钉钉通知Skill:AI干完活自动钉钉提醒你
前端