吐槽下官方的文档写的真烂~~~
官方文档地址(中文)
FCM是google提供的一个消息推送服务,支持IOS, ANDROID, WEB浏览器等。
推送功能:
说明下:因为是google服务, 部分功能需要VPN才能达到效果
web端主要就是为了拿到用户注册的registration_id, 然后传给服务端, 服务端拿到registration_id来实现不同的推送效果
有两种方式可以实现:
准备工作:
说明:该协议走的是动态授权, 分OAuth2和服务账号授权, 这边用的是服务账号授权
流程说明:
1. 通过google-api-php-client 和 service-account-file.json 获取access_token(临时授权, 有过期时间)
2. 前端代码通过注册获取registration_id, 并上报服务端
3. 如果需要订阅功能,则通过旧版服务器密钥请求google接口实现设备的订阅
4. 使用access_token调用google接口来实现具体推送功能
namespace App\Util\FCM;
use Illuminate\Support\Facades\Redis; //Redis 工具类
/**
1. Google FCM 工具类
*/
class AccessToken
{
/**
* 获取access_token的方法,并对access_token做了缓存处理
* @param string $config_path require 下载的service-account-file.json文件存放路径
* @date 2019-11-22
* @return string
* @demo AccessToken::getAccessToken('/path/to/service-account-file.json')
* @throws \Google_Exception
*/
public static function getAccessToken($config_path)
{
$cacheKey = 'lh:google:fcm:accesstoken';
$temp = Redis::get($cacheKey);
if (empty($temp)) {
$temp = self::requestAccessToken($config_path);
Redis::set($cacheKey, $temp['access_token']);
Redis::expire($cacheKey, $temp['expires_in']);
return $temp['access_token'];
}
return $temp;
}
/**
* 调用google google-api-php-client 获取access_token 这个是通过google的服务账号授权(用于server端) 不是页面的OAuth授权
* @date 2019-11-22
* @param string $config_path option 配置文件路径
* @throws \Google_Exception
* @return [
* 'access_token' => 'ya29.*****', //访问令牌
* 'expires_in' => 3600, //访问令牌过期时间
* 'token_type' => 'Bearer', //token_type
* 'created' => 1574401624, //token 创建时间
* ]
*/
protected static function requestAccessToken($config_path)
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->setAuthConfig($config_path);
$client->setScopes(['https://www.googleapis.com/auth/firebase.messaging']); # 授予访问 FCM 的权限
return $client->fetchAccessTokenWithAssertion();
}
}
namespace App\Util\FCM;
/**
* Google FCM 工具类
*/
class Subscribe
{
/**
* 将设备添加到主题
* @author jeanku
* @date 2019-11-22
* @param string $topic_name require 根据业务自定义的主题名称
* @param string $register_token require 前端授权得到的REGISTRATION_TOKEN
* @return array
* @throws \Exception
*/
public static function addTopic($topic_name, $register_token)
{
$url = sprintf('https://iid.googleapis.com/iid/v1/%s/rel/topics/%s', $register_token, $topic_name);
return Curl::setHeader(self::getCommonHeader())->post($url);
}
/**
* 非推送消息的请求header
* @date 2019-11-22
* @return array
*/
protected static function getCommonHeader()
{
return [
'Content-Type: application/json',
'Authorization: key=' . env('FCM_SERVER_KEY'), //env('FCM_SERVER_KEY')旧版服务器密钥
];
}
}
namespace App\Util\FCM;
/**
* Google FCM 工具类
*/
class Message
{
public static $common = [
'name' => null,
'data' => null,
"notification" => null,
"android" => null,
"webpush" => null,
"apns" => null,
"fcm_options" => null,
"token" => null,
"topic" => null,
"condition" => null,
];
/**
* 生成topic推送数据
* @author jeanku
* @date 2019-11-22
* @param string $topic required topic key
* @param array $messgeData required message data
* @return array
*/
public static function formatTopicMessage($topic, $messgeData)
{
self::$common['topic'] = $topic;
self::$common['webpush'] = $messgeData;
return ['message' => array_filter(self::$common)];
}
/**
* 获取web端推送的数据
* @author jeanku
* @date 2019-11-22
* @param array $notification required [
* 'title' => '',
* 'body' => '',
* ]
* @param array $data option []
* @param array $header option []
* @param array $fcm_options option [
* 'link' => 'http://***' //点击跳转的链接
* ]
* @return array
*/
public static function getWebMessage($notification, $data = null, $header = null, $fcm_options = null)
{
return array_filter([
'header' => $header,
'data' => $data,
'notification' => $notification,
'fcm_options' => $fcm_options,
]);
}
}
namespace App\Util\FCM;
/**
* Curl类
*
* @desc curl Class support post&get
* @package \Ananzu\Util
* @date 2016-05-18
*/
class Curl
{
const REQUEST_TIMEOUT = 30; //超时时间
protected $_ch = null; //curl的句柄
protected $param_type = 0; //参数类型
protected $_header = []; //curl的句柄
protected static $_instance = null;
protected function __construct()
{
}
protected static function instance()
{
if (empty(self::$_instance)) {
self::$_instance = new self();
}
return self::$_instance;
}
/**
* set header
* @date 2019-11-22
* @param array $header required 请求header
* @return Curl|null
*/
public static function setHeader($header)
{
self::instance()->_header = $header;
return self::instance();
}
/**
* 传参方式
* @date 2019-11-22
* @param int $type requrie 传参方式 0:默认 1:raw
* @return Curl|null
*/
public static function setParamType($type)
{
self::instance()->param_type = $type;
return self::instance();
}
/**
* GET request
* @date 2019-11-22
* @param string $url required 请求的url
* @param array $params required 请求的参数
* @param int $timeout 请求的超时时间
* @return array
* @throws \Exception
*/
public static function get($url, $params = array(), $timeout = self::REQUEST_TIMEOUT)
{
return self::instance()->request($url, 'GET', $params, $timeout);
}
/**
* POST request
* @date 2019-11-22
* @param string $url required 请求的url
* @param array $params required 请求的参数
* @param int $timeout 请求的超时时间
* @return array
* @throws \Exception
*/
public static function post($url, $params = array(), $timeout = self::REQUEST_TIMEOUT)
{
return self::instance()->request($url, 'POST', $params, $timeout);
}
/**
* request 请求的方法
* @date 2019-11-22
* @param string $url required 请求的url
* @param string $method required 请求的method
* @param array $params required 请求的参数
* @param int $timeout 请求的超时时间
* @demo Curl::request('http://asd.com.cn',['name'=>123,'age'=>88],Curl::CURL_REQUEST_POST)
* @return array
* @throws \Exception
*/
protected static function request($url, $method, $params = array(), $timeout = self::REQUEST_TIMEOUT)
{
$model = self::instance();
$model->_ch = curl_init();
$model->_setParams($url, $params, $method);
curl_setopt($model->_ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($model->_ch, CURLOPT_HTTPHEADER, $model->_header);
curl_setopt($model->_ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($model->_ch, CURLOPT_HEADER, 0);
$aRes = curl_exec($model->_ch);
if ($error = curl_errno($model->_ch)) {
throw new \Exception(curl_error($model->_ch), $error);
}
curl_close($model->_ch);
return $aRes;
}
/**
* ssl处理 如果是https,则设置相关的配置信息
* @date 2016-05-18
* @param string $url required 请求的url
* @return void
*/
protected function _setSsl($url)
{
if (true === strstr($url, 'https://', true)) {
curl_setopt($this->_ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($this->_ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($this->_ch, CURLOPT_DNS_USE_GLOBAL_CACHE, 0);
curl_setopt($this->_ch, CURLOPT_FOLLOWLOCATION, 1);
}
}
/**
* curl请求方式和POST|GET请求数据处理
* @date 2016-05-18
* @param string $url required 请求的url
* @param array $data required 请求的参数
* @param string $method required 请求的方式 POST|GET
* @return bool
*/
protected function _setParams($url, $data, $method)
{
$this->_setSsl($url);
switch ($method) {
case 'POST':
if ($this->param_type == 1) {
$_postData = is_array($data) ? json_encode($data) : $data;
} else {
$_postData = is_array($data) ? http_build_query($data) : $data;
}
curl_setopt($this->_ch, CURLOPT_POST, true);
curl_setopt($this->_ch, CURLOPT_POSTFIELDS, $_postData);
curl_setopt($this->_ch, CURLOPT_URL, $url);
break;
case 'GET':
$_getData = is_array($data) ? http_build_query($data) : $data;
$uri = preg_match('/\?/', $url) ? '&' . $_getData : '?' . $_getData;
curl_setopt($this->_ch, CURLOPT_URL, $url . $uri);
break;
default:
return false;
}
}
}
namespace App\Util\FCM;
/**
* Google FCM 工具类
*/
class Notification
{
/**
* 将设备添加到主题
* @author jeanku
* @date 2019-11-22
* @param array $data require 请求数据
* @return array
* @throws \Exception
*/
public static function push($data)
{
$url = 'https://fcm.googleapis.com/v1/projects/longhash-notification/messages:send';
return Curl::setParamType(1)->setHeader(self::getAccessTokenHeader())->post($url, $data);
}
/**
* 推送消息的请求header
* @date 2019-11-22
* @return array
* @throws \Google_Exception
*/
protected static function getAccessTokenHeader()
{
return [
'Content-Type: application/json',
'Authorization: Bearer ' . AccessToken::getAccessToken(env('SERVICE_ACCOUNT_JSON_FILE')),
];
}
}
# 订阅
$topic_name = "article_push"; //自定义的主题名称 字符串
$register_token = '****'; //前端注册的设备register_token
SubscribeModule::addTopic($topic_name, $register_token);
# 订阅消息推送
$notification = ['title' => 'test', 'body' => 'this is a test']; //推送名称 描述
$option = ["link" => 'http://www.***.com/uri']; //点击推送跳转的地址
$webMsg = Message::getWebMessage(notification, null, null, $option); //生成推送到浏览器的消息格式
$topicWebMsg = Message::formatTopicMessage($topic_name, $msg); //生成主题推送消息
Notification::push($topicWebMsg); //推送消息
#单个推送
//todo
#多个推送
//todo
如果使用旧版http 协议来处理, 推荐一个第三方拓展github地址
旧版推送就非常简单了, 就是一个http请求, 需要在header添加Content-Type: application/json 和 Authorization: key=我是旧版服务密匙