ElasticSearch - 基于 拼音分词器 和 IK分词器 模拟实现“百度”搜索框自动补全功能_自动补全的词条如何实现(4)

    "type": "text",
    "analyzer": "my_analyzer" //创建倒排索引使用 my_analyzer 分词器.
    "search_analyzer": "ik_smart"  //搜索时使用 ik_smart 分词器.
  }
}

}
}


### 1.4、completion suggester 查询


#### 1.4.1、基本概念和语法


es 中提供了 completion suggester 查询来实现自动补全功能.  这个查询会匹配用户输入内容开头的词条并返回.


为了提高补全查询的效率,对于文档中的字段类型有一些约束,如下:


1. 参与补全查询的字段必须是 completion 类型.
2. 参与 自动补全字段 的内容一般是多个词条形成的数组.




POST /test2/_search
{
“suggest”: {
“title_suggest”: { //自定义补全名
“text”: “s”, //用户在搜索框中输入的关键字
“completion”: { // completion 是自动补全中的一种类型(最常用的)
“field”: “补全时需要查询的字段名”, //这里的字段名指向的是一个数组(字段必须是 completion 类型),就是要根据数组中的字段进行查询,然后自动补全
“skip_duplicates”: true, //如果查询时有重复的词条,是否自动跳过(true 为跳过)
“size”: 10 // 获取前 10 条结果.
}
}
}
}



#### 1.4.2、示例一


这里我用一个示例来演示 completion suggester 的用法.


首先创建索引库(参与自动补全的字段类型必须是 completion).



PUT /test2
{
“mappings”: {
“properties”: {
“title”: {
“type”: “completion”
}
}
}
}


插入示例数据(字段内容一般是用来补全的多个词条形成的数组.)



POST test2/_doc
{
“title”: [“Sony”, “WH-1000XM3”]
}
POST test2/_doc
{
“title”: [“SK-II”, “PITERA”]
}
POST test2/_doc
{
“title”: [“Nintendo”, “switch”]
}


这里我们设置关键字为 "s",来自动补全查询,如下:



POST /test2/_search
{
“suggest”: {
“title_suggest”: {
“text”: “s”,
“completion”: {
“field”: “title”,
“skip_duplicates”: true,
“size”: 10
}
}
}
}


