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

发布时间 - 2026-01-06 00:00:00    点击率:

本文介绍如何通过 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() 构建子查询,提升可读性与可维护性。

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


# mysql  # laravel  # 作用域  # sql  # 封装  # default  # table  # 两人  # 有过  # 仅代表  # 那条  # 私信  # 绑定  # 每条  # 仅能  # 即时通讯  # 为空 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: laravel怎么使用数据库工厂(Factory)生成带有关联模型的数据_laravel Factory生成关联数据方法  利用JavaScript实现拖拽改变元素大小  Laravel的路由模型绑定怎么用_Laravel Route Model Binding简化控制器逻辑  Linux虚拟化技术教程_KVMQEMU虚拟机安装与调优  如何快速完成中国万网建站详细流程?  如何用搬瓦工VPS快速搭建个人网站?  google浏览器怎么清理缓存_谷歌浏览器清除缓存加速详细步骤  高防服务器租用指南:配置选择与快速部署攻略  javascript事件捕获机制【深入分析IE和DOM中的事件模型】  ,网页ppt怎么弄成自己的ppt?  Python高阶函数应用_函数作为参数说明【指导】  网站制作价目表怎么做,珍爱网婚介费用多少?  如何在建站宝盒中设置产品搜索功能?  成都品牌网站制作公司,成都营业执照年报网上怎么办理?  Laravel DB事务怎么使用_Laravel数据库事务回滚操作  Laravel如何使用withoutEvents方法临时禁用模型事件  免费制作统计图的网站有哪些,如何看待现如今年轻人买房难的情况?  免费网站制作appp,免费制作app哪个平台好?  东莞专业网站制作公司有哪些,东莞招聘网站哪个好?  长沙企业网站制作哪家好,长沙水业集团官方网站?  如何确保FTP站点访问权限与数据传输安全?  如何在Windows虚拟主机上快速搭建网站?  如何用AI一键生成爆款短视频文案?小红书AI文案写作指令【教程】  千库网官网入口推荐 千库网设计创意平台入口  JavaScript常见的五种数组去重的方式  javascript如何操作浏览器历史记录_怎样实现无刷新导航  Laravel怎么自定义错误页面_Laravel修改404和500页面模板  历史网站制作软件,华为如何找回被删除的网站?  Android实现代码画虚线边框背景效果  Laravel如何实现全文搜索功能?(Scout和Algolia示例)  新三国志曹操传主线渭水交兵攻略  Laravel如何使用Laravel Vite编译前端_Laravel10以上版本前端静态资源管理【教程】  Laravel Blade模板引擎语法_Laravel Blade布局继承用法  电商网站制作多少钱一个,电子商务公司的网站制作费用计入什么科目?  Laravel怎么进行数据库回滚_Laravel Migration数据库版本控制与回滚操作  香港服务器WordPress建站指南:SEO优化与高效部署策略  消息称 OpenAI 正研发的神秘硬件设备或为智能笔,富士康代工  html5的keygen标签为什么废弃_替代方案说明【解答】  制作ppt免费网站有哪些,有哪些比较好的ppt模板下载网站?  如何在云虚拟主机上快速搭建个人网站?  Laravel Vite是做什么的_Laravel前端资源打包工具Vite配置与使用  最好的网站制作公司,网购哪个网站口碑最好,推荐几个?谢谢?  如何确认建站备案号应放置的具体位置?  Linux系统命令中screen命令详解  如何在IIS服务器上快速部署高效网站?  JS中页面与页面之间超链接跳转中文乱码问题的解决办法  Python企业级消息系统教程_KafkaRabbitMQ高并发应用  如何用PHP快速搭建高效网站?分步指南  Laravel如何使用Spatie Media Library_Laravel图片上传管理与缩略图生成【步骤】  如何在腾讯云服务器快速搭建个人网站?