老板说:明天来加班写个FCM消息推送功能......

剪发的Tony 2019-12-02 17:32:18 ⋅ 116 阅读
前言

最近在开发一款国际版的APP,项目中需要支持客户端消息推送,自己实现肯定是不可能的,需要寻找第三方的SDK。在做技术调研的时候决定使用google的FCM框架来实现,有个缺点就是大陆是接收不到的(fq可以)。那么本章就给大家分享一下如何基于Spring Boot集成Firebase实现FCM消息推送功能。

必要条件

1、大陆开发者要准备好vpn(你懂的)。
2、申请Google Firebase账号。
3、获取Firebase秘钥。
4、有效的客户端token令牌(app客户端开发者提供)。

开发
第一步:申请开发者账号

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

第二步:添加一个项目

如下图所示,添加一个项目:





TPS:注意必须与android项目本地包名一致就是manifest中package的路径一样。

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

点击左上角——设置(图标)——项目设置,点击google-services.json下载,如下图所示:

下载google-services.json文件


将下载好的文件放到Spring Boot项目的resources目录下,例如:


第四步:集成Firebase

pom引用firebase,要fq才能下载,如果没有条件就需要手动导入下载好的jar包。

<dependency>
<groupId>com.google.firebase</groupId>
<artifactId>firebase-admin</artifactId>
<version>6.5.0</version>
</dependency>
第五步:编写FCM推送消息请求实体

这里只是做了个发送消息的简单封装实体,方便数据传输:

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实战联盟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 = "消息标题" , required = true)
private String title;
@ApiModelProperty(value = "消息内容" , 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实战联盟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数据库
* @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);
}

