Laravel 9集成MeiliSearch调用pagination()分页结果截断问题
问题根因
你的判断完全准确:
query()回调的执行时机是搜索引擎已经完成检索、返回分页结果之后,此时再用whereHas/whereDoesntHave做过滤,是在搜索引擎返回的固定数量结果里做二次筛选,自然会出现每页条数不足、提前翻到空页的问题——本质是分页计算在过滤之前就做完了。- 你尝试传闭包给Scout的
where()方法报500错误,是因为Scout的where()和Eloquent的where()逻辑完全不通用:它仅支持「字段名=匹配值」的简单键值对过滤,不接受闭包、关联查询这类关系型数据库才支持的语法,这和你查到的文档说明一致,搜索引擎本身没有表关联的概念,没法直接解析跨表的关联判断逻辑。
可行解决方案
方案1:冗余关联状态到搜索索引(推荐,性能最好,符合Scout+MeiliSearch设计逻辑)
搜索引擎的核心设计思路就是提前把过滤、搜索需要的字段都存在索引里,避免查询时做跨表计算,你只需要把「是否有销售记录」的状态提前同步到MeiliSearch的索引中即可:
- 修改Article模型的
toSearchableArray()方法,新增冗余的过滤字段:public function toSearchableArray() { return [ 'id' => $this->id, 'title' => $this->title, 'content' => $this->content, // 新增冗余字段:标记当前文章是否存在销售记录 'has_sold' => $this->sales()->exists(), // 其余需要纳入搜索/排序的字段按实际需求补充 ]; } - 执行命令重新全量导入索引,让新增字段生效:
php artisan scout:import "App\Models\Article" - 搜索时直接用Scout原生
where做过滤,不要用query()回调做结果筛选:
注意事项:当sales表的数据发生新增、删除时,要同步触发关联Article模型的索引更新,避免冗余字段和实际数据不一致。可以在Sale模型的模型事件中,调用对应文章的$searchQuery = Article::search($this->meiliSearchText); match($this->showSold) { "true" => $searchQuery->where('has_sold', true), "false" => $searchQuery->where('has_sold', false), default => null }; $paginateResult = $searchQuery->paginate($this->pagination);searchable()方法更新索引即可。
方案2:主键中转查询(适合小数据量场景,无需修改索引结构)
如果不想维护冗余字段、且匹配搜索关键词的总数据量不大(万级以内),可以先从搜索引擎取出所有匹配结果的主键,再交给Eloquent做关联过滤和分页:
// 先从MeiliSearch取出所有匹配关键词的文章ID $matchArticleIds = Article::search($this->meiliSearchText)->keys(); // 用ID集合做SQL查询,所有关联过滤、分页逻辑走Eloquent原生逻辑 $paginateResult = Article::whereIn('id', $matchArticleIds) ->when($this->showSold === "true", fn($q) => $q->whereHas('sales')) ->when($this->showSold === "false", fn($q) => $q->whereDoesntHave('sales')) ->paginate($this->pagination);
注意事项:如果匹配关键词的结果超过10万条,一次性取出所有ID会带来较高的内存占用和接口延迟,大数据量场景优先选择方案1。
内容的提问来源于stack exchange,提问作者Musamba
相关产品推荐
相关产品推荐