![](https://img-blog.csdnimg.cn/3c17ad6ebbbb4bcda9b7d65825618174.png)


![](https://img-blog.csdnimg.cn/37d7fef8eb4645409e8d713f6901a93c.png)



#### 1.4.3、示例二


首先创建索引库,如下参与自动补全的字段为 suggestion(通过 copy title 得到).



PUT /test
{
“mappings”: {
“properties”: {
“title”: {
“type”: “text”,
“analyzer”: “ik_smart”,
“copy_to”: “suggestion”
},
“suggestion”: {
“type”: “completion”
}
}
}
}


 插入示例数据



POST test/_doc/1
{
“title”: “今天天气真好”
}

POST test/_doc/2
{
“title”: “我想出去玩”
}

POST test/_doc/3
{
“title”: “我要去找小伙伴”
}


设置关键字为 "我"



POST /test/_search
{
“suggest”: {
“title_suggest”: {
“text”: “我”,
“completion”: {
“field”: “suggestion”,
“skip_duplicates”: true,
“size”: 10
}
}
}
}


自动补全效果如下


![](https://img-blog.csdnimg.cn/223058a1703f41eb93d373eae5dda1a2.png)



#### 1.4.4、示例(黑马旅游)


这里我们基于之前实现的黑马旅游案例来做栗子,实现步骤如下:


##### a)修改 hotel 索引库结构,设置自定义拼音分词器.


1.设置自定义分词器.


2. 修改索引库的 name、all 字段(建立倒排索引使用 拼音分词器,搜索时使用 ik 分词器).


3. 给索引库添加一个新字段 suggestion,类型为 completion 类型,使用自定义分词器.



PUT /hotel
{
“settings”: {
“analysis”: {
“analyzer”: {
“text_anlyzer”: {
“tokenizer”: “ik_max_word”,
“filter”: “py”
},
“completion_analyzer”: {
“tokenizer”: “keyword”,
“filter”: “py”
}
},
“filter”: {
“py”: {
“type”: “pinyin”,
“keep_full_pinyin”: false,
“keep_joined_full_pinyin”: true,
“keep_original”: true,
“limit_first_letter_length”: 16,
“remove_duplicated_term”: true,
“none_chinese_pinyin_tokenize”: false
}
}
}
},
“mappings”: {
“properties”: {
“id”:{
“type”: “keyword”
},
“name”:{
“type”: “text”,
“analyzer”: “text_anlyzer”,
“search_analyzer”: “ik_smart”,
“copy_to”: “all”
},
“address”:{
“type”: “keyword”,
“index”: false
},
“price”:{
“type”: “integer”
},
“score”:{
“type”: “integer”
},
“brand”:{
“type”: “keyword”,
“copy_to”: “all”
},
“city”:{
“type”: “keyword”
},
“starName”:{
“type”: “keyword”
},
“business”:{
“type”: “keyword”,
“copy_to”: “all”
},
“location”:{
“type”: “geo_point”
},
“pic”:{
“type”: “keyword”,
“index”: false
},
“all”:{
“type”: “text”,
“analyzer”: “text_anlyzer”,
“search_analyzer”: “ik_smart”
},
“suggestion”:{
“type”: “completion”,
“analyzer”: “completion_analyzer”
}
}
}
}



##### b)给 HotelDoc 类添加 suggestion 字段


suggestion 字段(包含多个字段的数组,这里可以使用 List 表示),内容包含 brand、business.



> 
> Ps:name、all 是可以分词的,自动补全的 brand、business 是不可分词的,要使用不同的分词器组合.
> 
> 
> 



@Data
@NoArgsConstructor
public class HotelDoc {
private Long id;
private String name;
private String address;
private Integer price;
private Integer score;
private String brand;
private String city;
private String starName;
private String business;
private String location;
private String pic;
private Object distance;
private Boolean isAD;
private List suggestion;

public HotelDoc(Hotel hotel) {
    this.id = hotel.getId();
    this.name = hotel.getName();
    this.address = hotel.getAddress();
    this.price = hotel.getPrice();
    this.score = hotel.getScore();
    this.brand = hotel.getBrand();
    this.city = hotel.getCity();
    this.starName = hotel.getStarName();
    this.business = hotel.getBusiness();
    this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
    this.pic = hotel.getPic();
    this.suggestion = new ArrayList<>();
    suggestion.add(brand);
    suggestion.add(business);
}

}



##### c)将数据重新导入到 hotel 索引库中


将 hotel 索引库删了,然后重建(a 中的 DSL).  通过单元测试将所有信息从数据库同步到 es 上.



@Test
public void testBulkDocument() throws IOException {
    //1.获取酒店所有数据
    List<Hotel> hotelList = hotelService.list();
    //2.构造请求
    BulkRequest request = new BulkRequest();
    //3.准备参数
    for(Hotel hotel : hotelList) {
        //转化为文档(主要是地理位置)
        HotelDoc hotelDoc = new HotelDoc(hotel);
        String json = objectMapper.writeValueAsString(hotelDoc);
        request.add(new IndexRequest("hotel").id(hotel.getId().toString()).source(json, XContentType.JSON));
    }
    //4.发送请求
    client.bulk(request, RequestOptions.DEFAULT);
}


##### d)基于 JavaRestClient 编写 DSL


例如自动补全关键为 "h" 的内容.



@Test
public void testSuggestion() throws IOException {
    //1.创建请求
    SearchRequest request = new SearchRequest("hotel");
    //2.准备参数
    request.source().suggest(new SuggestBuilder().addSuggestion(
        "testSuggestion",
            SuggestBuilders
                    .completionSuggestion("suggestion")
                    .prefix("h")
                    .skipDuplicates(true)
                    .size(10)
    ));
    //3.发送请求,接收响应
    SearchResponse search = client.search(request, RequestOptions.DEFAULT);
    //4.解析响应
    handlerResponse(search);
}

这里可以对应着 DSL 语句来写.


![](https://img-blog.csdnimg.cn/f538eb56bc7448c484718ad410480bfd.png)



对查询结果的处理如下:



    //4.处理自动补全结果
    Suggest suggest = response.getSuggest();
    if(suggest != null) {
        CompletionSuggestion suggestion = suggest.getSuggestion("testSuggestion");
        for (CompletionSuggestion.Entry.Option option : suggestion.getOptions()) {
            String text = option.getText().toString();
            System.out.println(text);
        }
    }

这里可以对应着 DSL 语句来写.


![](https://img-blog.csdnimg.cn/409b856fd1b74fdd84e2257a1db65d31.png)


运行结果如下:


![](https://img-blog.csdnimg.cn/9a4fb90e23834ab2ae2ef3c9c6b152f3.png)



### 1.5、黑马旅游案例


#### 1.5.1、需求


首先搜索框的自动补全功能.


最终实现效果就类似于 百度的搜索框,比如当我们输入 "byby",他就会立马自动补全出有关 byby 关键字的信息,如下图:


![](https://img-blog.csdnimg.cn/0f4191f3bf844db190cf4a9b523e3374.png)



#### 1.5.2、前端对接


在搜索框中输入,会触发以下请求. 这里前端就传入一个参数 key.


![](https://img-blog.csdnimg.cn/65d9fd488c49406f923c9917c1a567c8.png)


这里约定,返回的是一个 List,内容就是自动补全的所有信息.



#### 1.5.3、实现 controller


这里使用 @RequestParam 接收前端传入的参数,然后调用 IhotelService 接口处理即可.



@RequestMapping("/suggestion")
public List<String> suggestion(@RequestParam("key") String prefix) {
    return hotelService.suggestion(prefix);
}


#### 1.5.4、创建接口并实现.


在 IhotelService 接口中创建 suggestion 方法.



public interface IHotelService extends IService {

PageResult search(RequestParams params);

Map<String, List<String>> filters(RequestParams params);

List<String> suggestion(String prefix);

}


接着在 IhotelService 的实现类 HotelService 中实现该方法.


具体的实现,就和前面写的测试案例基本一致了~  要注意的点就是补全的关键字不是写死的,而是前端传入的 prefix.



@Override
public List<String> suggestion(String prefix) {
    try {
        //1.创建请求
        SearchRequest request = new SearchRequest("hotel");
        //2.准备参数
        request.source().suggest(new SuggestBuilder().addSuggestion(
                "mySuggestion",
                SuggestBuilders
                        .completionSuggestion("suggestion")
                        .prefix(prefix)
                        .skipDuplicates(true)
                        .size(10)
        ));
        //3.发送请求,接收响应
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //4.解析响应(处理自动补全结果)
        Suggest suggest = response.getSuggest();
        List<String> suggestionList = new ArrayList<>();
        if(suggest != null) {
            CompletionSuggestion suggestion = suggest.getSuggestion("mySuggestion");
            for (CompletionSuggestion.Entry.Option option : suggestion.getOptions()) {
                String text = option.getText().toString();
                suggestionList.add(text);
            }
        }
        return suggestionList;
    } catch (IOException e) {
        System.out.println("[HotelService] 自动补全失败!prefix=" + prefix);
        e.printStackTrace();
        return null;
    }
}

}

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

}
return suggestionList;
} catch (IOException e) {
System.out.println(“[HotelService] 自动补全失败!prefix=” + prefix);
e.printStackTrace();
return null;
}
}
}

[外链图片转存中…(img-jwxzErfb-1714699523377)]
[外链图片转存中…(img-ZRsdqTSM-1714699523378)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 29
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值