基于Ionic、PHP、MySQL实现赛事通知定时触发方案咨询
精准定时推送赛事通知的可行方案(适配PHP+MySQL+Firebase栈)
嘿,这个需求我之前帮朋友处理过类似的——CronJob确实搞不定指定某一精准日期时间的触发,毕竟它是按固定间隔轮询的。结合你用的Ionic+PHP+MySQL+Firebase技术栈,给你几个落地性拉满的方案:
方案1:改进版CronJob + MySQL任务队列(最易上手)
这是成本最低的方案,不用额外加服务,直接复用现有技术栈:
- 创建任务表:在MySQL里建一个
event_notification_tasks表,用来存待推送的任务:
CREATE TABLE event_notification_tasks ( id INT AUTO_INCREMENT PRIMARY KEY, event_id INT NOT NULL, push_time DATETIME NOT NULL, participant_tokens TEXT NOT NULL, -- 存储参与者的FCM令牌,用逗号分隔 status ENUM('pending', 'executed', 'failed', 'cancelled') DEFAULT 'pending', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (event_id) REFERENCES events(id) );
赛事创建时写入任务:当用户创建赛事并设置举办日期时,把推送任务插入这个表,
push_time设为赛事举办当日的目标通知时间(比如赛事开始前30分钟)。编写PHP执行脚本:写一个
process_notifications.php脚本,核心逻辑是:- 查询当前时间前后5分钟内的
pending任务(时间范围可根据精度需求调整) - 遍历任务,调用FCM API给所有参与者推送通知
- 更新任务状态为
executed,推送失败则标记为failed(后续可加重试逻辑)
- 查询当前时间前后5分钟内的
核心SQL查询示例:
$pdo = new PDO('mysql:host=localhost;dbname=your_db', 'user', 'pass'); $stmt = $pdo->prepare(" SELECT * FROM event_notification_tasks WHERE push_time BETWEEN DATE_SUB(NOW(), INTERVAL 5 MINUTE) AND DATE_ADD(NOW(), INTERVAL 5 MINUTE) AND status = 'pending' "); $stmt->execute(); $tasks = $stmt->fetchAll(PDO::FETCH_ASSOC);
- 配置CronJob:设置CronJob每隔5分钟跑一次这个脚本:
*/5 * * * * /usr/bin/php /path/to/process_notifications.php
注意:一定要确保数据库和服务器的时区一致,最好统一用UTC时间存储,避免时区误差导致推送时间不准。
方案2:Redis延迟队列(更高精度)
如果你的服务器能安装Redis,这个方案的精度比CronJob高很多(可做到秒级触发):
- 存储延迟任务:赛事创建时,把推送任务(包含赛事ID、参与者令牌、推送时间)作为JSON字符串存入Redis的ZSet(有序集合),
score设为推送时间的时间戳:
$redis = new Redis(); $redis->connect('localhost', 6379); $taskData = json_encode([ 'event_id' => $eventId, 'participant_tokens' => $tokens, 'push_time' => $pushTimestamp ]); $redis->zAdd('delayed_notification_tasks', $pushTimestamp, $taskData);
- 常驻脚本轮询执行:写一个PHP常驻脚本,不断从ZSet中取出
score小于等于当前时间戳的任务,执行推送后删除该任务:
while (true) { $redis = new Redis(); $redis->connect('localhost', 6379); $currentTime = time(); // 取出一条待执行的任务 $tasks = $redis->zRangeByScore('delayed_notification_tasks', 0, $currentTime, ['limit' => [0, 1]]); if (!empty($tasks)) { $taskData = json_decode($tasks[0], true); // 调用FCM推送通知 sendFCMNotification($taskData['participant_tokens'], '赛事即将开始!'); // 删除已执行的任务 $redis->zRem('delayed_notification_tasks', $tasks[0]); } // 每秒轮询一次 sleep(1); }
- 用Supervisor管理常驻脚本:防止脚本意外退出,用Supervisor配置守护进程:
[program:event_notification_worker] command=/usr/bin/php /path/to/redis_worker.php autostart=true autorestart=true user=www-data stdout_logfile=/var/log/event_notification_worker.log stderr_logfile=/var/log/event_notification_worker.err.log
方案3:利用FCM自带的调度推送功能(最省心)
既然你已经在用Firebase,FCM本身就支持调度推送,完全不用自己维护任务队列,直接让Firebase帮你精准触发:
- 调用FCM调度API:赛事创建时,通过FCM的
sendAPI,指定sendTime参数为推送的UTC时间戳,FCM会在指定时间自动推送:
$apiKey = 'your-fcm-server-key'; $url = 'https://fcm.googleapis.com/fcm/send'; $payload = [ 'message' => [ 'topic' => 'event_' . $eventId, // 用topic群发更适合多人场景,提前让参与者订阅对应赛事topic 'notification' => [ 'title' => '赛事提醒', 'body' => '你参与的赛事今天开始啦!' ], 'android' => [ 'notification' => [ 'channel_id' => 'sports-event-channel' ] ], 'apns' => [ 'payload' => [ 'aps' => [ 'alert' => [ 'title' => '赛事提醒', 'body' => '你参与的赛事今天开始啦!' ] ] ] ], 'sendTime' => $pushTimestamp // UTC时间戳,比如赛事开始前30分钟的时间戳 ] ]; $headers = [ 'Authorization: key=' . $apiKey, 'Content-Type: application/json' ]; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); $response = curl_exec($ch); curl_close($ch);
注意:FCM调度推送最多支持提前28天设置,如果你的赛事有超过28天的,可能需要结合方案1或2来处理。另外,如果赛事被修改或取消,记得调用FCM的API取消对应的调度任务。
额外注意事项
- 失败重试:不管用哪个方案,都要加重试机制——比如推送失败的任务重新加入队列,或者标记为
failed后隔一段时间重试。 - 任务取消:当赛事被修改日期或者取消时,要及时删除对应的待推送任务,避免误推。
- 时区处理:所有时间统一用UTC存储和传递,避免不同时区导致的时间偏差。
内容的提问来源于stack exchange,提问作者Felipe Cortés
相关产品推荐
相关产品推荐

