
九保すこひです(フリーランスのITコンサルタント、エンジニア)
さてさて、私はプログラムに長年携わってきたので多少はコードは書くことができるようになってきたのですが、ことデザインとなるとほぼ訓練したことがないため、とても難しく感じてしまします。
しかし、そんな私には救世主がいます。
それが「envato market」つまり、海外の有料テンプレート販売サイトですね。
そして、この間もいろいろとテンプレートを「ウィンドウ・ショッピング」していたところ、ある機能を作ったことがないことに気がつきました。
それは・・・・・・
リアルタイム・オンライン通知機能
です。
例えば、フェイスブックなどに採用されているんですが、以下のように「オンライン or オフライン」がひと目でわかるコンテンツで、さらにページの再読み込みをしなくてもリアルタイムでその状態が切り替わるというものです。
そこで
今回はこの機能をLaravel + Vue
で実装してみます。
ぜひ、マッチングサービスなどの開発に役立ててください
(最後に実際に開発したソースコード一式をダウンロードできますよ)
「デザイン本注文しました!
…積ん読になりませんように」
開発環境: Laravel 8.x、Vue 3、TailwindCss 2
目次 [非表示]
前提として
ログイン機能がすでにインストールされていることが前提です。
もしまだの方は以下のどちらかを参考にしてインストールしておいてください。
どのようにして実装するか
実際問題で言うと、ユーザーがログインしてサイトに居続けていることを厳密に把握することは難しいです。
・・・というのも、以下のようなケースの場合はどうしても離脱した情報が途切れてしまうからです。
- ログインした
- いくつかページを見た
- 急な電話がかかってきたので、ブラウザ自体を閉じた
そのため、今回はページにアクセスするたびに「最終アクセス日時」をその都度保存し、もしそこから15分以上経過していたら離脱したものとして判別するようにします。
なお、今回はリアルタイムで表示を変化させたいので、以下の記事で使った Pusher(無料プランあり)を使います。
では、楽しんでやっていきましょう
Pusher が使えるようにする
Pusher
は2021/01/21
現在、機能が以下2つになっていますので今回はChannels
を使って実装します。
- Channels: リアルタイムコンテンツの作成
- Beams: プッシュ通知
なお、無料プランの条件は以下になります。
- 同時接続は、最大100ユーザー
- 200,000回/1日のメッセージまで
では、以下のようにChannels
へ移動し、「Create app」ボタンをクリックします。
すると、以下のようなフォームが表示されるので各項目を入力をしてください。
そして、「Create app」をクリックすると登録完了です。
登録が完了すると、ページ左側メニューがありますので、その中から「App Keys」をクリックします。
ページ移動すると、以下のようにアクセスに必要なキーが表示されていますので、これを次の項目で.env
へ登録します。
Laravelの設定を変更する
では、先ほど取得したキーを.env
へ登録します。
.env
# 省略 ...
PUSHER_APP_ID=******
PUSHER_APP_KEY=********************
PUSHER_APP_SECRET=********************
PUSHER_APP_CLUSTER=***
また、同じファイルの中にあるBROADCAST_DRIVER
もpusher
へ変更しておいてください。
BROADCAST_DRIVER=pusher
次に、BroadcastServiceProvider
を有効にします。(コメントアウトを外すだけです)
config/app.php
// 省略
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
App\Providers\BroadcastServiceProvider::class, //
ここのコメントを解除しました
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
App\Providers\FortifyServiceProvider::class,
App\Providers\JetstreamServiceProvider::class,
// 省略
これで設定は完了です。
必要なパッケージをインストールする
Laravel
でPusher
を利用するにはいくつかパッケージが必要になりますので、これらをインストールしていきます。
以下のコマンドを実行してください。
(PHP側)
composer require pusher/pusher-php-server "~4.0"
(JavaScript側)
npm install --save-dev laravel-echo pusher-js
インストールが完了したら、npm
のビルドで有効になるよう以下のファイルを変更してください。(というかコメントアウトを解除するだけです)
resources/js/bootstrap.js
// 省略
import Echo from 'laravel-echo';
window.Pusher = require('pusher-js');
window.Echo = new Echo({
broadcaster: 'pusher',
key: process.env.MIX_PUSHER_APP_KEY,
cluster: process.env.MIX_PUSHER_APP_CLUSTER,
forceTLS: true
});
では、以下のコマンドでビルド(ひとつのファイルにまとめる)をしましょう。
npm run dev
これで、必要なJavaScript
パッケージは全てpublic/js/app.js
の中に入っています。
イベントをつくる
続いてLaravel
からPusher
に変化を通知するためのイベントをつくります。
以下のコマンドを実行してください。
php artisan make:event UserAccessed
するとファイルが作成されるので中身を以下のように変更します。
app/Events/UserAccessed.php
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class UserAccessed implements ShouldBroadcast //
ここを追加しました
{
// 省略
public function broadcastOn()
{
$channel_name = 'online_users'; //
ここを追加しました
return new Channel($channel_name); //
ここを追加しました
}
}
最終アクセス日時を保存できるようにする
次に「ユーザーが最後にアクセスした日時」を保存できるようにします。
まず、初期状態のusers
テーブルにはない「last_accessed_at」を追加します。
database/migrations/****_**_**_******_create_users_table.php
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->foreignId('current_team_id')->nullable();
$table->text('profile_photo_path')->nullable();
$table->dateTime('last_accessed_at')->nullable(); //
ここを追加しました
$table->timestamps();
});
この状態で一度DBを初期化します。
以下のコマンドを実行してください。
php artisan migrate:fresh --seed
これで、新しいフィールドが追加されました。
なお、自動的にlast_accessed_at
がCarbonインスタンス
に変換してくれるようにしておきます。
また、コードをすっきりさせることができるため、is_online
という「最終アクセスが15分以内かどうか」が分かるデータもAccessor
でつくっておきましょう。
app/Models/User.php
protected $casts = [
'email_verified_at' => 'datetime',
'last_accessed_at' => 'datetime' //
ここを追加しました
];
// 省略
protected $appends = [
'profile_photo_url',
'is_online' //
ここを追加しました
];
// Accessor
public function getIsOnlineAttribute() { //
ここを追加しました
$last_accessed_at = $this->last_accessed_at;
return (
!is_null($last_accessed_at) &&
now()->diffInMinutes($last_accessed_at) <= 15 // 最終アクセスが15分以内の場合
);
}
これで、$user->last_accessed_at
は、日時データに変換されますし、$user->is_online
は、true
or false
でオンライン状態かどうかが分かるようになりました。
※ なお、「15分」の部分はconfig/app.php
などで共通化しておくほうが後で便利かと思います。
では続いて「ログインしていたら必ず実行される」イベント・リスナーを追加してlast_accessed_at
がその都度更新されるようにします。
app/Providers/EventServiceProvider.php
// 省略
protected $listen = [
Registered::class => [
SendEmailVerificationNotification::class,
],
'Illuminate\Auth\Events\Authenticated' => [ //
ここを追加しました
'App\Listeners\LogAuthenticated',
],
];
ただし、これだけではイベント・リスナー本体はまだ存在していないので、以下のコマンドを実行してファイルを作成します。
php artisan event:generate
【追記:2021.08.15】
訪問ユーザーさんからのご指摘をいただき、「generate:event」→「event:generate」へ修正しました。皆さん、いつもありがとうございます。m(_ _)m
すると、ファイルが作成されますので、中身を以下のようにします。
app/Listeners/LogAuthenticated.php
<?php
namespace App\Listeners;
use App\Events\UserAccessed; //
ここを追加しました
use Illuminate\Auth\Events\Authenticated;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class LogAuthenticated
{
// 省略
public function handle(Authenticated $event)
{
$user = $event->user;
if(!$user->is_online) { //
最終アクセスが15分より前の場合
UserAccessed::dispatch(); //
ここでイベントを実行しています
}
$user->last_accessed_at = now(); //
アクセス日時を更新
$user->save();
}
}
これで、ログイン状態でアクセスされると、毎回last_accessed_at
が更新されるようになり、さらに最終アクセスが15分より前の場合、Pusher
に新たなログインを通知することができるようになりました。
ルートをつくる
今回必要なルートは以下の2つです。
- オンライン通知を実行するページ
- ユーザー情報を取得するAjax用ページ
実際には以下のようになります。
routes/web.php
Route::get('users', function(){ return \App\Models\User::get(); });
Route::get('online_users', function(){ return view('online_users'); });
※ 今回はテストなので省略して書いていますが、本番環境ではコントローラーを使うことをおすすめします。
ビューをつくる
ここまで長かったですが、やっと本題のリアルタイム・オンライン通知を実装します。
resources/views/online_users.blade.php
<html>
<head>
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet">
</head>
<body>
<div id="app" class="p-3">
<div class="grid grid-cols-1 text-3xl p-2">
<h1 class="mb-4 text-green-500 font-bold">リアルタイム・オンライン通知</h1>
</div>
<div class="grid grid-cols-6">
<div>
<div class="px-2 py-1">
<small class="text-gray-500">ユーザー</small>
</div>
<div class="col-span-1 bg-blue-100 px-3 py-2 text-blue-700">
<div v-for="u in users">
<div class="grid grid-cols-2 mb-2">
<div v-text="u.name"></div>
<div v-if="u.is_online" class="text-green-700 text-xs text-right font-bold">オンライン</div>
<div v-else class="text-gray-400 text-xs text-right">オフライン</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="/js/app.js"></script>
<script src="https://unpkg.com/vue@3.0.2/dist/vue.global.prod.js"></script>
<script>
Vue.createApp({
data() {
return {
users: []
}
},
methods: {
getUsers() { //
ユーザー情報をAjaxで取得する
axios.get('/users')
.then(response => {
this.users = response.data;
});
}
},
mounted() {
this.getUsers();
Echo.channel('online_users')
.listen('UserAccessed', e => {
this.getUsers(); //
リアルタイム通知があれば自動更新
});
}
}).mount('#app');
</script>
</body>
</html>
なお、Vue.js
をCDN
で読み込んでいますが、これもnpm
のビルドで/js/app.js
にひとまとめにしておいたほうがいいでしょう。(今回は複雑になるので割愛させていただきましたm(_ _)m)
おまけ:ログアウトの通知もやってみる
説明が複雑になるので、ここまででは紹介しませんでしたが、逆にログアウトしたときもリアルタイム通知を実行し、オンライン → オフラインへ変更するには以下の手順を行ってください。(ほぼ同じ内容なので駆け足で行きます)
app/Providers/EventServiceProvider.php
// 省略
protected $listen = [
Registered::class => [
SendEmailVerificationNotification::class,
],
'Illuminate\Auth\Events\Authenticated' => [
'App\Listeners\LogAuthenticated',
],
//
ここを追加しました
'Illuminate\Auth\Events\Logout' => [
'App\Listeners\LogSuccessfulLogout',
],
];
コマンドを実行します。
php artisan event:generate
リスナーの中身を変更します。
app/Listeners/LogSuccessfulLogout.php
<?php
namespace App\Listeners;
use App\Events\UserAccessed;
use Illuminate\Auth\Events\Logout;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class LogSuccessfulLogout
{
// 省略
public function handle(Logout $event)
{
$user = $event->user;
$user->last_accessed_at = null;
$user->save();
UserAccessed::dispatch(); // Pusherへ通知
}
}
これでログアウトのリアルタイム通知も完了です
テストしてみる
では、実際にテストしてみましょう
まず、Google Chrome
で「太郎さん」でログインし、「http://******/online_users」へアクセスします。
先ほどログインした「太郎さん(つまり自分自身)」がオンラインになっています。
では、太郎さんはこのままの状態で置いておいて、次はブラウザを変えてFirefox
でログインしてみましょう。
ログインするユーザーは「次郎さん」です。
「LOGIN」ボタンをクリックすると・・・・・・
はい
太郎さんのGoogle Chrome
には一切触っていないのに、自動で次郎さんがオンライン状態として表示されました。
成功です
では、今度は次郎さん(Firefox
)で「http://******/online_users」へアクセスし、Google Chrome
の太郎さんを「ログアウト」させてみましょう。
はい
今度は、Firefox
には触っていないのに、自動的に太郎さんがオフライン状態になりました。
こちらも成功です
ダウンロードする
今回実際に開発したソースコード一式を以下からダウンロードすることができます。
【Laravel + Vue 】リアルタイムでオンライン通知する機能をつくる※ ただし、.env
やconfig
への設定マイグレーションなどはご自身で行っていただく必要があります。
おわりに
ということで、今回はリアルタイム・オンライン通知機能を作ってみました。
なお、以前作ったリアルタイム・チャットとは違って少しハマってしまい、「Pusherとの接続はできるのに、通知ができない」という状態になってしまいました。(小一時間同じままでした・・・)
いろいろやってみたので実際のところ違うかもしれませんが、結果として.env
のBROADCAST_DRIVER
を変更していないことが原因(のよう)でした。
BROADCAST_DRIVER=pusher
今後、私のようにハマってしまわないないように備忘録としてここに残しておきます
なお、今回はTailwindCss
を使って実装してみました。
というのも、最近少しずつTailwindCss
も勉強してるからなんですが、TailwindCss
は知れば知るほど便利さが分かってきました。
このあたりもいつか「TailwindCss
の好きな所・まとめ」というような記事を化書けたらと考えています。
ぜひご期待くださいね。
ではでは〜
「Twitter APIって申請が厳しいですね・・・
ある個人開発を諦めざるを
得なくなってしまいました(ぐったり)」