十、SpringBoot中使用 ElasticsearchTemplate 实施高亮搜索

  补充:

  • QueryBuilders.termQuery()是精确搜索,对检索词不分词
  • QueryBuilders.queryStringQuery(keyWord).defaultField(field) 先对检索词进行分词,再进行检索
     比如:搜索“斗罗大陆”,由于ik分词器将“斗罗大陆”分词为“斗”、“罗”、“大陆”和“陆”,若使用termQuery(),将搜索不出《斗罗大陆》,若使用queryStringQuery()则可以搜索出!!

  ElasticsearchTemplate是Springboot为我们自动装载的Elasticsearch模板,使用该模板,基本可以满足我们的搜索需求!

  案例背景介绍:用户输入一个关键词:“历史”,要求同时在索引库中“name”、“author”、"category"三个字段进行搜索。

package com.yuedu.service;

import com.yuedu.dao.BookMapper;
import com.yuedu.entity.Book;
import com.yuedu.entity.Category;
import com.yuedu.entity.ResultEnum;
import com.yuedu.exception.BaseException;
import com.yuedu.mapper.BookVo2Book;
import com.yuedu.util.StringUtil;
import com.yuedu.vo.BookVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.lang.reflect.Method;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * @author 咸鱼
 * @date 2019-01-25 11:08
 */
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@Slf4j
public class BookService {

    private final BookMapper bookMapper;
    private final CategoryService categoryService;

    private final ElasticsearchTemplate elasticsearchTemplate;

    private static final String BOOK_NAME = "name";
    private static final String AUTHOR = "author";
    private static final String CATEGORY = "category";
    private static final Integer KEY_WORLD_MAX_LENGTH = 20;

    /**
     * 根据关键词查书籍
     * @param keyWord 关键字
     * @param start 分页开始下标
     * @param size 每页数量
     * @return {@link Book}
     */
    public List<Book> searchBooks(String keyWord, Integer start, Integer size){
        if (StringUtils.isBlank(keyWord)){
            throw new BaseException(ResultEnum.PARAM_ERROR);
        }
        //处理搜索的关键词,避免过长
        if (keyWord.length() > KEY_WORLD_MAX_LENGTH){
            keyWord = keyWord.substring(0, KEY_WORLD_MAX_LENGTH);
        }
        return highLightQuery(keyWord, start, size);
    }

    /**
     * 高亮查询
     * @param keyWord 查询关键词
     * @param start 开始下标
     * @param size 每页数量
     * @return {@link Book}
     */
    private List<Book> highLightQuery(String keyWord, Integer start, Integer size) {
        /*1.创建QueryBuilder(即设置查询条件)这儿创建的是组合查询(也叫多条件查询),后面会介绍更多的查询方法
	     *组合查询BoolQueryBuilder
	     * must(QueryBuilders)   :AND
	     * mustNot(QueryBuilders):NOT
	     * should:               :OR
	     */
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        queryBuilder.should(QueryBuilders.termQuery(BOOK_NAME, keyWord))
                .should(QueryBuilders.termQuery(AUTHOR, keyWord))
                .should(QueryBuilders.termQuery(CATEGORY, keyWord));

        //设置分页(从第一页开始,一页显示10条)
        //注意开始是从0开始,有点类似sql中的方法limit 的查询
        Pageable page = PageRequest.of(start, size);

        //构建查询
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                //设置高亮字段
                .withHighlightFields(new HighlightBuilder.Field(BOOK_NAME),
                        new HighlightBuilder.Field(AUTHOR),
                        new HighlightBuilder.Field(CATEGORY))
                .withPageable(page)
                .build();

        //实施查询,注意:这里的泛型最后和 elasticsearch 中的字段对应
        AggregatedPage<BookVo> bookPage = elasticsearchTemplate.queryForPage(searchQuery, BookVo.class, new SearchResultMapper() {
            @Override
            public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
                List<BookVo> bookVoList = new ArrayList<>();
                //命中记录
                SearchHits hits = response.getHits();
                for (SearchHit hit : hits){
                    if (hits.totalHits <= 0){
                        return null;
                    }
                    BookVo bookVo = new BookVo();
                    bookVo.setId(hit.getId());
                    bookVo.setName(String.valueOf(hit.getSource().get("name")));
                    bookVo.setAuthor(String.valueOf(hit.getSource().get("author")));
                    bookVo.setCover_img(String.valueOf(hit.getSource().get("cover_img")));
                    bookVo.setCreate_date(formateDate(String.valueOf(hit.getSource().get("create_date"))));

                    //设置高亮(若对应字段有高亮的话)
                    setHighLight(hit, BOOK_NAME, bookVo);
                    setHighLight(hit, AUTHOR, bookVo);

                    bookVoList.add(bookVo);
                }
                return new AggregatedPageImpl<>((List<T>)bookVoList);
            }
        });
        //POJO 和 VO 转换
        return BookVo2Book.MAPPER.bookVos2Books(bookPage.getContent()) ;
    }

    /**
     * 设置高亮
     * @param hit 命中记录
     * @param filed 字段
     * @param object 待赋值对象
     */
    private static void setHighLight(SearchHit hit, String filed, Object object){
        //获取对应的高亮域
        Map<String, HighlightField> highlightFields = hit.getHighlightFields();
        HighlightField highlightField = highlightFields.get(filed);
        if (highlightField != null){
            //取得定义的高亮标签
            String highLightMessage = highlightField.fragments()[0].toString();
            // 反射调用set方法将高亮内容设置进去
            try {
                String setMethodName = parSetMethodName(filed);
                Class<?> Clazz = object.getClass();
                Method setMethod = Clazz.getMethod(setMethodName, String.class);
                setMethod.invoke(object, highLightMessage);
            } catch (Exception e) {
                log.error("反射报错", e);
            }
        }
    }

    /**
     * 根据字段名,获取Set方法名
     * @param fieldName 字段名
     * @return  Set方法名
     */
    private static String parSetMethodName(String fieldName){
        if (StringUtils.isBlank(fieldName)){
            return null;
        }
        int startIndex = 0;
        if (fieldName.charAt(0) == '_'){
            startIndex = 1;
        }
        return "set" + fieldName.substring(startIndex, startIndex + 1).toUpperCase()
                + fieldName.substring(startIndex + 1);
    }

    private boolean insertBook(Book book){
        return bookMapper.insertBook(book) == 1;
    }

    /**
     * 将 yyyy-MM-dd'T'HH:mm:ss.SSS Z 转换成 Date
     */
    private static Date formateDate(String dateStr){
        SimpleDateFormat format = null;
        try {
            if (dateStr.endsWith("Z")){
                dateStr = dateStr.replace("Z", " UTC");
                format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS Z");
                return format.parse(dateStr);
            } else {
                format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                return format.parse(dateStr);
            }
        } catch (ParseException e) {
            log.error("转换时间失败!", e);
        }
        return null;
    }
}

  上面用到的 QueryBuilders.termQuery(BOOK_NAME, keyWord) 是查询的一种,叫精确查询,关于其它查询,参见:

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值