Laravel5.5集成极光推送_解决推送失败重推问题

一、背景

之前公司在做了一个关于现场互动的产品,以微信小程序为用户端,电视盒子APP(安卓应用)为显示端,产品主打的内容为:用户在小程序端发弹幕和霸屏信息,然后服务端把用户发送的内容推送到安卓端(电视盒子APP)显示。

推送能力我们选择了第三方SDK,极光推送。在应用的过程中,有一个难点,因为是第三方推送能力,我们无法保证推送一定成功,尽管极光返回结果状态显示为推送成功,但在实际情况中也可能是推送失败,所以唯一成功的标识是电视盒子APP收到了推送,因此,针对这个问题,思考了很久,最终采取了一个相对有效的方案解决这个问题。

下面记录整个思路。

二、Laravel集成极光推送(参考:https://github.com/jpush/jpush-api-php-client

1. 在项目根目录下,找到composer.json,添加jpush(极光推送包)依赖:

"require": {
    "jpush/jpush": "*"
}

2. 打开dos系统(linux不用),跳转至项目根目录,执行composer命令安装:

composer install

三、完成极光推送基础代码

1. 创建极光推送类

2. 完成基础代码

<?php
namespace App\Jpush;

use JPush\Client as JPush;
use App\Models\DeviceJpush;
use App\Models\JpushAppConfig;

class JpushSend
{
    /**
     *  作用:根据安卓设备注册极光Id推送消息
     *  参数:
     *          $appId  —— 极光应用码(存在数据库中的极光应用配置对应的ID)
     *          $deviceId —— 设备码, 安卓设备能够通过设备的编码传参到极光注册一个唯一register_id,这个关系可以保存在数据库中
     *          $date     —— 推送数据
     *  返回:推送结果
     */

    public function send($appId, $deviceId, $data)
    {
        $registerId  = DeviceJpush::where(['device_id' => $deviceId, 'app_id' => $appId])->value('register_id');

        $jpushConfig = JpushAppConfig::find($appId);

        $client      = new JPush($jpushConfig->app_key, $jpushConfig->master_secret, null);

        return $client->push()
                      ->setPlatform('android')
                      ->addRegistrationId($registerId)
                      ->message('jpush', array('extras' => $data))
                      ->options(array('time_to_live' => 10))
                      ->send();
    }

}

3. 控制器中应用推送

<?php
namespace App\Http\Controllers\Home;

use App\Models\Device;                // 引入设备数据库操作模型
use App\Jpush\JpushSend;              // 引入我们刚才写好的极光推送基础类
use App\Models\DeviceJpush;           // 引入保存设备注册极光推送的register_id数据库操作模型
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

Class TestJpush extends Controller
{
    protected $jpush;

    public function __construct()
    {
        $this->jpush = new JpushSend();
    }    
 
    /*
     *   保存安卓设备注册极光的register_id到数据库
     *   参数:
     *        app_id     :极光应用配置存在我们服务器数据库中的数据ID
     *        device_code:安卓设备码
     *        register_id:安卓设备调用极光接口注册的ID值(register_id),这个让java工程师自己去找接口解决
     */
    public function deviceRegister(Request $request)
    {
        $data = $request->validate([
            'app_id'      => 'required',
            'device_code' => 'required',
            'register_id' => 'required'
        ]);

        // 1. 根据设备码(device_code)获取设备ID(device_id)
        $device_id = Device::where('device_code', $data['device_code'])->find(['id']);

        // 2. 联合设备ID(device_id)和极光应用ID(app_id),保存设备在极光注册的register_id
        DeviceJpush::insert([
            'app_id'      => $data['app_id'],
            'device_id'   => $device_id,
            'register_id' => $data['register_id']
        ]);

        return responseOk();
    }

    /*
     *   极光推送数据
     *   参数:
     *        app_id   :极光应用配置保存在数据库中的ID值
     *        device_id:设备保存在数据库中的ID
     *        data     :要推送的数据,数组
     */
    public function push(Request $request)
    {
        $data = $request->validate([
            'app_id'    => 'required',
            'device_id' => 'required',
            'data'      => 'required'
        ]);

        $this->jpush->send($data['app_code'], $data['device_id'], $data['data']);

        return responseOk();
    }

}

3. 上面提到的数据库设计如下:

# 设备数据表(device)
id、device_code、device_name ……

# 极光应用数据表(jpush_app_config)
id、app_key、master_secret、package_name ……

# 设备极光注册信息(device_jpush),注意,register_id是一个字符串
id、device_id、app_id、register_id ……

四、解决推送不成功的方法

经过以上三个步骤,我们绝大多数推送应该都可以实现,但我们最前面也说到,推送实际的过程是在极光的服务器上完成,我们只是调用了极光的接口把数据传了过去而已,至于是否推送成功,我们不能完全保证,因此,需要做特殊情况处理,保证每一条数据都能推送成功,因此,以 “安卓端接收到推送” 为成功的唯一依据,采取以下措施:

1. 创建一张推送记录表:

# 推送记录表,包含推送的数据和推送状态,push_record
# 这里data数据保存推送数据数组转换成的json字符串
id, device_id, app_id, data, push_status

2. 执行推送时,采取事件操作(Laravel事件系统参考:https://laravel-china.org/docs/laravel/5.5/events/1318

(1)注册推送事件:打开 /app/Providers/EventServiceProvider.php,添加事件:

protected $listen = [
    'App\Events\JpushMsg' => [
         'App\Listeners\SendJpushMsg',
     ],
];

(2)进入dos系统,跳转到项目根目录,执行命令生成事件文件:

php artisan event:generate

(3)完成Events:打开生成的事件文件,/app/Events/JpushMsg.php,添加代码:

<?php

namespace App\Events;

use App\Models\PushRecord;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class JpushMsg
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $pushRecord;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct(PushRecord $pushRecord)
    {
        $this->pushRecord = $pushRecord;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new PrivateChannel('channel-name');
    }
}

(3)完成Listeners:执行推送事件后,将推送记录保存在推送记录数据库中,且push_status推送状态为0,然后再将推送的数据延迟1分钟传入制作好的队列进行检查push_status是否改变为1,如果仍然为0,则为推送失败,重新推送;同时,在缓存中记录推送次数,队列中判断,如果超过10次仍然推送失败,则放弃重新推送当前的数据。

<?php

namespace App\Listeners;

use Cache;
use Carbon\Carbon;
use App\Events\JpushMsg;
use App\Jpush\JpushSend;
use App\Jobs\CheckPushMsg;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\DispatchesJobs;

class SendJpushMsg implements ShouldQueue    // 这里继承ShouldQueue接口用于将该事件传入队列中执行
{
    use DispatchesJobs;

    public $jpushSend;

    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
        $this->jpushSend = new JpushSend();
    }

    /**
     * Handle the event.
     *
     * @param  JpushBarrage  $event
     * @return void
     */
    public function handle(JpushMsg $event)
    {
        // 1. 获取推送数据
        $pushRecord = $event->pushRecord;
        
        // 2. 组装推送数据,将记录ID加入到数据中
        $data = array_merge(['record_id'=>$pushRecord->id], json_decode($pushRecord->data, true));
        
        // 3. 执行推送
        $this->jpushSend->send($pushRecord->app_id, $pushRecord->device_id, $data);
        
        // 4. 设置已推送的次数     
        $num = Cache::get('record'.$pushRecord->id);
        $num = empty($num) ? 1 : ($num + 1);
        Cache::put('record'.$pushRecord->id, $num, 60);
        
        // 5. 执行延迟1分钟的队列操作,1分钟后在队列中判断当前数据是否已成功推送
        CheckPushMsg::dispatch($pushRecord->id)->delay(Carbon::now()->addMinutes(1));
    }
}