/**
* 单设备推送
* @param appName 应用的名字
* @param token 注册token
* @param title 推送题目
* @param bady 推送内容
* @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;
}
//构建消息内容
Message message = Message.builder().setNotification(new Notification(title,body))
.setToken(token)
.build();
//发送后,返回messageID
String response = FirebaseMessaging.getInstance(firebaseApp).send(message);
System.out.println("单个设备推送成功 : "+response);
}

/**
* 给设备订阅主题
* @param appName 应用的名字
* @param tokens 设备的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;
}
//订阅,返回主题管理结果对象。
TopicManagementResponse response = FirebaseMessaging.getInstance(firebaseApp).subscribeToTopic(tokens, topic);
System.out.println("添加设备主题,成功:" + response.getSuccessCount() + ",失败:" + response.getFailureCount());
}
/**
* 按主题推送
* @param appName 应用的名字
* @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;
}
//构建消息
Message message = Message.builder()
.setNotification(new Notification(title,body))
.setTopic(topic)
.build();
//发送后,返回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\":\"测试标题\",\"body\":\"测试内容\",\"tokens\":\"1,2\"}")
@PostMapping(value = "/pushFireBaseAll", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public void pushFireBaseAll(@RequestBody FCMSendMessageReqDTO fcmSendMessageReqDTO) {
log.info("进入批量FireBase推送 pushFireBaseAll:[{}]", fcmSendMessageReqDTO.toString());
//添加tokens
List<String> tokens = Arrays.asList(fcmSendMessageReqDTO.getTokens().split(","));
//设置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()); //设置主题
FireBaseUtil.sendTopicMes(appName, fcmSendMessageReqDTO.getTopic(), fcmSendMessageReqDTO.getTitle(), fcmSendMessageReqDTO.getBody()); //按主题推送
} catch (Exception e) {
e.printStackTrace();
}
} else {
log.info("如果FirebaseApp已经初始化");
try {
FireBaseUtil.registrationTopic(appName, tokens, fcmSendMessageReqDTO.getTopic()); //设置主题
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\":\"测试标题\",\"body\":\"测试内容\",\"tokens\":\"1\"}")
@PostMapping(value = "/pushFireBase", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public void pushFireBase(@RequestBody FCMSendMessageReqDTO fcmSendMessageReqDTO) {
log.info("进入批量FireBase推送 pushFireBaseAll:[{}]", fcmSendMessageReqDTO.toString());
//添加tokens
List<String> tokens = Arrays.asList(fcmSendMessageReqDTO.getTokens().split(","));
//设置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已经初始化");
try {
FireBaseUtil.pushSingle(appName, tokens.get(0), fcmSendMessageReqDTO.getTitle(), fcmSendMessageReqDTO.getBody()); //单推
} catch (IOException e) {
e.printStackTrace();
} catch (FirebaseMessagingException e) {
e.printStackTrace();
}
}
}
}

Spring Boot 项目中集成了swagger ,小伙伴在使用的时候可以去掉。代码是没问题的,都已经测试过。

总结

1、一定要确认网络是否可以FQ,如果不行是访问不了firebase service的。否则会报以下错误,如图所示:



2、xxx.json 存放位置的目录要做好引用,小编是用mac运行的,如果是windows获取json文件的相对路径会有问题。
3、给特定设备推送消息时,需要提前获取到设备的deviceToken。
总之,以上代码可以帮助大家快速入门,并且编写出可以推送成功的示例,如果在使用中有什么问题大家可以留言一起讨论哦~~~
4、国产手机一般阉割了Google service的服务,需要自己去找第三方资源安装,会产生以下问题:

A、应用“杀死”后基本收不到消息。
B、Android8 系统不管应用是否可用都出现收不到消息问题。
有资源的小伙伴可以多测试集中机型哦。

5、设备必须是android4.0以上,Google Play Services 必须是 11.2.0以上版本(这个没有测试,拥有的资源基本都是6以上)。



全部评论: 0

    我有话说:

    3分钟学会 React-Native 消息【附源码】

    作为一个独立的APP应用怎么能没有消息呢?

    HTTP/2.0 服务器实现

    HTTP/2允许服务器在请求之前先响应信息到客户端(之前客户端有过请求),如果实现了HTTP缓存,的响应信息可以在客户端被缓存(可通过no-cache进行配置)。

    老板要我开发一个简单的工作流引擎

    第1关 一天,老板找到我,要做简单的工作流引擎。 我查了一天啥是工作流,然后做出了如下版本: 按顺序添加任意审批人组成一个链表,最后加一个结束节点 记录当前审批人,当审批完后,审批人向后

    2018年8技巧构建更好的Node.js应用程序

    2018年8技巧构建更好的Node.js应用程序

    Firefox 86 发布,加入完全 Cookie 保护功能

    新特性: Firefox 现在支持以画中画模式同时观看多视频。 Firefox 将 Total Cookie Protection(完全 Cookie 保护)功能引入进严格模式。在

    精品推荐:无,无新闻,无广告,2倍速看视频,看直播的超强浏览器

    今天给大家推荐一款小众却功能强大的应用,这款应用不怎么出名,一直很低调,但是却受到众多好评,相对其他大众的浏

    PHPKafka 1.0 发布,支持全部 50 API

    简介 PHP Kafka 客户端,支持 PHP-FPM、Swoole 环境使用。 通讯协议的结构基于 Java 版本中的 JSON 文件生成,这可能是有史以来支持消息类型最多的 PHP Kafka

    PHPKafka 1.0 发布,支持全部 50 API

    简介 PHP Kafka 客户端,支持 PHP-FPM、Swoole 环境使用。 通讯协议的结构基于 Java 版本中的 JSON 文件生成,这可能是有史以来支持消息类型最多的 PHP Kafka

    Dubbo3.0 了:服务发现百万集群,可伸缩微服务架构

    来自:高可用架构原文:https://mp.weixin.qq.com/s/_Ih4AyL2c1JjyLwKCPmphg 本文是一篇关于 Dubbo 地址性能的压测文章,我们期望通过对比的方式

    消息队列常见问题(二):消息队列产生大量的消息堆积怎么解决?

    上一节列举了生产上消息队列产生大量的消息堆积会有哪些后果,那相对应的解决方法有哪些呢?1、消息被丢弃情况如果要实现防止消息过期问题,最好不要设置过期时间!那设置了过期时间导致消息丢失怎么补救呢?答案

    Chrome OS 88 自定义屏保界面,可指纹登录第三方网站

    上周 Google 推出了 Chrome 88,在一周之后,Google 正式向 Chromebook 用户 Chrome OS 88,这也是 2021 年首大版本更新。这个版本加入了多项新

    微信小程序电商实战-商品详情加入购物车(下)

    今天会接着上一篇开始商品详情页加入购物车的部分,直接看效果......

    「轻阅读」“我公司做技术总监吧” “要代码吗?” “不代码你干嘛?”

    标题来源于一段真实的对话,老赵,小李都是我的朋友,我作为中间人介绍他们认识,我们约在上海码农圣地--张江某咖啡馆。

    好文推荐:我是如何用redis做实时订阅

    目前项目已上前线,运行平稳~~~

    微信小程序-template使用:实现购物车商品数量加减功能

    上一篇我们实现了购物车功能,里面有用到template模板功能实现购物车商品数量加减和价格计算功能......

    你的老板逼你上微服务了吗?

    “ 这些年软件的设计规模越来越庞大,业务需求也越来越复杂,针对系统的性能、高吞吐率、高稳定性、高扩展等特性提出了更高的要求。   图片来自 Pexels可以业务需求是软件架构能力的

    消息队列常见问题(一):生产上消息队列产生大量的消息堆积会有什么后果?

    大多数消息堆积原因是Consumer出现了问题,并且没有被运维/开发监控到即使修复问题,导致大量的消息都积压在 MQ 中,那么会造成哪些后果呢?1、消息被丢弃例如 RabbitMQ 中的一条消息设置