首頁 >php教程 >php手册 >基於Codeigniter框架實現的APNS批次推送—叮咚,查水錶

基於Codeigniter框架實現的APNS批次推送—叮咚,查水錶

WBOY
WBOY原創
2016-08-18 08:57:581365瀏覽

   最近兼職公司已經眾籌成功的無線門鈴的消息推播出現了問題,導致有些用戶接收不到推播的消息,真是嚇死寶寶了,畢竟自己一手包辦的後台服務,影響公司信譽是多麼的尷尬,容我簡單介紹一下我們的需求:公司開發的是一款無線門鈴系統,如果有人在門外按了門鈴開關,門鈴開關會發射一個信號,屋裡的接收網關接收到信號會發出響聲,同時也會推播訊息到用戶手機,即使這支手機是遠端的,也就是主人不在家也知道有人按了家裡的門鈴。這裡後台需要解決的問題是搭建APNS推送的Provider,因為要想把消息推送到蘋果手機,按照蘋果公司設計的機制,必須透過自己的伺服器推送到蘋果的PUSH伺服器,再由它推送到手機,每個手機對應一個deviceToken,我這裡介紹的重點並不是這個平台怎麼搭建,這個國內網路的教學已經相當豐富了。例如可以參考:一步一步教你做ios推播

網上的教程大多是走的通的,但是他們操作的對像是一個手機,我的意思是它們是一次給一個手機終端推送消息,在我們公司設計的產品中,同一個帳戶可以在多個手機上登入(理論上是無數個,因為我在後台並沒有限制),每個手機對應的deviceToken是不同的,另外公司的產品還設計了分享功能,也就是主用戶可以把設備分享給其他用戶,而其他用戶也有可能在不同裝置上同時登錄,如果有人按了門鈴要向所有已登入的用戶包含分享的用戶推播訊息,也就是要批次推送到很多個手機終端。當然我這裡舉的例子並不會有這麼複雜,所有的問題抽像出來其實就是一個問題:給你一個儲存deviceToken的數組,APNS如何批量推送給多個用戶?

 

首先我們設計一個資料庫,用來儲存使用者的推送令牌(deviceToken),為簡單起見,這個表格就兩個欄位。

client_id deviceToken
1 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

 

這裡我使用的是CodeIgniter3的框架,我們新建一個Model,來管理使用者deviceToken資料。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
<?php
// ios推送令牌管理
 
 
 
class Apns_model extends CI_Model
{
 
    public function __construct()
    {
        $this->load->database();
    }
 
 
    /**
     * 新推播令牌
     * create a apns如果已經存在就更新這個deviceToken
     * $data is an array organized by controller
     */
    public function create($data)
    {
        if($this->db->replace('tb_apns'->db->replace('         {
            return
TRUE;         }
        else
        {
            return
FALSE;         }
    }
   
 
 
    
//刪除某位使用者的推播令牌
    public
function delete($user_id)($user_id)(
$user_id)
    
{
                  if(isset(
$user_id
)){$user_id)){$user_id)){$user_id)){             $result=$this->db->'
$this
->db->' array(
'client_id' => $user_id)); 
🎜            🎜🎜return🎜 🎜TRUE;           🎜🎜        🎜🎜}🎜🎜else🎜🎜{🎜🎜
            return FALSE;
        }
 
    }
 
 
    //根據推播令牌刪除推送令牌
    public function deletebytoken($token)
    {
         
        if (isset($token)) {
            $result=$this->db->'$this->db->' array('deviceToken'=> $token));
            
return TRUE;  
        
}else{
            
return FALSE;
        
}  
    
}  
 
 
    
//詢問某位使用者的iso推送令牌
    
public function get($client_id)$client_id
)     
🎜{🎜🎜
$sql = "從`tb_apns`中選取deviceToken,其中`client_id`='$client_id'""           
$結果
=
$這個->資料庫->查詢($這個->資料庫->查詢($);           if
(
$結果->num_rows()>0)         {
            回傳 $結果
->result_array();         }
        否則
        {
            回傳FALSE;
        }
    }
 
   
 
}

在我後台的第一個版本中,按照網上的教程,大多是一次給一個終端推送訊息的,我稍微改了一下,將所有取得的deviceToken存在$deviceTokens數組中,參數$message是需要推送的訊息,使用for迴圈依序從陣列中取出一個deviceToken來推送,然後計數,如果所有的推播成功則傳回true。這個方法看似沒有任何破綻的,而且也測試成功了,所以我就直接上線了,(主要是我也沒想到公司會突然出這樣一個產品,把推送功能的地位抬得很高,我一直以為是可有可無的)。

 

