手打不易,如果转摘,请注明出处!
本文链接:

https://zhangxiaofan.blog.csdn.net/article/details/159434022


背景

因项目需要,APP的某个搜索框TAB,在原来搜索的基础上,需要添加新的搜索对象。

为方便理解,我们可以假设有如下需求:

现有微信 APP 通讯录搜索功能仅支持个人微信联系人检索;为满足用户一站式搜索需求,本次功能升级需扩展搜索范围,新增企业微信联系人检索能力。用户在通讯录搜索框中输入关键词时,需同时匹配个人微信与企业微信联系人,支持全拼、简拼、连写、分词等多种拼音检索方式,并按照业务规则排序、关键词高亮展示,提升搜索体验与效率。

简单的说,需求目标有2个:

  1. 数据源扩展:支持同时检索个人微信(WeChat)、企业微信(WeCom)两类本地联系人数据。

  2. 多维度拼音检索:覆盖中文全拼、简拼、连写拼音、原始中文分词四种检索方式,满足用户不同输入习惯

这个其实就是 搜索框合并多数据源搜索;假设个人微信(WeChat)和企业微信(WeCom)的联系人,都是存在手机或PC本地的Elasticsearch上。

步骤

本章节分三大模块逐步实现需求:先搭建个人微信联系人索引体系,再搭建企业微信联系人索引体系,最终实现双索引混合搜索,完整覆盖索引配置、分词器、查询模板、测试验证全流程。

WeChat联系人索引

索引创建

实现个人微信联系人的 Elasticsearch 搜索能力,核心是解决中文拼音多方式搜索、自定义排序。

创建索引

我们假设有age、sex、userName三个字段,希望用户在微信联系人中可以通过简拼、全拼、连写、分词等多种方式快速查找朋友。

PUT /wechat_contact
{
  "mappings": {
    "properties": {
      "age": {
        "type": "long"
      },
      "sex": {
        "type": "integer"
      },
      "userName": {
        "type": "text",
        "fields": {
          "keyword": {
            "type": "keyword",
            "ignore_above": 256
          },
          "jianpin": {
            "type": "text",
            "analyzer": "pinyinJianPinAnalyzer"
          },
          "jianpin_all": {
            "type": "text",
            "analyzer": "pinyinJianPinAllAnalyzer"
          },
          "quanpin": {
            "type": "text",
            "analyzer": "pinyinQuanPinAnalyzer"
          },
          "quanpin_all": {
            "type": "text",
            "analyzer": "pinyinQuanPinAllAnalyzer"
          }
        }
      }
    }
  },
  "settings": {
    "index": {
      "number_of_shards": "8",
      "number_of_replicas": "1"
    },
    "analysis": {
      "filter": {
        "pinyin_jianpin_filter": {
          "lowercase": "true",
          "keep_original": "false",
          "keep_first_letter": "false",
          "keep_separate_first_letter": "true",
          "type": "pinyin",
          "limit_first_letter_length": "50",
          "keep_full_pinyin": "false"
        },
        "pinyin_quanpin_all_filter": {
          "keep_joined_full_pinyin": "true",
          "lowercase": "true",
          "none_chinese_pinyin_tokenize": "false",
          "keep_original": "false",
          "remove_duplicated_term": "true",
          "keep_separate_first_letter": "false",
          "trim_whitespace": "true",
          "type": "pinyin",
          "limit_first_letter_length": "100",
          "keep_none_chinese_in_joined_full_pinyin": "true",
          "keep_first_letter": "false",
          "keep_none_chinese": "true",
          "keep_full_pinyin": "false"
        },
        "pinyin_quanpin_filter": {
          "lowercase": "true",
          "keep_original": "false",
          "keep_first_letter": "false",
          "keep_separate_first_letter": "false",
          "type": "pinyin",
          "keep_none_chinese": "true",
          "limit_first_letter_length": "100",
          "keep_full_pinyin": "true"
        },
        "pinyin_jianpin_all_filter": {
          "keep_separate_first_letter": "false",
          "none_chinese_pinyin_tokenize": "false",
          "type": "pinyin",
          "keep_none_chinese": "false",
          "limit_first_letter_length": "100",
          "keep_full_pinyin": "false"
        }
      },
      "analyzer": {
        "pinyinJianPinAllAnalyzer": {
          "filter": [
            "pinyin_jianpin_all_filter",
            "lowercase"
          ],
          "type": "custom",
          "tokenizer": "keyword"
        },
        "pinyinQuanPinAllAnalyzer": {
          "filter": [
            "pinyin_quanpin_all_filter",
            "lowercase"
          ],
          "type": "custom",
          "tokenizer": "keyword"
        },
        "pinyinQuanPinAnalyzer": {
          "filter": [
            "pinyin_quanpin_filter",
            "lowercase"
          ],
          "type": "custom",
          "tokenizer": "standard"
        },
        "pinyinJianPinAnalyzer": {
          "filter": [
            "pinyin_jianpin_filter",
            "lowercase"
          ],
          "type": "custom",
          "tokenizer": "standard"
        }
      },
      "tokenizer": {
        "ngram_tokenizer": {
          "token_chars": [],
          "min_gram": "1",
          "type": "ngram",
          "max_gram": "1"
        }
      }
    }
  }
}
索引解析
分析器 tokenizer filter 作用
pinyinJianPinAnalyzer standard pinyin_jianpin_filter + lowercase 将中文按字切分,每个字生成其拼音首字母(如“张” → z)。
pinyinJianPinAllAnalyzer keyword pinyin_jianpin_all_filter + lowercase 将整个输入作为一个词元,转换为拼音首字母连写(如“张小凡” → zxf)。
pinyinQuanPinAnalyzer standard pinyin_quanpin_filter + lowercase 将中文按字切分,每个字生成完整拼音(如“张” → zhang)。
pinyinQuanPinAllAnalyzer keyword pinyin_quanpin_all_filter + lowercase 将整个输入作为一个词元,转换为完整拼音连写(如“张小凡” → zhangxiaofan)。

