一个基于 Web Push API 的实时推送通知系统,使用 Service Worker 实现完美的推送体验。部署在 EdgeOne Pages 上,前后端一体化,无需独立服务器。
这种推送方式目前只适合用户接收调试信息,不适合用于生产环境。原因有三:
注意:
利用 Service Worker 的实时推送功能,实现浏览器原生的推送通知系统。后端基于 EdgeOne Pages 的 Node Functions,使用 Web Push 库发送推送消息,EdgeOne KV 存储订阅信息。前端通过 Service Worker 接收并处理推送通知,所有消息持久化存储到 IndexedDB。
| 浏览器 | 平台 | 支持状态 | 推送服务 |
|---|---|---|---|
| Chrome | 桌面/移动 | ✅ 完全支持 | FCM |
| Firefox | 桌面/移动 | ✅ 完全支持 | Mozilla Push Service |
| Edge | 桌面 | ✅ 支持 | WNS (Windows Push Notification Service) |
| Edge | 移动端 | ❌ 不支持 | 返回无效 endpoint |
| Safari | --- | --- | --- |
Edge 移动端不支持 Web Push
https://permanently-removed.invalid/... 的无效 endpoint网络环境要求
fcm.googleapis.com)HTTPS 要求
首页推送消息展示
消息详情页
手动推送(需要验证 PUSH_TOKEN)
调试日志页面
底部导航
Service Worker 功能
推送接口
POST /api/push - 发送推送消息(支持 PUSH_TOKEN 认证,支持广播和定向推送)GET /api/vapid-key - 获取 VAPID 公钥订阅接口
POST /api/subscribe - 保存/取消推送订阅(支持白名单过滤)可能原因:
解决方案:
fcm.googleapis.com)原因: 这是 Edge 移动端返回的无效 endpoint,系统会自动清理此类无效订阅。
原因:
测试推送服务连接:
# 测试 FCM 连接
curl -I https://fcm.googleapis.com
答案: endpoint 是浏览器自动生成的
不同浏览器的 endpoint 示例:
https://fcm.googleapis.com/fcm/send/...https://updates.push.services.mozilla.com/...https://wns2-sg2p.notify.windows.com/...https://permanently-removed.invalid/...(无效)答案: 使用内置的调试日志功能
作用:
生成方法:
pnpm run generate-vapid
原因: 浏览器的安全限制
解决方案: 项目已实现兼容方案
运行初始化脚本,自动安装依赖、准备环境变量、生成 VAPID 密钥:
bash scripts/dev.sh
脚本会自动创建
.env文件并生成 VAPID 密钥对。将生成的密钥配置到 EdgeOne Pages 控制台即可。
pnpm run dev
启动 Vite 开发服务器(端口 5173)。注意:本地开发时后端 API 不可用,需要部署到 EdgeOne Pages 才能测试完整功能。
部署到 EdgeOne Pages:
pnpm run build
将项目根目录连接到 EdgeOne Pages,EdgeOne 会自动识别 edge-functions/、node-functions/ 目录和静态资源进行部署。
project-root/ ├── index.html # HTML 入口 ├── package.json # 依赖和脚本 ├── vite.config.ts # Vite 配置 ├── tailwind.config.js # Tailwind 配置 ├── postcss.config.js # PostCSS 配置 ├── components.json # shadcn/ui 配置 ├── tsconfig.json # TypeScript 配置 ├── tsconfig.app.json # 应用 TypeScript 配置 ├── tsconfig.node.json # Node TypeScript 配置 ├── .env.example # 环境变量示例 │ ├── src/ # 前端源代码 │ ├── App.tsx # 应用主组件 │ ├── main.tsx # 应用入口 │ ├── style.css # 全局样式 │ ├── components/ # React 组件 │ │ ├── ui/ # Radix UI 组件 │ │ ├── BackgroundImage.tsx │ │ └── BottomNav.tsx │ ├── pages/ # 页面组件 │ │ ├── Home.tsx # 首页(消息列表) │ │ ├── Detail.tsx # 消息详情 │ │ ├── Push.tsx # 发送推送 │ │ ├── Settings.tsx # 设置页 │ │ └── DebugLog.tsx # 调试日志 │ ├── services/ # 服务层 │ │ ├── dbService.ts # IndexedDB 服务 │ │ ├── pushService.ts # 推送服务 │ │ └── debugService.ts # 调试日志服务 │ ├── types/ # TypeScript 类型 │ ├── utils/ # 工具函数 │ └── lib/ # 工具函数 │ ├── public/ # 静态资源 │ ├── favicon.svg │ └── sw.js # Service Worker │ ├── edge-functions/ # EdgeOne 边缘函数 │ └── kv/index.js # KV 存储操作 │ ├── node-functions/ # EdgeOne Node 函数 │ ├── kv-client.js # KV 客户端封装 │ └── api/ │ ├── vapid-key/index.js # GET /api/vapid-key │ ├── subscribe/index.js # POST /api/subscribe │ └── push/index.js # POST /api/push │ ├── scripts/ # 脚本 │ └── dev.sh # 开发环境初始化 │ └── dist/ # 构建输出(Vite 产物)
获取 VAPID 公钥(前端订阅推送时使用)
响应:
{
"publicKey": "BMVYhYEUXHMPeaAvO4f0NmA1jvk5DkpCjOWl-4Tx2YiheMU7pu7Ef0VZ1M0bY90ySSVKoTXY8y9AMY9pY5q9pT0"
}
保存或取消推送订阅(支持白名单过滤)
请求体:
{
"action": "subscribe",
"userId": "user_1234567890_abc123",
"endpoint": "https://fcm.googleapis.com/...",
"keys": {
"p256dh": "...",
"auth": "..."
}
}
参数说明:
action - "subscribe" 订阅,"unsubscribe" 取消订阅userId - 用户唯一标识endpoint - 推送服务端点 URLkeys.p256dh - 用户代理公钥keys.auth - 认证密钥订阅成功响应:
{
"success": true,
"message": "订阅成功"
}
白名单拒绝响应:
{
"success": false,
"message": "非白名单用户拒绝订阅"
}
发送推送消息(可通过 PUSH_TOKEN 认证,支持广播和定向推送)
请求头(可选):
Authorization: Bearer your-push-token
请求体:
{
"title": "消息标题",
"content": "消息内容",
"imageUrl": "https://example.com/image.jpg",
"targetUserId": "user_123"
}
参数说明:
title(必填)- 推送标题content(必填)- 推送内容imageUrl(可选)- 封面图片 URLtargetUserId(可选)- 目标用户 ID,不传则广播给所有订阅者响应:
{
"success": true,
"message": "推送完成",
"pushedCount": 5,
"totalSubscriptions": 6,
"failedCount": 1
}
基础颜色:
--background - 背景色--foreground - 前景色--card - 卡片背景--card-foreground - 卡片前景主题色:
--primary - 主色调--primary-foreground - 主色调前景--secondary - 次色调--secondary-foreground - 次色调前景渐变色:
--gradient-start - 渐变起始色--gradient-end - 渐变结束色--gradient-accent-1 - 辅助渐变色 1--gradient-accent-2 - 辅助渐变色 2功能色:
--success - 成功色--success-foreground - 成功色前景--destructive - 危险/错误色--destructive-foreground - 危险色前景玻璃态:
--glass-bg - 玻璃态背景--glass-hover - 玻璃态悬停--glass-strong - 玻璃态强背景--glass-border - 玻璃态边框在 EdgeOne Pages 控制台配置以下环境变量:
| 变量 | 说明 | 必需 |
|---|---|---|
VAPID_PUBLIC_KEY | Web Push 公钥 | 是 |
VAPID_PRIVATE_KEY | Web Push 私钥 | 是 |
EMAIL | VAPID 联系邮箱 | 否 |
PUSH_TOKEN | 推送 API 认证令牌(不设置则关闭认证) | 否 |
ALLOWED_USERS | 用户白名单,逗号分隔(不设置则允许任何人订阅) | 否 |
KV_API_KEY | KV 存储访问密钥 | 是 |
pnpm run generate-vapid
将项目连接到 EdgeOne Pages:
pnpm run builddistedge-functions/ 和 node-functions/ 目录| 变量 | 说明 |
|---|---|
VITE_BG_URL | 自定义背景图片 URL |
VITE_API_URL | API 地址(部署后同域,通常不需要设置) |
Service Worker 源文件位于 public/sw.js,构建时会被 Vite 自动复制到 dist/ 目录。修改 Service Worker 代码后需要重新构建:
pnpm run build
所有颜色都使用 CSS 变量,自动适配亮色和深色模式。
keyval-store 数据库,messages 对象存储)webpush_subscription: 前缀)MIT