如何通过自托管SpringBoot无外部服务向前端发送推送通知
完全自建、无第三方依赖的SpringBoot移动端推送实现方案
核心逻辑
要绕开Firebase这类第三方推送服务,本质就是让移动端和你预留的专用推送节点之间直连长连接,所有消息流转全走自己的服务器集群,不需要经过任何外部服务商的系统,完全可控。
具体落地步骤
1. 专用推送节点配置(你规划的单独Ubuntu节点)
- 不要把推送逻辑耦合到现有业务SpringBoot应用里,单独在这台节点部署一个轻量的推送微服务,和其余两台业务节点走内网通信即可。负载均衡规则里把这台节点从普通业务请求的轮询池里摘出来,专门预留带宽处理长连接。
- 长连接优先选WebSocket协议,SpringBoot原生就支持,引入
spring-boot-starter-websocket依赖就能快速搭建服务端,对Android和后续Flutter的兼容性都很好,比裸写TCP Socket省大量适配工作。 - 服务端维护一份连接映射表:移动端首次建立连接时,携带自身绑定的用户ID、设备唯一标识,服务端将「用户ID-设备标识-活跃连接实例」的映射存在本地内存,同时同步到你现有SQL数据库做持久化,设备断连时自动清理无效映射。
- 做离线消息兜底:设备处于断连状态时,待推送的消息先写入SQL库的离线消息表,等设备下次重连成功,先拉取所有未读离线消息,再启动长连接保活流程,避免丢消息。
- 先调系统参数:Ubuntu默认单进程文件句柄上限是1024,先修改
/etc/security/limits.conf里的nofile参数到65535以上,不然长连接数过千就会报错,普通家用PC配置调完参数撑几万条长连接完全够用。
2. Android测试阶段实现
- 不需要对接任何厂商或Google的系统级推送通道,App启动后启动一个前台服务,维持和推送节点的WebSocket长连接,初始测试阶段把心跳间隔设为30秒,后续根据你家宽带的NAT超时规则调整即可,避免空闲连接被运营商路由强制断开。
- 长连接收到消息体后,直接调用Android原生
NotificationCompat接口弹出本地通知,全程不需要依赖Google服务框架。测试阶段记得给App开电池优化白名单,避免系统杀后台导致连接断开。 - 重连逻辑做指数退避:网络异常断开后,第一次等1秒重连,失败就依次等2秒、4秒、8秒,最长重连间隔设为5分钟,不要无限制高频重连把节点打挂。
3. 后续Flutter迁移适配
- 推送节点的代码完全不需要改动,Flutter端直接用
web_socket_channel包对接同一个WebSocket接口即可,本地通知调用flutter_local_notifications插件实现。前后端提前统一消息体JSON格式,固定包含消息ID、标题、内容、跳转路径、发送时间字段,迁移时后端零改动。
4. 集群适配注意事项
- 给推送节点分配固定的内网/公网访问地址,不要让WebSocket连接走普通业务的负载均衡轮询,不然每次重连落到不同节点会导致消息投递失败。
- 其余业务节点需要发推送时,直接走内网调用推送节点的发信接口即可,接口只允许内网访问,加简单的token鉴权,避免公网暴露被恶意盗刷。
可选优化方向
- 如果后续觉得WebSocket保活流量消耗高,可以把长连接换成MQTT协议,在推送节点部署开源的MQTT broker即可,比原生WebSocket更适合推送场景,Android和Flutter端的MQTT客户端库都非常成熟,同样不需要依赖任何外部服务。
- 消息传输可以加一层RSA+AES混合加密,避免公网传输时被窃听篡改,密钥在App首次启动时通过业务接口协商即可。
内容的提问来源于stack exchange,提问作者Priyesh Jakhmola
相关产品推荐
相关产品推荐

