您现在的位置是:网站首页 > 心得笔记
laravel中消息通知系统
Laravel 的消息通知系统
Laravel 自带了一套极具扩展性的消息通知系统,尤其还支持多种通知频道,我们将利用此套系统来向用户发送消息提醒。
什么是通知频道?
通知频道是通知传播的途径,Laravel 自带的有数据库、邮件、短信(通过 Nexmo)以及 Slack。本章节中我们将使用数据库通知频道,后面也会使用到邮件通知频道。
1. 准备数据库
数据通知频道会在一张数据表里存储所有通知信息。包含了比如通知类型、JSON 格式数据等描述通知的信息。我们后面会通过查询这张表的内容在应用界面上展示通知。但是在这之前,我们需要先创建这张数据表,Laravel 自带了生成迁移表的命令,执行以下命令即可:
$ php artisan notifications:table
会生成 database/migrations/{$timestamp}_create_notifications_table.php
迁移文件,执行 migrate
命令将表结构写入数据库中:
$ php artisan migrate
我们还需要在 users
表里新增 notification_count
字段,用来跟踪用户有多少未读通知,如果未读通知大于零的话,就在站点的全局顶部导航栏显示红色的提醒。
$ php artisan make:migration add_notification_count_to_users_table --table=users
打开生成的文件,修改为以下:
database/migrations/{$timestamp}_add_notification_count_to_users_table.php
<?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class AddNotificationCountToUsersTable extends Migration{ public function up() { Schema::table('users', function (Blueprint $table) { $table->integer('notification_count')->unsigned()->default(0); }); } public function down() { Schema::table('users', function (Blueprint $table) { $table->dropColumn('notification_count'); }); }}
再次应用数据库修改:
$ php artisan migrate
2. 生成通知类
Laravel 中一条通知就是一个类(通常存在 app/Notifications 文件夹里)。看不到的话不要担心,运行一下以下命令即可创建:
$ php artisan make:notification TopicReplied
修改文件为以下:
app/Notifications/TopicReplied.php
<?php namespace App\Notifications; use Illuminate\Bus\Queueable; use Illuminate\Notifications\Notification; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; use App\Models\Reply; class TopicReplied extends Notification{ use Queueable; public $reply; public function __construct(Reply $reply) { // 注入回复实体,方便 toDatabase 方法中的使用 $this->reply = $reply; } public function via($notifiable) { // 开启通知的频道 return ['database']; } public function toDatabase($notifiable) { $topic = $this->reply->topic; $link = $topic->link(['#reply' . $this->reply->id]); // 存入数据库里的数据 return [ 'reply_id' => $this->reply->id, 'reply_content' => $this->reply->content, 'user_id' => $this->reply->user->id, 'user_name' => $this->reply->user->name, 'user_avatar' => $this->reply->user->avatar, 'topic_link' => $link, 'topic_id' => $topic->id, 'topic_title' => $topic->title, ]; }}
每个通知类都有个 via()
方法,它决定了通知在哪个频道上发送。我们写上 database
数据库来作为通知频道。
因为使用数据库通知频道,我们需要定义 toDatabase()
。这个方法接收 $notifiable
实例参数并返回一个普通的 PHP 数组。这个返回的数组将被转成 JSON 格式并存储到通知数据表的 data
字段中。
3. 触发通知
我们希望当用户回复主题后,通知到主题作者。故触发通知的时机是:『回复发布成功后』,在模型监控器里,我们可以在 created
方法里实现此部分代码,修改 created()
方法为以下:
app/Observers/ReplyObserver.php
<?php ... use App\Notifications\TopicReplied; class ReplyObserver{ public function created(Reply $reply) { $reply->topic->reply_count = $reply->topic->replies->count(); $reply->topic->save(); // 通知话题作者有新的评论 $reply->topic->user->notify(new TopicReplied($reply)); } . . .}
请注意顶部引入 TopicReplied
。默认的 User
模型中使用了 trait —— Notifiable,它包含着一个可以用来发通知的方法 notify()
,此方法接收一个通知实例做参数。虽然 notify()
已经很方便,但是我们还需要对其进行定制,我们希望每一次在调用 $user->notify()
时,自动将 users
表里的 notification_count
+1 ,这样我们就能跟踪用户未读通知了。
打开 User.php
文件,将 use Notifiable, MustVerifyEmailTrait;
修改为以下:
app/Models/User.php
<?php ... use Auth; class User extends Authenticatable implements MustVerifyEmailContract{ use MustVerifyEmailTrait; use Notifiable { notify as protected laravelNotify; } public function notify($instance) { // 如果要通知的人是当前用户,就不必通知了! if ($this->id == Auth::id()) { return; } // 只有数据库类型通知才需提醒,直接发送 Email 或者其他的都 Pass if (method_exists($instance, 'toDatabase')) { $this->increment('notification_count'); } $this->laravelNotify($instance); } . . .}
请注意顶部 Auth 的引入。
我们对 notify()
方法做了一个巧妙的重写,现在每当你调用 $user->notify()
时, users
表里的 notification_count
将自动 +1。
测试一番后,刷新数据库即可看到 notifications
表里多了一条数据,并且 notifiable_id
为 1 ,也就是 1 号用户 alice有一条新通知:
制作通知列表页面
1. 新建路由器
首先我们需要新建路由入口:
routes/web.php
...Route::resource('notifications', 'NotificationsController', ['only' => ['index']]);
2. 顶部导航栏入口
我们希望用户在访问网站时,能在很显眼的地方提醒他你有未读信息,接下来我们会利用上 notification_count
字段,新增下面的 消息通知标记
区块:
resources/views/layouts/_header.blade.php
... <!-- Authentication Links --> @guest <li class="nav-item"><a class="nav-link" href="{{ route('login') }}">登录</a></li> <li class="nav-item"><a class="nav-link" href="{{ route('register') }}">注册</a></li> @else <li class="nav-item"> <a class="nav-link mt-1 mr-3 font-weight-bold" href="{{ route('topics.create') }}"> <i class="fa fa-plus"></i> </a> </li> <li class="nav-item notification-badge"> <a class="nav-link mr-3 badge badge-pill badge-{{ Auth::user()->notification_count > 0 ? 'hint' : 'secondary' }} text-white" href="{{ route('notifications.index') }}"> {{ Auth::user()->notification_count }} </a> </li> <li class="nav-item dropdown">...
消息通知样式:
/* 消息通知 */.notification-badge { .badge { font-size: 12px; margin-top: 14px; } .badge-secondary { background-color: #EBE8E8; } .badge-hint { background-color: #d15b47 !important; }}
3. 控制器
如果你点击红色标示,跳转到NotificationsController的index()列表页
使用命令行生成控制器:
$ php artisan make:controller NotificationsController
修改控制器的代码如下:
app/Http/Controllers/NotificationsController.php
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Auth; class NotificationsController extends Controller{ public function __construct() { $this->middleware('auth'); } public function index() { // 获取登录用户的所有通知 $notifications = Auth::user()->notifications()->paginate(20); return view('notifications.index', compact('notifications')); }}
控制器的构造方法 __construct()
里调用 Auth 中间件,要求必须登录以后才能访问控制器里的所有方法。
新建nitifications.index模板:
resources/views/notifications/index.blade.php
@extends('layouts.app')@section('title', '我的通知')@section('content') <div class="container"> <div class="col-md-10 offset-md-1"> <div class="card "> <div class="card-body"> <h3 class="text-xs-center"> <i class="far fa-bell" aria-hidden="true"></i> 我的通知 </h3> <hr> @if ($notifications->count()) <div class="list-unstyled notification-list"> @foreach ($notifications as $notification) @include('notifications.types._' . snake_case(class_basename($notification->type))) @endforeach {!! $notifications->render() !!} </div> @else <div class="empty-block">没有消息通知!</div> @endif </div> </div> </div> </div>@stop
通知数据库表的 Type 字段保存的是通知类全称,如 :App\Notifications\TopicReplied 。 snake_case(class_basename($notification->type))
渲染以后会是 —— topic_replied
。class_basename()
方法会取到 TopicReplied
,Laravel 的辅助方法 snake_case()
会字符串格式化为下划线命名。
创建_topic_replied.blade.php文件:
resources/views/notifications/types/_topic_replied.blade.php
<li class="media @if ( ! $loop->last) border-bottom @endif"> <div class="media-left"> <a href="{{ route('users.show', $notification->data['user_id']) }}"> <img class="media-object img-thumbnail mr-3" alt="{{ $notification->data['user_name'] }}" src="{{ $notification->data['user_avatar'] }}" style="width:48px;height:48px;" /> </a> </div> <div class="media-body"> <div class="media-heading mt-0 mb-1 text-secondary"> <a href="{{ route('users.show', $notification->data['user_id']) }}">{{ $notification->data['user_name'] }}</a> 评论了 <a href="{{ $notification->data['topic_link'] }}">{{ $notification->data['topic_title'] }}</a> {{-- 回复删除按钮 --}} <span class="meta float-right" title="{{ $notification->created_at }}"> <i class="far fa-clock"></i> {{ $notification->created_at->diffForHumans() }} </span> </div> <div class="reply-content"> {!! $notification->data['reply_content'] !!} </div> </div></li>
我们可以通过 $notification->data
拿到在通知类 toDatabase()
里构建的数组。
刷新页面即可看到我们的消息通知列表:
5. 清除未读消息标示
下面我们来开发去除顶部未读消息标示的功能 —— 当用户访问通知列表时,将所有通知状态设定为已读,并清空未读消息数。
接下来在 User 模型中新增 markAsRead()
方法:
app/Models/User.php
<?php ... class User extends Authenticatable implements MustVerifyEmailContract{ . . . public function markAsRead() { $this->notification_count = 0; $this->save(); $this->unreadNotifications->markAsRead(); }}
修改控制器的 index()
方法,新增清空未读提醒的状态:
app/Http/Controllers/NotificationsController.php
<?php ... class NotificationsController extends Controller{ . . . public function index() { // 获取登录用户的所有通知 $notifications = Auth::user()->notifications()->paginate(20); // 标记为已读,未读数量清零 Auth::user()->markAsRead(); return view('notifications.index', compact('notifications')); }}
现在进入消息通知页面,未读消息标示将被清除: