一个基于 Web Push API 的实时推送通知系统,使用 Service Worker 实现完美的推送体验。
这种推送方式目前只适合用户接收调试信息,不适合用于生产环境。原因有三:
注意:
利用 Service Worker 的实时推送功能,实现浏览器原生的推送通知系统。后端使用 Web Push 库发送推送消息,前端通过 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/auth/login - 登录获取 JWT token推送接口
POST /api/push - 发送推送消息(需要 PUSH_TOKEN)GET /api/vapid-key - 获取 VAPID 公钥订阅接口
POST /api/subscribe - 保存推送订阅信息到数据库DELETE /api/subscribe/:id - 删除订阅(需要 JWT token)GET /api/subscribe/list - 获取订阅列表(需要 JWT token)记录接口
GET /api/records/list - 获取推送记录列表(需要 JWT token)日志系统
可能原因:
解决方案:
fcm.googleapis.com).env 文件中已配置有效的 VAPID 密钥原因: 这是 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/...(无效)答案: 使用内置的调试日志功能
作用:
生成方法:
npx web-push generate-vapid-keys
原因: 浏览器的安全限制
解决方案: 项目已实现兼容方案
运行初始化脚本,自动安装依赖、准备环境变量、生成 VAPID 密钥:
bash scripts/dev.sh
脚本会自动创建
.env文件并生成 VAPID 密钥对,你只需配置JWT_SECRET、ADMIN_PASSWORD、PUSH_TOKEN
# 同时启动前后端
pnpm run dev:all
# 或分别启动
pnpm run dev:web # 前端 (端口 5173)
pnpm run dev:server # 后端 (端口 3001)
# 构建前端
pnpm run build
# 启动后端服务器
pnpm run start
服务器将在 http://localhost:3001 启动。
workspace/ ├── package.json # 根目录配置(仅含 concurrently) ├── pnpm-lock.yaml # 根目录 lock 文件 ├── docker-compose.yml # Docker Compose 配置 ├── Dockerfile # Docker 构建配置 │ ├── web/ # 前端项目 │ ├── package.json # 前端依赖配置 │ ├── pnpm-lock.yaml # 前端 lock 文件 │ ├── index.html # HTML 入口 │ ├── 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 # 前端环境变量 │ ├── .env.example # 前端环境变量示例 │ ├── public/ # 静态资源 │ │ ├── favicon.svg │ │ └── sw.js # Service Worker │ └── src/ # 源代码 │ ├── App.tsx # 应用主组件 │ ├── main.tsx # 应用入口 │ ├── style.css # 全局样式 │ ├── components/ # React 组件 │ │ ├── ui/ # Radix UI 组件 │ │ ├── BackgroundImage.tsx │ │ └── BottomNav.tsx │ ├── pages/ # 页面组件 │ │ ├── Home.tsx # 首页 │ │ ├── Detail.tsx # 详情页 │ │ ├── Admin.tsx # 管理后台 │ │ ├── Push.tsx # 推送发送 │ │ ├── Login.tsx # 登录页 │ │ ├── Settings.tsx # 设置页 │ │ └── DebugLog.tsx # 调试日志页 │ ├── services/ # 服务层 │ │ ├── authService.ts # 认证服务 │ │ ├── dbService.ts # IndexedDB 服务 │ │ ├── pushService.ts # 推送服务 │ │ └── debugService.ts # 调试日志服务 │ ├── types/ # TypeScript 类型 │ │ └── index.ts │ ├── utils/ # 工具函数 │ │ ├── formatDate.ts │ │ └── swRegistration.ts │ └── lib/ │ └── utils.ts # 工具函数 │ ├── server/ # 后端项目 │ ├── package.json # 后端依赖配置 │ ├── pnpm-lock.yaml # 后端 lock 文件 │ ├── index.js # Express 服务器入口 │ ├── .env # 后端环境变量 │ ├── .env.example # 后端环境变量示例 │ ├── db/ # 数据库 │ │ └── database.js # SQLite3 连接和初始化 │ ├── routes/ # 路由层 │ │ ├── auth.js # 认证路由 │ │ ├── push.js # 推送路由 │ │ ├── records.js # 推送记录路由 │ │ ├── subscribe.js # 订阅路由 │ │ └── vapid.js # VAPID 公钥路由 │ ├── core/ # 业务逻辑层 │ │ ├── auth.js # 认证逻辑 │ │ ├── push.js # 推送逻辑 │ │ ├── subscribe.js # 订阅逻辑 │ │ └── records.js # 记录查询逻辑 │ ├── utils/ # 工具函数 │ │ ├── jwt.js # JWT 生成和验证 │ │ ├── response.js # 响应格式化 │ │ └── logger.js # Winston 日志配置 │ └── middleware/ # 中间件 │ └── logger.js # 日志中间件 │ ├── scripts/ # 脚本文件 │ ├── dev.sh # 开发环境初始化脚本 │ └── docker.sh # Docker 构建推送脚本 │ ├── data/ # 数据目录(自动创建) │ ├── push.db # SQLite 数据库 │ └── log/ # 日志文件 │ └── dist/ # 构建输出目录
登录接口,获取 JWT token
请求体:
{
"password": "your-admin-password"
}
响应:
{
"success": true,
"token": "jwt-token-here"
}
发送推送消息(需要 PUSH_TOKEN)
请求头:
Authorization: Bearer your-push-token Content-Type: application/json
请求体:
{
"title": "消息标题",
"content": "消息内容",
"imageUrl": "https://example.com/image.jpg",
"targetUserId": "user_123"
}
参数说明:
title(必填)- 推送标题,最多 50 字符content(必填)- 推送内容,最多 200 字符imageUrl(可选)- 图片 URLtargetUserId(可选)- 目标用户 ID,留空则广播响应:
{
"success": true,
"message": "推送成功!已发送给 5 个用户",
"pushedCount": 5
}
保存推送订阅信息
请求体:
{
"userId": "user_1234567890_abc123",
"endpoint": "https://fcm.googleapis.com/...",
"keys": {
"p256dh": "...",
"auth": "..."
}
}
响应:
{
"success": true
}
获取推送记录(需要 JWT token)
请求头:
Authorization: Bearer your-jwt-token
响应:
{
"success": true,
"records": [
{
"id": "magic_push_record:1",
"title": "消息标题",
"content": "消息内容",
"imageUrl": "https://...",
"timestamp": 1234567890,
"targetUserId": "user_123"
}
]
}
获取 VAPID 公钥(前端订阅推送时使用)
响应:
{
"success": true,
"publicKey": "BMVYhYEUXHMPeaAvO4f0NmA1jvk5DkpCjOWl-4Tx2YiheMU7pu7Ef0VZ1M0bY90ySSVKoTXY8y9AMY9pY5q9pT0"
}
项目使用 CSS 变量实现主题切换,支持亮色和深色模式。
基础颜色:
--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 - 玻璃态边框// 使用主题色
<div className="bg-primary text-primary-foreground" />
// 使用渐变
<div className="bg-gradient-to-br from-gradient-start to-gradient-end" />
// 使用玻璃态
<div className="bg-glass backdrop-blur-md border border-glass-border" />
// 使用成功色
<div className="bg-success text-success-foreground" />
在开发模式下,Vite 会自动将 /api 请求代理到后端服务器(默认端口 3001)。可通过环境变量 VITE_API_URL 自定义。配置在 vite.config.ts:
proxy: {
'/api': {
target: process.env.VITE_API_URL || 'http://localhost:3001',
changeOrigin: true,
}
}
Service Worker 源文件位于 public/sw.js,构建时会被 Vite 自动复制到 dist/ 目录。修改 Service Worker 代码后需要重新构建:
pnpm run build
在开发环境中,Service Worker 也会被注册(移除了 import.meta.env.PROD 条件),方便测试。
所有颜色都使用 CSS 变量,自动适配亮色和深色模式。硬编码颜色值已全部替换为 CSS 变量。
keyval-store 数据库,messages 对象存储)data/push.db)项目使用 Winston 日志框架,特性包括:
后端 (server/.env):
| 变量 | 说明 | 必需 |
|---|---|---|
PORT | 服务端口,默认 3001 | 否 |
JWT_SECRET | JWT 签名密钥 | 是 |
ADMIN_PASSWORD | 管理员登录密码 | 是 |
PUSH_TOKEN | 推送 API 认证令牌 | 是 |
VAPID_PUBLIC_KEY | Web Push 公钥 | 是 |
VAPID_PRIVATE_KEY | Web Push 私钥 | 是 |
前端 (web/.env):
| 变量 | 说明 | 必需 |
|---|---|---|
VITE_BG_URL | 背景图片 URL | 否 |
VITE_API_URL | API 地址 | 否 |
docker run -d \
--name magic-push \
-p 3001:3001 \
-e ADMIN_PASSWORD=your-admin-password \
-e JWT_SECRET=your-jwt-secret \
-e PUSH_TOKEN=your-push-token \
-e VAPID_PUBLIC_KEY=your-vapid-public-key \
-e VAPID_PRIVATE_KEY=your-vapid-private-key \
-v $(pwd)/data:/app/data \
--restart unless-stopped \
your-username/magic-push:latest
# 构建并推送到 Docker Hub 和 CNB
./scripts/docker.sh # main 分支 -> latest 标签
./scripts/docker.sh dev # dev 分支 -> dev 标签
需要设置环境变量:
DOCKERHUB_USERNAME - Docker Hub 用户名DOCKERHUB_TOKEN - Docker Hub PAT 令牌CNB_DOCKER_REGISTRY - CNB Docker 注册表地址CNB_REPO_SLUG_LOWERCASE - CNB 仓库路径MIT