使用新FCM API推送通知失败求助:报错及配置问题排查
FlutterFire FCM推送通知异常问题解决
核心问题分析
你的代码存在多个关键错误,直接导致推送失败:
- 请求URL错误:原URL末尾多余
HTTP/后缀,且项目ID用了无效占位符idd,无法正确定位FCM资源 - 认证方式混淆:FCM v1 API不使用服务器密钥,需要基于服务账号的OAuth2令牌;你错误地将私钥格式内容作为
key=值传入,完全不符合v1 API的认证要求 - 请求体结构不匹配:v1 API的推送内容必须包裹在
message字段内,目标设备标识需用token而非Legacy API的to字段 - VAPID Key未配置:
getToken方法中vapidKey为空,Web平台无法生成有效的FCM Token
分步修复方案
方案1:改用FCM Legacy API(快速解决)
如果不想处理v1 API的复杂认证,直接切换到更简单的Legacy API:
- 修正请求URL:改为
https://fcm.googleapis.com/fcm/send - 修正认证头:
Authorization值设为key=你的服务器密钥(从Firebase控制台>云消息传递>服务器密钥获取) - 补全设备Token:
to字段填入getToken方法获取到的真实设备Token
修复后的sendNotification代码:
Future<void> sendNotification() async { try { final response = await http.post( Uri.parse('https://fcm.googleapis.com/fcm/send'), headers: <String, String>{ 'Content-Type': 'application/json', 'Authorization': 'key=你的服务器密钥', }, body: jsonEncode(<String, dynamic>{ 'notification': <String, dynamic>{ 'body': 'This is a test notification', 'title': 'Test Notification' }, 'priority': 'high', 'data': <String, dynamic>{ 'click_action': 'FLUTTER_NOTIFICATION_CLICK', 'id': '1', 'status': 'done' }, 'to': '真实设备FCM Token', // 替换为getToken获取到的token }), ); if (response.statusCode == 200) { print('Notification sent successfully'); print('Response body: ${response.body}'); } else { print('Failed to send notification'); print('Response status: ${response.statusCode}'); print('Response body: ${response.body}'); } } catch (e) { print('Exception occurred while sending notification: $e'); } }
方案2:正确配置FCM v1 API(长期维护)
如果坚持使用v1 API,需完成以下配置:
- 获取服务账号密钥:Firebase控制台>项目设置>服务账号>生成新私钥,下载JSON配置文件
- 添加依赖:在
pubspec.yaml中添加googleapis_auth: ^1.4.1和http: ^0.13.5 - 生成OAuth2令牌:通过服务账号密钥生成FCM API的访问令牌
- 修正请求URL:替换
idd为你的实际项目ID,移除末尾HTTP/,即https://fcm.googleapis.com/v1/projects/你的项目ID/messages:send - 调整请求体结构:将推送内容包裹在
message字段内,用token指定目标设备
修复后的完整示例代码:
import 'package:googleapis_auth/googleapis_auth.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; import 'package:firebase_messaging/firebase_messaging.dart'; final FirebaseMessaging messaging = FirebaseMessaging.instance; void requestPermission() async { try { NotificationSettings settings = await messaging.requestPermission( alert: true, announcement: false, badge: true, carPlay: false, criticalAlert: false, provisional: false, sound: true, ); print('User granted permission: ${settings.authorizationStatus}'); } catch (e) { print('Error requesting permission: $e'); } } void getToken() async { try { String? token = await messaging.getToken( vapidKey: '你的Web VAPID密钥', // 从Firebase控制台>云消息传递>Web配置获取 ); print("FCM Token: $token"); } catch (e) { print('Error getting FCM token: $e'); } } Future<String> getOAuthToken() async { // 加载服务账号JSON内容(生产环境请勿硬编码,建议从安全配置读取) final serviceAccountJson = jsonDecode(''' { "type": "service_account", "project_id": "你的项目ID", "private_key_id": "你的私钥ID", "private_key": "-----BEGIN PRIVATE KEY-----\n你的私钥内容\n-----END PRIVATE KEY-----\n", "client_email": "你的服务账号邮箱", "client_id": "你的客户端ID", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_x509_cert_url": "你的客户端证书URL" } '''); final credentials = ServiceAccountCredentials.fromJson(serviceAccountJson); final scopes = ['https://www.googleapis.com/auth/firebase.messaging']; final client = await clientViaServiceAccount(credentials, scopes); final token = client.credentials.accessToken.data; client.close(); return token; } Future<void> sendNotification() async { try { final oauthToken = await getOAuthToken(); final response = await http.post( Uri.parse('https://fcm.googleapis.com/v1/projects/你的项目ID/messages:send'), headers: <String, String>{ 'Content-Type': 'application/json', 'Authorization': 'Bearer $oauthToken', }, body: jsonEncode(<String, dynamic>{ 'message': { 'token': '真实设备FCM Token', 'notification': <String, dynamic>{ 'body': 'This is a test notification', 'title': 'Test Notification' }, 'data': <String, dynamic>{ 'click_action': 'FLUTTER_NOTIFICATION_CLICK', 'id': '1', 'status': 'done' }, 'android': { 'priority': 'high' }, 'apns': { 'headers': { 'apns-priority': '10' } } } }), ); if (response.statusCode == 200) { print('Notification sent successfully'); print('Response body: ${response.body}'); } else { print('Failed to send notification'); print('Response status: ${response.statusCode}'); print('Response body: ${response.body}'); } } catch (e) { print('Exception occurred while sending notification: $e'); } }
额外注意事项
- 确保
getToken方法填入正确的VAPID密钥,否则Web平台无法生成FCM Token - 测试时必须使用真实设备Token,不可留空
- 生产环境禁止硬编码服务账号JSON,建议通过环境变量或安全配置中心读取
内容的提问来源于stack exchange,提问作者John Smith
相关产品推荐
相关产品推荐