实际效果分词可以用下面语句测试:

POST /wechat_contact/_analyze
{
  "text": "张小凡" ,
  "analyzer":"pinyinJianPinAnalyzer"
}

POST /wechat_contact/_analyze
{
  "text": "张小凡" ,
  "analyzer":"pinyinJianPinAllAnalyzer"
}

POST /wechat_contact/_analyze
{
  "text": "张小凡" ,
  "analyzer":"pinyinQuanPinAnalyzer"
}

POST /wechat_contact/_analyze
{
  "text": "张小凡" ,
  "analyzer":"pinyinQuanPinAllAnalyzer"
}

创建查询模板

查询模板用于简化搜索语句、提升开发效率,我们先编写原生查询语句,再封装为可复用的模板,同时实现女性联系人优先展示的业务排序需求。

原查询语句

个人微信联系人的原生搜索语句

{
  "query": {
    "bool": {
      "minimum_should_match": 1,
      "filter": [],
      "should": [
        {
          "term": {
            "sex":{
              "value": 0, // 假设业务要求女性优先展示
              "boost": 1000
            }
          }
        },
        {
          "dis_max": {
            "queries": [
              {
                "constant_score": {
                  "filter": {
                    "term": {
                      "userName.keyword": {
                        "value": "{$searchKey}"
                      }
                    }
                  },
                  "boost": 200
                }
              },
              {
                "constant_score": {
                  "filter": {
                    "match_phrase": {
                      "userName": "{$searchKey}"
                    }
                  },
                  "boost": 95
                }
              },
              {
                "constant_score": {
                  "filter": {
                    "match_phrase": {
                      "userName.quanpin": "{$searchKey}"
                    }
                  },
                  "boost": 90
                }
              },
              {
                "constant_score": {
                  "filter": {
                    "match_phrase": {
                      "userName.jianpin": "{$searchKey}"
                    }
                  },
                  "boost": 85
                }
              }
            ]
          }
        }
      ]
    }
  },
  "highlight": {
    "fields": {
      "userName": {
        "number_of_fragments": 0
      },
      "userName.jianpin": {
        "number_of_fragments": 0
      },
      "userName.keyword": {
        "number_of_fragments": 0
      },
      "userName.quanpin": {
        "number_of_fragments": 0
      },
      "userName.quanpin_all": {
        "number_of_fragments": 0
      }
    },
    "post_tags": [
      "</font>"
    ],
    "pre_tags": [
      "<font color='red'>"
    ]
  },
  "from": 1,
  "size": 10
}
创建模板

将原生查询语句封装为 ES 脚本模板,通过动态参数searchKey实现灵活搜索,简化后续测试与调用成本

POST _scripts/wechat_contact_search_template
{
  "script": {
    "params": {
      "searchKey": ""
    },
    "lang": "mustache",
    "source": {
      "query": {
        "bool": {
          "minimum_should_match": 1,
          "filter": [],
          "should": [
            {
              "term": {
                "sex":{
                  "value": 0, // 假设业务要求女性优先展示
                  "boost": 1000
                }
              }
            },
            {
              "dis_max": {
                "queries": [
                  {
                    "constant_score": {
                      "filter": {
                        "term": {
                          "userName.keyword": {
                            "value": "{{searchKey}}"
                          }
                        }
                      },
                      "boost": 100
                    }
                  },
                  {
                    "constant_score": {
                      "filter": {
                        "match_phrase": {
                          "userName.quanpin_all": "{{searchKey}}"
                        }
                      },
                      "boost": 90
                    }
                  },
                  {
                    "constant_score": {
                      "filter": {
                        "match_phrase": {
                          "userName.jianpin_all": "{{searchKey}}"
                        }
                      },
                      "boost": 80
                    }
                  },
                  {
                    "constant_score": {
                      "filter": {
                        "match_phrase": {
                          "userName": "{{searchKey}}"
                        }
                      },
                      "boost": 20
                    }
                  },
                  {
                    "constant_score": {
                      "filter": {
                        "match_phrase": {
                          "userName.quanpin": "{{searchKey}}"
                        }
                      },
                      "boost": 15
                    }
                  },
                  {
                    "constant_score": {
                      "filter": {
                        "match_phrase": {
                          "userName.jianpin": "{{searchKey}}"
                        }
                      },
                      "boost": 10
                    }
                  }
                ]
              }
            }
          ]
        }
      },
      "highlight": {
        "fields": {
          "userName": {
            "number_of_fragments": 0
          },
          "userName.jianpin": {
            "number_of_fragments": 0
          },
          "userName.keyword": {
            "number_of_fragments": 0
          },
          "userName.quanpin": {
            "number_of_fragments": 0
          },
          "userName.quanpin_all": {
            "number_of_fragments": 0
          }
        },
        "post_tags": [
          "</font>"
        ],
        "pre_tags": [
          "<font color='red'>"
        ]
      },
      "from": 0,
      "size": 10
    }
  }
}
模板解析
{
  "query": {
    "bool": {
      "minimum_should_match": 1,
      "filter": [],
      "should": [
        { "term": { "sex": { "value": 0, "boost": 1000 } } },        // 女性优先
        { "dis_max": { "queries": [ ... ] } }                         // 拼音匹配
      ]
    }
  },
  "highlight": { ... },
  "from": 0,
  "size": 10
}

为了方便测试,这里女性优先级最高:

  • sex 字段执行 term 查询,匹配值为 0(假设女性)的文档。
  • 赋予超高 boost = 1000,使得女性文档在得分上获得巨大优势,从而排在搜索结果前列。

内部包含六个 constant_score 查询,每个查询使用不同的字段进行精确匹配(termmatch_phrase),并赋予不同的固定得分(boost)。dis_max 会从所有匹配的子句中选取 最高得分 作为该子句的总得分。

子句 查询类型 字段 匹配逻辑 得分 (boost)
1 term userName.keyword 精确匹配整个字符串 100
2 match_phrase userName.quanpin_all 短语匹配连写全拼 90
3 match_phrase userName.jianpin_all 短语匹配连写简拼 80
4 match_phrase userName(text) 短语匹配原始中文分词 20
5 match_phrase userName.quanpin 短语匹配分词全拼 15
6 match_phrase userName.jianpin 短语匹配分词简拼 10

注意match_phrase 要求查询词经过该字段的分析器处理后生成的词项序列,与文档中该字段的词项序列完全一致(顺序相同)。term 则要求完全相等。

整体得分计算如下:

bool 查询中多个 should 子句的得分 相加。因此:

总分 = score(sex term) + score(dis_max)
  • score(sex term):由 term 查询本身的相关性得分(基于词频、逆文档频率等)乘以 boost=1000 得到。
  • score(dis_max)dis_max 内所有匹配子句的最高 boost(因为这些子句都是 constant_score,匹配即得固定分)。

查询测试

插入数据
POST /wechat_contact/_bulk
{"index":{"_id":1}}
{"userName":"张小凡","age":18,"sex":1}
{"index":{"_id":2}}
{"userName":"李张娜","age":25,"sex":0}
{"index":{"_id":3}}
{"userName":"吴张刚","age":32,"sex":1}
{"index":{"_id":4}}
{"userName":"刘洋","age":28,"sex":1}
{"index":{"_id":5}}
查询测试
POST /wechat_contact/_search/template
{
  "id": "wechat_contact_search_template",
  "params": {
    "searchKey": "张"
  }
}
结果

结果按照预期排序:女性联系人(sex=0)排在最前面,其他联系人按照匹配的精度排序。highlight 功能清晰地标出了匹配的部分

{
  "hits": {
    "total": {
      "value": 3,
      "relation": "eq"
    },
    "max_score": 1020.0,
    "hits": [
      {
        "_index": "wechat_contact",
        "_type": "_doc",
        "_id": "2",
        "_score": 1020.0,
        "_source": {
          "userName": "李张娜",
          "age": 25,
          "sex": 0
        }
      },
      {
        "_index": "wechat_contact",
        "_type": "_doc",
        "_id": "1",
        "_score": 20.0,
        "_source": {
          "userName": "张小凡",
          "age": 18,
          "sex": 1
        }
      },
      {
        "_index": "wechat_contact",
        "_type": "_doc",
        "_id": "3",
        "_score": 20.0,
        "_source": {
          "userName": "吴张刚",
          "age": 32,
          "sex": 1
        }
      }
    ]
  }
}

WeCom联系人索引

复刻个人微信联系人的配置逻辑,实现企业微信联系人的搜索能力,仅修改字段名区分数据源,保证双数据源搜索逻辑统一。

创建索引

创建企业微信联系人索引wecom_contact,拼音分词配置与个人微信完全一致,仅修改字段名(age2/userName2),保持搜索能力对齐。

PUT /wecom_contact
{
  "mappings": {
    "properties": {
      "age2": {
        "type": "long"
      },
      "sex": {
        "type": "integer"
      },
      "userName2": {
        "type": "text",
        "fields": {
          "keyword": {
            "type": "keyword",
            "ignore_above": 256
          },
          "jianpin": {
            "type": "text",
            "analyzer": "pinyinJianPinAnalyzer"
          },
          "jianpin_all": {
            "type": "text",
            "analyzer": "pinyinJianPinAllAnalyzer"
          },
          "quanpin": {
            "type": "text",
            "analyzer": "pinyinQuanPinAnalyzer"
          },
          "quanpin_all": {
            "type": "text",
            "analyzer": "pinyinQuanPinAllAnalyzer"
          }
        }
      }
    }
  },
  "settings": {
    "index": {
      "number_of_shards": "8",
      "number_of_replicas": "1"
    },
    "analysis": {
      "filter": {
        "pinyin_jianpin_filter": {
          "lowercase": "true",
          "keep_original": "false",
          "keep_first_letter": "false",
          "keep_separate_first_letter": "true",
          "type": "pinyin",
          "limit_first_letter_length": "50",
          "keep_full_pinyin": "false"
        },
        "pinyin_quanpin_all_filter": {
          "keep_joined_full_pinyin": "true",
          "lowercase": "true",
          "none_chinese_pinyin_tokenize": "false",
          "keep_original": "false",
          "remove_duplicated_term": "true",
          "keep_separate_first_letter": "false",
          "trim_whitespace": "true",
          "type": "pinyin",
          "limit_first_letter_length": "100",
          "keep_none_chinese_in_joined_full_pinyin": "true",
          "keep_first_letter": "false",
          "keep_none_chinese": "true",
          "keep_full_pinyin": "false"
        },
        "pinyin_quanpin_filter": {
          "lowercase": "true",
          "keep_original": "false",
          "keep_first_letter": "false",
          "keep_separate_first_letter": "false",
          "type": "pinyin",
          "keep_none_chinese": "true",
          "limit_first_letter_length": "100",
          "keep_full_pinyin": "true"
        },
        "pinyin_jianpin_all_filter": {
          "keep_separate_first_letter": "false",
          "none_chinese_pinyin_tokenize": "false",
          "type": "pinyin",
          "keep_none_chinese": "false",
          "limit_first_letter_length": "100",
          "keep_full_pinyin": "false"
        }
      },
      "analyzer": {
        "pinyinJianPinAllAnalyzer": {
          "filter": [
            "pinyin_jianpin_all_filter",
            "lowercase"
          ],
          "type": "custom",
          "tokenizer": "keyword"
        },
        "pinyinQuanPinAllAnalyzer": {
          "filter": [
            "pinyin_quanpin_all_filter",
            "lowercase"
          ],
          "type": "custom",
          "tokenizer": "keyword"
        },
        "pinyinQuanPinAnalyzer": {
          "filter": [
            "pinyin_quanpin_filter",
            "lowercase"
          ],
          "type": "custom",
          "tokenizer": "standard"
        },
        "pinyinJianPinAnalyzer": {
          "filter": [
            "pinyin_jianpin_filter",
            "lowercase"
          ],
          "type": "custom",
          "tokenizer": "standard"
        }
      },
      "tokenizer": {
        "ngram_tokenizer": {
          "token_chars": [],
          "min_gram": "1",
          "type": "ngram",
          "max_gram": "1"
        }
      }
    }
  }
}

