Spring Boot集成Firebase 實現(xiàn)FCM的消息推送功能

前言

最近在開發(fā)一款國際版的APP午乓,項目中需要支持客戶端消息推送益愈,自己實現(xiàn)肯定是不可能的,需要尋找第三方的SDK敏释。在做技術(shù)調(diào)研的時候決定使用google的FCM框架來實現(xiàn)钥顽,有個缺點就是大陸是接收不到的(fq可以)蜂大。那么本章就給大家分享一下如何基于Spring Boot集成Firebase實現(xiàn)FCM消息推送功能县爬。

必要條件

1、大陸開發(fā)者要準(zhǔn)備好vpn(你懂的)添谊。
2财喳、申請Google Firebase賬號。
3斩狱、獲取Firebase秘鑰耳高。
4、有效的客戶端token令牌(app客戶端開發(fā)者提供)所踊。

開發(fā)
第一步:申請開發(fā)者賬號

地址:https://console.firebase.google.com/

第二步:添加一個項目

如下圖所示泌枪,添加一個項目:


添加項目
添加步驟
12.pic.jpg

TPS:注意必須與android項目本地包名一致就是manifest中package的路徑一樣秕岛。

第三步:下載google-services.json文件

點擊左上角——設(shè)置(圖標(biāo))——項目設(shè)置碌燕,點擊google-services.json下載误证,如下圖所示:

下載google-services.json文件

將下載好的文件放到Spring Boot項目的resources目錄下,例如:

存放目錄

第四步:集成Firebase

pom引用firebase修壕,要fq才能下載愈捅,如果沒有條件就需要手動導(dǎo)入下載好的jar包。

<dependency>
      <groupId>com.google.firebase</groupId>
      <artifactId>firebase-admin</artifactId>
      <version>6.5.0</version>
</dependency>
第五步:編寫FCM推送消息請求實體

這里只是做了個發(fā)送消息的簡單封裝實體慈鸠,方便數(shù)據(jù)傳輸:

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
/**
 *
 * 功能描述: FCM推送消息請求實體
 * @auther: IT實戰(zhàn)聯(lián)盟Line
 * @date: 2019年11月25日09:54:51
 */
@NoArgsConstructor
@Data
@ApiModel(value = "FCM推送消息請求實體")
public class FCMSendMessageReqDTO implements Serializable {
    private static final long serialVersionUID = 8317264020451674205L;
    @ApiModelProperty(value = "消息標(biāo)題" , required = true)
    private String title;
    @ApiModelProperty(value = "消息內(nèi)容" , required = true)
    private String body;
    @ApiModelProperty(value = "用戶token集合" , required = true)
    private String tokens;
    @ApiModelProperty(value = "主題" , required = false)
    private String topic;
}
第六步:編寫FCM推送消息工具類

該工具類支持單個和批量推送蓝谨,這里批量采用的是給主題推。

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import com.google.auth.oauth2.GoogleCredentials;
import java.util.concurrent.ConcurrentHashMap;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.messaging.AndroidConfig;
import com.google.firebase.messaging.AndroidConfig.Builder;
import com.google.firebase.messaging.AndroidNotification;
import com.google.firebase.messaging.FirebaseMessaging;
import com.google.firebase.messaging.FirebaseMessagingException;
import com.google.firebase.messaging.Message;
import com.google.firebase.messaging.Notification;
import com.google.firebase.messaging.TopicManagementResponse;

/**
 * @Auther: IT實戰(zhàn)聯(lián)盟Line
 * @Date: 2019-11-23 14:02
 * @Description:  Google_FireBase推送工具類
 */
public class FireBaseUtil {
    //存放多個實例的Map
    private static Map<String, FirebaseApp> firebaseAppMap = new ConcurrentHashMap<>();
    //獲取AndroidConfig.Builder對象
    private static com.google.firebase.messaging.AndroidConfig.Builder androidConfigBuilder=AndroidConfig.builder();
    //獲取AndroidNotification.Builder對象
    private static AndroidNotification.Builder androidNotifiBuilder=AndroidNotification.builder();

    /**
     * 判斷SDK是否初始化
     * @param appName
     * @return
     */
    public static boolean isInit(String appName) {
        return firebaseAppMap.get(appName) != null;
    }

    /**
     * 初始化SDK
     * @param jsonPath      JSON路徑
     * @param dataUrl       firebase數(shù)據(jù)庫
     * @param appName       APP名字
     * @throws IOException
     */
    public static void initSDK(String jsonPath, String dataUrl,String appName) throws IOException {
        InputStream serviceAccount = Thread.currentThread().getContextClassLoader().getResourceAsStream(jsonPath);
        FirebaseOptions options = new FirebaseOptions.Builder()
                .setCredentials(GoogleCredentials.fromStream(serviceAccount))
                .setDatabaseUrl(dataUrl).build();
        //初始化firebaseApp
        FirebaseApp firebaseApp = FirebaseApp.initializeApp(options);
        //存放
        firebaseAppMap.put(appName,firebaseApp);
    }

    /**
     * 單設(shè)備推送
     * @param appName      應(yīng)用的名字
     * @param token        注冊token
     * @param title        推送題目
     * @param bady         推送內(nèi)容
     * @return
     * @throws IOException
     * @throws FirebaseMessagingException
     */
    public static void pushSingle(String appName, String token, String title, String body) throws IOException, FirebaseMessagingException{
        //獲取實例
        FirebaseApp firebaseApp = firebaseAppMap.get(appName);
        //實例為空的情況
        if (firebaseApp == null) {
            return;
        }
        //構(gòu)建消息內(nèi)容
        Message message = Message.builder().setNotification(new Notification(title,body))
                .setToken(token)
                .build();
        //發(fā)送后青团,返回messageID
        String response = FirebaseMessaging.getInstance(firebaseApp).send(message);
        System.out.println("單個設(shè)備推送成功 : "+response);
    }

    /**
     * 給設(shè)備訂閱主題
     * @param appName     應(yīng)用的名字
     * @param tokens      設(shè)備的token,最大1000個
     * @param topic       要添加的主題
     * @return
     * @throws FirebaseMessagingException
     * @throws IOException
     */
    public static void registrationTopic(String appName, List<String> tokens, String topic) throws FirebaseMessagingException, IOException {
        //獲取實例
        FirebaseApp firebaseApp = firebaseAppMap.get(appName);
        //實例不存在的情況
        if(firebaseApp == null) {
            return;
        }
        //訂閱譬巫,返回主題管理結(jié)果對象。
        TopicManagementResponse response = FirebaseMessaging.getInstance(firebaseApp).subscribeToTopic(tokens, topic);
        System.out.println("添加設(shè)備主題督笆,成功:" + response.getSuccessCount() + ",失斅簟:" + response.getFailureCount());
    }
    /**
     * 按主題推送
     * @param appName      應(yīng)用的名字
     * @param topic        主題的名字
     * @param title        消息題目
     * @param body         消息體
     * @return
     * @throws FirebaseMessagingException
     * @throws IOException
     */
    public static void sendTopicMes(String appName, String topic, String title, String body) throws FirebaseMessagingException, IOException {
        //獲取實例
        FirebaseApp firebaseApp = firebaseAppMap.get(appName);
        //實例不存在的情況
        if(firebaseApp == null) {
            return;
        }
        //構(gòu)建消息
        Message message = Message.builder()
                .setNotification(new Notification(title,body))
                .setTopic(topic)
                .build();
        //發(fā)送后,返回messageID
        String response = FirebaseMessaging.getInstance(firebaseApp).send(message);
        System.out.println("主題推送成功: " + response);
    }
}
第七步:編寫FCM推送Controller

一共兩個Controller娃肿,單推和群推烟零。

import com.google.firebase.messaging.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;

import java.io.*;
import java.util.*;
@Api(value = "FireBase", description = "FireBase")
@RequestMapping("/")
@RestController
@Slf4j
public class FireBaseController {
    //渠道名字,也是APP的名字
    public static String appName = "FCM后臺新增的項目名稱";

