es 9.x 必须严格区分 bool 查询的 must(影响评分)和 filter(不评分、可缓存),电商搜索中关键词用 must,状态/类目/价格等确定性条件必须放 filter;category_path 需设为 keyword 类型并用 prefix 查询;laravel 参数须白名单校验;聚合应与主查询分离以避免筛选污染。

bool 查询必须拆开 must 和 filter,否则评分错乱
ES 9.x 要求 bool 查询严格区分逻辑作用:must 影响相关性打分,filter 不打分且可被缓存。电商搜索里,关键词匹配(如 multi_match)必须放 must,而上架状态、类目 ID、价格区间这些确定性条件必须塞进 filter。
常见错误是把 term 或 range 写进 must 里,结果导致所有商品得分被拉平,排序失效;或者漏掉 filter 导致查询无法利用 query cache,QPS 下降明显。
-
must里只放全文检索项,例如:['multi_match' => ['query' => $kw, 'fields' => ['title^3', 'brand^2']]] -
filter放确定性条件,例如:['term' => ['status' => 'on_sale']]、['range' => ['price' => ['gte' => $min, 'lte' => $max]]] - 如果用了
should(比如“品牌A 或 品牌B”),必须显式加'minimum_should_match' => 1,否则可能返回空结果
category_path 用 prefix 而不是 term,才能支持类目树筛选
电商类目是树形结构,比如 “1-2-5-” 表示一级类目 1 → 二级 2 → 三级 5。用户点进二级类目时,要查所有下属三级商品,term 只能精确匹配完整路径,根本做不到;prefix 才能天然支持前缀匹配。
但前提是字段 mapping 必须设为 "type": "keyword",否则 prefix 查不到内容。别在 text 类型上硬套 prefix —— 分词后字符串被切碎,前缀就没了。
- mapping 示例:
"category_path": {"type": "keyword"} - 查询写法:
['prefix' => ['category_path' => '1-2-']](查所有二级类目 2 下的商品) - 不推荐用
wildcard替代,性能差且易触发慢查询
Laravel 中参数校验必须白名单,不能直接拼 DSL
ES 没有预编译机制,用户输入若直接塞进 query body,可能通过 script、regexp 或恶意 function_score 触发注入或 DoS。实际项目中,所有外部参数都要过白名单校验。
- 关键词先
trim()+strip_tags()+mb_substr($kw, 0, 100)截断 - 排序字段限定在数组:
['price', 'sales_count', 'created_at'],禁止传_script或_doc - 分页参数
from/size做intval()校验,size≤ 100,from + size≤ 10000 - 类目 ID、品牌 ID 等枚举值,用
in_array($catId, $allowedCats, true)二次确认
聚合结果要和主查询分离,避免干扰商品列表
侧边栏的筛选项(品牌列表、价格区间、颜色分布)靠 aggs 返回,但很多人把 aggs 和 query 写在同一请求里,结果发现聚合结果被主查询的 filter 过滤掉了——比如用户已选“品牌A”,聚合出来的品牌列表只剩 A,失去筛选意义。
正确做法是发两个请求:一个带完整 filter 查商品列表;另一个去掉用户已选的 filter 条件(保留基础状态过滤如 status),只留类目路径等上下文,再跑一次带 aggs 的请求。
- 品牌聚合示例:
"brands": {"terms": {"field": "brand_id", "size": 20}} - 价格区间聚合:
"price_ranges": {"range": {"field": "price", "ranges": [{"to": 100}, {"from": 100, "to": 500}, {"from": 500}]}} - 注意:聚合字段必须是
keyword类型,text字段默认不开fielddata,会报fielddata=true错误











