從 0 開始打造聊天室,搞定 Laravel 實時通訊 —— 傳送訊息

MArtian發表於2022-02-01

從 0 開始打造聊天室,搞定 Laravel 實時通訊 —— 傳送實時訊息

這一節我們來實現傳送實時訊息功能,前面我們已經鋪墊好了所有需要的工作,下面開始吧。

建立訊息記錄

之前對路由沒有做訪問限制,需要新增許可權,只有登入使用者才可以訪問話題訊息頁面

Route::group(['middleware' => ['auth']], function() {
    Route::get('/topics', 'TopicController@index');
    Route::get('/topics/{topic}', 'TopicController@show');
    Route::post('/topics/{topic_id}/discussions', 'DiscussionController@store');
});

開啟 TopicController.php 編寫 store 方法

public function store(Request $request, $topic_id)
{
    return Discussion::query()->create([
        'user_id' => $request->user()->id,
        'topic_id' => $topic_id,
        'body' => $request->body,
    ])->load('user'); // 返回這條訊息和它的所屬使用者
 }

開啟 Topic.vue 檔案,傳遞使用者的訊息

...

mounted() {
    this.discussions = this.topic.discussions;
},
methods : {
    create(){
    axios.post(`/topics/${this.topic.id}/discussions`, {
        body: this.talking, // 使用者傳送的內容
    }).then((response)=>{
        this.discussions.push(response.data)
    });
        this.talking = '';
    }
}
</script>

實時通訊

開啟 routes/channels.php 路由,來定義一個話題 websocket 監聽許可權

Broadcast::channel('topics.channel.{topic}', function ($user, \App\Models\Topic $topic) {
    return true // 為了方便測試,先返回 true
});

建立 event

php artisan make:event DiscussionCreated
class DiscussionCreated implements ShouldBroadcast
{
  use Dispatchable, InteractsWithSockets, SerializesModels;

 public $discussion;

 public function __construct($discussion)
 {  
     $this->discussion = $discussion;  // 初始化時的訊息就是要廣播的訊息
 } 

 public function broadcastOn()
 {  
     return new PrivateChannel('topics.channel.'. $this->discussion->topic->id); // 要廣播的頻道地址,就是我們剛剛定義的 private channel
  }
}

再次開啟 TopicController.php 編寫 store 方法

public function store(Request $request, $topic_id)
{
    $discussion = Discussion::query()->create([
        'user_id' => $request->user()->id,
        'topic_id' => $topic_id,
        'body' => $request->body,
    ])->load('user');

    broadcast(new DiscussionCreated($discussion))->toOthers(); // 廣播給除了自己之外的人

    return $discussion;
}

新增 websocket 監聽,開啟 Topic.vue,重構 <script> 部分程式碼

<script>
export default {
props: ['topic'],
data(){
    return {
        discussions : this.topic.discussions,
        talking : '',
    }
},
mounted() { window.Echo.private(`topics.channel.${this.topic.id}`).listen('DiscussionCreated', (discussion) => { 
        this.discussions.push(discussion);
    });
    /**頁面初始化後,監聽剛剛 `channel.php` 路由中定義的通訊地址,監聽事件就使用 event class 的全名。
    這裡的 discussion 就是前面 DiscussionCreated 事件返回的資料,我們獲取到這個資料後,把它推入到上面迴圈的 discussions 陣列裡面 
    **/ 
},
methods : {
    create(){
        axios.post(`/topics/${this.topic.id}/discussions`, {
            body: this.talking,
        }).then((response)=>{
            this.discussions.push(response.data)});
            this.talking = '';
        }
    }
}
</script>

不出意外的話,到這裡已經實現了實時通訊功能了。

但是我們會遇到一個錯誤

從 0 開始打造聊天室,搞定 Laravel 實時通訊 —— 傳送實時訊息

這裡報錯的原因是:

<div v-for="discussion in this.discussions">
    <h4> {{ discussion.user.name }} </h4>
    <p>{{ discussion.body }}</p>
</div>

因為沒有關聯的 user 資料,所以 discussion.user.name 無法獲取到 .name 屬性,導致了報錯。

我們再回去看 DiscussionController.phpstore 方法

public function store(Request $request, $topic_id)
{
    $discussion = Discussion::query()->create([
        'user_id' => $request->user()->id,
        'topic_id' => $topic_id,
        'body' => $request->body,
    ])->load('user'); // 看這裡

    broadcast(new DiscussionCreated($discussion));

    return $discussion;
}

上面我們明明已經關聯了 user 關係,但是在 DiscussionCreated 事件中,並不會把關聯關係也傳遞過來,如果要傳遞關聯關係,需要在事件中自定義要關聯的資料。

開啟 events/DiscussionCreated,新增方法

public function broadcastWith()
{
    return [
        'body' => $this->discussion->body,
        'user' => $this->discussion->user,
    ];
}

到此實時通訊就結束了,但是我們還需要處理一個頻道許可權的問題,還記得之前定義的 private channel 嗎?

開啟 routes/channel.php

Broadcast::channel('topics.channel.{topic}', function ($user, \App\Models\Topic $topic) {
    return $topic->users->contains($user); 
});

function 閉包中的兩個引數分別是當前請求的使用者 $user ,和請求的話題 $topic,許可權判斷也很簡單,只需要判斷當前話題的所有使用者中是否包含請求的使用者的即可。

從 0 開始打造聊天室,搞定 Laravel 實時通訊 —— 傳送實時訊息

這是我新年的第一篇文章,關於 laravel-websocket 通訊就到此為止了,感謝閱讀。後面會寫一些關於 Laravel 原始碼解讀的文章,歡迎關注 :two_hearts:

本作品採用《CC 協議》,轉載必須註明作者和本文連結
我從未見過一個早起、勤奮、謹慎,誠實的人抱怨命運。

相關文章