基于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都默认开启,自建的话需要手动安装),可以直接在数据库层面定时触发任务:
- 先启用扩展:
CREATE EXTENSION IF NOT EXISTS pg_cron;
- 创建定时任务,比如每分钟检查一次到点的未发送任务,调用你写的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:
- 先安装依赖:
npm install node-schedule pg firebase-admin
- 写一个定时检查的脚本:
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
相关产品推荐
相关产品推荐

