1. 程式人生 > >利用客服訊息和模板訊息實現微信群發(突破群發介面的上限)

利用客服訊息和模板訊息實現微信群發(突破群發介面的上限)

1、關於群發介面和訊息介面

關於群發介面
1.訂閱號每天可以群發訊息一條,服務號每月(自然月)四條的群發許可權。開發者模式下,可以通過高階群發介面,實現更靈活的群發能力。
2.注意
● 對於認證訂閱號,群發介面每天可成功呼叫1次,此次群發可選擇傳送給全部使用者或某個標籤;
● 對於認證服務號雖然開發者使用高階群發介面的每日呼叫限制為100次,但是使用者每月只能接收4條,無論在公眾平臺網站上,還是使用介面群發,使用者每月只能接收4條群發訊息,多於4條的群發將對該使用者傳送失敗;
● 具備微信支付許可權的公眾號,在使用群發介面上傳、群發圖文訊息型別時,可使用a標籤加入外鏈;
關於客服訊息和模板訊息介面
當用戶和公眾號產生特定動作的互動時(具體動作列表請見下方說明),微信將會把訊息資料推送給開發者,開發者可以在一段時間內(目前修改為48小時)呼叫客服介面,通過POST一個JSON資料包來發送訊息給普通使用者。此介面主要用於客服等有人工訊息處理環節的功能,方便開發者為使用者提供更加優質的服務。
模板訊息僅用於公眾號向用戶傳送重要的服務通知,只能用於符合其要求的服務場景中,如信用卡刷卡通知,商品購買成功通知等。不支援廣告等營銷類訊息以及其它所有可能對使用者造成騷擾的訊息。

2、背景

簡單的描述一下業務的背景,目前是做一個供求的微信公眾號,每當使用者付費傳送一次供求的資訊,我們需要將本條訊息推送給所有的使用者,這樣可以讓使用者及時收到訊息,實現訊息的實時性和保證訊息的有效性。但是群發的介面根本不能滿足我們的需求,於是我們利用模板訊息和客服訊息的介面來實現我們的需求,在此,有的人可能會發問,為什麼客服訊息的接收要求這麼變態還要利用它呢,其實是因為,群發的訊息在某些裝置上收到的時候,就像微信好友傳送的一條資訊,更加吸引使用者去關注。
另外,我們還需要知道,假如我們的使用者用成千上萬的人話,那麼群發的方式是十分的耗時的,微信支付提供了notify的非同步通知

,之前我也一直嘗試利用這個現場的非同步通知來實現群發,但是根據實際的使用,這個非同步IO的閾值差不多在6分鐘左右,可是實際上傳送一次模板的網路耗時加上資料庫的IO其實還是時間還是挺長的,因此我們不得不使用非同步多執行緒來實現我們的群發的功能這裡我利用的是swoole的擴充套件來完成這一需求的。
swoole的非同步郵件群發的demo,大家可以參照我的另一篇文章thinkphp5+swoole實現非同步郵件群發(SMTP方式)這裡可以較為詳細的瞭解服務端和客戶端的構建。

3、環境說明

centos7
swoole2.0+
tp5.0+
郵件傳送
crontab定時任務

4、實現

4.1建立服務端

我這裡就直接貼程式碼了,需要注意的地方都寫在了相關程式碼的註釋,注意看一下

