Индивидуальный и групповой запрос чата для разговоров в MongoDB - PullRequest
1 голос
/ 23 февраля 2020

Я работал над запросом MongoDB, который становится все более и более сложным. Я уже открыл пару тем, но я чувствую, что было бы лучше, чтобы объяснить весь мой случай и настроить запрос сразу, чтобы удовлетворить все мои потребности, потому что я чувствую, что мне нужно изменить все, как я хочу вводить все новые и новые условия.

Итак, для моего случая я хочу создать приложение для чата. Пользователи могут вести беседы один на один, а также групповые чаты с участием до 25 человек. Идея моей базы данных состоит в том, чтобы сохранить документ conversations и документ messages, которые связаны друг с другом с помощью _id из беседы. Это мой conversations документ:

{
    "_id" : ObjectId("5e35f2c840713a43aeeeb3d9"),
    "n" : "Example Group Chat",
    "members" : [ 
        {
            "uID" : "1",
            "j" : 1580580922,
            "i" : "1",
            "r" : "admin",
            "a" : 1
        }, 
        {
            "uID" : "4",
            "j" : 1580580922,
            "l" : 1581863346,
            "i" : "1",
            "r" : "member",
            "a" : 0
        }, 
        {
            "uID" : "5",
            "j" : 1580581922,
            "i" : "1",
            "r" : "member",
            "a" : 1
        }, 
        {
            "uID" : "9",
            "j" : 1580593922,
            "i" : "1",
            "r" : "member",
            "a" : 1
        }, 
        {
            "uID" : "3",
            "j" : 1580594920,
            "i" : "1",
            "r" : "member",
            "a" : 1
        }, 
        {
            "uID" : "8",
            "j" : 1580594999,
            "i" : "1",
            "r" : "member",
            "a" : 1
        }
    ]
}

{
    "_id" : ObjectId("5e39d5d740713a43aeef5b26"),
    "members" : [ 
        {
            "uID" : "1",
            "j" : 1580580922,
            "i" : "1",
            "r" : "member",
            "a" : 1
        }, 
        {
            "uID" : "2",
            "j" : 1580580922,
            "i" : "1",
            "r" : "member",
            "a" : 1
        }
    ]
}

. Вы можете видеть групповой чат (первый) и чат один на один (второй). Групповые чаты могут иметь имена (n), индивидуальные чаты - нет. Каждый диалог имеет массив участников, в котором хранятся идентификатор пользователя (uID), дата присоединения (j), оставленная дата (l), поле приглашенного userID (i), поле роли (r) и активное поле ( а). Мне, вероятно, не понадобится поле "active", так как у меня есть отметка времени соединения / оставления, но все же. Я, вероятно, удалю его позже, так что это поле, вероятно, не будет включено.

Далее у меня есть messages документ следующим образом:

{
    "_id" : ObjectId("5e4917bca59ce44ef2770086"),
    "c_ID" : ObjectId("5e35f2c840713a43aeeeb3d9"),
    "msg" : "Whats good?",
    "fromID" : "1",
    "__v" : 0,
    "t" : 1582369525,
    "d" : {
        "4" : 1582369525
    },
    "r" : {
        "4" : 1582369525
    }
}

Это содержит само сообщение (msg), пользователь, который его отправил (fromID), отметка времени в UNIX Epoch (t) и вложенные коллекции доставок (d) и reads (r) и, конечно, объект ObjectID разговора (c_ID).

В настоящее время у меня есть следующий запрос, после большой помощи здесь, в StackOverflow:

db.conversations.aggregate([
    {
      $lookup: {
        from: "messages",
        foreignField: "c_ID",
        localField: "_id",
        as: "messages"
      }
    },
    {
      "$unwind": "$messages"
    },
    {
      "$sort": {
        "messages.t": -1
      }
    },
    {
      "$group": {
        "_id": "$_id",
        "lastMessage": {
          "$first": "$messages"
        },
        "allFields": {
          "$first": "$$ROOT"
        }
      }
    },
    {
      "$replaceRoot": {
        "newRoot": {
          "$mergeObjects": [
            "$allFields",
            {
              "lastMessage": "$lastMessage"
            }
          ]
        }
      }
    },
    {
      $project: {
        messages: 0
      }
    },
    {
      $match: {
        "members.uID": "1"
      }
    },
    {
      $sort: {
        "lastMessage.t": 1
      }
    },
    {
      $limit: 10
    },
    {
      $project: {
        members: {
              $filter: {
                input: "$members",
                as: "member",
                cond: {
                  $and: [
                    {
                      $ne: [
                        "$$member.uID",
                        "1"
                      ]
                    },
                    {
                      $or: [
                        {
                          $eq: [
                            "$$member.l",
                            undefined
                          ]
                        },
                        {
                          $lt: [
                            "$$member.l",
                            "$$member.j"
                          ]
                        }
                      ]
                    }
                  ]
                }
              }
        },
        n: 1,
        lastMessage: 1
      }
    }
  ])

