获取每个用户对话的最新消息(含发送方与接收方信息)

13次阅读

获取每个用户对话的最新消息(含发送方与接收方信息)

本文介绍如何通过 sql 子查询与多表连接,从 `messages` 表中高效获取每个用户对(from_id/to_id)的最新消息,并关联 `users` 表展示双方姓名、头像等完整会话摘要。

在构建即时通讯或私信模块时,一个常见需求是:为当前用户列出所有有过聊天记录的联系人,并显示每段对话中时间最新的那条消息(无论该消息是当前用户发出的还是收到的)。但原始代码仅能获取“当前用户作为发送方(from_id)”的最新消息,遗漏了对方主动发起的会话,且未正确处理双向对话关系。

核心问题在于:messages 表中每条记录仅代表单向交互(A→B 或 B→A),而“两人之间的最新消息”应视为一个无向会话单元。因此,需先标准化对话标识(例如按 LEAST(from_id, to_id) 和 GREATEST(from_id, to_id) 分组),再取每组中 created_at 最大的记录。

以下是推荐的 laravel 实现方案(兼容 mysql 5.7+ / 8.0+):

✅ 正确 SQL 查询逻辑(支持双向会话)

SELECT      u1.id AS user_id,     u1.name AS user_name,     CONCAT('https://www.interwebs.co.in/puzzle/attach/', u1.avatar) AS user_image,     u2.id AS other_user_id,     u2.name AS other_user_name,     CONCAT('https://www.interwebs.co.in/puzzle/attach/', u2.avatar) AS other_user_image,     m.body AS message,     m.attachment,     m.seen AS seen_count,     m.created_at FROM messages m INNER JOIN (     -- 步骤1:为每对用户(无序)找出最新消息时间戳     SELECT          LEAST(from_id, to_id) AS user_a,         GREATEST(from_id, to_id) AS user_b,         MAX(created_at) AS latest_time     FROM messages     WHERE from_id = ? OR to_id = ?     GROUP BY user_a, user_b ) latest ON      LEAST(m.from_id, m.to_id) = latest.user_a      AND GREATEST(m.from_id, m.to_id) = latest.user_b      AND m.created_at = latest.latest_time -- 步骤2:关联 users 表,动态识别当前用户与对方 INNER JOIN users u1 ON u1.id = CASE      WHEN m.from_id = ? THEN m.from_id ELSE m.to_id  END INNER JOIN users u2 ON u2.id = CASE      WHEN m.from_id = ? THEN m.to_id ELSE m.from_id  END ORDER BY m.created_at DESC;

✅ Laravel 中安全执行(使用参数绑定防注入)

$userId = $request->user_id;  $sql = "SELECT      u1.id AS user_id,     u1.name AS user_name,     CONCAT('https://www.interwebs.co.in/puzzle/attach/', u1.avatar) AS user_image,     u2.id AS other_user_id,     u2.name AS other_user_name,     CONCAT('https://www.interwebs.co.in/puzzle/attach/', u2.avatar) AS other_user_image,     m.body AS message,     m.attachment,     m.seen AS seen_count,     m.created_at FROM messages m INNER JOIN (     SELECT          LEAST(from_id, to_id) AS user_a,         GREATEST(from_id, to_id) AS user_b,         MAX(created_at) AS latest_time     FROM messages     WHERE from_id = ? OR to_id = ?     GROUP BY user_a, user_b ) latest ON      LEAST(m.from_id, m.to_id) = latest.user_a      AND GREATEST(m.from_id, m.to_id) = latest.user_b      AND m.created_at = latest.latest_time INNER JOIN users u1 ON u1.id = CASE      WHEN m.from_id = ? THEN m.from_id ELSE m.to_id  END INNER JOIN users u2 ON u2.id = CASE      WHEN m.from_id = ? THEN m.to_id ELSE m.from_id  END ORDER BY m.created_at DESC";  $chats = DB::select($sql, [$userId, $userId, $userId, $userId]);

⚠️ 注意事项

  • 索引优化:确保 messages 表上存在复合索引 INDEX(from_id, to_id, created_at) 和 INDEX(to_id, from_id, created_at),大幅提升分组与连接性能;
  • 空值处理:若 avatar 字段可能为空,建议在 CONCAT 前用 COALESCE(u1.avatar, ‘default.png’) 防止拼接出无效 URL;
  • 时间精度:若存在毫秒级重复 created_at,可追加 id 作为第二排序键(如 ORDER BY created_at DESC, id DESC)确保唯一性;
  • Laravel 9+ 推荐替代方案:可封装为 Eloquent 查询作用域(Scope),或使用 DB::table()->fromSub() 构建子查询,提升可读性与可维护性。

该方案真正实现了「以用户为中心的会话聚合」——无论消息由谁发起、谁接收,只要两人之间存在交互,即归为同一会话并提取最新一条,完美匹配真实业务场景。

text=ZqhQzanResources