/**
     * description:服務端
     */
    public function asyncSend()
    {
        $serv = new \swoole_server('0.0.0.0', 9090);
        $serv->set(array(
            'task_worker_num' => 60,
            'daemonize' => true,
            'log_file' => '/var/www/html/myswl/tp/swoole.log'
            )
        );
        $serv->on('receive', function ($serv, $fd, $from_id, $data) {
            $task_id = $serv->task($data);
            echo "開始投遞非同步任務 id=$task_id\n";
        });

        $serv->on('task', function ($serv, $task_id, $from_id, $data) {
            echo "接收非同步任務[id=$task_id]" . PHP_EOL;
            $data = json_decode($data,true);

//這裡要特別的說明,我這裡用到了資料庫的遠端連結,因為支付的功能在另一臺window虛擬雲主機上的,所以不得不利用遠端訪問的方式。Db::connect($conn,true);的第二個引數給給予關注,因為我們沒傳送一次其實都會去進行一次遠端資料庫連線,所以頻繁的連線中,肯定會有連線失敗的情況,因此我們需要做好斷線重連的配置
            $conn = 'mysql://使用者名稱:資料庫遠端連結地址:3306/密碼#utf8';
            $db = Db::connect($conn,true);

            $now = date('Y-m-d H:i:s');
            $users = $db
                ->table('tp_receiver')
                ->field('openid')
                ->where("(`expire_time` > '{$now}' OR `send_status` = 1 ) AND `receive_status` = 1 ")
                ->limit($data['flag']*500,500)
                ->select();

            echo $db->getLastSql();

            echo 'send start--' . date('H:i:s') . PHP_EOL;
            foreach ($users as $user) {
                $token = $db->table('tp_accesstoken')->field('accesstoken')->find();
//這裡是客服訊息
                $templData2 = array(
                    'touser' => $user['openid'],
                    'msgtype' => 'text',
                     'text' => array('content' => $data['content']."\n\n點選下方“資訊查詢”檢視更多求購資訊。"."\n回覆0取消接收資訊,回覆1重新接收資訊")
                );

                $res = $this->sendCustomMessage($templData2,$token['accesstoken']);
                $res = json_decode($res, true);
//判斷客服訊息是否成功傳送
                if($res['errcode'] == 45047 || $res['errcode'] == 45015) {
                    $templData = array(
                        'touser' => $user['openid'],
                        'template_id' => '模板訊息',
                        'url' => '',
                        'data' => array(
                            'first' => array('value' => '您好,您收到一條新的提醒', 'color' => '#173177'),
                            'keyword1' => array('value' => '求購資訊', 'color' => '#173177'),
                            'keyword2' => array('value' => $data['content'], 'color' => '#173177'),
                            'keyword3' => array('value' => $data['phone'], 'color' => '#173177'),
                            'keyword4' => array('value' => date('Y-m-d H:i:s'), 'color' => '#173177'),
                            'remark' => array('value' => "點選下方“資訊查詢”檢視更多求購資訊。"."回覆0取消接收資訊,回覆1重新接收資訊", 'color' => '#173177')
                        ),
                    );
                    $res = $this->sendTemplateMessage($templData, $token['accesstoken']);
                    Log::write('send to'.$user['openid'].$res . PHP_EOL);
                }
            }
            echo 'send end--' . date('H:i:s') . PHP_EOL;

            $serv->finish('');
        });

        $serv->on('finish', function ($serv, $task_id, $data) {
            echo 'finish time--' . microtime(true) . PHP_EOL;
            echo "非同步任務[id=$task_id]完成" . PHP_EOL;
        });

        $serv->start();
    }

然後我們在CLI模式下進入專案的根目錄,執行

php public/index.php demo/wechat/asyncSend

這樣我們的服務端就以守護程序的模式一直執行來我們的後臺了,通過ps -aux | grep asyncSend
可以看見,已經有62個程序在處於S(睡眠待喚醒)的狀態了,除了60個task程序還用一個master和一個woker程序。

process.png

4.2構建客戶端

程式碼如下,需要注意的地方都寫在了相關程式碼的註釋,注意看一下

/**
     * description:客戶端
     */
    public function index()
    {
//因為這個群發比較敏感,我們需要做一個token的機制,我這邊就用最簡單的傳送方和接收方都以明文的方式來做了。
            $token = $_GET['token'];
            if($token != 'test'){
                exit;
            }
//content是傳送的內容,因為不可預估裡面的東西,所以進行加解密
            $content = rawurldecode($_GET['content']);
            $flag = $_GET['flag'];
            $id = $_GET['id'];
            $phone = $_GET['phone'];

            $data['content'] = $content;
            $data['flag'] = $flag;
            $data['phone'] = $phone;

            Log::write(self::json_encode($data));

            $insert = [
                'flag'=>$flag,
                'miaomu_id'=>$id,
                'status'=>1,
                'createtime'=>date('Y-m-d H:i:s'),
                'content'=>$content
            ];
            Db::table('task')->insert($insert);

//非同步客戶端
            $client = new \swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC);
            $ret = $client->connect("127.0.0.1", 9090);
