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

Flutter按用户角色发送Firebase通知的报错解决方法

问题解决:Flutter中通过Firebase向指定角色用户发送通知

错误原因

FirebaseMessaging是客户端SDK,仅负责设备令牌管理、通知接收与处理,本身没有sendNotification方法。发送FCM通知必须通过服务器端API完成(避免在客户端暴露敏感密钥,保证安全性)。

实现方案

下面推荐两种可靠的实现方式,优先使用Firebase云函数(更安全):

方式一:使用Firebase Cloud Functions(推荐)

通过云函数查询目标用户令牌并调用FCM Admin SDK发送通知,全程在服务器端完成,无需暴露密钥。

1. 编写云函数(Node.js)

首先初始化Firebase Functions(若未配置,先安装firebase-tools并执行firebase init functions),然后修改functions/index.js:

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

// 定义可被Flutter客户端调用的云函数
exports.sendReportNotification = functions.https.onCall(async (data, context) => {
  // 验证调用者身份(可选,确保只有登录用户能触发)
  if (!context.auth) {
    throw new functions.https.HttpsError("unauthenticated", "请先登录");
  }

  // 从Firestore查询所有role为reviewer或admin的用户
  const usersSnapshot = await admin.firestore()
    .collection("users")
    .where("role", "in", ["reviewer", "admin"])
    .get();

  const tokens = [];
  usersSnapshot.forEach(doc => {
    const token = doc.data().fcmToken;
    if (token) tokens.push(token);
  });

  if (tokens.length === 0) {
    return { success: true, message: "无目标用户需要通知" };
  }

  // 构造通知内容
  const payload = {
    notification: {
      title: "新举报提醒",
      body: `${data.reportedBy}提交了一条举报,请及时处理`,
      clickAction: "FLUTTER_NOTIFICATION_CLICK"
    },
    data: {
      reportId: data.reportId // 自定义数据,用于客户端跳转至举报详情
    }
  };

  // 批量发送通知
  const response = await admin.messaging().sendMulticast({
    tokens: tokens,
    notification: payload.notification,
    data: payload.data
  });

  functions.logger.log(`成功发送${response.successCount}条,失败${response.failureCount}条`);
  return { success: true, successCount: response.successCount, failureCount: response.failureCount };
});

2. Flutter客户端调用云函数

先安装依赖:

flutter pub add cloud_functions firebase_auth

在Report按钮的点击事件中调用云函数:

import 'package:cloud_functions/cloud_functions.dart';
import 'package:firebase_auth/firebase_auth.dart';

void handleReportSubmit(String reportId) async {
  final currentUser = FirebaseAuth.instance.currentUser;
  if (currentUser == null) {
    // 处理未登录逻辑,比如提示用户登录
    return;
  }

  try {
    final callable = FirebaseFunctions.instance.httpsCallable('sendReportNotification');
    final result = await callable.call({
      'reportedBy': currentUser.displayName ?? currentUser.email,
      'reportId': reportId,
    });

    print('通知发送结果:${result.data}');
    // 显示成功提示,比如SnackBar
  } catch (e) {
    print('发送失败:$e');
    // 显示错误提示
  }
}

方式二:Flutter客户端直接调用FCM HTTP API(不推荐)

此方式需要暴露FCM服务器密钥,存在安全风险,仅用于测试场景。

1. 获取FCM服务器密钥

在Firebase控制台 → 项目设置 → 云消息传递 → 复制服务器密钥。

2. 编写发送代码

先安装依赖:

flutter pub add http cloud_firestore firebase_auth

实现发送逻辑:

import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';

void sendNotificationToAdmins() async {
  // 从Firestore获取目标用户令牌
  final usersSnapshot = await FirebaseFirestore.instance
      .collection('users')
      .where('role', 'in', ['reviewer', 'admin'])
      .get();

  final tokens = usersSnapshot.docs
      .map((doc) => doc.data()['fcmToken'])
      .where((token) => token != null)
      .cast<String>()
      .toList();

  if (tokens.isEmpty) return;

  const serverKey = '你的FCM服务器密钥';
  const fcmUrl = 'https://fcm.googleapis.com/fcm/send';

  final headers = {
    'Content-Type': 'application/json',
    'Authorization': 'key=$serverKey',
  };

  final body = jsonEncode({
    'registration_ids': tokens,
    'notification': {
      'title': '新举报提醒',
      'body': '有用户提交了举报,请处理',
    },
    'data': {
      'reportId': 'report_123',
    },
  });

  final response = await http.post(Uri.parse(fcmUrl), headers: headers, body: body);

  if (response.statusCode == 200) {
    print('通知发送成功');
  } else {
    print('发送失败:${response.body}');
  }
}

补充:客户端令牌保存

确保用户登录时,将FCM令牌保存到Firestore的用户文档中:

import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';

Future<void> _backgroundMessageHandler(RemoteMessage message) async {
  await Firebase.initializeApp();
  print('后台收到通知:${message.notification?.title}');
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();

  // 配置后台通知处理
  FirebaseMessaging.onBackgroundMessage(_backgroundMessageHandler);

  // 获取并保存FCM令牌
  final token = await FirebaseMessaging.instance.getToken();
  final user = FirebaseAuth.instance.currentUser;
  if (user != null && token != null) {
    await FirebaseFirestore.instance.collection('users').doc(user.uid).update({
      'fcmToken': token,
    });
  }

  runApp(MyApp());
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 07:13:32