本文目录导读:

- 方案一:使用Laravel内置的Notifiable Trait(推荐)
- 方案二:自定义通知表(更灵活)
- 方案三:前端实现示例(Vue/React)
- 方案四:使用缓存优化
- 方案五:实时更新(使用Pusher/Broadcasting)
- 最佳实践建议
在Laravel中实现通知已读/未读标记功能,有几种不同的方案,我来详细介绍最常用和灵活的几种方法:
使用Laravel内置的Notifiable Trait(推荐)
迁移表结构
// 创建通知表迁移 php artisan notifications:table php artisan migrate
模型中使用Notifiable
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use Notifiable;
// 获取未读通知
public function unreadNotifications()
{
return $this->notifications()->whereNull('read_at');
}
// 获取已读通知
public function readNotifications()
{
return $this->notifications()->whereNotNull('read_at');
}
}
控制器操作
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class NotificationController extends Controller
{
/**
* 获取所有通知
*/
public function index()
{
$notifications = auth()->user()->notifications()
->paginate(20);
return response()->json([
'data' => $notifications,
'unread_count' => auth()->user()->unreadNotifications()->count()
]);
}
/**
* 获取未读通知
*/
public function unread()
{
$notifications = auth()->user()->unreadNotifications()
->paginate(20);
return response()->json($notifications);
}
/**
* 标记单个通知为已读
*/
public function markAsRead($id)
{
$notification = auth()->user()->notifications()
->where('id', $id)
->first();
if ($notification) {
$notification->markAsRead();
return response()->json(['message' => '标记成功']);
}
return response()->json(['message' => '通知不存在'], 404);
}
/**
* 批量标记为已读
*/
public function markAllAsRead()
{
auth()->user()->unreadNotifications()
->update(['read_at' => now()]);
return response()->json(['message' => '全部标记为已读']);
}
/**
* 标记为未读
*/
public function markAsUnread($id)
{
$notification = auth()->user()->notifications()
->where('id', $id)
->first();
if ($notification) {
$notification->markAsUnread();
return response()->json(['message' => '标记为未读成功']);
}
return response()->json(['message' => '通知不存在'], 404);
}
/**
* 删除通知
*/
public function delete($id)
{
auth()->user()->notifications()
->where('id', $id)
->delete();
return response()->json(['message' => '删除成功']);
}
}
路由配置
// routes/api.php 或 routes/web.php
Route::middleware('auth')->group(function () {
Route::get('/notifications', [NotificationController::class, 'index']);
Route::get('/notifications/unread', [NotificationController::class, 'unread']);
Route::put('/notifications/{id}/read', [NotificationController::class, 'markAsRead']);
Route::put('/notifications/read-all', [NotificationController::class, 'markAllAsRead']);
Route::put('/notifications/{id}/unread', [NotificationController::class, 'markAsUnread']);
Route::delete('/notifications/{id}', [NotificationController::class, 'delete']);
});
创建通知类
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
class OrderStatusNotification extends Notification implements ShouldQueue
{
use Queueable;
protected $order;
public function __construct($order)
{
$this->order = $order;
}
public function via($notifiable)
{
return ['database', 'mail']; // 支持数据库和邮件
}
public function toDatabase($notifiable)
{
return [
'order_id' => $this->order->id,
'status' => $this->order->status,
'message' => '您的订单状态已更新为:' . $this->order->status,
'url' => route('orders.show', $this->order->id)
];
}
public function toMail($notifiable)
{
return (new MailMessage)
->subject('订单状态更新')
->line('您的订单状态已更新为:' . $this->order->status)
->action('查看订单', url('/orders/' . $this->order->id))
->line('感谢您的支持!');
}
}
发送通知
// 在业务代码中发送通知 use App\Notifications\OrderStatusNotification; // 发送给单个用户 $user->notify(new OrderStatusNotification($order)); // 发送给多个用户 Notification::send($users, new OrderStatusNotification($order)); // 延迟发送 $user->notify((new OrderStatusNotification($order))->delay(now()->addMinutes(5)));
自定义通知表(更灵活)
如果需要更多自定义字段,可以创建自定义的通知表:
// 创建自定义通知表迁移
php artisan make:migration create_custom_notifications_table
// 迁移文件
Schema::create('custom_notifications', function (Blueprint $table) {
$table->id();
$table->morphs('notifiable');
$table->string('type');
$table->morphs('related_model'); // 关联的业务模型
$table->string('title');
$table->text('message');
$table->json('data')->nullable();
$table->timestamp('read_at')->nullable();
$table->timestamp('sent_at')->nullable();
$table->timestamps();
$table->index(['notifiable_id', 'read_at']); // 优化查询
});
前端实现示例(Vue/React)
Vue前端实现
<template>
<div class="notification-dropdown">
<button @click="toggleDropdown" class="bell-icon">
<span class="badge" v-if="unreadCount > 0">{{ unreadCount }}</span>
🔔
</button>
<div v-if="isOpen" class="dropdown-menu">
<div class="header">
<h3>通知</h3>
<button @click="markAllAsRead" v-if="unreadCount > 0">全部已读</button>
</div>
<div class="notifications-list">
<div v-for="notification in notifications" :key="notification.id"
:class="['notification-item', { unread: !notification.read_at }]"
@click="readNotification(notification)">
<div class="notification-content">
<div class="title">{{ notification.data.title }}</div>
<div class="message">{{ notification.data.message }}</div>
<div class="time">{{ formatTime(notification.created_at) }}</div>
</div>
<button v-if="notification.read_at" @click.stop="markUnread(notification.id)">标记未读</button>
</div>
</div>
<div v-if="notifications.length === 0" class="empty-state">
暂无通知
</div>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
isOpen: false,
notifications: [],
unreadCount: 0,
polling: null
};
},
mounted() {
this.fetchNotifications();
this.startPolling();
},
beforeDestroy() {
if (this.polling) {
clearInterval(this.polling);
}
},
methods: {
fetchNotifications() {
axios.get('/api/notifications')
.then(response => {
this.notifications = response.data.data;
this.unreadCount = response.data.unread_count;
})
.catch(error => {
console.error('获取通知失败:', error);
});
},
readNotification(notification) {
if (notification.read_at) return;
axios.put(`/api/notifications/${notification.id}/read`)
.then(() => {
notification.read_at = new Date();
this.unreadCount--;
// 如果需要跳转
if (notification.data.url) {
window.location.href = notification.data.url;
}
})
.catch(error => {
console.error('标记已读失败:', error);
});
},
markUnread(id) {
axios.put(`/api/notifications/${id}/unread`)
.then(() => {
const notification = this.notifications.find(n => n.id === id);
notification.read_at = null;
this.unreadCount++;
})
.catch(error => {
console.error('标记未读失败:', error);
});
},
markAllAsRead() {
axios.put('/api/notifications/read-all')
.then(() => {
this.notifications.forEach(n => n.read_at = new Date());
this.unreadCount = 0;
})
.catch(error => {
console.error('全部标记已读失败:', error);
});
},
toggleDropdown() {
this.isOpen = !this.isOpen;
if (this.isOpen) {
this.fetchNotifications();
}
},
startPolling() {
this.polling = setInterval(() => {
this.fetchNotifications();
}, 30000); // 每30秒轮询一次
},
formatTime(date) {
const d = new Date(date);
const now = new Date();
const diff = now - d;
if (diff < 60000) return '刚刚';
if (diff < 3600000) return `${Math.floor(diff / 60000)}分钟前`;
if (diff < 86400000) return `${Math.floor(diff / 3600000)}小时前`;
return d.toLocaleDateString();
}
}
};
</script>
<style scoped>
/* 样式略 */
</style>
使用缓存优化
// 使用缓存缓存未读数量
class NotificationService
{
public function getUserUnreadCount($userId)
{
return \Cache::remember(
"user_unread_notifications_{$userId}",
60, // 缓存60秒
function () use ($userId) {
return \App\Models\User::find($userId)
->unreadNotifications()
->count();
}
);
}
public function clearUnreadCache($userId)
{
\Cache::forget("user_unread_notifications_{$userId}");
}
}
实时更新(使用Pusher/Broadcasting)
// 在通知模型中添加broadcast channel
class OrderStatusNotification extends Notification
{
use Queueable;
public function via($notifiable)
{
return ['database', 'broadcast'];
}
public function toBroadcast($notifiable)
{
return new BroadcastMessage([
'title' => '订单状态更新',
'message' => '您的订单状态已更新',
'url' => url('/orders/' . $this->order->id)
]);
}
public function broadcastType()
{
return 'order-status';
}
}
// 前端实时监听
// 使用Laravel Echo
window.Echo.private('App.User.' + userId)
.notification((notification) => {
this.notifications.unshift(notification);
this.unreadCount++;
});
最佳实践建议
- 数据库索引:确保
read_at字段有索引 - 批量操作:批量标记已读时使用
update()而不是循环处理 - 缓存优化:频繁查询未读数量时使用缓存
- 分页加载:通知列表使用分页,避免一次性加载太多
- 实时性:根据需求判断是否使用websocket实时推送
- 并发处理:注意处理并发标记已读的情况
- 安全性:确保用户只能操作自己的通知
这个方案涵盖了大多数场景,可以根据具体需求选择使用,如果需要更详细的某个部分实现,请告诉我。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播