//當無法連線的時候,傳送告警郵件
            if (empty($ret)) {
                SendMail::postmail('[email protected]','警告','error!connect to swoole_server failed');
                SendMail::postmail('[email protected]','警告','error!connect to swoole_server failed');
                Log::write('error!connect to swoole_server failed');
            } else {
                $client->send(self::json_encode($data));
            }
    }
    ```
###4.3埠監控
這個群發已經涉及到金額了,所以我們更要關係服務的執行穩定了,這裡我們簡單的利用crontab定時任務和Php的一些shell相關函式來實現埠的監控。
本次用到的定時任務

/1 * * * curl http://你的域名/index.php/demo/Jrmm/checkPortStatus?token=test

就是實現一個每分鐘去執行我們下面php程式碼的一個任務,這裡我沒有直接用shell來操作,原因有3點,1是我不是很熟悉shell命令,2是我們不太熟悉shell命令,3是寫在php裡面更方便我去寫相關程式碼和利用已有的一些方法,比如郵件傳送。這樣雖然多了一點網路資源的消耗,但是也還划算。
具體的監控程式碼,這邊實現的時候會出現很多許可權的問題,我就不多說了,遇到的時候自行百度。
/**
     * description:8082服務埠監控
     */
    public function checkPortStatus(){
        if (!isset($_GET['token']) || $_GET['token'] != 'test'){
            exit();
        }
        $res1 = exec('sudo netstat -lpn | grep 9090');
        Log::write($res1);
        if($res1 == ''){
            Log::write('9090stop');
            SendMail::postmail('[email protected]','警告','9090埠服務錯誤');
            SendMail::postmail('[email protected]','警告','9090埠服務錯誤');
//重啟我們的服務端,這裡需要注意的是,我沒有用到swoole提供的平滑重啟的功能,很可能會造成資料的丟失,這別額外的需要注意
            exec('sudo php /var/www/html/myswl/tp/public/index.php demo/jrmm/asyncSend');
            $res = exec('sudo netstat -lpn | grep 8082');
            if($res != ''){
                Log::write('9090restart success');
                SendMail::postmail('[email protected]','警告解除','9090埠重啟成功');
                SendMail::postmail('[email protected]','警告解除','9090埠重啟成功');
            }
        }
    }
###4.4實現
我們利用PHP curl函式來模擬一次支付成功後呼叫我們群發的功能。

content=test;for(j=0;j<3;j++){
url=http:///index.php/demo/Jrmm/index.?flag=.j.’&id=1.’&token=test’.’&phone=’110&content=’.rawurlencode(content);this->http_post(url);  
        }  
function http_post(
url){
ch=curlinit();curlsetopt(ch, CURLOPT_URL, url);curlsetopt(ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt(ch,CURLOPTHEADER,0);res= curl_exec(ch);curlclose(ch);
return $res;
}
“`
為什麼迴圈三次呢,因為我們通過測試傳送傳送1500次訊息的時候,耗時差不多6分鐘,但是我們的專案的併發很低,那麼就無法充分利用我們開啟的60個task程序,所以我們將1500分成三次去傳送那麼實際上我們消耗了幾乎可以忽略不計的網路消耗,讓我們的傳送的效能提高了三倍多,實際的專案中,傳送1500多條實際耗時只要不到兩分鐘。當然當併發量更大的時候,我們還可以採用佇列的方式來處理,這樣需要我們隊task程序管理更加的熟練。

5、截圖

收到的推送訊息

jieshou.png

告警
gaojing.png

傳送的日誌

sengdLog.png

6、專案完整程式碼下載