laravel搜索框下拉提示优先用completion suggester,需字段映射为type: "completion"并配置ik+pinyin自定义analyzer,写入时title必须为数组,后端须严格校验输入参数防注入。

用 completion suggester 实现前缀补全最轻量
直接上结论:Laravel 项目里做搜索框下拉提示词,优先选 completion suggester,不是 prefix 查询,也不是 match_phrase_prefix。它专为补全设计,延迟低、内存友好、支持权重排序,且天然跳重(skip_duplicates)。
关键限制是:字段必须声明为 "type": "completion",不能复用已有 text 或 keyword 字段。建索引时就得定好,后期改 mapping 需 reindex。
常见错误现象:search_phase_execution_exception 提示 field [title] is not a completion field,就是 mapping 没配对;或者返回空数组,大概率是文档没写入或 analyzer 不匹配。
- 映射定义示例(Laravel 中用
Http::put()或 kibana 执行):{"mappings":{"properties":{"title":{"type":"completion","analyzer":"ik_smart","search_analyzer":"ik_smart"}} - 写入文档时,
title必须是数组:{"title": ["笔记本电脑", "笔记本支架"]},单字符串会报错 - 查询 DSL 中
text值要 trim 并限制长度(mb_substr($kw, 0, 20)),避免超长触发too_long_frame_exception
中文拼音补全必须配 pinyin 分词器 + ik 联合分析
只用 ik_smart,用户输“bi ji ben”,根本搜不到“笔记本”——因为 ik 不产出拼音 token。必须在 mapping 里给 completion 字段配自定义 analyzer,把 ik_max_word 和 pinyin filter 串起来。
容易踩的坑:装了拼音插件但没重启 ES 容器,或者 mapping 里漏写 "search_analyzer",导致查询时用默认 standard 分词,拼音 token 全丢光。
- 推荐 analyzer 配置(注意
keep_original: true保留原文,否则只返回拼音):{"analysis":{"analyzer":{"my_completion_analyzer":{"tokenizer":"ik_max_word","filter":["pinyin_filter"]}},"filter":{"pinyin_filter":{"type":"pinyin","keep_full_pinyin":false,"keep_joined_full_pinyin":true,"keep_original":true,"limit_first_letter_length":16,"remove_duplicated_term":true}}}} - 字段 mapping 中引用该 analyzer:
"analyzer": "my_completion_analyzer", "search_analyzer": "my_completion_analyzer" - 测试分词是否生效:
POST /your_index/_analyze?analyzer=my_completion_analyzer输入“笔记本”,应看到["bi ji ben", "笔记本"]等多个 term
Laravel 后端请求 ES 的安全边界必须卡死
用户输入直接拼进 suggest DSL 是高危操作。ES 没有预编译机制,恶意输入如 "a\"} } }, \"script\": {\"source\": \"...\"} 可能触发任意代码执行(尤其老版本 ES)。
别信“前端校验就够了”。后端必须做三道过滤:参数白名单、长度截断、字符清洗。
- 只允许传
q(搜索词)、size(最多返回几条)、field(限定字段名,如title或sku),其他一律 ignore -
size强制转为 int 并限制在1..20区间,防 deep pagination - 关键词先
strip_tags()再mb_substr($q, 0, 30),中文按字节截易乱码,务必用mb_*函数 - DSL 构造不用字符串拼接,用 PHP 数组 +
json_encode(),避免引号逃逸漏洞
前端 debounce 和 loading 状态不处理,体验直接崩
ES suggest 接口本身快(通常
另一个隐形坑:没设 loading 状态,用户快速删字再输,旧请求还在跑,新请求结果覆盖旧结果前,UI 会短暂显示空列表或脏数据。
- 建议 debounce 时间设为
200ms,太短压不住抖动,太长(如 500ms)用户感知卡顿 - 每次新请求发出前,用
AbortController主动 cancel 上一个未完成的 fetch - 下拉容器加
min-height和opacity过渡,避免高度突变引起页面跳动 - ES 返回空数组时,不要清空 input,而是保持 placeholder 提示“暂无匹配”,否则用户以为输入失效











