从 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
}
}
再次打开 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;
}
添加 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 协议》,转载必须注明作者和本文链接
推荐文章: