【问题标题】:Laravel, how to avoid n + 1 issue in private chat api structure?Laravel,如何避免私聊api结构中的n + 1问题?
【发布时间】:2018-02-15 02:43:56
【问题描述】:

我正在尝试创建如下所示的 api JSON 响应结构:

[
  {
  "id": 1,
  "name": "jon abraham",
  "email": "jonabe2442@gmail.com",
  "image": "/images/avatar-default.jpg",
  "messages": [
    {message: 'hello!', user_id: 1}, 
    {message: 'yo!', user_id: 3}
   ]
  },
  {
  "id": 2,
  "name": "sherry hopkins",
  "email": "shop2663@gmail.com",
  "image": "/images/avatar-default.jpg",
  "messages": [
    {message: 'hey!', user_id: 2}, 
    {message: 'hi!', user_id: 3}
   ]
  }
]

JSON 响应表示属于当前登录用户所属同一公司的所有用户的列表。它还将显示该用户和当前登录用户之间的消息数组(如果没有消息,则为空数组)。

由于无法为私人聊天建立适当的数据库结构/雄辩的关系,我已经设法创建了我想要的大部分 api 结构,但消息部分除外。

这是我的私人聊天数据库结构。

对话

|编号 |参与者_1_id |参与者_2_id

Conversation_messages

|编号 |发件人 ID |接收者 ID |文字

这是我返回 api 的控制器代码:

public function userWithMessages
    {   

    	$user = Auth::user();
        $company_ids = $user->companies()->pluck('companies.id')->toArray();

        $users = User::join('company_user', 'users.id', 'company_user.user_id')
                ->join('companies', 'company_user.user_id', 'companies.id')
                ->whereIn('companies.id', $company_ids)
                ->select('users.name', 'users.email', 'users.image')
                ->get();

    	return response()->json($users, 200);

    }

我正在尝试编写查询,以免遇到 n + 1 问题,但我似乎无法找到与用户对话的关系。原因是,在对话表上,我不知道 user_id 是 participant_1_id 还是 participant_2_id,这意味着我不能使用 hasMany 关系。

有没有一种方法可以让我从用户那里获取有关当前登录用户的消息数据,而无需编写 foreach 循环并查询每个用户 (n + 1) 问题。

【问题讨论】:

  • 我认为您的数据库结构可以改进,不仅可以简化问题,还可以使您的聊天服务更加通用(轻松支持群聊)。这是一个选项,还是您专门寻找这个问题的解决方案?
  • 同样在您的 API 响应中,您没有区分属于不同对话的消息。这是故意的吗?

标签: php mysql sql laravel


【解决方案1】:

我会提出以下数据库结构。这将不仅支持用户之间的多个对话,而且每个对话支持两个以上的参与者,从而允许您进行群组对话。

user
-   id
-   first_name
-   last_name
...

conversations
-   id
-   created_at
-   updated_at

conversation_user
-   id
-   conversation_id
-   user_id
-   created_at

message
-   id
-   conversation_id
-   user_id
-   message
-   created_at
-   updated_at

上述架构的模型定义和关系如下所示:

<?php

class User extends Eloquent
{
    public function conversations() 
    {
        return $this->belongsToMany(Conversation::class)->latest('updated_at');
    }

    public function messages() 
    {
        return $this->hasMany(Message::class)->latest();
    }
}

class Conversation extends Eloquent
{
    public function participants() 
    {
        return $this->belongsToMany(User::class)->oldest();
    }

    public function messages()
    {
        return $this->hasMany(Message::class)->latest();
    }
}

class Message extends Eloquent
{
    public function conversation() 
    {
        return $this->belongsTo(Conversation::class);
    }

    public function sender()
    {
        return $this->belongsTo(User::class);
    }
}

要查找所有用户及其对话,您可以使用以下查询。这将使用两个查询来查找所有用户和对话。

$users = User::with('conversations')->get();

要查找所有用户及其消息,您可以使用以下查询。这将使用两个查询来查找所有用户和对话。

$users = User::with('messages')->get();

要查找所有用户及其对话和消息,您可以合并上述查询。这将使用三个查询来查找所有用户、对话和消息。但请注意,消息不会嵌套在对话下方。

$users = User::with('conversations', 'messages')->get();

编辑: 类似以下的内容将为您提供所有用户,以及他们与当前用户进行的任何对话。如果您还在您的with() 呼叫中包含conversations.messages,您也会收到这些对话中的消息。尽管这可能很快成为一个大型数据集。

$user = Auth::user();

$users = User::with([
        'conversations' => function ($query) use ($user) {
            return $query->whereHas('user', function ($query) use ($user) {
                return $query->where('user_id', $user->id);
            });
        },
        'conversations.messages',
    ])
    ->get();

我很乐意回答您可能对上述内容提出的任何后续问题。

【讨论】:

  • 谢谢 fubar,我可以使用你的数据库结构,但我需要从查询中得到的只是属于该用户与当前登录用户的对话(我希望我说得通) .
  • 所以基本上它应该为每个用户返回一个对话(因为每个用户最多只能与当前登录的用户进行一个对话)。
  • 明确一点,当您说对话时,您是指Conversation 模型,还是包含Conversation 的所有Message 模型?
  • 对话模型,因为使用User::with('conversations') 会返回用户的所有对话(但我只想要用户与当前登录用户的对话)。
  • 我已经更新了我的答案。如果您打算多次重复使用该逻辑,则可以将该逻辑包装到查询范围中。
猜你喜欢
  • 2020-01-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-30
  • 2020-03-08
  • 2019-09-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多