spring boot使用elasticsearch分词,排序,分页,高亮简单示例

作者: adm 分类: java 发布时间: 2024-03-12

记,写一个简单的es分词demo,es版本6.8.12

如果使用es7有些方法可能会有所改变,请参考7的文档

1. 创建ES实体
怎么简单怎么来

@Data
@Document(indexName = "goods")
public class GoodsEsItem implements Serializable {
    //主键
    @Id
    private Long id;

    @Field(type = FieldType.Text, analyzer = "ik_max_word")  //商品名
    private String name;
    
    private String category;


    @Field(type = FieldType.Date)
    private Date create_time;

    @Field(type = FieldType.Date)
    private Date update_time;

}

2. 创建查询实体

@Data
public class GoodsQueryVo {
    /**
     * 关键字
     */
    private String keyword;

    /**
     * 分类
     */
    private String category;


    /**
     * 当前页
     */
    private Integer current;

    /**
     * 每页大小
     */
    private Integer pageSize;

    /**
     * 排序字段
     */
    private String sfield;

    /**
     * 排序格式asc,desc
     */
    private String sm;
}

3. 查询方法实现
3.1 核心代码

@Override
public Map searchByKeyword(GoodsQueryVo goodsQueryVo) {
    	//构建查询条件
        NativeSearchQueryBuilder queryBuilder = queryBuilder(goodsQueryVo);

        //添加高亮域
        HighlightBuilder.Field field = new HighlightBuilder.
                Field("name").                      //指定的高亮域
                preTags("").   //前缀
                postTags("").                      //后缀
                fragmentSize(100);
        queryBuilder.withHighlightFields(field);
        //执行搜索。HighlightResultMapper处理高亮文本
        AggregatedPage results = elasticsearchRestTemplate.queryForPage(queryBuilder.build(), PblGoodsEsItem.class, new HighlightResultMapper());
        //结果集
        Map resultMap = new HashMap();
        resultMap.put("list", results.getContent());
        resultMap.put("total", results.getTotalElements());
        return resultMap;
}

3.2 构建查询条件
商品名称加了 @Field(type = FieldType.Text, analyzer = “ik_max_word”) 会自动分词

private NativeSearchQueryBuilder queryBuilder(GoodsQueryVo goodsQueryVo) {
    //关键字分词
    EsIkResult esIkResult = keywordToken(goodsQueryVo);
    List tokens = esIkResult.getTokens().stream().map(EsIkResult.token::getToken).collect(Collectors.toList());
    //QueryBuilder构建
    NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

    //多条件组合查询对象
    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

    //关键字条件should构建or条件,must构建and条件
    BoolQueryBuilder keywordQuery = QueryBuilders.boolQuery();
    tokens.forEach(e -> {
        keywordQuery.should(QueryBuilders.termQuery("name", e));
    });
	
    //分类条件
    if (!StringUtils.isEmpty(goodsQueryVo.getCategory())) {
        boolQuery.must(QueryBuilders.termQuery("category",goodsQueryVo.getCategory()));
    }
    //这里的关系为(keyword1 or keyword2) and category
    boolQuery.must(keywordQuery);
    //分页
    queryBuilder.withPageable(PageRequest.of(currentPage(goodsQueryVo), goodsQueryVo.getPageSize()));
    //排序
    String sfield = goodsQueryVo.getSfield();
    String sm = goodsQueryVo.getSm();
    if (!StringUtils.isEmpty(sfield) && !StringUtils.isEmpty(sm)) {
        queryBuilder.withSort(
            SortBuilders.fieldSort(sfield)   //排序域
            .order(SortOrder.valueOf(sm)));  //排序方式
    }
    return queryBuilder.withQuery(boolQuery);
}

分页处理

public int currentPage(GoodsQueryVo goodsQueryVo) {
    try {
        Object currentPage = goodsQueryVo.getCurrent();
        return Integer.parseInt(currentPage.toString()) > 0 ? Integer.parseInt(currentPage.toString()) - 1 : 0;
    } catch (Exception e) {
        return 0;
    }
}

3.2.1 关键词分词
当输入衣服鞋子的时候会将关键字分为衣服,鞋子去查询

@Data
public class EsIkResult {

    private List tokens;

    @Data
    public static class token{
        private String token;

        @JSONField(name = "start_offset")
        private Integer startOffset;

        @JSONField(name = "end_offset")
        private Integer endOffset;

        private String type;

        private Integer position;
    }
}

HttpUtil是自己封装的http请求类,可以根据自己的方式去发出请求

private EsIkResult keywordToken(GoodsQueryVo goodsQueryVo) {
    HashMap params = new HashMap<>();
    params.put("analyzer", "ik_max_word");
    params.put("text", goodsQueryVo.getKeyword());
    return HttpUtil.post("http://localhost:9200/_analyze?pretty", params, EsIkResult.class);
}

3.3 高亮处理
高亮处理mapper,处理高亮数据,复制用即可,不需要记

