如何通过C#向Flutter应用发送云消息及推送通知
基于C#实现Firestore新增文档触发Flutter通知的落地方案
方案选型
你可以根据自己的运维条件二选一:
- 方案1:C#常驻后台服务 + Firestore实时监听 + FCM推送:全链路用C#实现,逻辑完全自控,不需要依赖GCP云函数,是最贴合需求的方案
- 方案2:Firestore云函数触发器 + C#业务接口:不需要自己维护常驻监听进程,适合运维资源有限的场景
方案1 详细操作步骤(全C#栈实现)
前置准备
- 准备Firebase项目,在控制台「项目设置-服务账号」页面生成服务账号私钥JSON文件,妥善保存
- 准备一台常驻运行的服务器,部署C#服务(可以是控制台程序、Windows服务、ASP.NET Core后台服务,不要把监听逻辑写在WinForm客户端里,客户端关闭会直接断连漏消息)
- Flutter端提前集成FCM能力,将每个用户登录后的设备注册Token,存在Firestore的
UserTokens集合中,集合字段至少包含UserId(对应用户唯一标识)、Token(FCM设备令牌)、LastActiveTime(设备最后活跃时间) - C#项目通过Nuget安装两个依赖包:
Google.Cloud.Firestore、FirebaseAdmin
第一步:初始化Firestore与FCM客户端
using Google.Cloud.Firestore; using FirebaseAdmin; using FirebaseAdmin.Messaging; using Google.Apis.Auth.OAuth2; // 替换成你自己的Firebase项目ID string projectId = "你的Firebase项目ID"; // 替换成你下载的服务账号密钥文件本地路径 string credentialPath = "你的服务账号密钥.json"; // 初始化Firestore客户端 FirestoreDb firestoreDb = FirestoreDb.Create(projectId, GoogleCredential.FromFile(credentialPath)); // 初始化FCM推送客户端 FirebaseApp.Create(new AppOptions() { Credential = GoogleCredential.FromFile(credentialPath) .CreateScoped("https://www.googleapis.com/auth/firebase.messaging"), ProjectId = projectId }); FirebaseMessaging messagingClient = FirebaseMessaging.DefaultInstance;
第二步:注册Requests集合新增文档监听
CollectionReference requestsCollection = firestoreDb.Collection("Requests"); // 初始查询游标:如果是服务首次启动,从当前时间开始监听;如果需要避免重启漏消息,可以把最后处理的文档时间存在本地配置/独立Firestore文档里,启动时读取这个时间作为查询起点 Timestamp listenStartTime = Timestamp.FromDateTime(DateTime.UtcNow); Query listenQuery = requestsCollection.WhereGreaterThan("CreateTime", listenStartTime); // 注册快照监听 listenQuery.Listen(async (snapshot) => { foreach (DocumentChange change in snapshot.Changes) { // 只处理新增文档事件,跳过修改、删除操作 if (change.ChangeType != DocumentChange.Type.Added) continue; DocumentSnapshot newRequestDoc = change.Document; // 替换成你自己的Requests文档对应的实体类 RequestModel newRequest = newRequestDoc.ConvertTo<RequestModel>(); // 异步触发推送,不要阻塞监听线程 _ = PushNotificationToUser(messagingClient, newRequest.TargetUserId, newRequestDoc.Id, newRequest.Content); } }, ex => { // 这里加监听断连的日志、重连逻辑即可 Console.WriteLine($"监听异常:{ex.Message}"); }); // 阻塞保持进程运行,不要让进程退出 await Task.Delay(Timeout.Infinite);
如果需要保证服务重启、断连期间的消息100%不丢,不要直接用服务启动时间作为监听起点,单独维护一个最后处理成功的文档创建时间戳,每次处理完新文档就更新这个时间戳,服务启动时从这个时间点开始监听即可
第三步:实现通知推送逻辑
async Task PushNotificationToUser(FirebaseMessaging messaging, string targetUserId, string requestId, string requestContent) { // 查询目标用户绑定的所有有效设备Token QuerySnapshot tokenSnapshot = await firestoreDb.Collection("UserTokens") .WhereEqualTo("UserId", targetUserId) .WhereGreaterThan("LastActiveTime", Timestamp.FromDateTime(DateTime.UtcNow.AddDays(-30))) .GetSnapshotAsync(); List<string> validTokens = tokenSnapshot.Documents.Select(d => d.GetValue<string>("Token")).ToList(); if (!validTokens.Any()) return; // 构造通知内容 MulticastMessage pushMessage = new MulticastMessage() { Tokens = validTokens, Notification = new Notification() { Title = "新申请提醒", Body = $"您收到一条新的申请:{requestContent}" }, // 传递给Flutter端的业务参数 Data = new Dictionary<string, string>() { {"requestId", requestId}, {"action", "OPEN_REQUEST_DETAIL"} } }; // 批量推送 BatchResponse response = await messaging.SendMulticastAsync(pushMessage); // 清理失效的Token,避免后续重复推送无效地址 if (response.FailureCount > 0) { List<string> invalidTokens = response.Responses .Zip(validTokens, (resp, token) => new { resp, token }) .Where(item => !item.resp.IsSuccess) .Select(item => item.token) .ToList(); // 补全从UserTokens集合删除invalidTokens的逻辑即可 } }
Flutter端配套处理
- App启动时申请通知权限,初始化FCM后将当前设备的Token和登录用户ID绑定,上传到Firestore的
UserTokens集合 - 注册FCM前台、后台消息监听,点击通知时取出Data字段里的
requestId,跳转到对应申请详情页 - 应用在被杀后台状态时,FCM会自动弹出系统通知,不需要额外做保活处理
方案2 轻量实现(无常驻服务可选)
如果不想维护长期运行的C#服务进程,可以直接在Firebase控制台配置Firestore触发器,当Requests集合有新增文档时自动触发云函数,云函数中调用你部署的C# WebAPI接口做业务校验,校验通过后由C#接口调用FCM完成推送。
这种方案下C#侧只需要写普通的HTTP接口即可,不需要维护Firestore长连接监听,注意要给接口加签名校验,避免被恶意调用触发推送。
注意:绝对不要在WinForm客户端直接监听Requests集合做推送,第一是客户端无法保证24小时在线,必然出现消息漏发;第二是客户端直接读取全集合数据会有越权风险,普通用户可以通过抓包获取所有申请的敏感数据,所有监听、推送逻辑必须放在服务端运行。
内容的提问来源于stack exchange,提问作者mkz
相关产品推荐
相关产品推荐