Я столкнулся с парой проблем с выше:

  • Мне нужно убедиться, что Участнику по-прежнему разрешено видеть разговор. Если они уходят, и, таким образом, поле l устанавливается для члена с отметкой времени, превышающей дату присоединения, пользователь все еще может видеть этот диалог, но последнее сообщение должно быть перед левой отметкой времени. Например: участник с userID 4 покинул групповой чат, но чат все еще виден в его обзоре с сообщениями ДО его оставленной даты (1581863346)
  • Я хочу убедиться, что lastMessage всегда Последнее сообщение, прежде чем кто-то оставил. Опять же, в приведенном выше примере пользователю с идентификатором пользователя 4 разрешено просматривать только сообщения с временными метками, меньшими, чем его левая дата (поэтому в данном конкретном случае c до 1581863346)
  • РЕДАКТИРОВАТЬ: Это более приятно иметь необязательные для основной функциональности, но было бы здорово включить поле memberCount, которое является просто количество пользователей, основываясь на тех же критериях: нет оставленной даты (не определено) или дата присоединения больше, чем оставленная дата, поэтому получите «фактическое» количество членов, находящихся в настоящее время в группе.

Есть идеи о том, как улучшить мой запрос, чтобы включить два вышеуказанных условия? Или также есть способ оптимизировать мой запрос по производительности? Приветствия!

Кстати: это может быть важно, но я думаю, что это имеет смысл, если вы прочитали все до конца: этот запрос будет использоваться в обзоре всех ваших активных бесед, отсортированных по метке времени. Что вы увидите в первую очередь, если откроете Facebook Messenger или WhatsApp, скажем.

РЕДАКТИРОВАТЬ: Основываясь на ответе @Tunmee, я нашел еще два условия, которые необходимо было применить: - Последнее сообщение должно быть опубликовано не только до левой даты (l), но также и после даты присоединения (j). - Я хочу возвращать только те разговоры, в которых извлекается последнее сообщение, поэтому только при наличии сообщения чата, которое нужно показать пользователю.

Я задал следующий запрос. Это выглядит хорошо? Есть какие-то проблемы?

db.conversations.aggregate([
  {
    $match: {
      "members.uID": "4"
    }
  },
  {
    $addFields: {
      user: {
        $arrayElemAt: [
          {
            $filter: {
              input: "$members",
              as: "member",
              cond: {
                $eq: [
                  "$$member.uID",
                  "4"
                ]
              }
            }
          },
          0
        ]
      }
    }
  },
  {
    $lookup: {
      from: "messages",
      let: {
        user: "$user",
        conversatoinId: "$_id"
      },
      pipeline: [
        {
          $match: {
            $expr: {
              $and: [
                {
                  $eq: [
                    "$c_ID",
                    "$$conversatoinId"
                  ]
                },
                {
                  $cond: {
                    if: {
                      $lt: [
                        {
                          $ifNull: [
                            "$$user.l",
                            0
                          ]
                        },
                        "$$user.j"
                      ]
                    },
                    then: true,
                    else: {
                      $lt: [
                        "$t",
                        "$$user.l"
                      ]
                    }
                  }
                },
                {
                  $gt: [
                    "$t",
                    "$$user.j"
                  ]
                }
              ]
            }
          },

        },
        {
          $sort: {
            "t": -1
          },

        }
      ],
      as: "messages"
    }
  },
  {
    $project: {
      lastMessage: {
        $arrayElemAt: [
          "$messages",
          0
        ]
      },
      n: 1,
      members: 1
    }
  },
  {
    $sort: {
      "lastMessage.t": 1
    }
  },
  {
    $project: {
      members: {
        $filter: {
          input: "$members",
          as: "member",
          cond: {
            $and: [
              {
                $ne: [
                  "$$member.uID",
                  "4"
                ]
              },
              {
                $or: [
                  {
                    $eq: [
                      "$$member.l",
                      undefined
                    ]
                  },
                  {
                    $lt: [
                      "$$member.l",
                      "$$member.j"
                    ]
                  }
                ]
              }
            ]
          }
        }
      },
      memberCount: {
        $size: {
          $filter: {
            input: "$members",
            as: "member",
            cond: {
              $and: [
                {
                  $ne: [
                    "$$member.uID",
                    "4"
                  ]
                },
                {
                  $or: [
                    {
                      $eq: [
                        "$$member.l",
                        undefined
                      ]
                    },
                    {
                      $lt: [
                        "$$member.l",
                        "$$member.j"
                      ]
                    }
                  ]
                }
              ]
            }
          }
        }
      },
      n: 1,
      lastMessage: 1
    }
  },
  {
    $match: {
      lastMessage: {
        $exists: true
      }
    }
  },
  {
    $limit: 10
  }
])

