消息推送是 APP 开发中非常重要的功能,可以让不在前台运行的 APP,及时进行消息通知。在新闻内容、促销活动、产品信息、版本更新提醒、订单状态提醒等诸多场景中都有应用。
以下以 iOS 为例,介绍下消息推送的机制,以及实现一些基础代码。
APNs(英文全称:Apple Push Notification service),即苹果推送通知服务。
消息推送分为本地通知和远程通知,本地通知是由本地应用触发的,基于时间行为的一种通知形式,如闹钟定时、待办事项提醒,又或者一个应用在一段时间不使用通常会提示用户使用此应用等,都是本地通知。
因为本地通知没有服务器的参与,所以我们在此不展开讨论,主要来了解下远程通知。
远程通知的主要流程为:
在 APNs 下载 push 证书,并设置在服务器中。
APP 获取 APNs 分配的 deviceToken(应用唯一标识,不同设备,不同应用的 deviceToken 都会不同),提交给服务器,服务器记录下 deviceToken。
消息推送时,服务器将 deviceToken 和要发送的消息提交给 APNs,并使用 push 证书签名。
APNs 会在自己已注册的 iPhone 列表中根据 deviceToken 查找目标 iPhone。然后将消息发送给目标 iPhone。
iPhone 把消息发送给相应的程序,并且按已设定的方式弹出。
自己实现远程推送的功能会有很多的配置工作,再加上以后还有 Android 设备,有很多繁琐的工作要处理,所以我们一般使用第三方的推送服务,省去了很多开发工作。同时第三方推出的数据统计也满足了运营的需求。
极光推送 是我们常用的第三方推送服务商,以下简称 Jpush。Jpush 同时支持 iOS 和 Android 平台的消息推送,服务器只需要实现一套代码即可。
注册 Jpush 账号并创建应用后,我们会得到 key 和 secret。
由于使用了第三方服务,远程通知的流程变为:
服务器配置 Jpush 的 AppKey 和 Secret。
APP 获取 APNs 分配的 deviceToken,去 Jpush 注册并获取 RegistrationID。
APP 将 RegistrationID 提交到服务器,服务器记录下对应用户的 RegistrationID。
消息推送时,服务器通过 Jpush sdk 提交 RegistrationID 及消息到 Jpush 服务器。
Jpush 负责之后的消息推送。
原来是将 deviceToken 提交到服务器,现在是将 RegistrationID 提交到服务器,RegistrationID 相当于设备在 Jpush 的唯一标识,服务器将 RegistrationID 与单个用户绑定,这样就能推送消息给指定用户。
为 users
表增加字段:
php artisan make:migration add_registration_id_to_users_table --table=users
database/migrations/XXX_add_registration_id_to_users_table.php
.
.
.
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('registration_id')->nullable();
});
}
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('registration_id');
});
}
.
.
.
执行迁移:
php artisan migrate
app/Http/Controllers/Api/UsersController.php
.
.
.
public function update(UserRequest $request)
{
$user = $this->user();
$attributes = $request->only(['name', 'email', 'introduction', 'registration_id']);
.
.
.
修改模型可修改字段:
.
.
.
protected $fillable = [
'name', 'phone', 'email', 'password', 'introduction', 'avatar',
'weixin_openid', 'weixin_unionid', 'registration_id'
];
.
.
.
composer require jpush/jpush
为了方便使用,进行一下简单的封装:
php artisan make:provider JpushServiceProvider
app/Providers/JpushProvider.php
namespace App\Providers;
use JPush\Client;
use Illuminate\Support\ServiceProvider;
class JpushServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* @return void
*/
public function boot()
{
//
}
/**
* Register the application services.
*
* @return void
*/
public function register()
{
$this->app->singleton(Client::class, function ($app) {
return new Client(config('jpush.key'), config('jpush.secret'));
});
$this->app->alias(Client::class, 'jpush');
}
}
config/app.php
.
.
.
App\Providers\EasySmsServiceProvider::class,
App\Providers\JpushServiceProvider::class,
.
.
.
创建配置文件:
touch config/jpush.php
config/jpush.php
return [
'key' => env('JPUSH_KEY'),
'secret' => env('JPUSH_SECRET'),
];
在 env 文件中填写 Jpush 的 key 和 secret
.env
# jpush
JPUSH_KEY=9c6f53edad67db7ec24bfe32
JPUSH_SECRET=deeb2a04669ab79******
这样我们就可以直接依赖注入 JPush\Client
或者 app('jpush')
来使用 Jpush 的 SDK。
php artisan make:listener PushNotification
监听事件,这里我们可以在 DatabaseNotification 创建时,推送消息给客户端:
app/Providers/EventServiceProvider.php
protected $listen = [
\SocialiteProviders\Manager\SocialiteWasCalled::class => [
// add your listeners (aka providers) here
'SocialiteProviders\Weixin\WeixinExtendSocialite@handle'
],
'eloquent.created: Illuminate\Notifications\DatabaseNotification' => [
'App\Listeners\PushNotification',
],
];
app/Listeners/PushNotification.php
namespace App\Listeners;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\DatabaseNotification;
use JPush\Client;
class PushNotification implements ShouldQueue
{
protected $client;
/**
* Create the event listener.
*
* @return void
*/
public function __construct(Client $client)
{
$this->client = $client;
}
/**
* Handle the event.
*
* @param NotificationSent $event
* @return void
*/
public function handle(DatabaseNotification $notification)
{
// 本地环境默认不推送
if (app()->environment('local')) {
return;
}
$user = $notification->notifiable;
// 没有 registration_id 的不推送
if (!$user->registration_id) {
return;
}
// 推送消息
$this->client->push()
->setPlatform('all')
->addRegistrationId($user->registration_id)
->setNotificationAlert(strip_tags($notification->data['reply_content']))
->send();
}
}
逻辑很简单,当通知存入数据库后,也就是监听 elequent.created:Illuminate\Notifications\DatabaseNotification
这个事件。如果用户已经有了 Jpush 的 registration_id,则使用 Jpush SDK 将消息内容推送到目标用户的 APP 中,注意我们使用了 strip_tags
去除了 notification 数据中的 HTML标签。