基于Expo的应用:借助Firebase Cloud Function实现推送通知的技术问询
嘿,你的思路完全靠谱——不用eject Expo项目,靠存储Expo推送令牌到Firebase,再用Cloud Functions调用Expo的推送API发通知,完美绕开FCM的兼容问题。我来一步步给你拆解具体实现:
步骤1:在Expo应用中获取并存储Expo推送令牌到Firebase
首先得在你的Expo项目里搞定令牌的获取和存储:
- 先安装必要依赖:
expo install expo-notifications firebase
- 编写获取权限+存储令牌的代码(可以放在App.js或者专门的工具文件里):
import * as Notifications from 'expo-notifications'; import { getAuth } from 'firebase/auth'; import { doc, setDoc, getFirestore } from 'firebase/firestore'; import { useEffect } from 'react'; // 请求通知权限 async function requestNotificationPermissions() { const { status } = await Notifications.requestPermissionsAsync(); if (status !== 'granted') { alert('得开启通知权限才能收到推送哦'); return false; } return true; } // 获取Expo令牌并存储到Firebase Firestore async function saveExpoTokenToFirebase() { const hasPermission = await requestNotificationPermissions(); if (!hasPermission) return; // 获取当前用户的Expo推送令牌 const token = (await Notifications.getExpoPushTokenAsync()).data; const auth = getAuth(); const db = getFirestore(); // 确保用户已登录,然后把令牌存在用户文档里 if (auth.currentUser) { await setDoc( doc(db, 'users', auth.currentUser.uid), { expoPushToken: token }, { merge: true } // 只更新令牌字段,不覆盖用户其他数据 ); } } // 应用启动时调用这个函数,更新令牌(因为令牌可能会变化) useEffect(() => { saveExpoTokenToFirebase(); }, []);
小贴士:Expo的推送令牌可能会在用户重装应用、切换设备时变化,所以每次应用启动都重新获取并更新是个好习惯。
步骤2:编写Firebase Cloud Function发送推送通知
接下来要在Firebase这边写Cloud Function,负责从数据库拿令牌,调用Expo的推送API发通知:
- 先在本地初始化Firebase Functions(如果还没弄过):
firebase init functions
- 在Functions目录下安装Expo的服务器SDK:
cd functions npm install expo-server-sdk
- 编写Function代码(functions/index.js):
const functions = require("firebase-functions"); const admin = require("firebase-admin"); const { Expo } = require("expo-server-sdk"); // 初始化Firebase Admin admin.initializeApp(); // 初始化Expo SDK客户端 const expo = new Expo(); // 定义一个HTTP触发的Function(你也可以改成定时触发、数据库触发等) exports.sendExpoPushNotifications = functions.https.onRequest(async (req, res) => { // 从Firestore读取所有带Expo令牌的用户 const usersSnapshot = await admin.firestore().collection('users').where('expoPushToken', '!=', null).get(); // 构造推送消息数组 const messages = []; usersSnapshot.forEach(doc => { const userData = doc.data(); // 先验证令牌是否是有效的Expo令牌 if (Expo.isExpoPushToken(userData.expoPushToken)) { messages.push({ to: userData.expoPushToken, sound: 'default', // 通知音效 title: '你的应用名称', body: '这里是推送内容', data: { userId: doc.id, customKey: '自定义数据' }, // 可以传自定义数据给客户端 }); } }); // Expo推荐把消息分成chunk批量发送(避免请求过大) const chunks = expo.chunkPushNotifications(messages); const tickets = []; // 批量发送消息 for (const chunk of chunks) { try { const ticketChunk = await expo.sendPushNotificationsAsync(chunk); tickets.push(...ticketChunk); } catch (error) { functions.logger.error('发送推送时出错:', error); } } // 可选:处理推送回执,清理无效令牌 const receiptIds = tickets.filter(ticket => ticket.id).map(ticket => ticket.id); const receiptChunks = expo.chunkPushNotificationReceiptIds(receiptIds); for (const chunk of receiptChunks) { try { const receipts = await expo.getPushNotificationReceiptsAsync(chunk); for (const receiptId in receipts) { const receipt = receipts[receiptId]; if (receipt.status === 'error') { functions.logger.error(`推送失败,回执ID ${receiptId}: ${receipt.message}`, receipt.details?.error); // 如果是设备未注册(令牌无效),从数据库删除这个令牌 if (receipt.details?.error === 'DeviceNotRegistered') { const invalidTokenUsers = await admin.firestore() .collection('users') .where('expoPushToken', '==', receiptId) .get(); invalidTokenUsers.forEach(doc => { doc.ref.update({ expoPushToken: admin.firestore.FieldValue.delete() }); }); } } } } catch (error) { functions.logger.error('获取推送回执时出错:', error); } } res.status(200).send('推送任务已成功触发'); });
步骤3:部署并测试你的方案
- 部署Cloud Function:
firebase deploy --only functions
- 测试推送:
- 打开你的Expo应用,确保用户已登录,令牌成功存储到Firebase Firestore的
users集合里 - 调用部署好的Function的HTTP地址(可以在Firebase控制台的Functions页面找到),或者触发你设置的其他触发条件(比如定时、数据库更新)
- 检查Expo Go应用是否收到推送通知
注意:如果是iOS设备,还要确保你的Expo项目配置了APNs证书(在Expo Dashboard的项目设置里),不然iOS收不到推送。
内容的提问来源于stack exchange,提问作者Sufiyaan Mitha
相关产品推荐
相关产品推荐

