Springboot和Android项目接入Firebase Cloud Message(FCM)
一、官网
https://firebase.google.com/docs/cloud-messaging?hl=zh-cn
二、Springboot项目集成主要流程
- 创建 Firebase 项目并获取服务账号密钥
- 添加依赖项
- 配置 Firebase 服务
- 发送推送通知
1. 创建 Firebase 项目并获取服务账号密钥
- 登录 Firebase 控制台,创建一个新项目。
- 在左侧菜单中,点击 “Project settings”(项目设置)。
- 转到 “Service accounts”(服务账户)选项卡。
- 点击 “Generate new private key”,这将下载一个 JSON 文件,包含了你的 Firebase 服务账户的密钥。
- 将这个密钥文件保存到 Spring Boot 项目中(例如
src/main/resources/
目录)。
2. 添加依赖项
implementation("com.google.firebase:firebase-admin:9.4.3")
3. 配置 Firebase 服务
初始化 Firebase App,加载服务账户密钥。
package com.cn.gasservice.config
import com.google.firebase.FirebaseApp
import com.google.firebase.FirebaseOptions
import com.google.firebase.messaging.FirebaseMessaging
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.stereotype.Component
import java.io.FileInputStream
/**
* @Author : Cook
* @Date : 2025/1/15
* @Desc :
* @Version:
*/
@Configuration
class FirebaseConfig {
init {
val serviceAccount = FileInputStream("src/main/resources/fcm.json")
val options = FirebaseOptions.builder()
.setCredentials(com.google.auth.oauth2.GoogleCredentials.fromStream(serviceAccount))
.build()
if (FirebaseApp.getApps().isEmpty()) {
FirebaseApp.initializeApp(options)
}
}
@Bean("firebaseMessaging")
fun getFirebaseMessaging(): FirebaseMessaging = FirebaseMessaging.getInstance()
}
4. 发送推送通知
使用firebaseMessaging.sendEachForMulticastAsync方法或者其他单独发送同步或者异步根据自己需求调用。主要参数【token】是由终端获取并上传而来,具体获取的方法在下面Android项目中。
推送内容可以自己调用putData方法传递,终端接手后自己根据消息类型自行处理,也可以使用对应的setNotification方法
setNotification(Notification notification)
对于推送结果的记录如果是单独推送的话比较好处理,直接记录就可以,
如果是批量推送的话,可以根据token和用户ID的绑定关系来处理,参照下面的方法,或者有其他更好的办法可以交流一下。
package com.cn.gasservice.service.impl
import com.cn.gasservice.constant.consist.AppConstants.KEY_MESSAGE
import com.cn.gasservice.mapper.PushMessageLogMapper
import com.cn.gasservice.mapper.PushRegistrationMapper
import com.cn.gasservice.model.dto.PushMessageLogDTO
import com.cn.gasservice.model.dto.PushRegistrationDTO
import com.cn.gasservice.model.dto.SendMsgDTO
import com.cn.gasservice.model.vo.PushMsgVO
import com.cn.gasservice.service.intf.PushService
import com.cn.gasservice.utils.AppUtils.object2Json
import com.cn.gasservice.utils.LogUtils
import com.google.api.core.ApiFuture
import com.google.firebase.messaging.BatchResponse
import com.google.firebase.messaging.FirebaseMessaging
import com.google.firebase.messaging.MulticastMessage
import org.springframework.beans.BeanUtils
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Service
import java.util.concurrent.Executors
/**
* @Author : Cook
* @Date : 2025/1/15
* @Desc :
* @Version:
*/
@Service
class PushServiceImpl(
@Qualifier("firebaseMessaging")
private val firebaseMessaging: FirebaseMessaging,
private val mapper: PushRegistrationMapper,
private val pushMessageLogMapper: PushMessageLogMapper
) : PushService {
override fun fcmMsg(msg: SendMsgDTO) {
val map = mapper.queryTokenMapByUserId(msg.receivers)
val tokens = map.keys
val pushMsgVO = PushMsgVO()
BeanUtils.copyProperties(msg, pushMsgVO)
pushMsgVO.messageId = msg.id
val pushData = object2Json(pushMsgVO)
val multicastMessage = MulticastMessage.builder()
.putData(KEY_MESSAGE, pushData)
.addAllTokens(tokens)
.build()
val future = firebaseMessaging.sendEachForMulticastAsync(multicastMessage)
updatePushLog(future, pushMsgVO, pushData, map, tokens)
LogUtils.info("FCM push msg: $pushData")
}
fun updatePushLog(
future: ApiFuture<BatchResponse>,
msg: PushMsgVO,
pushData: String,
map: Map<String, PushRegistrationDTO>,
tokenList: Collection<String>
) {
future.addListener(Runnable {
val tokens = arrayListOf<String>()
tokens.addAll(tokenList)
val result = future.get() // 获取任务结果
val pushLogList = arrayListOf<PushMessageLogDTO>()
val sendResponseList = result?.responses ?: arrayListOf()
sendResponseList.forEachIndexed { index, data ->
LogUtils.info("updatePushLog: ${data.messageId} ---${data.isSuccessful}----${data.exception}")
val token = tokens[index]
pushLogList.add(PushMessageLogDTO().apply {
messageId = "${msg.messageId}"
userId = map[token]?.userId ?: ""
deviceType = map[token]?.deviceType ?: ""
pushStatus = if (data.isSuccessful) 1 else 2
errorMessage = data.exception?.toString()
errorCode = data.exception?.errorCode?.name
messageContent = pushData
registrationId = token
})
}
val insertResult = pushMessageLogMapper.insertAll(pushLogList)
LogUtils.info("updatePushLog success: $insertResult")
}, Executors.newSingleThreadExecutor())
}
override fun registerPush(pushRegistration: PushRegistrationDTO): Boolean {
val pg = mapper.selectByUserID(pushRegistration)
return if (pg == null) {
mapper.add(pushRegistration) > 0
} else {
pg.registrationId = pushRegistration.registrationId
mapper.update(pg) > 0
}
}
}
三、Android项目集成流程
创建应用后根据官方流程添加集成即可
1、Android 13以上需要动态获取通知权限
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun MultiplePermissionsRequest() {
val permissionsState = rememberMultiplePermissionsState(
permissions = listOf(
android.Manifest.permission.POST_NOTIFICATIONS,
)
)
// 请求权限
LaunchedEffect(Unit) {
permissionsState.launchMultiplePermissionRequest()
}
}
2、在MyApplication中的onCreate方法中初始化FCM
有可能初始化失败,加个Catch
try {
FirebaseApp.initializeApp(this)
} catch (e: Exception) {
e.printStackTrace()
}
3、添加对应的服务
[onMessageReceived]方法处理收到的消息逻辑
[onNewToken]初始化成功后会收到token信息,可以判断当前是否登录,如果已登录就上传token和用户ID绑定,如果没有登录就先存储,等登录的时候一起上传到服务端。
package com.co.post.data.push
import android.util.Log
import com.co.post.data.model.db.Message
import com.co.post.data.repository.PushDataRepository
import com.co.post.utils.CacheHelper
import com.co.post.utils.Constants.KEY_PUSH_DATA
import com.co.post.utils.Constants.MSG_TYPE_TEXT
import com.co.post.utils.NotificationHelper
import com.co.post.utils.fromJson
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
/**
* @Author : Cook
* @Date : 2025/1/15
* @Desc :
* @Version:
*/
class FCMService : FirebaseMessagingService() {
private val pushDataRepository: PushDataRepository by inject()
override fun onCreate() {
super.onCreate()
}
override fun onMessageReceived(remoteMessage: RemoteMessage) {
// 处理接收到的消息
Log.e("FCM", "From: ${remoteMessage.from} ${remoteMessage.data[KEY_PUSH_DATA]} ")
CoroutineScope(Dispatchers.Default).launch {
try {
val msgData = remoteMessage.data[KEY_PUSH_DATA] ?: ""
if (msgData.isNotEmpty()) {
val msg = msgData.fromJson<Message>()
msg?.let {
it.isRead = false
pushDataRepository.add(it)
with(Dispatchers.Main) {
if (msg.messageType == MSG_TYPE_TEXT) {
NotificationHelper.showNotification(
it.title,
it.content,
it.messageId
)
}
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
override fun onNewToken(token: String) {
CacheHelper.pushToken = token
if (CacheHelper.isLogged()) {
CoroutineScope(Dispatchers.IO).launch {
pushDataRepository.registerPush(token)
}
}
// 获取新的 FCM 令牌
Log.e("FCM", "Refreshed token: $token")
}
}