插入数据

POST /wecom_contact/_bulk
{"index":{"_id":1}}
{"userName2":"张小凡","age2":18,"sex":1}
{"index":{"_id":2}}
{"userName2":"李张娜","age2":25,"sex":0}
{"index":{"_id":3}}
{"userName2":"吴张刚","age2":32,"sex":1}
{"index":{"_id":4}}
{"userName2":"刘洋","age2":28,"sex":1}
{"index":{"_id":5}}

创建模板

创建企业微信联系人专用查询模板,仅修改匹配字段为userName2,其余逻辑(权重、高亮、排序)与个人微信完全一致。

POST _scripts/wecom_contact_search_template
{
  "script": {
    "params": {
      "searchKey": ""
    },
    "lang": "mustache",
    "source": {
      "query": {
        "bool": {
          "minimum_should_match": 1,
          "filter": [],
          "should": [
            {
              "term": {
                "sex":{
                  "value": 0,
                  "boost": 1000
                }
              }
            },
            {
              "dis_max": {
                "queries": [
                  {
                    "constant_score": {
                      "filter": {
                        "term": {
                          "userName2.keyword": {
                            "value": "{{searchKey}}"
                          }
                        }
                      },
                      "boost": 100
                    }
                  },
                  {
                    "constant_score": {
                      "filter": {
                        "match_phrase": {
                          "userName2.quanpin_all": "{{searchKey}}"
                        }
                      },
                      "boost": 90
                    }
                  },
                  {
                    "constant_score": {
                      "filter": {
                        "match_phrase": {
                          "userName2.jianpin_all": "{{searchKey}}"
                        }
                      },
                      "boost": 80
                    }
                  },
                  {
                    "constant_score": {
                      "filter": {
                        "match_phrase": {
                          "userName2": "{{searchKey}}"
                        }
                      },
                      "boost": 20
                    }
                  },
                  {
                    "constant_score": {
                      "filter": {
                        "match_phrase": {
                          "userName2.quanpin": "{{searchKey}}"
                        }
                      },
                      "boost": 15
                    }
                  },
                  {
                    "constant_score": {
                      "filter": {
                        "match_phrase": {
                          "userName2.jianpin": "{{searchKey}}"
                        }
                      },
                      "boost": 10
                    }
                  }
                ]
              }
            }
          ]
        }
      },
      "highlight": {
        "fields": {
          "userName2": {
            "number_of_fragments": 0
          },
          "userName2.jianpin": {
            "number_of_fragments": 0
          },
          "userName2.keyword": {
            "number_of_fragments": 0
          },
          "userName2.quanpin": {
            "number_of_fragments": 0
          },
          "userName2.quanpin_all": {
            "number_of_fragments": 0
          }
        },
        "post_tags": [
          "</font>"
        ],
        "pre_tags": [
          "<font color='red'>"
        ]
      },
      "from": 0,
      "size": 10
    }
  }
}

查询测试

调用企业微信查询模板,验证单数据源搜索功能正常。

POST /wecom_contact/_search/template
{
  "id": "wecom_contact_search_template",
  "params": {
    "searchKey": "张"
  }
}

结果跟WeChat排序和得分类似,接下来我们看看,如何把2个不同的"数据源"(索引),放在一起搜索,同时按一定的得分规则排序。

多索引混合搜索(WeChat+WeCom)

实现同时搜索个人微信 + 企业微信两个索引,合并搜索结果,保持统一的排序、高亮、权重规则,完成业务需求。

原查询语句(多索引)

为了实现多索引混合搜索,我们将两个索引的匹配子句合并到同一个 dis_max 查询中。需要注意的是,由于两个索引的字段名不同(userName vs userName2),我们需要在查询中同时包含这两套字段的匹配条件。

{
  "query": {
    "bool": {
      "minimum_should_match": 1,
      "filter": [],
      "should": [
        {
          "term": {
            "sex": "0"
          },
          "boost": 140
        },
        {
          "dis_max": {
            "queries": [
              // ---------- 索引1的字段 ----------
              {
                "constant_score": {
                  "filter": {
                    "term": { "userName.keyword": { "value": "{{searchKey}}" } }
                  },
                  "boost": 200
                }
              },
              {
                "constant_score": {
                  "filter": {
                    "match_phrase": { "userName": "{{searchKey}}" }
                  },
                  "boost": 95
                }
              },
              {
                "constant_score": {
                  "filter": {
                    "match_phrase": { "userName.quanpin": "{{searchKey}}" }
                  },
                  "boost": 90
                }
              },
              {
                "constant_score": {
                  "filter": {
                    "match_phrase": { "userName.jianpin": "{{searchKey}}" }
                  },
                  "boost": 85
                }
              },
              // ---------- 索引2的字段 ----------
              {
                "constant_score": {
                  "filter": {
                    "term": { "userName2.keyword": { "value": "{{searchKey}}" } }
                  },
                  "boost": 200
                }
              },
              {
                "constant_score": {
                  "filter": {
                    "match_phrase": { "userName2": "{{searchKey}}" }
                  },
                  "boost": 95
                }
              },
              {
                "constant_score": {
                  "filter": {
                    "match_phrase": { "userName2.quanpin": "{{searchKey}}" }
                  },
                  "boost": 90
                }
              },
              {
                "constant_score": {
                  "filter": {
                    "match_phrase": { "userName2.quanpin_all": "{{searchKey}}" }
                  },
                  "boost": 90
                }
              }
            ]
          }
        }
      ]
    }
  },
  "highlight": {
    "fields": {
      "userName": { "number_of_fragments": 0 },
      "userName.jianpin": { "number_of_fragments": 0 },
      "userName.keyword": { "number_of_fragments": 0 },
      "userName.quanpin": { "number_of_fragments": 0 },
      "userName.quanpin_all": { "number_of_fragments": 0 },
      "userName2": { "number_of_fragments": 0 },
      "userName2.keyword": { "number_of_fragments": 0 },
      "userName2.quanpin": { "number_of_fragments": 0 },
      "userName2.quanpin_all": { "number_of_fragments": 0 }
    },
    "post_tags": ["</font>"],
    "pre_tags": ["<font color='red'>"]
  },
  "from": 0,
  "size": 10
}

创建查询模板(多索引)

封装双索引混合搜索模板,整合个人微信userName和企业微信userName2所有匹配规则,支持动态关键词搜索。

POST _scripts/mixed_contact_search_template
{
  "script": {
    "params": {
      "searchKey": ""
    },
    "lang": "mustache",
    "source": {
      "query": {
        "bool": {
          "minimum_should_match": 1,
          "filter": [],
          "should": [
            {
              "term": {
                "sex":{
                  "value": 0, 
                  "boost": 1000
                }
              }
            },
            {
              "dis_max": {
                "queries": [
                  {
                    "constant_score": {
                      "filter": {
                        "term": {
                          "userName.keyword": {
                            "value": "{{searchKey}}"
                          }
                        }
                      },
                      "boost": 200
                    }
                  },
                  {
                    "constant_score": {
                      "filter": {
                        "match_phrase": {
                          "userName": "{{searchKey}}"
                        }
                      },
                      "boost": 100
                    }
                  },
                  {
                    "constant_score": {
                      "filter": {
                        "match_phrase": {
                          "userName.quanpin": "{{searchKey}}"
                        }
                      },
                      "boost": 50
                    }
                  },
                  {
                    "constant_score": {
                      "filter": {
                        "match_phrase": {
                          "userName.jianpin": "{{searchKey}}"
                        }
                      },
                      "boost": 20
                    }
                  },
                  {
                    "constant_score": {
                      "filter": {
                        "term": {
                          "userName2.keyword": {
                            "value": "{{searchKey}}"
                          }
                        }
                      },
                      "boost": 200
                    }
                  },
                  {
                    "constant_score": {
                      "filter": {
                        "match_phrase": {
                          "userName2": "{{searchKey}}"
                        }
                      },
                      "boost": 100
                    }
                  },
                  {
                    "constant_score": {
                      "filter": {
                        "match_phrase": {
                          "userName2.quanpin": "{{searchKey}}"
                        }
                      },
                      "boost": 50
                    }
                  },
                  {
                    "constant_score": {
                      "filter": {
                        "match_phrase": {
                          "userName2.jianpin": "{{searchKey}}"
                        }
                      },
                      "boost": 20
                    }
                  }
                ]
              }
            }
          ]
        }
      },
      "highlight": {
        "fields": {
          "userName": {
            "number_of_fragments": 0
          },
          "userName.jianpin": {
            "number_of_fragments": 0
          },
          "userName.keyword": {
            "number_of_fragments": 0
          },
          "userName.quanpin": {
            "number_of_fragments": 0
          },
          "userName.quanpin_all": {
            "number_of_fragments": 0
          },
          "userName2": {
            "number_of_fragments": 0
          },
          "userName2.keyword": {
            "number_of_fragments": 0
          },
          "userName2.quanpin": {
            "number_of_fragments": 0
          },
          "userName2.quanpin_all": {
            "number_of_fragments": 0
          }
        },
        "post_tags": [
          "</font>"
        ],
        "pre_tags": [
          "<font color='red'>"
        ]
      },
      "from": 0,
      "size": 10
    }
  }
}