$num)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
函數_send_apns($deviceTokens$訊息
    {
 
 
      //在此輸入您的私鑰密碼:密語
       $密碼 = 'xxxxx';
 
           /////////////////////////////////////////////// / //////////////////////////////////
 
       $ctx =stream_context_create();
       stream_context_set_option($ctx, 'ssl',xxx ' );        stream_context_set_option($ctx
, 'ssl',$S );          //開啟與APNS伺服器的連線        $fp
=stream_socket_client(
           'ssl://gateway.push.apple.com:2195'
$err
$
           $errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, );          
if(!$fp)
           exit("Failed to connect: $errstr"_.  
       
echo 'Connected to APNS' . PHP_EOL;  
        
// Create the payload body        
$body['aps'] = array(            'alert'
=> $message,            
'sound'
=> 'default'            
);
 
         
// Encode the payload as JSON
       
$payload
= json_encode($body);  
       
$num
=count($deviceTokens);($deviceTokens);        
$countOK=0;//統計發送成功的條數  
       
for($i=0;$i        {            $deviceToken=
$deviceTokens[  
           $deviceToken=preg_replace("/s/",, ;//刪除deviceToken裡的空格    
        
// Build the binary notification
           $msg
=
chr(0) .H , $deviceToken) . pack('n', strlen($payload)) . $pay           // Send it to the server            $result = fwrite(
$fp
,$m
$msg));
 
           if ($result)                      {
               
$countOK++;                         
}
       }
 
      // Close the connection to the server
       fclose(
$fp);
       if($countOK==$num)
==
$num)==
$num
)==
$num
)==$num
)
==
$num)
==
🎜= 🎜🎜           🎜🎜回傳🎜🎜TRUE;🎜🎜 🎜🎜       🎜🎜否則🎜🎜 🎜🎜           🎜🎜回傳🎜 🎜FALSE;🎜🎜 🎜 🎜 🎜🎜   🎜🎜}🎜🎜 🎜 🎜 🎜 🎜 🎜

就是上面的程式碼導致了後來推送出現了一系列問題。

第一個大問題是:這裡默認了所有的推送令牌都是有效的,而實際上,如果用戶直接刪除了app或app升級都有可能造成後台資料庫裡的deviceToken沒有發生更新,從而使推送令牌失效。但有人按了門鈴,後台還是會把它當成有效的deviceToken納入到$deviceTokens中,如何清除失效過期的deviceToken是個必須考慮的問題。

查閱相關資料發現APNS服務有一個The Feedback Service的服務,國內的博客基本上忽略了這個環節,很少有資料提及,還是谷歌找個官方網站比較可靠。以下簡單介紹一下這個服務:

 

在進行APNS遠端推送時,如果由於用戶卸載了app而導致推送失敗,APNS伺服器會記錄下這個deviceToken,加入到一個列表中,可以透過查詢這個列表,取得失效的推播令牌,從資料庫中清除這些失效的令牌可以避免下次推送時被加入到推送數組中。連結這項服務很簡單和推播工程類似,只不過地址不同,開發環境<span style="font-family: 宋体, SimSun; font-size: 18px;">feedback.push.apple.com</span> ,測試環境為 ,測試環境為 com埠都是2196。 APNS伺服器回傳的資料格式為:


 

🎜 🎜Timestamp🎜

時間戳(以四位元組time_t值),指示 APN 何時確定裝置上不再存在該應用。 此值依網路順序排列,表示自 UTC 1970 年 1 月 1 日午夜 12:00 以來的秒數。

令牌長度

設備令牌的長度,為網路順序中的兩位元組整數值。

設備令牌

二進位格式的設備令牌。

為了進行這項服務,我寫了一個CI框架的控制器

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?php
已定義('BASEPATH') 或 exit('不允許直接腳本存取');
'不允許直接腳本存取'
);   管理
擴充
CI_Controller {
      公用
函數
__construct()
    
{
        
parent::__construct();
        
// 載入資料庫         
$這個
->載入->資料庫();         $這個->負載->模型(
'apns_model'
);'apns_model'
);
    }  
    
函數
apnsfeedback()
    {
        $ctx =stream_context_create();          
$密碼
= 'xxxxx';         stream_context_set_option($ctx, 'ssl',
'ssl' .pem' );         stream_context_set_option($ctx, 'ssl', , 'ssl'
,
);           $fp =stream_socket_client('ssl://feedback.push.apple.com:2196'$ errorString , 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT , $ctx);
        if (!$fp) {
            echo 「無法連接回饋伺服器:$err $errstrn」;
            回傳
        }
        否則 {
            echo 「連接到回饋伺服器 OKn」;
            迴音 "<br>";
        }
 
        while ($devcon = freadp         {
            
$arr = 解包(
"H*"
,
陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn