无需FCM与Android Service实现推送通知的可行方案咨询
无需后台服务的推送实现方案(基于你的Express Node.js服务器)
这正是Messenger、Discord、X这类应用在用的无后台推送逻辑,核心是Web Push API——完全不需要你的App/网页保持后台或轮询,靠系统/浏览器的原生推送服务和你的Express服务器配合就能实现低功耗、低流量的推送。
具体实现步骤
1. 生成VAPID密钥对
VAPID是让推送服务识别你服务器身份的凭证,用web-push包直接生成:
npx web-push generate-vapid-keys
保存好输出的公钥和私钥,后面会用到。
2. 改造你的Express服务器
先装依赖:
npm install web-push body-parser
然后加推送相关的代码(直接整合到你现有的Express服务里就行):
const webpush = require('web-push'); const bodyParser = require('body-parser'); // 假设你已经有了express实例app app.use(bodyParser.json()); // 配置VAPID const vapidKeys = { publicKey: '你刚生成的VAPID公钥', privateKey: '你刚生成的VAPID私钥' }; webpush.setVapidDetails( 'mailto:your-email@example.com', // 推送服务用来联系你的邮箱,随便填一个就行 vapidKeys.publicKey, vapidKeys.privateKey ); // 生产环境别用内存存,换成MongoDB/PostgreSQL这类数据库 let userSubscriptions = []; // 接收前端的订阅请求 app.post('/push/subscribe', (req, res) => { const subscription = req.body; userSubscriptions.push(subscription); res.status(201).send('订阅成功'); }); // 触发推送的接口(你可以在业务逻辑里调用这个,比如有新消息时) app.post('/push/send', (req, res) => { const { title, content } = req.body; const payload = JSON.stringify({ title, body: content }); // 给所有订阅用户发推送 userSubscriptions.forEach(sub => { webpush.sendNotification(sub, payload) .catch(err => console.error('推送失败:', err)); }); res.status(200).send('通知已触发'); });
3. 前端(网页/移动端WebView)做订阅
不管是网页还是移动端用WebView套的应用,都要做这两步:
第一步:请求用户授权并订阅
if ('serviceWorker' in navigator && 'PushManager' in window) { window.addEventListener('load', async () => { // 注册Service Worker const swReg = await navigator.serviceWorker.register('/sw.js'); // 弹授权框 const perm = await Notification.requestPermission(); if (perm !== 'granted') return; // 生成订阅对象,发给你的服务器 const subscription = await swReg.pushManager.subscribe({ userVisibleOnly: true, // 必须设为true,所有推送都要让用户可见 applicationServerKey: urlBase64ToUint8Array('你的VAPID公钥') }); await fetch('/push/subscribe', { method: 'POST', body: JSON.stringify(subscription), headers: { 'Content-Type': 'application/json' } }); }); } // 工具函数:把base64公钥转成Uint8Array function urlBase64ToUint8Array(base64Str) { const padding = '='.repeat((4 - base64Str.length %4) %4); const base64 = (base64Str + padding).replace(/-/g, '+').replace(/_/g, '/'); const raw = window.atob(base64); return Uint8Array.from([...raw].map(c => c.charCodeAt(0))); }
第二步:写Service Worker文件(sw.js)
这个文件是用来接收推送并弹通知的,必须放在网站根目录:
// 收到推送时显示通知 self.addEventListener('push', (e) => { const data = e.data?.json() || { title: '新消息', body: '你有一条未读通知' }; const options = { body: data.body, icon: '/your-icon.png', // 可选:自定义通知图标 badge: '/your-badge.png' // 可选:手机状态栏的小图标 }; e.waitUntil(self.registration.showNotification(data.title, options)); }); // 用户点击通知时打开应用/页面 self.addEventListener('notificationclick', (e) => { e.notification.close(); e.waitUntil(clients.openWindow('/')); // 换成你要打开的页面 });
为什么这个方案比轮询好?
- 完全不用后台:推送逻辑由浏览器/系统的原生服务处理,你的App不用保持后台进程
- 极低功耗流量:只有有消息时才会触发推送,没有轮询的空请求浪费资源
- 跨平台兼容:支持所有主流浏览器,移动端用WebView也能适配
生产环境注意点
- 别用内存存订阅信息,换成数据库(MongoDB、Redis都行)
- 必须用HTTPS:除了localhost开发,正式环境必须部署在HTTPS域名下,否则Web Push API用不了
- 推送内容要合规:不能发用户不可见的推送,浏览器会直接拦截
内容的提问来源于stack exchange,提问作者Khỉ Em
相关产品推荐
相关产品推荐

