因为completion suggester专为前缀联想设计,基于fst毫秒响应、支持权重与上下文,而match_phrase需全文分词重排、延迟高且不精准;必须预先定义completion类型字段并正确配置analyzer,go客户端需严格按dsl结构调用,gin层须过滤es响应防xss和信息泄露。

为什么用 completion suggester 而不是 match_phrase?
关键词联想(如搜索框下拉提示)本质是前缀匹配 + 实时响应,match_phrase 会走全文分词 + 短语重排,延迟高、结果不精准;而 completion 类型专为 suggest 设计,底层用 FST(有限状态转换器),毫秒级返回、支持权重和上下文过滤。
常见错误是把 title 字段 mapping 设成 text 后硬套 completion 查询——ES 直接报 illegal_argument_exception: Field [title] is not a completion field。必须在索引创建时就声明为 completion 类型:
{
"mappings": {
"properties": {
"title_suggest": {
"type": "completion",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
}
}
}
}
注意:completion 字段不能和原字段复用,得单独建一个带 _suggest 后缀的字段,写入时同步填充。
Go 客户端里怎么发 completion 请求?
用 elastic/v7(ES 7.x)或 go-elasticsearch/v9(ES 8.0+)都行,但 DSL 结构固定,漏掉任意一层都会返回空建议:
-
Index:必须指定具体索引名,不能传"*"或空字符串 -
Suggest名称:任意字符串,但后续解析要对应,比如叫"title-suggest" -
Text:用户当前输入的关键词,如"人" -
Completion子句里的Field:必须和 mapping 里定义的字段名一致,如"title_suggest"
示例(elastic/v7):
res, err := client.Suggest().Index("blogs").Suggester(
elastic.NewCompletionSuggester("title-suggest").
Text("人").
Field("title_suggest").
Size(5),
).Do(ctx)
如果用 go-elasticsearch/v9,得手拼 JSON body 并调 esapi.SuggestRequest,且 Body 必须是 *bytes.Reader,别忘了 bytes.NewReader() 包一层。
中文联想不到“人工智能”?先查分词器和输入大小写
90% 的“输‘人工’没提示‘人工智能’”问题出在两处:
- mapping 里
completion字段没配analyzer,导致写入时“人工智能”被 standard 分词器切成["人工", "智能"],而用户输“人工”只能匹配到“人工”本身,无法触发整词联想 - 写入文档时,
title_suggest字段值是原始字符串(如"人工智能"),但查询时输的是小写"ren"或拼音首字母——completion不自动做大小写/拼音转换,得提前在 analyzer 链里加lowercase或pinyinfilter
验证方法:用 Kibana Console 执行 _analyze 看输入文本实际被切成啥:
GET /blogs/_analyze
{
"field": "title_suggest",
"text": "人工智能"
}
输出里如果没出现完整词条 "人工智能",说明 analyzer 配置没生效。
Gin 路由里怎么安全暴露 suggest 接口?
别直接把 ES 响应透传给前端,容易泄露内部字段或引发 XSS。建议在 Gin handler 里做三层过滤:
- 只取
suggest结果里的options[].text,丢弃score、_index等元信息 - 对
text做 HTML 转义(哪怕前端是纯 JSON,也防 future 意外渲染) - 加长度限制:用户输少于 1 个字符时直接返回空数组,避免高频空请求打满 ES
示例 handler 片段:
func (h *SearchHandler) Suggest(c *gin.Context) {
keyword := strings.TrimSpace(c.Query("q"))
if len(keyword)
<p>最易忽略的是超时控制——completion 查询虽快,但网络抖动或 ES 节点卡顿时,Gin handler 会一直等。务必用 <code>context.WithTimeout(ctx, 300*time.Millisecond)</code>,别依赖全局 HTTP 超时。</p>











