這一節我們來實現傳送實時訊息功能,前面我們已經鋪墊好了所有需要的工作,下面開始吧。
建立訊息記錄
之前對路由沒有做訪問限制,需要新增許可權,只有登入使用者才可以訪問話題訊息頁面
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:evnet 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>
不出意外的話,到這裡已經實現了實時通訊功能了。
但是我們會遇到一個錯誤
這裡報錯的原因是:
<div v-for="discussion in this.discussions">
<h4> {{ discussion.user.name }} </h4>
<p>{{ discussion.body }}</p>
</div>
因為沒有關聯的 user 資料,所以 discussion.user.name
無法獲取到 .name 屬性,導致了報錯。
我們再回去看 DiscussionController.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));
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
,許可權判斷也很簡單,只需要判斷當前話題的所有使用者中是否包含請求的使用者的即可。
這是我新年的第一篇文章,關於 laravel-websocket 通訊就到此為止了,感謝閱讀。後面會寫一些關於 Laravel 原始碼解讀的文章,歡迎關注
本作品採用《CC 協議》,轉載必須註明作者和本文連結