从 0 开始打造聊天室,搞定 Laravel 实时通信 —— 发送消息

从 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>

不出意外的话,到这里已经实现了实时通信功能了。

但是我们会遇到一个错误

从 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 协议》,转载必须注明作者和本文链接
悲观者永远正确,乐观者永远前行。
本帖由系统于 1年前 自动加精
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 13

创建event的artisan语句 event拼写返啦

1年前 评论
MArtian (楼主) 1年前
sharejia (作者) 1年前
MArtian (楼主) 1年前

打开 TopicController.php 编写store 方法 这里是不是 DiscussionController :joy:

1年前 评论
lalall (作者) 1年前
MArtian (楼主) 1年前
MArtian (楼主) 1年前

@sharejia 你们看文章都这么仔细地嘛?

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

少一个 namespace !


Route::group(['middleware' => ['auth'],'namespace' => 'App\Http\Controllers'], function() {
    Route::get('/topics', 'TopicController@index');
    Route::get('/topics/{topic}', 'TopicController@show');
    Route::post('/topics/{topic_id}/discussions', 'DiscussionController@store');
});
2年前 评论

请问TopicController.php的store方法有什么用呢?接口调用的不是DiscussionController里的吗?这两个store有什么区别吗?谢谢楼主~

1年前 评论
MArtian (楼主) 1年前

pusher.min.js:8 WebSocket connection to 'ws:/xxx:6001/app/321321?protocol=7&client=js&version=4.3.1&flash=false' failed: 会这个报错,是因为我本地的127.0.0.1不能访问还是因为laradock需要开辟新的端口呀

1年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!