Laravel Eloquent按用户分组并按最新消息时间排序,需包含message字段
在Laravel Eloquent中获取每个联系人的最新消息(含message字段)
要实现按用户分组获取每个联系人的最新消息(包含message字段),同时关联发送方和接收方用户数据,核心问题是直接分组会导致message字段无法对应到最新记录,所以需要先定位到每个对话的最新记录ID,再关联原表获取完整数据。
步骤1:定义模型关联
确保你的Message模型已经配置好关联关系:
// app/Models/Message.php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Message extends Model { protected $fillable = ['user_id', 'user_id_to', 'message', 'send_date']; // 关联消息发送者 public function user() { return $this->belongsTo(User::class, 'user_id'); } // 关联消息接收者 public function user_to() { return $this->belongsTo(User::class, 'user_id_to'); } }
步骤2:获取每个联系人的最新消息
这里提供两种实现方式,均能正确获取包含message的最新记录:
方式一:子查询获取最新消息ID
先通过子查询筛选出每个对话的最新消息ID,再根据ID查询完整记录:
$currentUserId = auth()->id(); // 当前登录用户ID // 子查询:按对话双方分组,拿到每组最新消息的ID $latestMessageIds = Message::selectRaw('MAX(id) as id') ->where(function ($query) use ($currentUserId) { // 筛选出当前用户发送或接收的消息 $query->where('user_id', $currentUserId) ->orWhere('user_id_to', $currentUserId); }) // 按对话双方的组合分组(避免A-B和B-A被当成两组) ->groupByRaw('LEAST(user_id, user_id_to), GREATEST(user_id, user_id_to)') ->pluck('id'); // 查询最新消息并关联用户数据 $latestMessages = Message::whereIn('id', $latestMessageIds) ->with(['user', 'user_to']) ->orderBy('send_date', 'desc') ->get();
方式二:JOIN关联最新记录
通过JOIN子查询直接匹配每个对话的最新发送时间,获取完整记录:
$currentUserId = auth()->id(); $latestMessages = Message::join( DB::raw('( SELECT LEAST(user_id, user_id_to) as user1, GREATEST(user_id, user_id_to) as user2, MAX(send_date) as max_send_date FROM messages WHERE user_id = ? OR user_id_to = ? GROUP BY user1, user2 ) as latest'), function ($join) { // 匹配对话双方的两种组合(A-B和B-A) $join->on(function ($query) { $query->on('messages.user_id', '=', 'latest.user1') ->on('messages.user_id_to', '=', 'latest.user2'); }) ->orOn(function ($query) { $query->on('messages.user_id', '=', 'latest.user2') ->on('messages.user_id_to', '=', 'latest.user1'); }) // 匹配最新发送时间 ->on('messages.send_date', '=', 'latest.max_send_date'); }, false // 关闭绑定参数自动转义,避免SQL语法错误 ) ->where(function ($query) use ($currentUserId) { $query->where('user_id', $currentUserId) ->orWhere('user_id_to', $currentUserId); }) ->with(['user', 'user_to']) ->orderBy('send_date', 'desc') ->select('messages.*') // 只取messages表的字段 ->get();
步骤3:封装成作用域(可选)
如果需要频繁使用这个逻辑,可以在Message模型中定义一个作用域:
// app/Models/Message.php public function scopeLatestPerContact($query, $userId) { $latestIds = self::selectRaw('MAX(id) as id') ->where(function ($q) use ($userId) { $q->where('user_id', $userId)->orWhere('user_id_to', $userId); }) ->groupByRaw('LEAST(user_id, user_id_to), GREATEST(user_id, user_id_to)') ->pluck('id'); return $query->whereIn('id', $latestIds)->orderBy('send_date', 'desc'); }
使用时只需一行代码:
$latestMessages = Message::latestPerContact(auth()->id())->with(['user', 'user_to'])->get();
为什么之前的方案没包含message?
直接对user_id或user_id_to分组时,message属于非聚合字段,在MySQL严格模式下会直接报错;即使关闭严格模式,返回的message也只是分组后随机一条的内容,而非最新消息的内容。必须先定位到最新记录的唯一标识(ID或send_date),再关联原表才能拿到正确的message。
内容的提问来源于stack exchange,提问作者J K
相关产品推荐
相关产品推荐

