出租车调度系统中基于Firebase GCM的多司机请求依次推送方案问询
你的思路完全靠谱!针对这个出租车调度的推送需求,我们可以把这个“依次推送最近3位司机”的思路落地成一套清晰的流程,既能避免Topic广播的多接单问题,又能满足“拒绝后自动推下一位”的要求。下面是具体的实现方案和细节:
可行的实现方案与细节
1. 核心逻辑确认
你提到的“记录最近3位司机,依次发送请求”的思路完全匹配需求:通过精准的单发推送替代广播,每次只给一位司机发请求,直到有人接单或者推完3位为止,完美解决多司机同时接单的问题。
2. 分步骤实现流程
(1)生成候选司机列表并存储
当乘客发起打车请求时:
- 调用地理位置服务,计算出当前空闲的3位最近司机
- 给这个打车请求生成唯一的
requestId,用来关联所有后续操作 - 将以下信息存入你的数据库(比如Firebase Realtime Database/Firestore):
- 乘客信息、请求位置
- 3位司机的
driverId、FCM Token、距离、状态(初始为pending) - 当前推送的候选索引(初始为0)
- 请求的整体状态(初始为
active)
(2)触发第一轮推送
从数据库中取出第一位司机的FCM Token,用Firebase的sendToDevice接口发送打车请求通知,同时把该司机的状态更新为notified(标记已推送)。
(3)处理司机的响应
司机端接收到通知后,不管是“接受”还是“拒绝”,都要向你的后端(或Firebase Cloud Functions)发送带requestId和操作类型的请求:
- 如果司机接单:
- 立即把请求的整体状态改为
completed - 给另外两位候选司机发送“请求已被他人接单”的通知,更新他们的状态为
cancelled - 终止后续推送流程,通知乘客已匹配到司机
- 立即把请求的整体状态改为
- 如果司机拒绝:
- 更新该司机的状态为
rejected - 检查候选列表是否还有未推送/未拒绝的司机:
- 有:取出下一位司机的Token,重复推送流程,同时更新当前候选索引
- 无:给乘客发送“暂无可用司机”的通知,把请求状态改为
failed
- 更新该司机的状态为
(4)超时处理(必加环节)
别忘了处理司机长时间不响应的情况:设置一个超时时间(比如30秒),如果超时未收到司机反馈,自动视为“拒绝”,触发下一位司机的推送。可以用Firebase Cloud Functions的定时触发器,或者后端定时器实现。
3. 关键代码示例(伪代码)
存储候选司机列表(Firestore为例)
// 生成唯一requestId const requestId = uuid.v4(); // 写入数据库 await db.collection('rideRequests').doc(requestId).set({ passengerId: 'passenger_123', location: { lat: 31.2304, lng: 121.4737 }, candidates: [ { driverId: 'driver_001', token: 'fcm_token_001', status: 'pending', distance: 0.6 }, { driverId: 'driver_002', token: 'fcm_token_002', status: 'pending', distance: 0.9 }, { driverId: 'driver_003', token: 'fcm_token_003', status: 'pending', distance: 1.3 } ], currentCandidateIndex: 0, requestStatus: 'active' });
发送FCM通知函数
const admin = require('firebase-admin'); async function sendRideRequest(token, requestId) { const message = { token: token, notification: { title: '新打车请求', body: '有乘客需要服务,请尽快查看' }, data: { requestId: requestId, type: 'RIDE_REQUEST' } }; await admin.messaging().send(message); }
处理司机拒绝的云函数
exports.handleDriverRejection = functions.https.onCall(async (data, context) => { const { requestId, driverId } = data; const requestDoc = db.collection('rideRequests').doc(requestId); const requestSnap = await requestDoc.get(); if (!requestSnap.exists || requestSnap.data().requestStatus !== 'active') { return { success: false, msg: '请求已处理' }; } const requestData = requestSnap.data(); // 更新当前司机状态为rejected const updatedCandidates = requestData.candidates.map(c => c.driverId === driverId ? { ...c, status: 'rejected' } : c ); const nextIndex = requestData.currentCandidateIndex + 1; if (nextIndex < updatedCandidates.length) { // 推送下一位司机 const nextDriver = updatedCandidates[nextIndex]; await sendRideRequest(nextDriver.token, requestId); // 更新数据库状态 await requestDoc.update({ candidates: updatedCandidates, currentCandidateIndex: nextIndex }); return { success: true, msg: '已推送至下一位司机' }; } else { // 3位都推完,通知乘客 await sendPassengerNotification(requestData.passengerId, '暂无可用司机'); await requestDoc.update({ candidates: updatedCandidates, requestStatus: 'failed' }); return { success: true, msg: '已通知乘客无可用司机' }; } });
4. 避坑注意事项
- FCM Token更新:司机的Token可能会因设备重装、应用更新等变化,所以司机端每次启动都要把最新Token同步到数据库
- 并发控制:用数据库事务处理请求状态的更新,避免极端情况下两个司机同时接单的冲突
- 通知清晰度:通知内容要包含乘客位置、距离等关键信息,帮助司机快速决策
内容的提问来源于stack exchange,提问作者Rishabh
相关产品推荐
相关产品推荐

