You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 08:56:08