說明:本部分主要基於三個示例來講明Pusher服務的使用。php
Channels:
頻道用來辨識程序內數據的場景或上下文,並與數據庫中的數據有映射關係。就像是聽廣播的頻道同樣,不一樣頻道接收不一樣電臺。css
Event:
若是頻道是用來辨識數據的,那事件就是對該數據的操做。就像數據庫有CRUD操做事件,那頻道就有類似的事件:頻道的create事件、頻道的read事件、頻道的update事件、頻道的delete/destroy事件。html
Event Data:
每個事件都有相應的數據,這裏僅僅是打印頻道發過來的文本數據,但也能夠包括允許用戶交互,如點擊操做查看更詳細的數據等等。這就像是聽廣播的內容,不單單被動聽,還能夠有更復雜的行爲,如互動同樣。jquery
如在上一篇中 Laravel Pusher Bridge 觸發了事件後,傳入了三個參數:laravel
$pusher->trigger('test-channel', 'test-event', ['text' => 'I Love China!!!']);
其中,test-channel 就是此次發送的頻道名字,test-event 就是該次事件的名稱,['text' => 'I Love China!!!']
就是此次發送的數據。git
在 routes.php 文件中加入:github
Route::controller('notifications', 'NotificationController');
在項目根目錄輸入以下指令,建立一個 NotificationController:ajax
php artisan make:controller NotificationController
同時在 resources/views/pusher 文件夾下建立一個 notification.blade.php 文件:數據庫
<!DOCTYPE html> <html> <head> <title>Real-Time Laravel with Pusher</title> <meta name="csrf-token" content="{{ csrf_token() }}" /> {{--<link href="//fonts.googleapis.com/css?family=Source+Sans+Pro:200,300,400,600,700,200italic,300italic" rel="stylesheet" type="text/css">--}} <link rel="stylesheet" type="text/css" href="http://d3dhju7igb20wy.cloudfront.net/assets/0-4-0/all-the-things.css" /> <script src="//cdn.bootcss.com/jquery/1.11.3/jquery.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/js/toastr.min.js"></script> <script src="//js.pusher.com/3.0/pusher.min.js"></script> <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/css/toastr.min.css"> <script> // Ensure CSRF token is sent with AJAX requests $.ajaxSetup({ headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') } }); // Added Pusher logging Pusher.log = function(msg) { console.log(msg); }; </script> </head> <body> <div class="stripe no-padding-bottom numbered-stripe"> <div class="fixed wrapper"> <ol class="strong" start="1"> <li> <div class="hexagon"></div> <h2><b>Real-Time Notifications</b> <small>Let users know what's happening.</small></h2> </li> </ol> </div> </div> <section class="blue-gradient-background splash"> <div class="container center-all-container"> <form id="notify_form" action="/notifications/notify" method="post"> <input type="text" id="notify_text" name="notify_text" placeholder="What's the notification?" minlength="3" maxlength="140" required /> </form> </div> </section> <script> function notifyInit() { // set up form submission handling $('#notify_form').submit(notifySubmit); } // Handle the form submission function notifySubmit() { var notifyText = $('#notify_text').val(); if(notifyText.length < 3) { return; } // Build POST data and make AJAX request var data = {notify_text: notifyText}; $.post('/notifications/notify', data).success(notifySuccess); // Ensure the normal browser event doesn't take place return false; } // Handle the success callback function notifySuccess() { console.log('notification submitted'); } $(notifyInit); </script> </body> </html>
NotificationController主要包含兩個方法:api
getIndex:
展現通知視圖
postNotify:
處理通知的POST請求
namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Http\Requests; use Illuminate\Support\Facades\App; class NotificationController extends Controller { public function getIndex() { return view('notification'); } public function postNotify(Request $request) { $notifyText = e($request->input('notify_text')); // TODO: Get Pusher instance from service container $pusher = App::make('pusher'); // TODO: The notification event data should have a property named 'text' // TODO: On the 'notifications' channel trigger a 'new-notification' event $pusher->trigger('notifications', 'new-notification', $notifyText); } }
個人環境輸入路由http://laravelpusher.app:8888...,而後在輸入框裏輸入文本後回車,console裏打印notification submitted
,說明通知已經發送了:
這時候查看Pusher Debug Console界面或者storage/logs/laravel.log文件:
說明服務端已經成功觸發事件了。
接下來使用Pusher JavaScript庫來接收服務端發來的數據,並使用toastr庫來UI展現通知,加入代碼:
//notification.blade.php ... $(notifyInit); // Use toastr to show the notification function showNotification(data) { // TODO: get the text from the event data // TODO: use the text in the notification toastr.success(data, null, {"positionClass": "toast-bottom-left"}); } var pusher = new Pusher("{{env("PUSHER_KEY")}}"); var channel = pusher.subscribe('notifications'); channel.bind('new-notification', function(data) { // console.log(data.text); // console.log(data); showNotification(data); });
$pusher對象訂閱notifications頻道並綁定new-notification事件,最後把從服務端發過來的數據用toastr.success形式UI展示出來。
如今,新開一個標籤頁而後輸入一樣的路由:http://laravelpusher.app:8888/notifications
,而後在A頁面輸入文本回車,再去B頁面看看通知是否正確顯示:
It is working!
爲了不觸發事件的用戶也會接收到Pusher發來的通知,能夠加上惟一連接標識socket_id並傳入trigger()函數,在客戶端該socket_id經過pusher.connection.socket_id獲取並把它做爲參數傳入post路由中:
//NotificationController.php public function postNotify(Request $request, $socketId) { $notifyText = e($request->input('notify_text')); // TODO: Get Pusher instance from service container $pusher = App::make('pusher'); // TODO: The notification event data should have a property named 'text' // TODO: On the 'notifications' channel trigger a 'new-notification' event $pusher->trigger('notifications', 'new-notification', $notifyText, $socketId); } //notification.blade.php var socketId = pusher.connection.socket_id; // Build POST data and make AJAX request var data = {notify_text: notifyText}; $.post('/notifications/notify'+ '/' + socketId, data).success(notifySuccess);
重刷AB頁面,A頁面觸發的事件A頁面不會接收到。
這部分主要擴展對Pusher的瞭解,使用不一樣的事件來識別不一樣的行爲,從而構建一個活動流(activity stream)。這不只能夠熟悉數據的發生行爲,還能夠當處理事件數據時解耦客戶端邏輯。
這裏使用github帳號來實現第三方登陸,這樣就能夠拿到認證的用戶數據並保存在Session裏,當用戶發生一些活動時就能夠辨識Who is doing What!
。在項目根目錄安裝laravel/socialite包:
composer require laravel/socialite
獲取github密鑰
登陸github
進入Setting->OAuth applications->Developer applications,點擊Register new application
HomePage URL填入http://laravelpusher.app:8888/(填本身的路由,這是個人路由),Authorization callback URL填http://laravelpusher.app:8888...
點擊Register application,就會生成Client ID和Client Secret
在項目配置文件.env中填入:
//填寫剛剛註冊的Authorization callback URL和生成的Client ID,Client Secret GITHUB_CLIENT_ID=YOUR_CLIENT_ID GITHUB_CLIENT_SECRET=YOUR_CLIENT_SECRET GITHUB_CALLBACK_URL=YOUR_GITHUB_CALLBACK_URL
須要告訴Socialite組件這些配置項,在config/services.php中:
return [ // Other service config 'github' => [ 'client_id' => env('GITHUB_CLIENT_ID'), 'client_secret' => env('GITHUB_CLIENT_SECRET'), 'redirect' => env('GITHUB_CALLBACK_URL'), ], ];
添加登陸模塊
在app/Http/Controllers/Auth/AuthController.php添加:
//AuthController.php ... /** * Redirect the user to the GitHub authentication page. * Also passes a `redirect` query param that can be used * in the handleProviderCallback to send the user back to * the page they were originally at. * * @param Request $request * @return Response */ public function redirectToProvider(Request $request) { return Socialite::driver('github') ->with(['redirect_uri' => env('GITHUB_CALLBACK_URL' ) . '?redirect=' . $request->input('redirect')]) ->redirect(); } /** * Obtain the user information from GitHub. * If a "redirect" query string is present, redirect * the user back to that page. * * @param Request $request * @return Response */ public function handleProviderCallback(Request $request) { $user = Socialite::driver('github')->user(); Session::put('user', $user); $redirect = $request->input('redirect'); if($redirect) { return redirect($redirect); } return 'GitHub auth successful. Now navigate to a demo.'; }
在路由文件routes.php中添加:
Route::get('auth/github', 'Auth\AuthController@redirectToProvider'); Route::get('auth/github/callback', 'Auth\AuthController@handleProviderCallback');
在瀏覽器中輸入路由:http://laravelpusher.app:8888...,進入github登陸頁面:
點擊贊成認證後會跳轉到http://laravelpusher.app:8888...,而且用戶數據保存在服務器的Session中,能夠經過Session::get('user')獲取用戶數據了。
在項目根目錄:
php artisan make:controller ActivityController
在ActivityController.php中添加:
public $pusher, $user; public function __construct() { $this->pusher = App::make('pusher'); $this->user = Session::get('user'); } /** * Serve the example activities view */ public function getIndex() { // If there is no user, redirect to GitHub login if(!$this->user) { return redirect('auth/github?redirect=/activities'); } // TODO: provide some useful text $activity = [ 'text' => $this->user->getNickname().' has visited the page', 'username' => $this->user->getNickname(), 'avatar' => $this->user->getAvatar(), 'id' => str_random(), //'id' => 1,//status-update-liked事件 ]; // TODO: trigger event $this->pusher->trigger('activities', 'user-visit', $activity); return view('activities'); } /** * A new status update has been posted * @param Request $request */ public function postStatusUpdate(Request $request) { $statusText = e($request->input('status_text')); // TODO: provide some useful text $activity = [ 'text' => $statusText, 'username' => $this->user->getNickname(), 'avatar' => $this->user->getAvatar(), 'id' => str_random() ]; // TODO: trigger event $this->pusher->trigger('activities', 'new-status-update', $activity); } /** * Like an exiting activity * @param $id The ID of the activity that has been liked */ public function postLike($id) { // TODO: trigger event $activity = [ // Other properties... 'text' => '...', 'username' => $this->user->getNickname(), 'avatar' => $this->user->getAvatar(), 'id' => $id, 'likedActivityId' => $id, ]; // TODO: trigger event $this->pusher->trigger('activities', 'status-update-liked', $activity); }
在路有文件routes.php中:
Route::controller('activities', 'ActivityController');
添加resources/views/pusher/activities.balde.php文件:
<!DOCTYPE html> <html> <head> <title>Real-Time Laravel with Pusher</title> <meta name="csrf-token" content="{{ csrf_token() }}" /> {{--<link href="//fonts.googleapis.com/css?family=Source+Sans+Pro:200,300,400,600,700,200italic,300italic" rel="stylesheet" type="text/css">--}} <link rel="stylesheet" type="text/css" href="http://d3dhju7igb20wy.cloudfront.net/assets/0-4-0/all-the-things.css" /> <link rel="stylesheet" type="text/css" href="https://pusher-community.github.io/real-time-laravel/assets/laravel_app/activity-stream-tweaks.css" /> <script src="//cdn.bootcss.com/jquery/1.11.3/jquery.min.js"></script> {{--<script src="//code.jquery.com/jquery-1.11.3.min.js"></script>--}} <script src="//js.pusher.com/3.0/pusher.min.js"></script> <script> // Ensure CSRF token is sent with AJAX requests $.ajaxSetup({ headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') } }); // Added Pusher logging Pusher.log = function(msg) { console.log(msg); }; </script> </head> <body> <div class="stripe no-padding-bottom numbered-stripe"> <div class="fixed wrapper"> <ol class="strong" start="2"> <li> <div class="hexagon"></div> <h2><b>Real-Time Activity Streams</b> <small>A stream of application consciousness.</small></h2> </li> </ol> </div> </div> <section class="blue-gradient-background"> <div class="chat-app light-grey-blue-background"> <form id="status_form" action="/activities/status-update" method="post"> <div class="action-bar"> <input id="status_text" name="status_text" class="input-message col-xs-9" placeholder="What's your status?" /> </div> </form> <div class="time-divide"> <span class="date"> Today </span> </div> <div id="activities"></div> </div> </section> <script id="activity_template" type="text/template"> <div class="message activity"> <div class="avatar"> <img src="" /> </div> <div class="text-display"> <p class="message-body activity-text"></p> <div class="message-data"> <span class="timestamp"></span> <span class="likes"><span class="like-heart">♥</span><span class="like-count"></span></span> </div> </div> </div> </script> <script> function init() { // set up form submission handling $('#status_form').submit(statusUpdateSubmit); // monitor clicks on activity elements $('#activities').on('click', handleLikeClick); } // Handle the form submission function statusUpdateSubmit() { var statusText = $('#status_text').val(); if(statusText.length < 3) { return; } // Build POST data and make AJAX request var data = {status_text: statusText}; $.post('/activities/status-update', data).success(statusUpdateSuccess); // Ensure the normal browser event doesn't take place return false; } // Handle the success callback function statusUpdateSuccess() { $('#status_text').val(''); console.log('status update submitted'); } // Creates an activity element from the template function createActivityEl() { var text = $('#activity_template').text(); var el = $(text); return el; } // Handles the like (heart) element being clicked function handleLikeClick(e) { var el = $(e.srcElement || e.target); if (el.hasClass('like-heart')) { var activityEl = el.parents('.activity'); var activityId = activityEl.attr('data-activity-id'); sendLike(activityId); } } // Makes a POST request to the server to indicate an activity being `liked` function sendLike(id) { $.post('/activities/like/' + id).success(likeSuccess); } // Success callback handler for the like POST function likeSuccess() { console.log('like posted'); } function addActivity(type, data) { var activityEl = createActivityEl(); activityEl.addClass(type + '-activity'); activityEl.find('.activity-text').html(data.text); activityEl.attr('data-activity-id', data.id); activityEl.find('.avatar img').attr('src', data.avatar); //activityEl.find('.like-count').html(parseInt(data.likedActivityId) + 1);//status-update-liked事件 $('#activities').prepend(activityEl); } // Handle the user visited the activities page event function addUserVisit(data) { addActivity('user-visit', data); } // Handle the status update event function addStatusUpdate(data) { addActivity('status-update', data); } function addLikeCount(data){ addActivity('status-update-liked', data); } $(init); /***********************************************/ var pusher = new Pusher('{{env("PUSHER_KEY")}}'); // TODO: Subscribe to the channel var channel = pusher.subscribe('activities'); // TODO: bind to each event on the channel // and assign the appropriate handler // e.g. 'user-visit' and 'addUserVisit' channel.bind('user-visit', function (data) { addUserVisit(data); // console.log(data,data.text); }); // TODO: bind to the 'status-update-liked' event, // and pass in a callback handler that adds an // activitiy to the UI using they // addActivity(type, data) function channel.bind('new-status-update', function (data) { addStatusUpdate(data); }); channel.bind('status-update-liked', function (data) { addLikeCount(data); // addStatusUpdate(data); }); </script> </body> </html>
在ActivityController包含三個事件:
訪問活動頁面事件:user-visit
發佈一個新的活動事件:new-status-update
給一個活動點贊事件:status-update-liked
user-visit:
新開A、B頁面,輸入路由http://laravelpusher.app:8888/activities
,當B頁面訪問後A頁面會出現剛剛頁面被訪問的用戶,B頁面訪問一次A頁面就增長一個訪問記錄,同理A頁面訪問B頁面也增長一個訪問記錄。做者在B頁面訪問的時候會收到Pusher發給B頁面的訪問記錄後,爲了避免讓Pusher數據發過來能夠添加socket_id,上文已有論述:
new-status-update:
同理,輸入路由http://laravelpusher.app:8888/activities
後在輸入框內填寫文本,如在B頁面填寫'Laravel is great!!!'後發現A頁面有新的活動通知,B頁面也一樣會收到Pusher發來的新的活動通知:
status-update-liked:
點贊事件須要修改activities.blade.php和ActivityController.php文件,上面代碼有註釋,去掉就行,總之就是一樣道理A頁面點贊後B頁面實時顯示活動:
Chat就是由用戶發起的Activity Stream,只不過UI界面不同而已。
Basic Chat
在項目根目錄輸入:
php artisan make:controller ChatController
在ChatController中寫兩個方法:
class ChatController extends Controller { var $pusher; var $user; var $chatChannel; const DEFAULT_CHAT_CHANNEL = 'chat'; public function __construct() { $this->pusher = App::make('pusher'); $this->user = Session::get('user'); $this->chatChannel = self::DEFAULT_CHAT_CHANNEL; } public function getIndex() { if(!$this->user) { return redirect('auth/github?redirect=/chat');//用戶沒有認證過則跳轉github頁面認證下 } return view('pusher.chat', ['chatChannel' => $this->chatChannel]); } //在chat視圖中處理AJAX請求,頻道是chat,事件是new-message,把頭像、暱稱、消息內容、消息時間一塊兒發送 public function postMessage(Request $request) { $message = [ 'text' => e($request->input('chat_text')), 'username' => $this->user->getNickname(), 'avatar' => $this->user->getAvatar(), 'timestamp' => (time()*1000) ]; $this->pusher->trigger($this->chatChannel, 'new-message', $message); } }
在resources/views/pusher文件夾下建立chat.blade.php文件:
<!DOCTYPE html> <html> <head> <title>Real-Time Laravel with Pusher</title> <meta name="csrf-token" content="{{ csrf_token() }}" /> <link rel="stylesheet" type="text/css" href="http://d3dhju7igb20wy.cloudfront.net/assets/0-4-0/all-the-things.css" /> <style> .chat-app { margin: 50px; padding-top: 10px; } .chat-app .message:first-child { margin-top: 15px; } #messages { height: 300px; overflow: auto; padding-top: 5px; } </style> {{--<script src="//code.jquery.com/jquery-1.11.3.min.js"></script>--}} <script src="//cdn.bootcss.com/jquery/1.11.3/jquery.min.js"></script> <script src="https://cdn.rawgit.com/samsonjs/strftime/master/strftime-min.js"></script> <script src="//js.pusher.com/3.0/pusher.min.js"></script> <script> // Ensure CSRF token is sent with AJAX requests $.ajaxSetup({ headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') } }); // Added Pusher logging Pusher.log = function(msg) { console.log(msg); }; </script> </head> <body> <div class="stripe no-padding-bottom numbered-stripe"> <div class="fixed wrapper"> <ol class="strong" start="2"> <li> <div class="hexagon"></div> <h2><b>Real-Time Chat</b> <small>Fundamental real-time communication.</small></h2> </li> </ol> </div> </div> <section class="blue-gradient-background"> <div class="container"> <div class="row light-grey-blue-background chat-app"> <div id="messages"> <div class="time-divide"> <span class="date">Today</span> </div> </div> <div class="action-bar"> <textarea class="input-message col-xs-10" placeholder="Your message"></textarea> <div class="option col-xs-1 white-background"> <span class="fa fa-smile-o light-grey"></span> </div> <div class="option col-xs-1 green-background send-message"> <span class="white light fa fa-paper-plane-o"></span> </div> </div> </div> </div> </section> <script id="chat_message_template" type="text/template"> <div class="message"> <div class="avatar"> <img src=""> </div> <div class="text-display"> <div class="message-data"> <span class="author"></span> <span class="timestamp"></span> <span class="seen"></span> </div> <p class="message-body"></p> </div> </div> </script> <script> function init() { // send button click handling $('.send-message').click(sendMessage); $('.input-message').keypress(checkSend); } // Send on enter/return key function checkSend(e) { if (e.keyCode === 13) { return sendMessage(); } } // Handle the send button being clicked function sendMessage() { var messageText = $('.input-message').val(); if(messageText.length < 3) { return false; } // Build POST data and make AJAX request var data = {chat_text: messageText}; $.post('/chat/message', data).success(sendMessageSuccess); // Ensure the normal browser event doesn't take place return false; } // Handle the success callback function sendMessageSuccess() { $('.input-message').val('') console.log('message sent successfully'); } // Build the UI for a new message and add to the DOM function addMessage(data) { // Create element from template and set values var el = createMessageEl(); el.find('.message-body').html(data.text); el.find('.author').text(data.username); el.find('.avatar img').attr('src', data.avatar) // Utility to build nicely formatted time el.find('.timestamp').text(strftime('%H:%M:%S %P', new Date(data.timestamp))); var messages = $('#messages'); messages.append(el) // Make sure the incoming message is shown messages.scrollTop(messages[0].scrollHeight); } // Creates an activity element from the template function createMessageEl() { var text = $('#chat_message_template').text(); var el = $(text); return el; } $(init); /***********************************************/ var pusher = new Pusher('{{env("PUSHER_KEY")}}'); var channel = pusher.subscribe('{{$chatChannel}}');//$chatChannel變量是從ChatController中傳過來的,用blade模板打印出來 channel.bind('new-message', addMessage); </script> </body> </html>
看下chat視圖代碼,sendMessage()函數是由點擊發送或回車觸發發送聊天信息,addMessage()函數更新聊天信息的UI。瞭解一點jQuery,也能看懂。好,如今本身與本身開始聊天,打開兩個頁面,做者的環境里路由爲http://laravelpusher.app:8888...這裏輸入你本身的路由就行):
總結:本部分主要以三個小示例來講明Laravel與Pusher相結合的實時WEB技術,包括:Notification、Activity Stream、Chat。照着教程作完,應該會有很大的收穫。有問題可留言。嘛,這兩天還想結合Model Event來新開篇文章,到時見。
歡迎關注Laravel-China。