    @ApiOperation(value = "批量FireBase推送", notes = "批量FireBase推送")
    @ApiImplicitParam(name = "fcmSendMessageReqDTO", value = "{\"title\":\"測試標(biāo)題\",\"body\":\"測試內(nèi)容\",\"tokens\":\"1,2\"}")
    @PostMapping(value = "/pushFireBaseAll", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public void pushFireBaseAll(@RequestBody FCMSendMessageReqDTO fcmSendMessageReqDTO) {
        log.info("進(jìn)入批量FireBase推送 pushFireBaseAll:[{}]", fcmSendMessageReqDTO.toString());
        //添加tokens
        List<String> tokens = Arrays.asList(fcmSendMessageReqDTO.getTokens().split(","));
        //設(shè)置Java代理,端口號是代理軟件開放的端口咸作,這個很重要锨阿。
        System.setProperty("proxyHost", "127.0.0.1");
        System.setProperty("proxyPort", "8081");
        //如果FirebaseApp沒有初始化
        if (!FireBaseUtil.isInit(appName)) {
            String jsonPath = "fcm/xxxx-firebase-adminsdk.json";
            String dataUrl = "https://xxxx.firebaseio.com/";
            //初始化FirebaseApp
            try {
                FireBaseUtil.initSDK(jsonPath, dataUrl, appName);
                FireBaseUtil.registrationTopic(appName, tokens, fcmSendMessageReqDTO.getTopic());  //設(shè)置主題
                FireBaseUtil.sendTopicMes(appName, fcmSendMessageReqDTO.getTopic(), fcmSendMessageReqDTO.getTitle(), fcmSendMessageReqDTO.getBody());    //按主題推送
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            log.info("如果FirebaseApp已經(jīng)初始化");
            try {
                FireBaseUtil.registrationTopic(appName, tokens, fcmSendMessageReqDTO.getTopic());  //設(shè)置主題
                FireBaseUtil.sendTopicMes(appName, fcmSendMessageReqDTO.getTopic(), fcmSendMessageReqDTO.getTitle(), fcmSendMessageReqDTO.getBody());    //按主題推送
            } catch (IOException e) {
                e.printStackTrace();
            } catch (FirebaseMessagingException e) {
                e.printStackTrace();
            }
        }
    }

    @ApiOperation(value = "FireBase推送", notes = "FireBase推送")
    @ApiImplicitParam(name = "fcmSendMessageReqDTO", value = "{\"title\":\"測試標(biāo)題\",\"body\":\"測試內(nèi)容\",\"tokens\":\"1\"}")
    @PostMapping(value = "/pushFireBase", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public void pushFireBase(@RequestBody FCMSendMessageReqDTO fcmSendMessageReqDTO) {
        log.info("進(jìn)入批量FireBase推送 pushFireBaseAll:[{}]", fcmSendMessageReqDTO.toString());
        //添加tokens
        List<String> tokens = Arrays.asList(fcmSendMessageReqDTO.getTokens().split(","));
        //設(shè)置Java代理,端口號是代理軟件開放的端口,這個很重要记罚。
        System.setProperty("proxyHost", "127.0.0.1");
        System.setProperty("proxyPort", "8081");
        //如果FirebaseApp沒有初始化
        if (!FireBaseUtil.isInit(appName)) {
            String jsonPath = "fcm/xxxx-firebase-adminsdk.json";
            String dataUrl = "https://xxxx.firebaseio.com/";
            //初始化FirebaseApp
            try {
                FireBaseUtil.initSDK(jsonPath, dataUrl, appName);
                FireBaseUtil.pushSingle(appName, tokens.get(0), fcmSendMessageReqDTO.getTitle(), fcmSendMessageReqDTO.getBody());  //單推
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            log.info("如果FirebaseApp已經(jīng)初始化");
            try {
                FireBaseUtil.pushSingle(appName, tokens.get(0), fcmSendMessageReqDTO.getTitle(), fcmSendMessageReqDTO.getBody());  //單推
            } catch (IOException e) {
                e.printStackTrace();
            } catch (FirebaseMessagingException e) {
                e.printStackTrace();
            }
        }
    }
}

Spring Boot 項目中集成了swagger 墅诡,小伙伴在使用的時候可以去掉。代碼是沒問題的桐智,都已經(jīng)測試過末早。

總結(jié)

1、一定要確認(rèn)網(wǎng)絡(luò)是否可以FQ说庭,如果不行是訪問不了firebase service的然磷。否則會報以下錯誤,如圖所示:


鏈接FCM服務(wù)異常

2刊驴、xxx.json 存放位置的目錄要做好引用姿搜,小編是用mac運行的,如果是windows獲取json文件的相對路徑會有問題捆憎。
3舅柜、給特定設(shè)備推送消息時,需要提前獲取到設(shè)備的deviceToken躲惰。
總之致份,以上代碼可以幫助大家快速入門,并且編寫出可以推送成功的示例础拨,如果在使用中有什么問題大家可以留言一起討論哦~~~
4氮块、國產(chǎn)手機(jī)一般閹割了Google service的服務(wù)绍载,需要自己去找第三方資源安裝,會產(chǎn)生以下問題:

A滔蝉、應(yīng)用“殺死”后基本收不到消息击儡。
B、Android8 系統(tǒng)不管應(yīng)用是否可用都出現(xiàn)收不到消息問題锰提。
有資源的小伙伴可以多測試集中機(jī)型哦曙痘。

5芳悲、設(shè)備必須是android4.0以上立肘,Google Play Services 必須是 11.2.0以上版本(這個沒有測試,擁有的資源基本都是6以上)名扛。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末谅年,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子肮韧,更是在濱河造成了極大的恐慌融蹂,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件弄企,死亡現(xiàn)場離奇詭異超燃,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)拘领,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進(jìn)店門意乓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人约素,你說我怎么就攤上這事届良。” “怎么了圣猎?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵士葫,是天一觀的道長。 經(jīng)常有香客問我送悔,道長慢显,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任欠啤,我火速辦了婚禮鳍怨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘跪妥。我一直安慰自己鞋喇,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布眉撵。 她就那樣靜靜地躺著侦香,像睡著了一般落塑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上罐韩,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天憾赁,我揣著相機(jī)與錄音,去河邊找鬼散吵。 笑死龙考,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的矾睦。 我是一名探鬼主播晦款,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼枚冗!你這毒婦竟也來了缓溅?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤赁温,失蹤者是張志新(化名)和其女友劉穎坛怪,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體股囊,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡袜匿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了稚疹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片居灯。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖贫堰,靈堂內(nèi)的尸體忽然破棺而出穆壕,到底是詐尸還是另有隱情,我是刑警寧澤其屏,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布喇勋,位于F島的核電站,受9級特大地震影響偎行,放射性物質(zhì)發(fā)生泄漏川背。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一蛤袒、第九天 我趴在偏房一處隱蔽的房頂上張望熄云。 院中可真熱鬧,春花似錦妙真、人聲如沸缴允。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽练般。三九已至矗漾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間薄料,已是汗流浹背敞贡。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留摄职,地道東北人誊役。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像谷市,于是被迫代替她去往敵國和親蛔垢。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,947評論 2 355

推薦閱讀更多精彩內(nèi)容