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

Flutter中如何通过FCM HTTP v1 API从设备端编写通知(替代控制台)

解决方案总览

首先明确核心原则:FCM HTTP v1 API的服务账号密钥绝对不能放在Flutter客户端,一旦暴露,任何人都可以冒用你的权限发送通知。必须通过后端服务或Firebase Cloud Functions中转调用,这是安全底线。下面分模块解决你的问题:

一、安全实现FCM推送(避免密钥暴露)

1. 用Firebase Cloud Functions作为中转(推荐,无需自建后端)

编写Callable Function,客户端调用这个函数,由函数安全调用FCM v1 API发送通知——Firebase环境会自动管理服务账号密钥,无需手动存储json文件。

示例Cloud Functions代码(Node.js):

const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();

exports.sendScheduledNotification = functions.https.onCall(async (data, context) => {
  // 可选:验证用户身份,确保只有合法用户调用
  if (!context.auth) {
    throw new functions.https.HttpsError("unauthenticated", "用户未登录");
  }

  const { token, title, body, scheduledTime } = data;
  
  // 校验定时时间合法性
  const delay = scheduledTime - Date.now();
  if (delay <= 0) {
    throw new functions.https.HttpsError("invalid-argument", "定时时间必须在未来");
  }

  // 延迟执行推送(自动使用FCM v1 API)
  setTimeout(async () => {
    await admin.messaging().send({
      token: token,
      notification: {
        title: title,
        body: body,
      },
      data: {
        // 自定义数据,用于后续点击通知的逻辑跳转
        type: "weather_alert",
        content: "明日降温提醒"
      }
    });
  }, delay);

  return { success: true, message: "定时任务已创建" };
});

2. 自建后端服务

如果已有自己的后端,直接在后端集成Firebase Admin SDK,客户端调用后端接口,由后端处理FCM推送逻辑,同样避免密钥暴露在客户端。

二、Flutter客户端处理流程

1. 获取设备推送令牌(FCM Token)

使用firebase_messaging插件获取令牌,传给后端/Cloud Functions作为推送目标:

import 'package:firebase_messaging/firebase_messaging.dart';

Future<String?> getFCMToken() async {
  String? token = await FirebaseMessaging.instance.getToken();
  return token;
}

2. 处理通知点击事件

结合firebase_messaging和flutter_local_notifications插件,覆盖前台、后台、应用未启动三种场景的点击处理:

步骤1:初始化本地通知插件

import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'dart:convert';

final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
    FlutterLocalNotificationsPlugin();

Future<void> initNotifications() async {
  const AndroidInitializationSettings initializationSettingsAndroid =
      AndroidInitializationSettings('@mipmap/ic_launcher');
  const InitializationSettings initializationSettings =
      InitializationSettings(android: initializationSettingsAndroid);
  
  // 初始化时绑定点击回调
  await flutterLocalNotificationsPlugin.initialize(initializationSettings,
      onDidReceiveNotificationResponse: (NotificationResponse response) {
    handleNotificationClick(response.payload);
  });
}

步骤2:监听FCM消息并处理点击

// 前台收到消息时,显示本地通知
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
  RemoteNotification? notification = message.notification;
  AndroidNotification? android = message.notification?.android;
  if (notification != null && android != null) {
    flutterLocalNotificationsPlugin.show(
      notification.hashCode,
      notification.title,
      notification.body,
      NotificationDetails(
        android: AndroidNotificationDetails(
          'weather_channel',
          '天气提醒',
          channelDescription: '接收天气相关推送通知',
          icon: android.smallIcon,
        ),
      ),
      payload: jsonEncode(message.data), // 传递自定义数据到点击回调
    );
  }
});

// 后台点击通知时触发
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
  handleNotificationClick(jsonEncode(message.data));
});

// 应用未启动时点击通知触发
Future<void> setupInitialMessageHandler() async {
  RemoteMessage? initialMessage =
      await FirebaseMessaging.instance.getInitialMessage();
  if (initialMessage != null) {
    handleNotificationClick(jsonEncode(initialMessage.data));
  }
}

// 统一处理点击逻辑
void handleNotificationClick(String? payload) {
  if (payload == null) return;
  Map<String, dynamic> data = jsonDecode(payload);
  String type = data['type'] ?? '';
  
  if (type == 'weather_alert') {
    // 跳转到天气提醒页面
    // Navigator.push(context, MaterialPageRoute(builder: (context) => WeatherAlertPage()));
  }
}

三、实现定时推送

方式1:服务端定时(推荐,稳定可靠)

  • 单次定时:如Cloud Functions示例,客户端传递定时时间,服务端计算延迟后执行推送。
  • 周期性定时:用Cloud Functions的pubsub.schedule触发器实现固定时间推送(比如每天早8点):
exports.dailyWeatherAlert = functions.pubsub.schedule('0 8 * * *') // 每天早上8点
  .timeZone('Asia/Shanghai')
  .onRun(async (context) => {
    // 从Firestore读取需要推送的用户token列表
    const usersSnapshot = await admin.firestore().collection('users').get();
    const tokens = usersSnapshot.docs.map(doc => doc.data().fcmToken).filter(token => token != null);
    
    await admin.messaging().sendEachForMulticast({
      tokens: tokens,
      notification: {
        title: '今日天气提醒',
        body: '今日气温20-28℃,注意防晒'
      },
      data: { type: 'weather_daily' }
    });
    
    return null;
  });

方式2:本地定时(仅临时场景)

用flutter_local_notifications的本地定时通知,但应用被杀死后会失效,仅适合短时间内的定时提醒,不适合天气类长期推送需求。

关键注意事项

  • 绝对禁止客户端存储服务账号密钥:FCM v1 API的密钥是服务器端专属凭证,客户端存储会导致泄露风险。
  • 用户身份验证:在Cloud Functions或后端接口中验证用户身份(比如Firebase Auth的ID Token),避免恶意调用。
  • Android通知渠道配置:Android 8.0+必须配置通知渠道,否则通知无法正常显示,示例中已包含相关配置。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 17:37:05