如何实现自定义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
相关产品推荐
相关产品推荐