public class HighlightResultMapper extends DefaultResultMapper {
    /***
     * 处理结果集
     */
    @Override
    public  AggregatedPage mapResults(SearchResponse response, Class clazz, Pageable pageable) {
        //所有数据
        for (SearchHit hit : response.getHits()) {
            //当前单条数据
            Map sourceMap = hit.getSourceAsMap();
            //高亮数据
            for (Map.Entry entry : hit.getHighlightFields().entrySet()) {
                String key = entry.getKey();
                if (sourceMap.containsKey(key)) {
                    Text[] fragments = entry.getValue().getFragments();
                    sourceMap.put(key, transTextArrayToString(fragments));
                }
            }
            hit.sourceRef(new ByteBufferReference(ByteBuffer.wrap(JSONObject.toJSONString(sourceMap).getBytes())));
        }
        return super.mapResults(response, clazz, pageable);
    }

    /***
     * 拼接数据碎片
     */
    private String transTextArrayToString(Text[] fragments) {
        if (null == fragments) {
            return "";
        }
        StringBuffer buffer = new StringBuffer();
        for (Text fragment : fragments) {
            buffer.append(fragment.string());
        }
        return buffer.toString();
    }
}

添加高亮,在上面·searchByKeyword方法有写到

此处会将name字段含有关键字的文本替换成 包含,前端用html回显即可

示例,搜索衣服

//添加高亮域
HighlightBuilder.Field field = new HighlightBuilder.
        Field("name").                      //指定的高亮域
        preTags("").   //前缀
        postTags("").                      //后缀
        fragmentSize(100);
queryBuilder.withHighlightFields(field);
//执行搜索。HighlightResultMapper处理高亮文本
AggregatedPage results = elasticsearchRestTemplate.queryForPage(queryBuilder.build(), PblGoodsEsItem.class, new HighlightResultMapper());

4.完整查询代码展示
ps:

高亮处理代码上方已给出,此处只给出搜索代码

HttpUtil是自己封装的http请求类,可以根据自己的方式去发出请求

@Service
public class GoodsEsServiceImpl implements GoodsEsService {

    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    @Override
    public Map searchByKeyword(GoodsQueryVo goodsQueryVo) {
        //构建查询条件
        NativeSearchQueryBuilder queryBuilder = queryBuilder(goodsQueryVo);

        //添加高亮域
        HighlightBuilder.Field field = new HighlightBuilder.
                Field("name").                      //指定的高亮域
                preTags("").   //前缀
                postTags("").                      //后缀
                fragmentSize(100);
        queryBuilder.withHighlightFields(field);
        //执行搜索。HighlightResultMapper处理高亮文本
        AggregatedPage results = elasticsearchRestTemplate.queryForPage(queryBuilder.build(), PblGoodsEsItem.class, new HighlightResultMapper());
        //结果集
        Map resultMap = new HashMap();
        resultMap.put("list", results.getContent());
        resultMap.put("total", results.getTotalElements());
        return resultMap;
    }

    private NativeSearchQueryBuilder queryBuilder(GoodsQueryVo goodsQueryVo) {
        //关键字分词
        EsIkResult esIkResult = keywordToken(goodsQueryVo);
        List tokens = esIkResult.getTokens().stream().map(EsIkResult.token::getToken).collect(Collectors.toList());
        //QueryBuilder构建
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        //多条件组合查询对象
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        //关键字条件should构建or条件,must构建and条件
        BoolQueryBuilder keywordQuery = QueryBuilders.boolQuery();
        tokens.forEach(e -> {
            keywordQuery.should(QueryBuilders.termQuery("name", e));
        });

        //分类条件
        if (!StringUtils.isEmpty(goodsQueryVo.getCategory())) {
            boolQuery.must(QueryBuilders.termQuery("category",goodsQueryVo.getCategory()));
        }
        //这里的关系为(keyword1 or keyword2) and category
        boolQuery.must(keywordQuery);
        //分页
        queryBuilder.withPageable(PageRequest.of(currentPage(goodsQueryVo), goodsQueryVo.getPageSize()));
        //排序
        String sfield = goodsQueryVo.getSfield();
        String sm = goodsQueryVo.getSm();
        if (!StringUtils.isEmpty(sfield) && !StringUtils.isEmpty(sm)) {
            queryBuilder.withSort(
                    SortBuilders.fieldSort(sfield)   //排序域
                            .order(SortOrder.valueOf(sm)));  //排序方式
        }
        return queryBuilder.withQuery(boolQuery);
    }

    public int currentPage(GoodsQueryVo goodsQueryVo) {
        try {
            Object currentPage = goodsQueryVo.getCurrent();
            return Integer.parseInt(currentPage.toString()) > 0 ? Integer.parseInt(currentPage.toString()) - 1 : 0;
        } catch (Exception e) {
            return 0;
        }
    }

    private EsIkResult keywordToken(GoodsQueryVo goodsQueryVo) {
        HashMap params = new HashMap<>();
        params.put("analyzer", "ik_max_word");
        params.put("text", goodsQueryVo.getKeyword());
        return HttpUtil.post("http://localhost:9200/_analyze?pretty", params, EsIkResult.class);
    }
}

如果觉得我的文章对您有用,请随意赞赏。您的支持将鼓励我继续创作!