You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 13:35:34