Разговоры принадлежат нескольким пользователям, но пользователь A удаляет, а пользователь B - нет. Как мы можем предотвратить это? - PullRequest
3 голосов
/ 03 февраля 2020

Я создаю функцию чата в нашем приложении. Основной чат c работает, у нас был запрос, чтобы получить разговоры, принадлежащие пользователю, получить разговор, сообщения и т. Д. c.

Теперь мы хотим добавить функцию, в которой участник разговор (разговоры могут иметь несколько участников) может удалить чат на их конце, но этот не удалит разговор на сервере. Вместо этого мы помечаем беседу как удаленную из точки Х для этого пользователя. В этом случае, когда участник, который удалил беседу, снова запрашивает диалог в нашем API, он не увидит сообщения до своего удаления.

Чтобы ясно понять концепцию, это то же самое, что WhatsApp, Telegram или большинство приложений чатов работают в настоящее время. Когда пользователь A и B взаимодействуют, если пользователь B решает удалить разговор со своего телефона, пользователь A по-прежнему будет видеть весь разговор. Если пользователь B (или A) снова отправит текстовые сообщения в беседе, пользователь B увидит только новые тексты.

Я не совсем уверен, как это работает на их концах, но структура, которая, кажется, работает для us следующий:

CREATE TABLE `conversations` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `starter_id` bigint(20) unsigned NOT NULL,
  `last_message_id` bigint(20) unsigned DEFAULT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `conversations_starter_id_index` (`starter_id`),
  KEY `conversations_last_message_id_index` (`last_message_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE `conversation_participants` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `conversation_id` bigint(20) unsigned NOT NULL,
  `participant_id` bigint(20) unsigned NOT NULL,
  `deleted_from_id` bigint(20) unsigned DEFAULT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `conversation_participants_conversation_id_index` (`conversation_id`),
  KEY `conversation_participants_participant_id_index` (`participant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE `conversation_messages` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `conversation_id` bigint(20) unsigned NOT NULL,
  `sender_id` bigint(20) unsigned NOT NULL,
  `message` text COLLATE utf8mb4_unicode_ci NOT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `conversation_messages_conversation_id_index` (`conversation_id`),
  KEY `conversation_messages_sender_id_index` (`sender_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

Как вы видите в conversation_participants, мы добавили deleted_from_id. Это deleted_from_id будет обновлено, когда пользователь B отправит запрос на сервер, чтобы удалить диалог. Он отправит разговор / идентификатор и последний разговор, который он видел, и обновит соответственно.

Мы используем Laravel в качестве основы и Eloquent для простой генерации запросов со связями. У нас есть конечная точка, которая запрашивает последние 25 разговоров для пользователя, а затем мы разбиваем их на страницы. Это запрос, сгенерированный для этого типа запроса:

select
  `conversations`.*,
  `conversation_participants`.`participant_id` as `pivot_participant_id`,
  `conversation_participants`.`conversation_id` as `pivot_conversation_id`,
  `conversation_participants`.`created_at` as `pivot_created_at`,
  `conversation_participants`.`updated_at` as `pivot_updated_at`
from
  `conversations`
  inner join `conversation_participants` on `conversations`.`id` = `conversation_participants`.`conversation_id`
where
  `conversation_participants`.`participant_id` = 1
  and exists (
    select
      *
    from
      `conversation_messages`
    where
      `conversations`.`id` = `conversation_messages`.`conversation_id`
  )
order by
  `id` desc

Приведенный выше запрос довольно прост, он возвращает диалоги для определенного пользователя c. Используя Laravel Conversation::with('messages')..., это позволяет нам легко фильтровать разговоры, в которых есть сообщения (мы не хотим, чтобы пустые разговоры возвращались).

Проблема в том, что мы пытались фильтровать больше и предотвращать разговоры что пользователь удалил на своем телефоне, чтобы показать в этом запросе. Мы не нашли способ.

Нашей первой догадкой было просто добавить настройку exists(), ограничивающую conversation_messages.id, например:

select
  `conversations`.*,
  `conversation_participants`.`participant_id` as `pivot_participant_id`,
  `conversation_participants`.`conversation_id` as `pivot_conversation_id`,
  `conversation_participants`.`deleted_from_id` as `pivot_deleted_from_id`,
  `conversation_participants`.`created_at` as `pivot_created_at`,
  `conversation_participants`.`updated_at` as `pivot_updated_at`
from
  `conversations`
  inner join `conversation_participants` on `conversations`.`id` = `conversation_participants`.`conversation_id`
where
  `conversation_participants`.`participant_id` = 1
  and exists (
    select
      *
    from
      `conversation_messages`
    where
      `conversations`.`id` = `conversation_messages`.`conversation_id`
      and `id` > conversation_participants.deleted_from_id
  )
order by
  `id` desc

Это будет "работать", но если пользователь удалил сообщение и, скажем, есть разговоры с сообщениями старше and id > conversation_participants.deleted_from_id, другие разговоры не будут возвращены. Это неверно, так как предотвращает показ любого другого разговора, даже если у него есть сообщения и он принадлежит участнику.

Мы также попробовали другой подход, используя некоторые объединения в exists(), чтобы попытаться предотвратить "" удаленный разговор »для отображения в списке:

select
  `conversations`.*,
  `conversation_participants`.`participant_id` as `pivot_participant_id`,
  `conversation_participants`.`conversation_id` as `pivot_conversation_id`,
  `conversation_participants`.`deleted_from_id` as `pivot_deleted_from_id`,
  `conversation_participants`.`created_at` as `pivot_created_at`,
  `conversation_participants`.`updated_at` as `pivot_updated_at`
from
  `conversations`
  inner join `conversation_participants` on `conversations`.`id` = `conversation_participants`.`conversation_id`
where
  `conversation_participants`.`participant_id` = 1
  and exists (
    select
      `conversation_messages`.*
    from
      `conversation_messages`
    join
      `conversations` on `conversations`.`id` = `conversation_messages`.`conversation_id`
    join
      `conversation_participants` on `conversations`.`id` = `conversation_participants`.`conversation_id`
    where
      `conversations`.`id` = `conversation_messages`.`conversation_id`
      and `conversation_participants`.`participant_id` = 1
      and `conversation_messages`.`id` > `conversation_participants`.`deleted_from_id`
  )
order by
  `id` desc

Но, к сожалению, это тоже не сработало.

Чтобы сделать тестирование более удобным, я настроил здесь БД Fiddle: https://www.db-fiddle.com/f/q6S3GfZNCbvYbtvRwXJxN7/0

Эта скрипка имеет несколько пользователей, несколько разговоров и несколько сообщений. Как видите, если вы выполните запрос сразу же, он вернет 28 разговоров, принадлежащих данному участнику.

При этом в таблице разговор_причастников есть строка для member_id = 1, которая в файлеословия_вопроса = 82 сообщение об удалении - это 82: (55,28,1,82,'2020-01-31 10:01:08','2020-01-31 10:01:08'), (строка 166 в скрипке). Сообщение 82 является последним сообщением в chat_id = 28, поэтому оно не должно отображаться в запросе, поскольку в нем нет сообщений.

В наших попытках найти решение мы также подумал, что, возможно, наличие строки conversations.last_message_id будет полезно для предотвращения появления разговоров ... но мы также не уверены в этом, поскольку не можем найти решение. Я решил оставить его в SQL на случай, если будет полезно найти решение.

Как я могу получить желаемые результаты? Что я упускаю?

Заранее спасибо

1 Ответ

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

Вот что я бы сделал.

Разговор содержит сообщения Сообщения содержат участников.

Я бы не оставил "Conversation_Participants", как видно в вашем случае. Вместо этого я бы оставил «Message_Participants».

Это будет структура моей таблицы

CREATE TABLE `conversations` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `starter_id` bigint(20) unsigned NOT NULL,
  `last_message_id` bigint(20) unsigned DEFAULT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `conversations_starter_id_index` (`starter_id`),
  KEY `conversations_last_message_id_index` (`last_message_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;


CREATE TABLE `conversation_messages` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `conversation_id` bigint(20) unsigned NOT NULL,
  `sender_id` bigint(20) unsigned NOT NULL,
  `message` text COLLATE utf8mb4_unicode_ci NOT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `conversation_messages_conversation_id_index` (`conversation_id`),
  KEY `conversation_messages_sender_id_index` (`sender_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE `message_participants` (
      `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
      `message_id` bigint(20) unsigned NOT NULL,
      `participant_id` bigint(20) unsigned NOT NULL,
      `created_at` timestamp NULL DEFAULT NULL,
      `updated_at` timestamp NULL DEFAULT NULL,
      PRIMARY KEY (`id`),
      )

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

Сейчас всякий раз, когда сообщение приходит в диалоге, в котором «n» участников, в таблице message_participants будет сделано «n» записей.

Таким образом, когда участник удаляет сообщение или группу сообщений из своего чата, Вы можете удалить соответствующие записи в таблице «message_participants», относящиеся к этому сообщению и участнику.

Таким образом, участник может удалить любое сообщение из любого чата в любом порядке. В вашей логике c вы упомянули "last_message_id". Это ограничит доступ пользователя к сообщениям вне определенного идентификатора, но с моим логином c он может хранить несколько сообщений с 2014 года, а затем удалять все до 2018 года, затем вести двухмесячный чат с 2018 года и удалять остальные. Я надеюсь, что вы поняли и надеюсь, что это поможет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...