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

如何实现自定义Web Push通知内容并解决推送前需重新订阅的问题

如何实现自定义Web Push通知内容并解决推送前需重新订阅的问题

嘿,我来帮你搞定这两个问题!先从实现自定义消息推送的表单开始,再解决重启Node后必须重新订阅的坑~

一、创建前端表单实现自定义消息推送

你需要在前端页面加一个简单的表单,让用户输入消息后提交到后端接口,后端再把消息推送给订阅的用户。

1. 前端HTML和JS代码

把这段代码加到你的网页里(比如index.html):

<!-- 自定义推送消息表单 -->
<form id="notificationForm" style="margin: 20px;">
  <input type="text" id="messageInput" placeholder="输入要推送的消息" required style="padding: 8px; width: 300px;">
  <button type="submit" style="padding: 8px 16px; margin-left: 10px;">发送通知</button>
</form>

<script>
// 监听表单提交事件
document.getElementById('notificationForm').addEventListener('submit', async (e) => {
  e.preventDefault(); // 阻止表单默认刷新行为
  const message = document.getElementById('messageInput').value;
  
  try {
    // 用POST请求把消息发给后端
    const response = await fetch('/webapp/send-notification', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json', // 告诉后端我们发的是JSON
      },
      body: JSON.stringify({ message }), // 把消息转成JSON字符串
    });
    
    const result = await response.json();
    alert(result.message); // 提示发送成功
    document.getElementById('messageInput').value = ''; // 清空输入框
  } catch (error) {
    console.error('发送失败:', error);
    alert('发送通知出错了,看看控制台日志吧');
  }
});
</script>

2. 修改后端Node.js接口

原来的接口是GET且硬编码了消息,现在改成POST来接收前端传的自定义消息,还要记得先加JSON解析的中间件:

// 先添加JSON解析中间件,否则后端拿不到req.body里的内容
const express = require('express');
const app = express();
app.use(express.json());

// 修改推送接口为POST,接收自定义消息
app.post('/webapp/send-notification', async (req, res) => {
  try {
    const { message } = req.body;
    // 校验消息是否为空
    if (!message) {
      return res.status(400).json({ error: '消息内容不能为空哦' });
    }

    // 这里先暂时用你原来的dummyDb,后面我们再改成持久化存储
    const subscription = dummyDb.subscription;
    if (!subscription) {
      return res.status(404).json({ error: '还没有用户订阅通知呢' });
    }

    await sendNotification(subscription, message);
    res.json({ message: '通知发送成功啦' });
  } catch (error) {
    console.error('发送通知出错:', error);
    res.status(500).json({ error: '发送通知失败了' });
  }
});

二、解决重启Node后需重新订阅的问题

你之前遇到的重启Node后推送报错,核心原因是dummyDb是内存存储——Node重启后,内存里的订阅数据就全没了,所以推送时找不到有效的订阅信息,自然报错。解决办法是把订阅数据持久化存储,比如用一个JSON文件或者数据库。

1. 用JSON文件持久化订阅数据

我们用Node的fs模块把订阅信息存在本地JSON文件里,重启Node后还能读取到:

const fs = require('fs').promises;
const path = require('path');

// 定义保存订阅数据的文件路径(和你的index.js同目录)
const SUBSCRIPTION_FILE = path.join(__dirname, 'subscriptions.json');

// 存储订阅信息的数组
let subscriptions = [];

// 启动服务器时读取本地的订阅数据
async function loadSubscriptions() {
  try {
    const data = await fs.readFile(SUBSCRIPTION_FILE, 'utf8');
    subscriptions = JSON.parse(data);
  } catch (error) {
    // 如果文件不存在,就初始化空数组并保存
    subscriptions = [];
    await saveSubscriptions();
  }
}

// 把订阅数据保存到JSON文件
async function saveSubscriptions() {
  await fs.writeFile(SUBSCRIPTION_FILE, JSON.stringify(subscriptions, null, 2));
}

// 修改你的订阅接口,把新的订阅保存到文件里
app.post('/webapp/subscribe', async (req, res) => {
  const newSubscription = req.body;
  // 检查是否已经存在这个订阅,避免重复添加
  const isExisting = subscriptions.some(sub => sub.endpoint === newSubscription.endpoint);
  if (!isExisting) {
    subscriptions.push(newSubscription);
    await saveSubscriptions();
  }
  res.json({ message: '订阅成功啦' });
});

// 更新推送接口,给所有订阅用户发消息(你也可以改成给特定用户发)
app.post('/webapp/send-notification', async (req, res) => {
  try {
    const { message } = req.body;
    if (!message) {
      return res.status(400).json({ error: '消息内容不能为空哦' });
    }

    if (subscriptions.length === 0) {
      return res.status(404).json({ error: '还没有用户订阅通知呢' });
    }

    // 给所有订阅用户发送通知
    const sendPromises = subscriptions.map(sub => sendNotification(sub, message));
    await Promise.all(sendPromises);
    
    res.json({ message: `已向${subscriptions.length}个订阅用户发送通知` });
  } catch (error) {
    console.error('发送通知出错:', error);
    res.status(500).json({ error: '发送通知失败了' });
  }
});

// 启动服务器前先加载订阅数据
loadSubscriptions().then(() => {
  app.listen(3000, () => {
    console.log('服务器跑在端口3000啦');
  });
});

2. 优化推送函数,处理失效订阅

有时候用户可能取消了订阅,或者订阅过期了,推送会失败。我们可以在sendNotification函数里处理这种情况,自动删除失效的订阅:

const webpush = require('web-push');

// 记得配置你的VAPID密钥(建议用环境变量,不要硬编码)
webpush.setVapidDetails(
  'mailto:your-email@example.com', // 你的邮箱,推送服务会用它联系你
  process.env.VAPID_PUBLIC_KEY,
  process.env.VAPID_PRIVATE_KEY
);

async function sendNotification(subscription, message) {
  try {
    // 发送通知,这里把消息包装成包含标题和内容的JSON(和你的Service Worker对应)
    await webpush.sendNotification(subscription, JSON.stringify({
      title: 'Example Website',
      body: message
    }));
  } catch (error) {
    console.error('推送失败,订阅可能已失效:', error);
    // 如果是订阅失效的错误(HTTP 410),从列表中删除这个订阅
    if (error.statusCode === 410) {
      subscriptions = subscriptions.filter(sub => sub.endpoint !== subscription.endpoint);
      await saveSubscriptions();
    }
    throw error;
  }
}

3. 为啥之前重启会报错?

原来的dummyDb.subscription只存在Node的内存里,一旦Node重启,内存就被清空了,订阅数据自然没了。现在用JSON文件持久化后,重启Node会自动读取之前保存的订阅信息,不用用户重新订阅就能推送啦~

备注:内容来源于stack exchange,提问作者TonyC

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.21 12:59:50