РЕДАКТИРОВАТЬ: Насколько я могу судить, все это выглядит хорошо в данный момент. Единственное, чего мне не хватает сейчас, так это того, что можно уловить следующую ситуацию: представьте, что есть групповой чат с 20 участниками. Название чата "Веселые пятницы". userID 4 присоединяется к этому групповому чату (я установил для поля j (объединение) значение 1582475543 (отметка времени), участвует в течение двух недель, а затем уходит (я установил для поля l (слева) значение 1583685143 (отметка времени)) Это все будет работать нормально. Однако, как я могу снова добавить userID 4 в тот же групповой чат через 1 неделю (отметка времени 1584289943) и убедиться, что userID 4 может видеть lastMessage, ЕСЛИ это либо между первым подключением / оставил ИЛИ, если он будет опубликован после того, как он присоединился снова?

Я хотел бы иметь возможность добавлять одного и того же пользователя более одного раза в массив members, но с разными jl) полей, а затем запросите lastMessage, чтобы быть между одним из них, что позволит для того, что я хочу сделать, как описано выше.

1 Ответ

1 голос
/ 23 февраля 2020

Попробуйте:

db.conversations.aggregate([
  {
    $match: {
      "members.uID": "4"
    }
  },
  {
    $addFields: {
      user: {
        $arrayElemAt: [
          {
            $filter: {
              input: "$members",
              as: "member",
              cond: {
                $eq: [
                  "$$member.uID",
                  "4"
                ]
              }
            }
          },
          0
        ]
      }
    }
  },
  {
    $lookup: {
      from: "messages",
      let: {
        user: "$user",
        conversatoinId: "$_id"
      },
      pipeline: [
        {
          $match: {
            $expr: {
              $and: [
                {
                  $eq: [
                    "$c_ID",
                    "$$conversatoinId"
                  ]
                },
                {
                  $cond: {
                    if: {
                      $lt: [
                        {
                          $ifNull: [
                            "$$user.l",
                            0
                          ]
                        },
                        "$$user.j"
                      ]
                    },
                    then: true,
                    else: {
                      $lt: [
                        "$t",
                        "$$user.l"
                      ]
                    }
                  }
                }
              ]
            }
          },

        },
        {
          $sort: {
            "t": -1
          },

        }
      ],
      as: "messages"
    }
  },
  {
    $project: {
      lastMessage: {
        $arrayElemAt: [
          "$messages",
          0
        ]
      },
      n: 1,
      members: 1
    }
  },
  {
    $match: {
      lastMessage: {
        $exists: true
      }
    }
  },
  {
    $sort: {
      "lastMessage.t": 1
    }
  },
  {
    $limit: 10
  },
  {
    $project: {
      members: {
        $filter: {
          input: "$members",
          as: "member",
          cond: {
            $and: [
              {
                $ne: [
                  "$$member.uID",
                  "1"
                ]
              },
              {
                $or: [
                  {
                    $eq: [
                      "$$member.l",
                      undefined
                    ]
                  },
                  {
                    $lt: [
                      "$$member.l",
                      "$$member.j"
                    ]
                  }
                ]
              }
            ]
          }
        }
      },
      n: 1,
      lastMessage: 1
    }
  }
])

Конвейер Объяснение:

  • match: соответствует только разговорам, в которых участвует пользователь;
  • addFields: добавляет поле «пользователь» к каждому документу беседы. В пользовательском поле хранится документ участника (из массива members) конкретного пользователя, для которого мы собираем данные.
  • lookup: Это похоже на этап lookup в конвейере, который вы опубликовали, однако этот использует параметры lookup#pipeline и lookup#let для добавления некоторых ограничений к загружаемым сообщениям.
  • lookup#let: Я в основном объявляю переменные для использования в lookup#pipeline.
  • lookup#pipeline#match: Эта стадия суб-конвейера гарантирует, что документ сообщения: (1) имеет идентификатор разговора (c_ID), который соответствует идентификатору документа беседы. (2) если пользователь покинул канал, т. Е. $$user.l меньше $$user.j, отметка времени сообщения должна быть меньше даты, когда пользователь покинул канал. Обратите внимание на использование оператора ifNull для установки поля $$user.l в 0, если оно было нулевым, т. Е. Пользователь вообще не покинул группу.
  • lookup#pipeline#sort: сортирует передаваемые сообщения с этапа матча в порядке убывания. Это добавлено, чтобы мы могли легко получить последнее сообщение;
  • project: изменяет форму документа, добавляя поле lastMessage и удаляя поле messages.
  • sort : Это то же самое, что было в опубликованном вами конвейере.
  • limit: Это то же самое, что было в опубликованном вами конвейере.
  • project: Это то же самое, что было в сообщении, которое вы опубликовали.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...