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

基于PostgreSQL存储时间在Node.js中调度Firebase推送通知

嘿,我之前也折腾过类似的需求,结合PostgreSQL的定时数据和Firebase推送其实没那么复杂,给你梳理一套可行的实现方案:

核心思路

简单来说就是:在PostgreSQL里存好待推送的任务(包含目标用户、推送时间、内容),然后定时检查数据库里到点且未发送的任务,触发你已经实现的Firebase推送逻辑,最后标记任务为已发送避免重复执行。

具体实现步骤

1. 完善PostgreSQL表结构

首先得确保你的表能存下定时推送的关键信息,比如可以建一个scheduled_notifications表:

CREATE TABLE scheduled_notifications (
    id SERIAL PRIMARY KEY,
    user_id VARCHAR(255) NOT NULL, -- 关联你的用户表ID
    fcm_token VARCHAR(255) NOT NULL, -- 目标用户的FCM推送令牌(也可以关联用户表查询)
    notification_title VARCHAR(255) NOT NULL,
    notification_body TEXT NOT NULL,
    scheduled_at TIMESTAMP WITH TIME ZONE NOT NULL, -- 要推送的时间(带时区避免时差问题)
    is_sent BOOLEAN DEFAULT FALSE, -- 是否已发送
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

如果你的用户表已经存了FCM令牌,这里可以只存user_id,推送时关联查询就行,不用重复存储。

2. 定时检查待推送任务

这里有两种常用方案,你可以根据自己的技术栈选择:

方案A:用PostgreSQL自带的定时扩展(pg_cron)

如果你的PostgreSQL服务器支持pg_cron扩展(大部分云服务商的PostgreSQL都默认开启,自建的话需要手动安装),可以直接在数据库层面定时触发任务:

  1. 先启用扩展:
CREATE EXTENSION IF NOT EXISTS pg_cron;
  1. 创建定时任务,比如每分钟检查一次到点的未发送任务,调用你写的Node.js接口:
-- 每分钟执行一次,触发Node.js的推送处理接口
SELECT cron.schedule(
    'minute-check-scheduled-notifications',
    '* * * * *', -- cron表达式:每分钟执行
    $$
    SELECT http_post('http://your-node-server:3000/api/send-scheduled-notifications', '{}');
    $$
);

这种方式需要PostgreSQL能访问到你的Node.js服务,如果你不想暴露接口,也可以写PL/pgSQL存储过程直接调用Firebase API,但我更推荐用Node.js处理——毕竟你已经有现成的Firebase推送逻辑了。

方案B:用Node.js的定时任务库

如果你不想依赖PostgreSQL的扩展,用Node.js自己的定时库更灵活,比如node-schedule或者node-cron:

  1. 先安装依赖:
npm install node-schedule pg firebase-admin
  1. 写一个定时检查的脚本:
const schedule = require('node-schedule');
const { Pool } = require('pg');
const admin = require('firebase-admin');

// 初始化PostgreSQL连接池
const pool = new Pool({
  user: 'your-db-user',
  host: 'your-db-host',
  database: 'your-db-name',
  password: 'your-db-password',
  port: 5432,
});

// 初始化Firebase(你应该已经有这段代码了)
const serviceAccount = require('./path-to-your-service-account-key.json');
admin.initializeApp({
  credential: admin.credential.cert(serviceAccount)
});

// 定义定时任务:每分钟执行一次
const job = schedule.scheduleJob('* * * * *', async () => {
  try {
    // 查询到点且未发送的推送任务,用FOR UPDATE锁定记录避免并发重复处理
    const res = await pool.query(`
      SELECT * FROM scheduled_notifications 
      WHERE scheduled_at <= NOW() AND is_sent = FALSE
      FOR UPDATE SKIP LOCKED; -- SKIP LOCKED跳过已被其他进程锁定的记录
    `);

    for (const notification of res.rows) {
      try {
        // 调用Firebase推送逻辑(你已经实现的部分)
        const message = {
          token: notification.fcm_token,
          notification: {
            title: notification.notification_title,
            body: notification.notification_body
          }
        };

        await admin.messaging().send(message);

        // 标记任务为已发送
        await pool.query(`
          UPDATE scheduled_notifications 
          SET is_sent = TRUE, updated_at = NOW() 
          WHERE id = $1;
        `, [notification.id]);

        console.log(`推送成功:用户${notification.user_id}`);
      } catch (pushError) {
        console.error(`推送失败:用户${notification.user_id}`, pushError);
        // 这里可以加重试逻辑,比如记录错误次数,超过次数就标记为失败
      }
    }
  } catch (dbError) {
    console.error('数据库查询失败', dbError);
  }
});

console.log('定时推送任务已启动');

这个脚本可以直接作为Node.js服务的一部分运行,或者单独启动一个进程。

3. 关键注意事项

  • 时区问题:一定要确保PostgreSQL的scheduled_at字段用TIMESTAMP WITH TIME ZONE,并且和你的Node.js服务器时区一致,不然会出现推送时间不准的情况。
  • 并发安全:用SELECT ... FOR UPDATE SKIP LOCKED可以避免多个进程同时处理同一条记录,防止重复推送。
  • 错误处理:推送失败时不要直接丢弃,可以记录错误日志,甚至设置重试机制(比如间隔5分钟重试3次),避免因为网络波动导致推送丢失。
  • 任务清理:可以定期清理已发送的任务(比如保留30天),避免数据库表越来越大。

内容的提问来源于stack exchange,提问作者Avinash Jagtap

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 00:27:53