.
说明
这是我 DC(DCloud)开发中遇到的第二个问题。
虽然个推功能强大,但是个推官网文档那叫一个简陋,导致众多开发者爬坑无数、心酸无比。
为什么要这样一个技术方案分享?
因为社区还没看到一个关于个推的整体解决方案,包含前台与后台的开发思路,而且开发源码。
希望对其他开发者提供点帮助,少踩几个坑,也希望 DC 更加完善。
.
推送效果
- 外推(外部推送)
简单的说,就是我们能够看到的推送。
比如,通知栏的推送列表,比如,苹果的临时横幅,我都称之为“外推”。
. - 内推(内部推送)
这种推送是隐藏性质的,它们不会出现在手机的通知栏或横幅,它们适用于这种场景:
①. APP 在前台时,收到推送时,会出现一个对话框来询问用户是否跳转。
②. 完全不提示用户,比如,用户账号在其他手机登录,APP 接收到推送时,强制用户退出。
. - 智推(智能推送)
APP 在前台时,发送内推。
APP 在后台或者关闭时,发送外推。
.
开发流程
- 个推注册
个推官网:http://getui.com/cn/index.html。
. - 配置推送
①. 配置个推参数(appid、appkey、appsecret)。
②. 配置自定义基座,方便调试。
. - 后台发送推送
定义一个推送的数据格式,便于前台、后台进行对接。
我在开发时,用的是个推官网提供的 PHP SDK(我对其进行了封装,具体见下方)。
. - APP接收推送并处理
对推送的数据进行解析,然后执行对应的业务逻辑(我对其进行了封装,具体见下方)。
.
测试数据
开发中使用了 5 台设备。
安卓:小米3(4.4.4)、锤子T2(5.1.1)、海马玩模拟器(4.2.2)、雷电模拟器(5.1.1)。
苹果:iPhone 6(iOS 11.0.3)。
个推支持 4 种推送模板。
①. 透传推送:点击通知打开网页模板(IGtLinkTemplate)。
②. 普通推送:点击通知打开应用模板(IGtNotificationTemplate)。
③. 链接推送:点击通知弹框下载模板(IGtNotyPopLoadTemplate)
④. 下载推送:透传消息模版(IGtTransmissionTemplate)。
因为 链接推送、下载推送 平时基本上不用,所以本方案没有纳入测试和封装,需要的朋友自己处理吧。
透传推送 和 普通推送 能够达到大体一致的效果。
透传推送,后台发送标准推送数据格式的推送时,APP 能收到一条带参数的推送。
普通推送,后台发送带透传数据的推送时,APP 也能收到一条带参数的推送。
所以,我对这两种推送模板做了分组对比测试。
还有一点,关于个推官方定义的 推送数据的标准格式和非标准格式,很多开发者都不是很理解,包括我在内,最开始因为不是很理解,按照自己想当然的思路对 5 台设备进行了测试,经过大量重复操作后,得到一组可观的数据,但是,无意中发现自己理解有误,导致之前的数据作废,也算是一个大坑,所以我还是简单的展示下两种模式的区别吧。
标准格式:
必须符合这个样子,{title:"标题",content:"内容",payload:"数据"}。
$Message1 = [
"title"=> "健康告知",
"content"=> "您的中二病已经很严重了!",
"payload"=>[
"push"=> "inner",
"event"=> "warning",
"silent"=> false,
"data"=> ""
]
];
非标准格式:
只要不是上面的那种格式就满足(比如,缺少 payload 参数)。
$Message2 = [
"title"=> "健康告知",
"content"=> "您的中二病已经很严重了!",
"push"=> "inner",
"event"=> "warning",
"silent"=> false,
"data"=> ""
];
最重要的一点:
只有发送标准格式数据,APP才能收到推送(外推)!!!
只有发送标准格式数据,APP才能收到推送(外推)!!!
只有发送标准格式数据,APP才能收到推送(外推)!!!
.
好了,下面是我测试的五组数据,如果你发现有些数据是错误的,请在评论里指出,免得误导别人。
.
数据分析:
①. 安卓
测试后发现,小米3、锤子T2 关闭应用后,自动清除了个推的后台驻留进程,所以这种情况是没有办法解决的,很显然,真实手机(小米3、锤子T2)的数据已经废了。
海马玩和雷电的数据完全一致,我们就看雷电的数据。
第一组数据方案(带透传信息的普通推送),如果你亲自测试,你会发现,APP 会收到两条推送,普通推送模板默认发送一条,设置了带有标准数据格式的透传信息也会发送一条,很显然,不符合实际场景,排除这种方案。
第四组数据方案(非标准格式的透传推送),实现内推,所以这种方案是有用的。
第二组和第三组数据方案,实现的效果一致,那么既然是方案整合(应该有内推功能),所以应该选第三组,因为它和第四组(内推)共用一个模板(IGtTransmissionTemplate),在实际操作中,只需要切换数据格式(标准、非标准)来实现 内推、外推 的切换,能够节省不少代码。
②. 苹果
普通推送,APP 会自动弹出一个 bug 一样的确认对话框,点击“取消”也会触发确认时间,所以,数据作废。
透传推送,苹果手机比较特别,APP 在前台时,个推会发送内推,在后台或关闭时,个推会借助苹果的 APN 发送苹果的推送。
综上,选择透传推送的两种数据格式(标准、非标准)能够完美解决推送问题。
.
后台源码
.
参数说明
- clientid
要发送的用户的 clientid。
. - title
推送标题。
. - content
推送内容。
. - push
推送类型,仅安卓支持,苹果设置无效。
outer:外推
inner:内推
smart:智推
. - system
接收推送的设备系统类型,因为它决定了工具类选择哪个推送数据格式。
ios:苹果系统。
android:安卓系统。
. - event
事件名称,APP 用来处理业务逻辑。
. - data
附加数据,可以携带业务数据发送给 APP,PHP 数组类型。
. - silent
是否启用静默模式,APP 接收到内推时,是否展示推送,布尔值。
.
hhPushClass.php
已经封装好的 PHP 类,逻辑都在里面,自己看吧,有问题可以在评论里指明。
类发送推送后,会将结果保存到 $result 中,包含了 arr、json 两种形式。
类里面的 $Message1、$Message2 是上文所说的定义的数据格式。
<?php
# 名称 : hhGpush
# 来源 : hhtools.php
# 版本 : 1.0.0
# 作者 : 立树
# 网站 : studio.houheaven.com
# 日期 : 2017-12-15
class hhGpush
{
public $gp_appid; // 应用 ID(AppID)
public $gp_appkey; // 应用 key(AppKey)
public $gp_token; // 应用令牌(MasterSecret)
public $gp_host; // 个推服务器
private $gpush; // 个推
public $result; // 推送结果
# 构造函数
public function __construct($appid,$appkey,$token)
{
// 属性初始化
$this->gp_appid = $appid;
$this->gp_appkey = $appkey;
$this->gp_token = $token;
$this->gp_host = "http://sdk.open.api.igexin.com/apiex.htm";
// 个推初始化
$this->gpush = new IGeTui($this->gp_host,$appkey,$token);
$this->result = [];
}
# 单个推送(支持安卓、苹果)
public function PushMsgToSingle($param)
{
// 参数检测
if( gettype($param)!="array" || !isset($param["clientid"]) )
{
exit("参数错误");
}
// 参数初始化
// 标题
$title = isset($param["title"])?$param["title"]:"";
// 内容
$content = isset($param["content"])?$param["content"]:"";
// 推送类型(外推:outer、内推:inner、智能推送:smart)
$push = isset($param["push"])?$param["push"]:"outer";
// 事件名称(APP根据此参数决定执行哪些功能)
$event = isset($param["event"])?$param["event"]:"";
// 内推时,是否给用户展示提示信息(比如强制用户退出就不会展示提示信息)
$silent = isset($param["silent"])?$param["silent"]:false;
// 推送数据,附加的业务数据
$data = isset($param["data"])?$param["data"]:"";
// 标准推送数据格式
$Message1 = [
"title"=> $title,
"content"=> $content,
"payload"=>[
"push"=> $push,
"event"=> $event,
"silent"=> $silent,
"data"=> $data
]
];
// 非标准推送数据格式
$Message2 = [
"title"=> $title,
"content"=> $content,
"push"=> $push,
"event"=> $event,
"silent"=> $silent,
"data"=> $data
];
// 用户状态
$aid = $this->gp_appid;
$cid = $param["clientid"];
$status = $this->gpush->getClientIdStatus($aid,$cid);
$this->result["arr"]["client_state"] = $status;
// 推送模板
$system = isset($param["system"])?$param["system"]:"android";
$system = in_array($system,array("android","ios"))?$system:"android";
if( $system=="android" )
{
// 安卓模板
switch($param["push"])
{
case "inner":
$Message2["push"] = "inner";
$msg = $Message2;
break;
case "outer":
$Message1["payload"]["push"] = "outer";
$msg = $Message1;
break;
case "smart":
$status = $this->gpush->getClientIdStatus($aid,$cid);
if( $status["result"]=="Online" )
{
$Message2["push"] = "smart";
$msg = $Message2;
}
else
{
$Message1["payload"]["push"] = "outer";
$msg = $Message1;
}
break;
}
}
else
{
// 苹果模板
$Message1["payload"]["push"] = "inner";
$msg = $Message1;
}
$tpl = $this->AwesomeTemplate($msg);
$this->result["arr"]["push_param"] = $msg;
// 推送消息
$msg = new IGtSingleMessage();
$msg->set_isOffline(true); // 是否离线
$msg->set_offlineExpireTime(12*3600*100); // 离线时间
$msg->set_data($tpl); // 推送消息模板
$msg->set_PushNetWorkType(0); // 设置是否根据WIFI推送消息,2为4G/3G/2G,1为wifi推送,0为不限制推送
// 接收方
$target = new IGtTarget();
$target->set_appId($aid);
$target->set_clientId($cid);
// 发送
$ret = $this->gpush->pushMessageToSingle($msg,$target);
$this->result["arr"]["push_state"] = $ret;
$this->result["json"] = json_encode($this->result);
}
// 透传推送模板
function AwesomeTemplate($param)
{
// 模板初始化
$template = new IGtTransmissionTemplate();
$template->set_appId($this->gp_appid); // 应用appid
$template->set_appkey($this->gp_appkey); // 应用appkey
// 安卓推送(外推+内推)、苹果内推
$template->set_transmissionType(2);
$template->set_transmissionContent(json_encode($param)); // 透传内容
// 苹果处于后台时的推送(外推)
$alertmsg = new DictionaryAlertMsg();
$alertmsg->actionLocKey = "ActionLockey"; // 个推官网提供,文档无说明
$alertmsg->launchImage = "launchimage"; // 个推官网提供,文档无说明
$alertmsg->locArgs = array("locargs"); // 个推官网提供,文档无说明
$alertmsg->locKey = $param["title"]; // 消息标题
$alertmsg->body = $param["content"]; // 消息内容
// iOS8.2 支持
$alertmsg->title = $param["title"]; // 消息标题
$alertmsg->titleLocKey = $param["title"]; // 消息标题
$alertmsg->titleLocArgs = array("TitleLocArg"); // 个推官网提供,文档无说明
$apn = new IGtAPNPayload();
$apn->alertMsg = $alertmsg;
//$apn->badge = 1;
//$apn->sound = "";
$param["payload"]["push"] = "outer"; // 调用此处代码证明是外推
foreach($param as $key=>$val)
{
$apn->add_customMsg($key,$val);
}
$apn->contentAvailable = 1; // 个推官网提供,文档无说明
$apn->category = "ACTIONABLE"; // 个推官网提供,文档无说明
$template->set_apnInfo($apn);
// 设置通知定时展示时间,结束时间与开始时间相差需大于6分钟
// 消息推送后,客户端将在指定时间差内展示消息(误差6分钟)
//$begin = "2017-12-14 15:20:00";
//$end = "2017-12-14 15:30:00";
//$template->set_duration($begin,$end);
return $template;
}
}
?>
代码调用
<?php
header("Content-Type: text/html; charset=utf-8");
require_once(dirname(__FILE__)."/IGt.Push.php"); // 个推 sdk
require_once(dirname(__FILE__)."/hhPushClass.php"); // 封装好的工具类
// 配置(替换成自己的)
$Appid = "111111";
$Appkey = "222222";
$Mastersecret = "333333";
// 实例化
$gpush = new hhGpush($Appid,$Appkey,$Mastersecret);
// 发送推送
$gpush->PushMsgToSingle([
"clientid"=> "xxxxxxxxxx",
"event"=> "warning",
"title"=> "健康告知",
"content"=> "您的中二病已经很严重了!",
"push"=> "smart",
"system"=> "android",
"silent"=> false
]);
// 打印结果
var_dump($gpush->result);
.
前台源码
.
参数
插件的唯一参数是一个回调函数。
收到推送事件(Receive)、推送点击事件(Click)已被内置在插件中,当这两个事件被触发时,插件会先已处理好推送逻辑,然后将推送的数据进行格式化,然后会触发这个回调函数,并将格式化的消息返回,以供开发者处理业务逻辑。
格式化的推送消息:
- title 消息标题
- descp 消息内容
- event 事件名称(开发者根据此参数决定执行业务逻辑)
- data 推送数据(服务器返回的业务数据)
.
Gpush.js
// Gpush.app.dc.1.1.0
// 作者 : 立树
// 日期 : 2017-12-19
// 来源 : hhtools.app.js
// 文档 : http://studio.houheaven.com
function Gpush(fnPushExec)
{
// 设置应用为前台事件
localStorage.setItem("isAppActive",true);
// 注册应用切换到后台事件
document.addEventListener("pause",function(){
localStorage.setItem("isAppActive",false);
});
// 注册应用切换到前台事件
document.addEventListener("resume",function(){
localStorage.setItem("isAppActive",true);
});
// 推送点击事件
plus.push.addEventListener("click",function(msg){
push_proc(msg);
});
// 推送接收事件
plus.push.addEventListener("receive",function(msg){
push_proc(msg);
});
// 推送消息预处理
function push_proc(msg)
{
// 解析
var payload = typeof(msg.payload)=="string"?JSON.parse(msg.payload):msg.payload;
// 消息格式化
var notice = {
title: msg.title, // 标题
descp: msg.content, // 内容
event: payload.event, // 事件名称(APP根据此参数决定执行哪些功能)
data: payload.data, // 推送数据,附加的业务数据
silent: payload.silent // 内推时,是否给用户展示提示信息(比如强制用户退出就不会展示提示信息)
};
// 系统检测
if( plus.os.name=="Android" )
{
// 推送检测
switch(payload.push)
{
case "smart":
// 智能推送
// 能进入这里的都是内推,然后判断APP的状态(前台、后台)
// 前台:直接进入内推的业务逻辑
// 后台:补发一个本地推送
if( localStorage.getItem("isAppActive")=="false" )
{
// 按照逻辑,或者以我强迫症的调性来说,是不会更改原本推送的状态的,因为我已经监听了用户从后台到前台的事件(把 isAppActive 改为 true),刚刚模拟出来的本地推送被点击后,会直接触发推送业务逻辑,简直完美。
// 但是,计划赶不上变化,在所有操作过程中,总会有先后顺序,这里就是,在用户切换应用到前台时(resume),还没来得及将应用状态 isAppActive 改为 true 的时候,这里的判断已经执行了,而 isAppActive 依然是 false,所以会导致再次创建本地推送。
// 所以啊,不说了,只好妥协了。
payload.push = "outer";
plus.push.createMessage(msg.content,JSON.stringify(payload),{
title: msg.title
});
}
else
push_check(notice);
break;
case "outer":
// 外推
push_exec(notice);
break;
case "inner":
// 内推
push_check(notice);
break;
}
}
else
{
// 推送检测
if( payload.push=="outer" )
{
// 外推
push_exec(notice);
}
else
{
// 内推
push_check(notice);
}
}
}
// 内推时提示信息处理
function push_check(notice)
{
if( notice.silent!=true )
{
plus.nativeUI.confirm("\n"+notice.descp+"\n\n",function(ret){
if( ret.index==1 )
{
push_exec(notice);
}
},{
title: notice.title,
buttons: ["取消","查看"]
});
}
else
push_exec(notice);
}
// 推送点击处理
function push_exec(notice)
{
// 事件处理
!fnPushExec || fnPushExec(notice);
}
}
代码调用
<?php
// 个推推送
Gpush(function(notice){
switch(notice.event)
{
case "warning": // 警告
alert(notice.descp);
break;
case "logout": // 退出登录
UserLogout(notice.data);
break;
}
});
.
以上,有疑问的可以在评论区留言,希望对你有帮助。