【发布时间】:2020-05-18 22:28:03
【问题描述】:
我正在我们的应用程序中构建一个聊天功能。基本的聊天工作,我们有一个查询来获取属于用户的对话、获取对话、消息等。
现在,我们想要添加一项功能,让对话的参与者(对话可以有多个参与者)可以删除他们自己的聊天,但这不会删除服务器中的对话.相反,我们会将对话标记为从该用户的 X 点删除。在这种情况下,当删除对话的参与者再次在我们的 API 中请求对话时,他将看不到删除之前的消息。
要清楚地理解这个概念,它与 WhatsApp、Telegram 或当今大多数聊天应用程序的工作方式相同。当用户 A 和 B 交互时,如果用户 B 选择从他的手机中删除对话,用户 A 仍然会看到整个对话。如果用户 B(或 A)在对话中再次发短信,用户 B 将只能看到新短信。
我不完全确定这对他们来说是如何工作的,但似乎对我们有用的结构如下:
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。当用户 B 向服务器发送删除对话的请求时,此deleted_from_id 将得到更新。它会发送他看到的conversation_id和最新的conversation_message_id,并进行相应的更新。
我们使用 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
上面的查询非常简单,它返回特定用户的对话。使用 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
这将“有效”,但如果用户删除了一条消息,并且假设存在与andid> 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
但不幸的是,这也不起作用。
为了让测试更方便,我在这里设置了一个 DB Fiddle:https://www.db-fiddle.com/f/q6S3GfZNCbvYbtvRwXJxN7/0
这个小提琴有多个用户、多个对话和多个消息。如您所见,如果您立即运行查询,它将返回属于给定参与者的 28 个对话。
也就是说,如果您在表 conversation_participants 中注意到,participant_id=1 有一行,在 conversation_id=82 上删除消息是 82:(55,28,1,82,'2020-01-31 10:01:08','2020-01-31 10:01:08'),(小提琴中的第 166 行)。消息 82 是 conversation_id=28 中的最后一条消息,因此它不应该在查询中显示,因为它没有消息。
在我们努力寻找解决方案的过程中,我们还认为拥有 conversations.last_message_id 行可能有助于防止出现对话......但我们也不确定这一点,因为我们找不到解决方案。我决定将它留在 SQL 中,以防万一找到解决方案有用。
我怎样才能得到想要的结果?我错过了什么?
提前致谢
【问题讨论】:
-
有点啰嗦,但你终于到了
-
@Strawberry 哈哈哈,真的不知道怎么问好
-
如果是我,我会从一个更简单的样本数据集开始,其中包含与问题相关的最少列数、行数和数据;我只是不想涉足所有这些
$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG和Lorem ipsit的东西,不知何故,我怀疑它是否相关。