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

基于Expo的应用:借助Firebase Cloud Function实现推送通知的技术问询

嘿,你的思路完全靠谱——不用eject Expo项目,靠存储Expo推送令牌到Firebase,再用Cloud Functions调用Expo的推送API发通知,完美绕开FCM的兼容问题。我来一步步给你拆解具体实现:

步骤1:在Expo应用中获取并存储Expo推送令牌到Firebase

首先得在你的Expo项目里搞定令牌的获取和存储:

  1. 先安装必要依赖:
expo install expo-notifications firebase
  1. 编写获取权限+存储令牌的代码(可以放在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发通知:

  1. 先在本地初始化Firebase Functions(如果还没弄过):
firebase init functions
  1. 在Functions目录下安装Expo的服务器SDK:
cd functions
npm install expo-server-sdk
  1. 编写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:部署并测试你的方案
  1. 部署Cloud Function:
firebase deploy --only functions
  1. 测试推送:
  • 打开你的Expo应用,确保用户已登录,令牌成功存储到Firebase Firestore的users集合里
  • 调用部署好的Function的HTTP地址(可以在Firebase控制台的Functions页面找到),或者触发你设置的其他触发条件(比如定时、数据库更新)
  • 检查Expo Go应用是否收到推送通知

注意:如果是iOS设备,还要确保你的Expo项目配置了APNs证书(在Expo Dashboard的项目设置里),不然iOS收不到推送。

内容的提问来源于stack exchange,提问作者Sufiyaan Mitha

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 09:53:19