Elasticsearch 9.x 要求 bool 查询严格区分 must(影响评分的全文检索)与 filter(确定性过滤),must 中混入 term 会导致评分异常、缓存失效;filter 应放 sale、price range、prefix 类目路径;should 必须设 minimum_should_match: 1;Laravel 动态拼接需判空、白名单校验、类型转换及高亮字段预设。

bool 查询必须显式拆分 must/filter/should
直接把所有条件塞进 must 会导致评分异常、缓存失效,甚至漏掉本该命中的记录。Elasticsearch 9.x 强制要求区分语义:只有影响相关度计算的才放 must,确定性过滤(如上架状态、类目 ID、价格区间)必须进 filter。
常见错误写法:['must' => [['term' => ['sale' => true]], ['match' => ['title' => '手机']]]] —— 这会让 sale 参与打分,且无法利用 filter 缓存。
-
filter里放:['term' => ['sale' => true]]、['range' => ['price' => ['gte' => 10, 'lte' => 500]]]、['prefix' => ['category_path' => '102/']] -
must里只放全文检索:['multi_match' => ['query' => 'iPhone', 'fields' => ['title^3', 'brandName^2']]] - 用了
should就必须设minimum_should_match: 1,否则默认为 0,整个 bool 查询可能返回空结果
Laravel 中拼接动态 bool 查询的防坑要点
手动拼数组容易漏判空、错嵌套、类型错位。比如用户没传价格,但代码仍写了 ['range' => ['price' => ['gte' => null]]],ES 会直接报错 failed to parse range query。
- 每个参数必须用
$request->filled()判空,不用empty()(会把'0'当空) - 价格范围要拆成
price_from和price_to两个字段,避免传数组导致调试困难 - 类目路径用
prefix而不是term,才能支持102/匹配所有子类目(如102/205/、102/206/) - 高亮字段需提前在 mapping 里设
"highlight": {"require_field_match": false},否则multi_match某个字段没命中就高亮不了
关键词搜索别硬套 match,优先用 query_string + default_field
如果只是简单搜关键词、不关心字段权重或分词细节,query_string 比 multi_match 更轻量、更少出错。它自动处理引号、布尔操作符(AND/OR),还能 fallback 到默认字段。
错误示例:['match' => ['title' => $keyword]] —— 用户搜 iPhone 15,ES 默认按空格分词,可能匹配到 title 含 iPhone 和 15 但不在一起的文档。
- 推荐写法:
['query_string' => ['query' => $keyword, 'default_field' => 'title^3 description brandName^2']] - 注意:
default_field值是字符串,不是数组;字段名后加^3表示权重 - 如果业务明确要求“必须完整匹配短语”,改用
match_phrase,但性能略低
参数白名单校验不是可选项,是必须项
ES 查询 DSL 是 JSON 结构,一旦把用户输入原样拼进 query,就等于开放了任意脚本执行入口。曾有项目因未校验 sort 字段,被传入 _script 导致 CPU 100%。
- 所有外部参数必须走白名单:只允许
keyword、category_id、price_from、sort_field等已知键名 -
sort_field值必须限制为['sales', 'price', 'created_at'],禁止传_score或脚本字段 - 类目 ID、品牌 ID 等数值型参数,强制用
(int)转换,防止注入"1 OR 1=1"类字符串 - 高亮字段名也要白名单控制,不能让用户传
highlight_fields=title,description,script:"..."
最易被忽略的是 filter 里的字段名本身——它不校验的话,攻击者可构造 category_path 以外的字段名触发未授权数据访问。


















