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

Laravel Eloquent:如何通过关联获取用户加入线程后的消息?

解决用户加入线程后获取对应消息的Eloquent关联方案

嘿,我明白你现在的困扰——想用Eloquent关联替代循环查询,避免N+1性能问题,同时精准过滤出用户加入线程后的消息对吧?咱们一步步来搞定这个事儿。

首先得说清楚:你之前尝试的hasManyThrough之所以失败,是因为这个关联类型适用于三层直接的一对多关系,但你的场景里需要用到participants中间表的created_at和deleted_at字段来过滤消息,而hasManyThrough默认不会处理belongsToMany关联里的pivot附加字段,所以直接用它搞不定。

接下来给你两种靠谱的实现方式,都是一次查询就能搞定,性能比循环好太多:

方法一:用Join构建查询(最直接高效)

在你的User模型里添加这个方法,它通过关联participants表来精准过滤消息时间范围:

use Illuminate\Database\Eloquent\Builder;
use App\Models\Message;

public function messagesAfterJoiningThreads(bool $showDeleted = false)
{
    $query = Message::query()
        ->select('messages.*')
        // 关联参与者表,找到当前用户加入的所有线程
        ->join('participants', function ($join) {
            $join->on('participants.thread_id', '=', 'messages.thread_id')
                 ->where('participants.user_id', '=', $this->id);
        })
        // 核心过滤逻辑:根据参与者记录的状态判断消息时间范围
        ->where(function (Builder $query) {
            // 参与者未退出线程:消息创建时间晚于加入时间
            $query->whereNull('participants.deleted_at')
                  ->where('messages.created_at', '>=', 'participants.created_at')
                  // 参与者已退出线程:消息创建时间在加入和退出之间
                  ->orWhere(function (Builder $query) {
                      $query->whereNotNull('participants.deleted_at')
                            ->whereBetween('messages.created_at', [
                                'participants.created_at',
                                'participants.deleted_at'
                            ]);
                  });
        });

    // 控制是否显示已软删除的消息
    if (!$showDeleted) {
        $query->whereNull('messages.deleted_at');
    }

    return $query->orderBy('messages.updated_at', 'desc')->get();
}

使用示例

// 获取当前用户加入线程后的消息(不显示已删除消息)
$messages = auth()->user()->messagesAfterJoiningThreads();

// 包含已删除的消息
$messages = auth()->user()->messagesAfterJoiningThreads(true);

方法二:自定义Eloquent关联(支持预加载)

如果你想把这个做成可预加载的Eloquent关联,可以用whereExists子查询来实现,这样能和其他关联一起用with()预加载:

use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\DB;
use App\Models\Message;

public function messagesAfterJoining(): HasMany
{
    return $this->hasMany(Message::class)
        ->whereExists(function ($query) {
            $query->select(DB::raw(1))
                  ->from('participants')
                  ->whereColumn('participants.thread_id', 'messages.thread_id')
                  ->where('participants.user_id', $this->id)
                  ->where(function ($query) {
                      $query->whereNull('participants.deleted_at')
                            ->where('messages.created_at', '>=', 'participants.created_at')
                            ->orWhere(function ($query) {
                                $query->whereNotNull('participants.deleted_at')
                                      ->whereBetween('messages.created_at', [
                                          'participants.created_at',
                                          'participants.deleted_at'
                                      ]);
                            });
                  });
        })
        ->orderBy('updated_at', 'desc');
}

使用示例

// 预加载用户及其加入线程后的消息
$user = User::with('messagesAfterJoining')->find(auth()->id());
$messages = $user->messagesAfterJoining;

为什么这两种方法比循环好?

你的循环实现会触发N+1查询:先查用户的所有线程,再逐个查每个线程的消息,线程越多性能越差。而上面的两种方法都是一次数据库查询就能拿到所有符合条件的消息,性能提升非常明显,也更符合Eloquent的最佳实践。

内容的提问来源于stack exchange,提问作者Jakob Graf

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 10:18:19