查询测试(多索引)

ES 支持多索引逗号分隔查询,调用混合搜索模板,同时查询wechat_contactwecom_contact两个索引

POST /wechat_contact,wecom_contact/_search/template
{
  "id": "mixed_contact_search_template",
  "params": {
    "searchKey": "zhang"
  }
}

查询结果

从结果中可以看到,两个索引的数据被无缝地合并在一起,且高亮功能在两个索引的字段上都能正常工作。证明了多索引混合搜索的可行性和实用性。最终我们完成了多索引混合搜索!

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 16,
    "successful" : 16,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 6,
      "relation" : "eq"
    },
    "max_score" : 1050.0,
    "hits" : [
      {
        "_index" : "wechat_contact",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1050.0,
        "_source" : {
          "userName" : "李张娜",
          "age" : 25,
          "sex" : 0
        },
        "highlight" : {
          "userName.quanpin" : [
            "李<font color='red'>张</font>娜"
          ]
        }
      },
      {
        "_index" : "wecom_contact",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1050.0,
        "_source" : {
          "userName2" : "李张娜",
          "age2" : 25,
          "sex" : 0
        },
        "highlight" : {
          "userName2.quanpin" : [
            "李<font color='red'>张</font>娜"
          ]
        }
      },
      {
        "_index" : "wechat_contact",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 50.0,
        "_source" : {
          "userName" : "张小凡",
          "age" : 18,
          "sex" : 1
        },
        "highlight" : {
          "userName.quanpin" : [
            "<font color='red'>张</font>小凡"
          ]
        }
      },
      {
        "_index" : "wecom_contact",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 50.0,
        "_source" : {
          "userName2" : "张小凡",
          "age2" : 18,
          "sex" : 1
        },
        "highlight" : {
          "userName2.quanpin" : [
            "<font color='red'>张</font>小凡"
          ]
        }
      },
      {
        "_index" : "wechat_contact",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 50.0,
        "_source" : {
          "userName" : "吴张刚",
          "age" : 32,
          "sex" : 1
        },
        "highlight" : {
          "userName.quanpin" : [
            "吴<font color='red'>张</font>刚"
          ]
        }
      },
      {
        "_index" : "wecom_contact",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 50.0,
        "_source" : {
          "userName2" : "吴张刚",
          "age2" : 32,
          "sex" : 1
        },
        "highlight" : {
          "userName2.quanpin" : [
            "吴<font color='red'>张</font>刚"
          ]
        }
      }
    ]
  }
}

总结

通过本文的实践,我们成功地将两个独立索引的搜索功能整合为一个统一的混合搜索。整个过程涵盖了索引设计、分析器配置、模板创建和混合查询等多个环节。这种方案具有以下优势:

  1. 数据隔离:不同数据源依然存储在不同的索引中,便于管理和维护。
  2. 逻辑统一:搜索逻辑在查询层统一,无需改动原有数据。
  3. 扩展性强:未来新增数据源时,只需在现有模板中添加对应的字段匹配条件即可。

希望本文的实践能够为有类似需求的开发者提供一些参考和启发!!!

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