(4)创建队列(Laravel队列系统参考:https://laravel-china.org/docs/laravel/5.5/queues/1324

  • 当前测试采用database驱动,实际上线最好改成redis比较好。使用数据库驱动需要先创建两张表,一张用于存储待上传的队列数据(jobs),一张表用于存储队列操作失败的数据(failed_jobs):
# 创建jobs
php artisan queue:table

# 创建failed_jobs
php artisan queue:failed-table

# 执行创建数据表命令
php artisan migrate
  • 生成检查推送的队列任务类
php artisan make:job CheckPushMsg
  • 完成任务类代码操作
<?php

namespace App\Jobs;

use Cache;
use App\Events\JpushMsg;
use App\Models\PushRecord;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;

class CheckPushMsg implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $recordId;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct($recordId)
    {
        $this->recordId = $recordId;
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        // 1. 根据id获取缓存的已推送次数
        $num = Cache::get('record'.$this->recordId);

        // 2. 判断缓存的推送次数,大于10,则不再继续执行推送
        if($num > 10) return;
        
        // 3. 获取已推送的信息记录
        $pushRecord = PushRecord::find($this->recordId);
        
        // 4. 判断推送状态,如果推送状态从0修改为1,则不再推送
        if($pushRecord->push_status === 1) return;

        // 5. 重新执行推送事件
        event(new JpushMsg($pushRecord));
    }

}

