Laravel聊天应用:如何用Ajax在有新消息时刷新页面/聊天区域
解决Laravel聊天应用实时消息刷新问题
你的问题很典型——普通Ajax只能实现发送方主动刷新,接收方没法实时感知新消息。我给你两种解决方案,从易到难,你可以根据项目需求选择:
方案一:Ajax轮询(快速上手,无需额外服务)
轮询的思路很简单:让接收方的页面定期向服务器发起请求,检查有没有新消息,有就更新页面。结合你的已读状态字段,还能顺便处理消息标记已读的逻辑。
1. 后端准备接口
首先在routes/api.php或者web.php里加两个路由:
// 检查指定会话的未读消息 Route::get('/messages/check-new/{conversationId}', [MessageController::class, 'checkNewMessages']); // 标记会话内的未读消息为已读 Route::post('/messages/mark-as-read/{conversationId}', [MessageController::class, 'markAsRead']);
然后在MessageController里实现对应的方法:
// 检查新消息 public function checkNewMessages($conversationId) { // 筛选当前用户作为接收方的未读消息 $newMessages = Message::where('conversation_id', $conversationId) ->where('recipient_id', auth()->id()) ->where('is_read', 0) ->get(); return response()->json([ 'has_new' => $newMessages->isNotEmpty(), 'messages' => $newMessages, 'count' => $newMessages->count() ]); } // 标记消息已读 public function markAsRead($conversationId) { Message::where('conversation_id', $conversationId) ->where('recipient_id', auth()->id()) ->where('is_read', 0) ->update(['is_read' => 1]); return response()->json(['success' => true]); }
2. 前端实现轮询逻辑
在你的聊天页面JS里加入这段代码,它会每3秒检查一次新消息:
// 替换成当前会话的ID(可以从Blade模板传递) const conversationId = {{ $conversation->id }}; let pollInterval; // 页面加载后启动轮询 $(document).ready(function() { startPolling(); }); function startPolling() { pollInterval = setInterval(() => { $.ajax({ url: `/messages/check-new/${conversationId}`, method: 'GET', success: function(res) { if (res.has_new) { // 有新消息时,要么调用你已有的刷新容器函数,要么直接追加新消息 // 推荐追加,避免全量刷新的卡顿: res.messages.forEach(msg => { const msgHtml = `<div class="message received">${msg.content}</div>`; $('#message-container').append(msgHtml); }); // 自动滚动到消息底部 $('#message-container').scrollTop($('#message-container')[0].scrollHeight); // 标记这些消息为已读 markMessagesAsRead(conversationId); } }, error: err => console.error('检查新消息失败:', err) }); }, 3000); // 3秒一次,可根据需求调整 } function markMessagesAsRead(conversationId) { $.ajax({ url: `/messages/mark-as-read/${conversationId}`, method: 'POST', data: { _token: '{{ csrf_token() }}' }, success: () => console.log('消息已标记为已读') }); } // 页面关闭时停止轮询,减少服务器压力 $(window).on('beforeunload', () => clearInterval(pollInterval));
方案二:WebSocket实时推送(体验更优,推荐)
轮询毕竟有延迟,而且频繁请求会浪费服务器资源。用WebSocket可以实现服务器主动给客户端推消息,真正的实时聊天体验。Laravel官方的Laravel Echo配合Pusher或者自建WebSocket服务就能搞定。
1. 配置基础依赖
先安装必要的包:
# 后端Pusher SDK composer require pusher/pusher-php-server # 前端Echo和Pusher客户端 npm install laravel-echo pusher-js
然后在.env里配置Pusher信息(如果用官方Pusher服务,或者用Laravel WebSockets自建):
BROADCAST_DRIVER=pusher PUSHER_APP_ID=你的APP_ID PUSHER_APP_KEY=你的APP_KEY PUSHER_APP_SECRET=你的APP_SECRET PUSHER_APP_CLUSTER=你的集群(比如ap1)
2. 创建消息广播事件
生成一个事件类,用来广播新消息:
php artisan make:event NewMessage
编辑app/Events/NewMessage.php:
namespace App\Events; use App\Models\Message; use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; class NewMessage implements ShouldBroadcast { use Dispatchable, SerializesModels; public $message; public function __construct(Message $message) { $this->message = $message; } // 只让会话的参与者订阅这个私有频道 public function broadcastOn() { return new PrivateChannel('conversation.'.$this->message->conversation_id); } // 自定义广播事件名称,方便前端监听 public function broadcastAs() { return 'new-message'; } }
3. 配置频道授权
在routes/channels.php里添加授权逻辑,确保只有会话参与者能订阅频道:
use App\Models\Conversation; Broadcast::channel('conversation.{conversationId}', function ($user, $conversationId) { $conversation = Conversation::findOrFail($conversationId); // 检查当前用户是会话的发起方还是接收方 return $conversation->user_id === $user->id || $conversation->recipient_id === $user->id; });
4. 发送消息时触发广播
修改你发送消息的控制器方法,保存消息后触发广播:
public function sendMessage(Request $request) { $message = Message::create([ 'conversation_id' => $request->conversation_id, 'sender_id' => auth()->id(), 'recipient_id' => $request->recipient_id, 'content' => $request->content, 'is_read' => 0 ]); // 只给其他用户广播,发送方自己已经通过Ajax更新了消息 broadcast(new NewMessage($message))->toOthers(); return response()->json(['success' => true, 'message' => $message]); }
5. 前端监听广播事件
先在resources/js/bootstrap.js里配置Laravel Echo:
import Echo from 'laravel-echo'; import Pusher from 'pusher-js'; window.Pusher = Pusher; window.Echo = new Echo({ broadcaster: 'pusher', key: import.meta.env.VITE_PUSHER_APP_KEY, cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER, forceTLS: true });
然后在聊天页面的JS里监听新消息:
const conversationId = {{ $conversation->id }}; // 订阅私有频道,监听新消息事件 window.Echo.private(`conversation.${conversationId}`) .listen('.new-message', (e) => { // 收到新消息,直接追加到容器 const msgHtml = `<div class="message received">${e.message.content}</div>`; $('#message-container').append(msgHtml); // 自动滚动到底部 $('#message-container').scrollTop($('#message-container')[0].scrollHeight); // 标记消息为已读 markMessagesAsRead(conversationId); }); // 标记已读的函数和轮询方案里的一样 function markMessagesAsRead(conversationId) { $.ajax({ url: `/messages/mark-as-read/${conversationId}`, method: 'POST', data: { _token: '{{ csrf_token() }}' }, success: () => console.log('消息已标记为已读') }); }
一些额外建议
- 轮询方案适合小型项目或者快速迭代,WebSocket适合追求体验的中大型项目;
- 不管用哪种方案,都可以在用户打开聊天页面时,自动标记该会话的所有未读消息为已读;
- 发送消息时,发送方可以直接把消息追加到容器,不用全量刷新,提升流畅度;
- 如果不想用Pusher服务,可以用
laravel/websockets包搭建自己的WebSocket服务器,成本更低。
内容的提问来源于stack exchange,提问作者Andreas Hunter
相关产品推荐
相关产品推荐

