在Flutter与Dart中实现云端定时通知
实现Todo应用云端定时通知的方案与代码示例
一、核心架构逻辑
- 后端负责存储Todo的定时规则,通过任务调度器在指定时间触发通知事件
- 借助推送服务(Web Push/FCM等)将通知推送到用户客户端
- 客户端处理推送通知的展示与交互
二、后端实现(Node.js + Express 示例)
1. 依赖安装
npm install express mongoose node-schedule web-push cors
mongoose:用于MongoDB数据存储,管理Todo和定时信息node-schedule:定时任务调度工具web-push:Web端推送通知支持
2. 代码实现
(1)Todo数据库模型(models/Todo.js)
const mongoose = require('mongoose'); const todoSchema = new mongoose.Schema({ title: String, content: String, notifyAt: Date, // 定时触发的UTC时间 userId: String, // 关联用户ID pushSubscription: Object, // 存储用户的推送订阅信息 notified: { type: Boolean, default: false } // 标记通知是否已发送 }); module.exports = mongoose.model('Todo', todoSchema);
(2)服务端核心逻辑
const express = require('express'); const mongoose = require('mongoose'); const schedule = require('node-schedule'); const webpush = require('web-push'); const cors = require('cors'); const Todo = require('./models/Todo'); const app = express(); app.use(cors()); app.use(express.json()); // 连接MongoDB mongoose.connect('mongodb://localhost:27017/todo-app', { useNewUrlParser: true, useUnifiedTopology: true }); // Web Push配置(替换为你的VAPID密钥,可通过web-push generate-vapid-keys生成) const vapidKeys = { publicKey: '你的公钥字符串', privateKey: '你的私钥字符串' }; webpush.setVapidDetails( 'mailto:your-email@example.com', vapidKeys.publicKey, vapidKeys.privateKey ); // 启动时加载未触发的定时任务 async function loadPendingJobs() { const pendingTodos = await Todo.find({ notified: false, notifyAt: { $gte: new Date() } }); pendingTodos.forEach(todo => { schedule.scheduleJob(todo.notifyAt, async () => { await sendNotification(todo); }); }); } loadPendingJobs(); // 推送通知函数 async function sendNotification(todo) { try { await webpush.sendNotification( todo.pushSubscription, JSON.stringify({ title: 'Todo提醒', body: `${todo.title}: ${todo.content}`, icon: '/icon.png' }) ); todo.notified = true; await todo.save(); } catch (err) { console.error('推送失败:', err.message); } } // 新增Todo并创建定时任务 app.post('/api/todos', async (req, res) => { const { title, content, notifyAt, userId, pushSubscription } = req.body; const todo = new Todo({ title, content, notifyAt: new Date(notifyAt), userId, pushSubscription }); await todo.save(); // 创建定时任务 schedule.scheduleJob(todo.notifyAt, async () => { await sendNotification(todo); }); res.status(201).json(todo); }); const PORT = process.env.PORT || 3000; app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
三、客户端实现(Web端示例)
1. 推送订阅与权限申请
if ('serviceWorker' in navigator && 'PushManager' in window) { window.addEventListener('load', async () => { // 注册Service Worker const registration = await navigator.serviceWorker.register('/sw.js'); // 请求通知权限 const permission = await Notification.requestPermission(); if (permission === 'granted') { // 获取推送订阅对象 const subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array('你的Web Push公钥') }); // 将订阅信息发送到后端(可结合用户登录态传递userId) await fetch('/api/todos/subscribe', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ subscription, userId: '当前用户唯一标识' }) }); } }); } // Base64公钥转Uint8Array工具函数 function urlBase64ToUint8Array(base64String) { const padding = '='.repeat((4 - base64String.length % 4) % 4); const base64 = (base64String + padding) .replace(/-/g, '+') .replace(/_/g, '/'); const rawData = window.atob(base64); return Uint8Array.from([...rawData].map(char => char.charCodeAt(0))); }
2. Service Worker处理推送(sw.js)
self.addEventListener('push', (event) => { const data = event.data?.json() || {}; const options = { body: data.body, icon: data.icon, badge: '/badge.png' }; event.waitUntil( self.registration.showNotification(data.title, options) ); }); // 点击通知打开应用主页 self.addEventListener('notificationclick', (event) => { event.notification.close(); event.waitUntil( clients.openWindow('/') ); });
四、生产环境注意事项
- 定时任务持久化:
node-schedule重启后会丢失任务,生产环境需用数据库存储待触发任务,服务启动时重新加载 - 时区处理:统一用UTC时间存储
notifyAt,避免时区偏差 - 重试机制:推送失败时可引入队列(如BullMQ)实现重试逻辑
- 移动端适配:移动端可替换为FCM(Firebase Cloud Messaging)实现跨平台推送
内容的提问来源于stack exchange,提问作者Sanghavi dev
相关产品推荐
相关产品推荐