五、完善第四步措施的控制器代码完善

<?php
namespace App\Http\Controllers\Home;

use App\Models\Device;                // 引入设备数据库操作模型
use App\Events\JpushMsg;              // 引入极光推送消息事件类
use App\Models\PushRecord;            // 引入推送记录数据库操作模型
use App\Models\DeviceJpush;           // 引入保存设备注册极光推送的register_id数据库操作模型
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

Class TestJpush extends Controller
{
    /*
     *   保存安卓设备注册极光的register_id到数据库
     *   参数:
     *        app_id     :极光应用配置存在我们服务器数据库中的数据ID
     *        device_code:安卓设备码
     *        register_id:安卓设备调用极光接口注册的ID值(register_id),这个让java工程师自己去找接口解决
     */
    public function deviceRegister(Request $request)
    {
        $data = $request->validate([
            'app_id'      => 'required',
            'device_code' => 'required',
            'register_id' => 'required'
        ]);

        // 1. 根据设备码(device_code)获取设备ID(device_id)
        $device_id = Device::where('device_code', $data['device_code'])->find(['id']);

        // 2. 联合设备ID(device_id)和极光应用ID(app_id),保存设备在极光注册的register_id
        DeviceJpush::insert([
            'app_id'      => $data['app_id'],
            'device_id'   => $device_id,
            'register_id' => $data['register_id']
        ]);

        return responseOk();
    }

    /*
     *   调用事件完成极光推送
     *   参数:
     *        app_id   :极光应用配置保存在数据库中的ID值
     *        device_id:设备保存在数据库中的ID
     *        data     :要推送的数据,json数组
     */
    public function push(Request $request)
    {
        $data = $request->validate([
            'app_id'    => 'required',
            'device_id' => 'required',
            'data'      => 'required'
        ]);
           
        // 1. 保存推送记录
        $pushRecord = PushRecord::insert([
            'app_id'    => $data['app_id'],
            'device_id' => $data['device_id'],
            'data'      => $data['data']
        ]);

        // 2. 调用推送事件
        event(new JpushMsg($pushRecord));

        return responseOk();
    }


    /*
     *    设备接收到推送后调用接口改变推送记录状态
     *    参数:record_id —— 推送记录ID
     */
    public function updatePushStatus(Request $rquest)
    {
        $data = $request->validate([
            'record_id' => 'required'
        ]);

        PushRecord::where('id', $data['record_id'])->update(['push_status'=>1]);

        return responseOk();
    }
    
}

总结以上,思路主要是以下4个步骤:

  • 保存每一条推送所需数据以及推送状态初始值 0 到数据库;
  • 执行第一次推送后,增加一个延迟任务检查该条推送是否成功;
  • 安卓端接收到推送则调用接口,更改当前推送记录状态为1;
  • 延迟任务再检查当前推送是否推送成功,如果失败,则重新推送,如果成功,则结束。

需要注意:

  • 失败的推送不能无限次循环推送,如果推送达到一定值的时候,就应该停止这个任务,否则失败任务如果产生堆积,一直执行,数据量增大后,会占用大量正常运行的带宽,不合理;
  • 第一次执行后,到底延迟多久才检查推送是否成功?这个时间需要按照自己把握,我这里是给的1分钟延迟。
  • 本篇文章中的代码是根据实际项目思考抽离出来的,没有在项目中跑过,所以如果直接复制到项目运行报错是有可能的,如果有误,欢迎留言告知,我及时更改,谢谢!

 

完!祝好运!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值