IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> ElasticSearch在分布式项目中的使用 -> 正文阅读

[大数据]ElasticSearch在分布式项目中的使用

ElasticSearch的使用

概念

ElasticSearch是一个基于Lucene的搜索搜索引擎。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。

同类产品 solr

直奔主题,整体思路

使用目的: 作为门户网站的分布式项目,其中的搜索栏,如果使用模糊搜索,不仅效率低,且对数据库造成较大的压力,千万级的数据无法在短时间内搜索,用户体验不好,所以必须使用ElasticSearch来帮助我们开发搜索模块

思路如下 图所示:

在这里插入图片描述

开发商品模块接口,目的: 通过数据库查询,获取商品信息,届时通过OpenFeign声明接口,给search模块微服务调用,用来初始化ES中的index索引

1. 商品微服务的接口开发

a. 正常接口开发: 使用的是MybatisPlus去连接Mysql的数据库,在对应的表,mall_goods , mall_goods_brand , mall_goods_cat 通过三表的连查去查找所有审核通过的商品信息

b. 使用OpenFeign的接口声明,去声明接口,给其他需要的微服务调用

goods微服务中的依赖

<!-- mvc 起步依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</depedency>
<!-- nacos的注册与发现 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</depedency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</depedency>
<!-- 端点监控 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</depedency>
<!-- druid起步依赖 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.10</version>
</depedency>
<!-- mysql驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</depedency>
<!-- mybatisplus 起步依赖 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.0</version>
</depedency>
<!-- entity依赖 -->
<dependency>
    <groupId>com.fengmi</groupId>
    <artifactId>fengmi-entity</artifactId>
    <version>1.0-SNAPSHOT</version>
</depedency>

goods微服务中的mapper层

public interface MallGoodsMapper extends BaseMapper<MallGoods> {

    // 查询所有审核通过的商品
    public List<MallGoods> findAllGoodsAudited();
}

goods微服务中的mapper层映射文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.fengmi.mapper.MallGoodsMapper">
    <!-- public List<MallGoods> findAllGoodsAudited(); -->
    <resultMap id="findAllGoodsAuditedMap" type="mallgoods">
        <id column="spu_id" property="spuId"/>
        <result column="goods_name" property="goodsName"/>
        <result column="price" property="price"/>
        <result column="album_pics" property="albumPics"/>
        <association property="mallGoodsBrand" javaType="mallGoodsBrand">
            <id column="brandId" property="id"/>
            <result column="brandName" property="name"/>
        </association>
        <association property="cat1" javaType="MallGoodsCat">
            <id column="cat1Id" property="id"/>
            <result column="cat1Name" property="name"/>
        </association>
        <association property="cat2" javaType="MallGoodsCat">
            <id column="cat2Id" property="id"/>
            <result column="cat2Name" property="name"/>
        </association>
        <association property="cat3" javaType="MallGoodsCat">
            <id column="cat3Id" property="id"/>
            <result column="cat3Name" property="name"/>
        </association>
    </resultMap>

    <select id="findAllGoodsAudited" resultMap="findAllGoodsAuditedMap">
        SELECT
        goods.spu_id,
        goods.goods_name,
        goods.price,
        goods.album_pics,
        goods.brand_id AS brandId,
        brand.name AS brandName,
        goods.category1_id AS cat1Id,
        cat1.name AS cat1Name,
        goods.category2_id AS cat2Id,
        cat2.name AS cat2Name,
        goods.category3_id AS cat3Id,
        cat3.name AS cat3Name
        FROM mall_goods goods
        LEFT JOIN mall_goods_brand brand ON goods.brand_id = brand.id
        LEFT JOIN mall_goods_cat cat1 ON goods.category1_id = cat1.id
        LEFT JOIN mall_goods_cat cat2 ON goods.category2_id = cat2.id
        LEFT JOIN mall_goods_cat cat3 ON goods.category3_id = cat3.id
        WHERE goods.audit_status = 1;
    </select>
</mapper>

goods微服务中的service层

public interface IMallGoodsService extends IService<MallGoods> {

    // 查询所有审核通过上线的商品信息
    public List<MallGoods> findAllGoodsInfo();
}
@Service
public class MallGoodsServiceImpl extends ServiceImpl<MallGoodsMapper, MallGoods> implements IMallGoodsService {

    @Override
    public List<MallGoods> findAllGoodsInfo() {
        return this.baseMapper.findAllGoodsAudited();
    }
}

goods微服务中的controller层

@RestController
@RequestMapping("/goods")
public class MallGoodsController {

    @Autowired
    private IMallGoodsService goodsService;

    @GetMapping("findAllGoodsInfo")
    public List<MallGoods> findAllGoodsInfo() {
        return goodsService.findAllGoodsInfo();
    }
}

goods微服务进行OpenFeign的接口声明

这里需要创建一个模块,专门用于OpenFeign的接口声明,注意分包!!!

import com.fengmi.goods.MallGoods;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;

@FeignClient("fengmi-goods")
@RequestMapping("goods")
public interface GoodsApi {

    @GetMapping("findAllGoodsInfo")
    public List<MallGoods> findAllGoodsInfo();

}

2. search模块微服务的接口开发

这一块微服务主要分两个步骤:

a. 初始化elasticsearch的索引和域 => 这需要查询到数据库中的数据(调用以上接口即可),通过ElasticsearchRestTemplate中的 save()方法去初始化

b. 查询操作 => 需要绑定查询的条件,如bool复合查询,highlight高亮显示,aggs分组,filters过滤等,这需要熟练使用ElasticSearchRestTemplate的Api

2.1 开发所需依赖

<!-- es的起步依赖 -->
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
 </dependency>

<!-- nacos的注册与发现 -->
 <dependency>
     <groupId>com.alibaba.cloud</groupId>
     <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
 </dependency>

 <dependency>
     <groupId>com.alibaba.cloud</groupId>
     <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
 </dependency>

<!-- springMVC 起步依赖 -->
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
 </dependency>

<!-- 端点监控 -->
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-actuator</artifactId>
 </dependency>

<!-- entity依赖 -->
 <dependency>
     <groupId>com.fengmi</groupId>
     <artifactId>fengmi-entity</artifactId>
     <version>1.0-SNAPSHOT</version>
 </dependency>

<!-- OpenFeign 的声明模块的依赖 -->
 <dependency>
     <groupId>com.fengmi</groupId>
     <artifactId>fengmi-api</artifactId>
     <version>1.0-SNAPSHOT</version>
 </dependency>

2.2 引导类中需要扫描

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
@EnableFeignClients(basePackages = {"com.fengmi.api.goods"}) // 扫描才能调用openfeign的接口哦
public class SearchApp {
    public static void main(String[] args) {
        SpringApplication.run(SearchApp.class,args);
    }
}

2.3 初始化ElasticSearch的接口

service 的接口
public interface ISearchService {
    // 初始化elasticsearch  看这里!!!!
    public ResultVO initEs();

    // 使用es查询并分页
    public PageResultVO<ESGoods> searchAndPageByES(SearchDTO searchDTO);
}
service的实现

明确

所谓的elasticsearch初始化主要分两个步骤:

  1. 删除之前可能存在的索引,对应Api => ElasticSearchRestTemplate.deleteIndex(对应的实体Class),之后再通过设计的实体去创建索引
  2. 查询数据库中的数据,然后遍历转换为设置的索引实体类,之后使用save()方法导入数据

至此,索引,域创建完毕,数据导入完毕

  • 索引实体类

实体类的设计对于其中字段要明确三个维度:

  1. 要不要分词,通过 type 去确认字段的类型,如果type确认字段是 TEXT 那么就可以通过 analyzer去确定分词的类型是 ik_max_word 或者 ik_smart ;如果字段的类型设置为了Keyword,表示这个字段是不使用分词的,就是其本身
  2. 要不要建立索引,通过 index 属性来设置,一般的字段都是需要建立索引的,但如图片地址等一些 不会用到的搜索条件,我们可以不建立索引
  3. 要不要储存,通过 store 属性来设置,一般来说我们设计的字段都是需要储存的
package com.fengmi.entity;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

import java.util.Date;
@Data
@Document(indexName = "es-goods",  shards = 1, replicas = 0)
public class ESGoods {

    @Id
    private Long spuId;                        // spuId

    @Field(type = FieldType.Text, analyzer = "ik_max_word",store=true)
    private String goodsName;

    @Field(type=FieldType.Long,index=true,store=true)
    private Long brandId;                       // 品牌id
    
    @Field(type = FieldType.Keyword,index=true,store=true)
    private String brandName;                   //品牌名称

     @Field(type=FieldType.Long,index=true,store=true)
    private Long cid1id;                        // 1级分类id
    @Field(type = FieldType.Keyword,index=true,store=true)
    private String cat1name;                    // 1级分类名称
   
     @Field(type=FieldType.Long,index=true,store=true)
    private Long cid2id;                        // 2级分类id
     @Field(type = FieldType.Keyword,index=true,store=true)
    private String cat2name;                    // 2级分类名称

    @Field(type=FieldType.Long,index=true,store=true)
    private Long cid3id;                        // 3级分类id
     @Field(type = FieldType.Keyword,index=true,store=true)
    private String cat3name;                    // 3级分类名称

	 @Field(type=FieldType.Date,index=true,store=true,format = DateFormat.date_time)
    private Date createTime;                    // 创建时间
    @Field(type=FieldType.Double,index=true,store=true)
    private Double price;                       // 价格,spu默认的sku的price
    @Field(type = FieldType.Keyword,index = false,store=true)
    private String smallPic;                    // 图片地址
}
  • 实体类设计完毕,下面开始做es初始化的工作,代码如下:
@Service
public class SearchService implements ISearchService {

    @Autowired
    private GoodsApi goodsApi;

    @Autowired
    private ElasticsearchRestTemplate restTemplate;

    @Override
    public ResultVO initEs() {
        // 删除原有索引
        restTemplate.deleteIndex(ESGoods.class);
        // 初始化索引
        restTemplate.createIndex(ESGoods.class);
        // 初始化分词器
        restTemplate.putMapping(ESGoods.class);

        // 远程调用goodsApi 查询商品信息
        List<MallGoods> allGoodsInfo = goodsApi.findAllGoodsInfo();

        if (allGoodsInfo == null || allGoodsInfo.size() < 0) {
            return new ResultVO(false, "初始化失败");
        }

        // 遍历 将实体类的映射设置到索引库
        List<ESGoods> goodsList = allGoodsInfo.stream().map(spu -> {
            ESGoods esGoods = new ESGoods();
            // id
            esGoods.setSpuId(spu.getSpuId());
            // brand 品牌信息
            esGoods.setBrandId(spu.getMallGoodsBrand().getId());
            esGoods.setBrandName(spu.getMallGoodsBrand().getName());
            // 分类信息
            esGoods.setCid1id(spu.getCat1().getId());
            esGoods.setCat1name(spu.getCat1().getName());
            esGoods.setCid2id(spu.getCat2().getId());
            esGoods.setCat2name(spu.getCat2().getName());
            esGoods.setCid3id(spu.getCat3().getId());
            esGoods.setCat3name(spu.getCat3().getName());
            // 维护创建时间,后面可以做排序处理
            esGoods.setCreateTime(new Date());
            // 商品名称
            esGoods.setGoodsName(spu.getGoodsName());
            esGoods.setPrice(spu.getPrice().doubleValue());
            // 图片是字符串形式,需要做处理
            String albumPics = spu.getAlbumPics();
            // 对字符串做处理要先进行非空判断
            if (!StringUtils.isEmpty(albumPics)) {
                String[] split = albumPics.split(",");
                // 非空判断
                if (split != null && split.length > 0) {
                    esGoods.setSmallPic(spu.getAlbumPics());
                }
            }
            return esGoods;
        }).collect(Collectors.toList());

        // 保存到es中
        restTemplate.save(goodsList);

        return new ResultVO(true, "elasticsearch初始化成功");
    }
}       
提供初始化的controller接口
@RestController
@RequestMapping("search")
public class SearchController {

    @Autowired
    private ISearchService searchService;
	
    // 初始化接口方法
    @RequestMapping("initES")
    public ResultVO initES() {
        return searchService.initEs();
    }
}    

至此,初始化的工作进行完毕

2.4 使用ElasticSearch查询接口开发

接下来就是重头戏了,使用elasticsearch来作为查询的工作

2.4.1 思路
  1. 先做基本查询的功能,需要多个域匹配,所以我们采用复合查询bool中的should来操作
  2. 基本查询完成之后进行分页的操作,可以通过设置分页来完成
  3. 对输入的关键字进行设置,采用高亮设置,以实现查询
  4. 分组,聚合查询,完成品牌和种类列表的实现
  5. 过滤,这里必须使用过滤,对上述查询的结果进行过滤的操作不会影响分数!
  6. 排序,对结果集添加排序的操作,这里实现价格的排序,其他方法是一致的
  • 先看看原型界面

在这里插入图片描述

明确要求

  1. 查询时,首先是要匹配几个域的,分别是: 商品的名称goodsName,商品的品牌brandName,商品三级分类的名称cat3name,而这几个域只要有一个匹配上就展示,这样能搜索到的商品就更多
  2. 域的分词,goodsName采用 ik_max_word分词,这样也能搜索到更多的产品,以提升用户的购买率,品牌和商品三级分类则可以不需要分词
  3. 以上要求组合起来可以得到:需要使用复合查询,should条件拼接
  4. 顺带可以将分页也处理了,只需添加分页设置即可
2.4.2 基本分页查询
public PageResultVO<ESGoods> searchAndPageByES(SearchDTO searchDTO) {
        // 非空判断
        if (searchDTO == null) {
            return new PageResultVO<>(false, "参数不合法");
        }
        // 从es中查询
        // 1. 多重匹配查询
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        // 2. 条件匹配,需要分词,匹配商品名称,从条件dto中获取,先做非空判断,keyword为空是查所有
        if (!StringUtils.isEmpty(searchDTO.getKeyword())) {
            MatchQueryBuilder goodsName = QueryBuilders.matchQuery("goodsName", searchDTO.getKeyword());
            // 3. 条件匹配,不分词,匹配品牌名称
            TermQueryBuilder brandName = QueryBuilders.termQuery("brandName", searchDTO.getKeyword());
            // 4. 条件匹配 , 不分词, 匹配3级分类名称
            TermQueryBuilder cat3name = QueryBuilders.termQuery("cat3name", searchDTO.getKeyword());
            // 5. 使用should匹配,只要有一个符合就可以,这样符合电商的性质
            boolQueryBuilder.should(goodsName).should(brandName).should(cat3name);
        }
        // 6. 设置分页信息,防止恶意攻击
        if (searchDTO.getPage() <= 0 || searchDTO.getSize() <= 0) {
            searchDTO.setPage(1);
            searchDTO.setSize(5);
        }
        PageRequest pageRequest = PageRequest.of(searchDTO.getPage() - 1, searchDTO.getSize());
    
    	// 7. 创建并设置查询对象
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder()
                // 设置查询条件
                .withQuery(boolQueryBuilder)
}
    
  • searchDTO类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SearchDTO {
    // 搜索的关键字
    private String keyword;

    // 当前页
    private Integer page = 1;

    // 每页条数
    private Integer size = 5;

    // 过滤所需字段
    private String brandNameFilter;

    private String cat3NameFilter;

    // 排序所需字段
    private String sortField;

}
  • 响应类:ResultVO 和PageResultVO
@Data
@AllArgsConstructor
@NoArgsConstructor
@RequiredArgsConstructor
public class ResultVO {

    @NonNull
    private boolean success;  //标记操作的状态

    @NonNull
    private String msg;    //错误信息


    private Object data ;   //数据

}
@Data
@AllArgsConstructor
@NoArgsConstructor
@RequiredArgsConstructor
public class PageResultVO<T> {
    private Long total; // 总记录数

    private Integer pages; // 总页数

    private List<T> data;  // 每页数据

    @NonNull
    private boolean success; // 响应标志位
    @NonNull
    private String msg;  // 响应信息
}
2.4.3 高亮设置

对于查询的关键字keyword可以设置高亮显示,这样的用户体验会更好

    @Override
    public PageResultVO<ESGoods> searchAndPageByES(SearchDTO searchDTO) {
        // 非空判断
        if (searchDTO == null) {
            return new PageResultVO<>(false, "参数不合法");
        }
        // 从es中查询
        // 1. 多重匹配查询
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        // 2. 条件匹配,需要分词,匹配商品名称,从条件dto中获取,先做非空判断
        if (!StringUtils.isEmpty(searchDTO.getKeyword())) {
            MatchQueryBuilder goodsName = QueryBuilders.matchQuery("goodsName", searchDTO.getKeyword());
            // 3. 条件匹配,不分词,匹配品牌名称
            TermQueryBuilder brandName = QueryBuilders.termQuery("brandName", searchDTO.getKeyword());
            // 4. 条件匹配 , 不分词, 匹配3级分类名称
            TermQueryBuilder cat3name = QueryBuilders.termQuery("cat3name", searchDTO.getKeyword());
            // 5. 使用should匹配,只要有一个符合就可以,这样符合电商的性质
            boolQueryBuilder.should(goodsName).should(brandName).should(cat3name);
        }
        // 6. 设置分页信息,防止恶意攻击
        if (searchDTO.getPage() <= 0 || searchDTO.getSize() <= 0) {
            searchDTO.setPage(1);
            searchDTO.setSize(5);
        }
        PageRequest pageRequest = PageRequest.of(searchDTO.getPage() - 1, searchDTO.getSize());

        // 7. 创建并设置查询对象
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder()
                // 设置查询条件
                .withQuery(boolQueryBuilder)
                // 设置分页条件
                .withPageable(pageRequest)
                // 设置高亮
                .withHighlightBuilder(getHighlightBuilder("goodsName"))
            
        // 8. 查询
        SearchHits<ESGoods> search = restTemplate.search(nativeSearchQueryBuilder.build(), ESGoods.class);
        // 9. 设置总条数
        PageResult pageResultVO = new PageResult();
        pageResultVO.setTotal(search.getTotalHits());

        // 10. 获取命中对象
        List<SearchHit<ESGoods>> searchHits = search.getSearchHits();
        // 11. 遍历,处理每页数据,和高亮
        List<ESGoods> esGoodsList = searchHits.stream().map(hit -> {
            // 获取内容
            ESGoods esGoods = hit.getContent();
            // 设置高亮信息
            Map<String, List<String>> highlightFields = hit.getHighlightFields();
            highlightFields.forEach((k, v) -> {
                // v 即是每个信息中高亮的数组属性,做非空判断
                if (v != null && v.size() > 0) {
                    // 如果k与域名相同,那么才设置高亮
                    if ("goodsName".equals(k)) {
                        esGoods.setGoodsName(v.get(0));
                    }
                }
            });
            return esGoods;
        }).collect(Collectors.toList());
        
        // 设置每页信息
        pageResultVO.setData(esGoodsList);
        return pageResultVO;
    }
	// 设置高亮字段 自定义方法
    private HighlightBuilder getHighlightBuilder(String... fields) {
        // 高亮条件
        HighlightBuilder highlightBuilder = new HighlightBuilder(); //生成高亮查询器
        for (String field : fields) {
            highlightBuilder.field(field);//高亮查询字段
        }
        highlightBuilder.requireFieldMatch(false);     //如果要多个字段高亮,这项要为false
        highlightBuilder.preTags("<span style=\"color:red\">");   //高亮设置
        highlightBuilder.postTags("</span>");
        //下面这两项,如果你要高亮如文字内容等有很多字的字段,必须配置,不然会导致高亮不全,文章内容缺失等
        highlightBuilder.fragmentSize(800000); //最大高亮分片数
        highlightBuilder.numOfFragments(0); //从第一个分片获取高亮片段

        return highlightBuilder;
    }
}
2.4.4 聚合统计品牌和分类信息

回忆一下在kibana中我们是怎么做聚合的,是与query同级,使用aggs来聚合,设置聚合组名,聚合桶只能使用term不分词,设置field字段对应的域

那么在代码中使用聚合也是在与查询同级

public PageResultVO<ESGoods> searchAndPageByES(SearchDTO searchDTO) {
        // 非空判断
        if (searchDTO == null) {
            return new PageResultVO<>(false, "参数不合法");
        }
        // 从es中查询
        // 1. 多重匹配查询
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        // 2. 条件匹配,需要分词,匹配商品名称,从条件dto中获取,先做非空判断
        if (!StringUtils.isEmpty(searchDTO.getKeyword())) {
            MatchQueryBuilder goodsName = QueryBuilders.matchQuery("goodsName", searchDTO.getKeyword());
            // 3. 条件匹配,不分词,匹配品牌名称
            TermQueryBuilder brandName = QueryBuilders.termQuery("brandName", searchDTO.getKeyword());
            // 4. 条件匹配 , 不分词, 匹配3级分类名称
            TermQueryBuilder cat3name = QueryBuilders.termQuery("cat3name", searchDTO.getKeyword());
            // 5. 使用should匹配,只要有一个符合就可以,这样符合电商的性质
            boolQueryBuilder.should(goodsName).should(brandName).should(cat3name);
        }
        // 6. 设置分页信息,防止恶意攻击
        if (searchDTO.getPage() <= 0 || searchDTO.getSize() <= 0) {
            searchDTO.setPage(1);
            searchDTO.setSize(5);
        }
        PageRequest pageRequest = PageRequest.of(searchDTO.getPage() - 1, searchDTO.getSize());

        // 6.1设置聚合
    	// terms设置组名,field设置对应域,size设置桶的个数
        TermsAggregationBuilder brandNameTermsAggregationBuilder = AggregationBuilders.terms("brandAgg").field("brandName").size(30);
        TermsAggregationBuilder cat3nameTermsAggregationBuilder = AggregationBuilders.terms("cat3Agg").field("cat3name").size(30);

        // 7. 创建并设置查询对象
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder()
                // 设置查询条件
                .withQuery(boolQueryBuilder)
                // 设置分页条件
                .withPageable(pageRequest)
                // 设置高亮
                .withHighlightBuilder(getHighlightBuilder("goodsName"))
                // 设置聚合
                .addAggregation(brandNameTermsAggregationBuilder)
                .addAggregation(cat3nameTermsAggregationBuilder);
        // 8. 查询
        SearchHits<ESGoods> search = restTemplate.search(nativeSearchQueryBuilder.build(), ESGoods.class);
        // 9. 设置总条数
        PageResult pageResultVO = new PageResult();
        pageResultVO.setTotal(search.getTotalHits());
        // 10. 获取命中对象
        List<SearchHit<ESGoods>> searchHits = search.getSearchHits();
        // 11. 遍历,处理每页数据,和高亮
        List<ESGoods> esGoodsList = searchHits.stream().map(hit -> {
            // 获取内容
            ESGoods esGoods = hit.getContent();
            // 设置高亮信息
            Map<String, List<String>> highlightFields = hit.getHighlightFields();
            highlightFields.forEach((k, v) -> {
                // v 即是每个信息中高亮的数组属性,做非空判断
                if (v != null && v.size() > 0) {
                    // 如果k与域名相同,那么才设置高亮
                    if ("goodsName".equals(k)) {
                        esGoods.setGoodsName(v.get(0));
                    }
                }
            });
            return esGoods;
        }).collect(Collectors.toList());
        // 12. 获取聚合信息
        Aggregations aggregations = search.getAggregations();
        // 从指定的域中获取聚合的信息
        Terms brandName = aggregations.get("brandAgg");
        Terms cat3name = aggregations.get("cat3Agg");
        // 获取桶
        List<? extends Terms.Bucket> brandNameBuckets = brandName.getBuckets();
        List<? extends Terms.Bucket> cat3nameBuckets = cat3name.getBuckets();
        // 遍历获取
        List<String> brandNameList = brandNameBuckets.stream().map(item -> (String) item.getKey()).collect(Collectors.toList());
        pageResultVO.setBrandNameList(brandNameList);
        List<String> cat3NameList = cat3nameBuckets.stream().map(item -> (String) item.getKey()).collect(Collectors.toList());
        pageResultVO.setCat3NameList(cat3NameList);
        // 设置每页信息
        pageResultVO.setData(esGoodsList);
        return pageResultVO;
    }


    // 设置高亮字段
    private HighlightBuilder getHighlightBuilder(String... fields) {
        // 高亮条件
        HighlightBuilder highlightBuilder = new HighlightBuilder(); //生成高亮查询器
        for (String field : fields) {
            highlightBuilder.field(field);//高亮查询字段
        }
        highlightBuilder.requireFieldMatch(false);     //如果要多个字段高亮,这项要为false
        highlightBuilder.preTags("<span style=\"color:red\">");   //高亮设置
        highlightBuilder.postTags("</span>");
        //下面这两项,如果你要高亮如文字内容等有很多字的字段,必须配置,不然会导致高亮不全,文章内容缺失等
        highlightBuilder.fragmentSize(800000); //最大高亮分片数
        highlightBuilder.numOfFragments(0); //从第一个分片获取高亮片段

        return highlightBuilder;
    }
2.4.5 过滤查询,通过聚合出的品牌名称和分类名称过滤

为什么要使用过滤而非在复合条件中添加?

关键字: 分数

使用复合条件查询对结果的分数会有影响,就会影响到排序,页面图片的改变就会变大,这里我们希望是不影响结果的分数,那么只能使用过滤的方式来实现

回忆一下在kibana中怎么使用过滤的:
过滤在bool复合查询中,使用filters与bool同级,使用term或者terms来设置过滤的字段,该字段是不分词的
所以在代码中使用过滤也是在bool中组合,而非与查询同级
public PageResultVO<ESGoods> searchAndPageByES(SearchDTO searchDTO) {
        // 非空判断
        if (searchDTO == null) {
            return new PageResultVO<>(false, "参数不合法");
        }
        // 从es中查询
        // 1. 多重匹配查询
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        // 2. 条件匹配,需要分词,匹配商品名称,从条件dto中获取,先做非空判断
        if (!StringUtils.isEmpty(searchDTO.getKeyword())) {
            MatchQueryBuilder goodsName = QueryBuilders.matchQuery("goodsName", searchDTO.getKeyword());
            // 3. 条件匹配,不分词,匹配品牌名称
            TermQueryBuilder brandName = QueryBuilders.termQuery("brandName", searchDTO.getKeyword());
            // 4. 条件匹配 , 不分词, 匹配3级分类名称
            TermQueryBuilder cat3name = QueryBuilders.termQuery("cat3name", searchDTO.getKeyword());
            // 5. 使用should匹配,只要有一个符合就可以,这样符合电商的性质
            boolQueryBuilder.should(goodsName).should(brandName).should(cat3name);
        }
        // 6. 设置分页信息,防止恶意攻击
        if (searchDTO.getPage() <= 0 || searchDTO.getSize() <= 0) {
            searchDTO.setPage(1);
            searchDTO.setSize(5);
        }
        PageRequest pageRequest = PageRequest.of(searchDTO.getPage() - 1, searchDTO.getSize());

        // 6.1设置聚合
        TermsAggregationBuilder brandNameTermsAggregationBuilder = AggregationBuilders.terms("brandAgg").field("brandName").size(30);
        TermsAggregationBuilder cat3nameTermsAggregationBuilder = AggregationBuilders.terms("cat3Agg").field("cat3name").size(30);

        // 6.2 设置过滤,过滤不会影响分数,过滤在bool中!!!!
        if (!StringUtils.isEmpty(searchDTO.getBrandNameFilter())) {
            // 不为空才设置
            TermQueryBuilder brandNameTermQueryBuilder = QueryBuilders.termQuery("brandName", searchDTO.getBrandNameFilter());
            // 绑定到复合查询中
            boolQueryBuilder.filter(brandNameTermQueryBuilder);
        }
        if (!StringUtils.isEmpty(searchDTO.getCat3NameFilter())) {
            // 不为空才设置
            TermQueryBuilder cat3namebrandNameTermQueryBuilder = QueryBuilders.termQuery("cat3name", searchDTO.getCat3NameFilter());
            // 绑定到复合查询中
            boolQueryBuilder.filter(cat3namebrandNameTermQueryBuilder);
        }

        // 7. 创建并设置查询对象
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder()
                // 设置查询条件,这里面已经设置了过滤了
                .withQuery(boolQueryBuilder)
                // 设置分页条件
                .withPageable(pageRequest)
                // 设置高亮
                .withHighlightBuilder(getHighlightBuilder("goodsName"))
                // 设置聚合
                .addAggregation(brandNameTermsAggregationBuilder)
                .addAggregation(cat3nameTermsAggregationBuilder);

        // 8. 查询
        SearchHits<ESGoods> search = restTemplate.search(nativeSearchQueryBuilder.build(), ESGoods.class);
        // 9. 设置总条数
        PageResult pageResultVO = new PageResult();
        pageResultVO.setTotal(search.getTotalHits());

        // 10. 获取命中对象
        List<SearchHit<ESGoods>> searchHits = search.getSearchHits();
        // 11. 遍历,处理每页数据,和高亮
        List<ESGoods> esGoodsList = searchHits.stream().map(hit -> {
            // 获取内容
            ESGoods esGoods = hit.getContent();
            // 设置高亮信息
            Map<String, List<String>> highlightFields = hit.getHighlightFields();
            highlightFields.forEach((k, v) -> {
                // v 即是每个信息中高亮的数组属性,做非空判断
                if (v != null && v.size() > 0) {
                    // 如果k与域名相同,那么才设置高亮
                    if ("goodsName".equals(k)) {
                        esGoods.setGoodsName(v.get(0));
                    }
                }
            });
            return esGoods;
        }).collect(Collectors.toList());

        // 12. 从查询结果中获取聚合信息
        Aggregations aggregations = search.getAggregations();
        // 从指定的域中获取聚合的信息
        Terms brandName = aggregations.get("brandAgg");
        Terms cat3name = aggregations.get("cat3Agg");
        // 获取桶
        List<? extends Terms.Bucket> brandNameBuckets = brandName.getBuckets();
        List<? extends Terms.Bucket> cat3nameBuckets = cat3name.getBuckets();
        // 遍历获取
        List<String> brandNameList = brandNameBuckets.stream().map(item -> (String) item.getKey()).collect(Collectors.toList());
        pageResultVO.setBrandNameList(brandNameList);
        List<String> cat3NameList = cat3nameBuckets.stream().map(item -> (String) item.getKey()).collect(Collectors.toList());
        pageResultVO.setCat3NameList(cat3NameList);

        // 设置每页信息
        pageResultVO.setData(esGoodsList);
        return pageResultVO;
    }
    // 设置高亮字段
    private HighlightBuilder getHighlightBuilder(String... fields) {
        // 高亮条件
        HighlightBuilder highlightBuilder = new HighlightBuilder(); //生成高亮查询器
        for (String field : fields) {
            highlightBuilder.field(field);//高亮查询字段
        }
        highlightBuilder.requireFieldMatch(false);     //如果要多个字段高亮,这项要为false
        highlightBuilder.preTags("<span style=\"color:red\">");   //高亮设置
        highlightBuilder.postTags("</span>");
        //下面这两项,如果你要高亮如文字内容等有很多字的字段,必须配置,不然会导致高亮不全,文章内容缺失等
        highlightBuilder.fragmentSize(800000); //最大高亮分片数
        highlightBuilder.numOfFragments(0); //从第一个分片获取高亮片段

        return highlightBuilder;
    }
2.4.6 设置排序

对于排序,我们可以通过页面原型发现,这里需要做的是对商品价格的排序

在查询的DTO中插入排序的字段String sortFilter,通过对传入的字段是asc还是desc来决定排序方式

这里需要注意的是:可能前端未传入排序方式,那么此时就不需要在查询中添加排序

回忆一下在kibana中我们是怎么使用排序的:
排序sort,和aggs聚合一样与query同级,设置排序的字段,order中设置排序方式,asc为升序,desc为降序
所以在代码中的查询中添加排序条件
  • 由此给出全部的查询在kibana中的查询语句,供参考和对比
GET es-goods/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "goodsName": "抢购"
          }
        },
        {
          "term": {
            "brandName": {
              "value": "良品铺子"
            }
          }
        },
        {
          "term": {
            "cat3name": {
              "value": ""
            }
          }
        }
      ],
      "filter": [
        {
          "term": {
            "brandName": "百草味"
          }
        }
      ]
    }
  },
  "aggs": {
    "brandName_agg": {
      "terms": {
        "field": "brandName",
        "size": 10
      }
    },
    "cat3Name_agg":{
      "terms": {
        "field": "cat3name",
        "size": 10
      }
    }
  },
  "sort": [
    {
      "price": {
        "order": "desc"
      }
    }
  ]
}
  • 最终代码实现:
@Override
    public PageResultVO<ESGoods> searchAndPageByES(SearchDTO searchDTO) {
        // 非空判断
        if (searchDTO == null) {
            return new PageResultVO<>(false, "参数不合法");
        }
        // 从es中查询
        // 1. 多重匹配查询
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        // 2. 条件匹配,需要分词,匹配商品名称,从条件dto中获取,先做非空判断
        if (!StringUtils.isEmpty(searchDTO.getKeyword())) {
            MatchQueryBuilder goodsName = QueryBuilders.matchQuery("goodsName", searchDTO.getKeyword());
            // 3. 条件匹配,不分词,匹配品牌名称
            TermQueryBuilder brandName = QueryBuilders.termQuery("brandName", searchDTO.getKeyword());
            // 4. 条件匹配 , 不分词, 匹配3级分类名称
            TermQueryBuilder cat3name = QueryBuilders.termQuery("cat3name", searchDTO.getKeyword());
            // 5. 使用should匹配,只要有一个符合就可以,这样符合电商的性质
            boolQueryBuilder.should(goodsName).should(brandName).should(cat3name);
        }
        // 6. 设置分页信息,防止恶意攻击
        if (searchDTO.getPage() <= 0 || searchDTO.getSize() <= 0) {
            searchDTO.setPage(1);
            searchDTO.setSize(5);
        }
        PageRequest pageRequest = PageRequest.of(searchDTO.getPage() - 1, searchDTO.getSize());

        // 6.1设置聚合
        TermsAggregationBuilder brandNameTermsAggregationBuilder = AggregationBuilders.terms("brandAgg").field("brandName").size(30);
        TermsAggregationBuilder cat3nameTermsAggregationBuilder = AggregationBuilders.terms("cat3Agg").field("cat3name").size(30);

        // 6.2 设置过滤,过滤不会影响分数,过滤在bool中!!!!
        if (!StringUtils.isEmpty(searchDTO.getBrandNameFilter())) {
            // 不为空才设置
            TermQueryBuilder brandNameTermQueryBuilder = QueryBuilders.termQuery("brandName", searchDTO.getBrandNameFilter());
            boolQueryBuilder.filter(brandNameTermQueryBuilder);
        }
        if (!StringUtils.isEmpty(searchDTO.getCat3NameFilter())) {
            // 不为空才设置
            TermQueryBuilder cat3namebrandNameTermQueryBuilder = QueryBuilders.termQuery("cat3name", searchDTO.getCat3NameFilter());
            boolQueryBuilder.filter(cat3namebrandNameTermQueryBuilder);
        }

        // 6.3 设置排序 , 排序与query同级
        // 提出变量,按条件赋值
        FieldSortBuilder price = null;
        if (!StringUtils.isEmpty(searchDTO.getSortField()) && "desc".equals(searchDTO.getSortField())) {
            // 不为空,且前端传过来的是desc ,则按降序排列
            price = SortBuilders.fieldSort("price").order(SortOrder.DESC);
        }

        if (!StringUtils.isEmpty(searchDTO.getSortField()) && "asc".equals(searchDTO.getSortField())) {
            // 不为空,且前端传过来的是desc ,则按降序排列
            price = SortBuilders.fieldSort("price").order(SortOrder.ASC);
        }

        // 7. 创建并设置查询对象
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder()
                // 设置查询条件
                .withQuery(boolQueryBuilder)
                // 设置分页条件
                .withPageable(pageRequest)
                // 设置高亮
                .withHighlightBuilder(getHighlightBuilder("goodsName"))
                // 设置聚合
                .addAggregation(brandNameTermsAggregationBuilder)
                .addAggregation(cat3nameTermsAggregationBuilder);

        // 7.1 判断排序条件是否存在
        if (price != null){
            // 存在则加上排序
            nativeSearchQueryBuilder.withSort(price);
        }

        // 8. 查询
        SearchHits<ESGoods> search = restTemplate.search(nativeSearchQueryBuilder.build(), ESGoods.class);
        // 9. 设置总条数
        PageResult pageResultVO = new PageResult();
        pageResultVO.setTotal(search.getTotalHits());

        // 10. 获取命中对象
        List<SearchHit<ESGoods>> searchHits = search.getSearchHits();
        // 11. 遍历,处理每页数据,和高亮
        List<ESGoods> esGoodsList = searchHits.stream().map(hit -> {
            // 获取内容
            ESGoods esGoods = hit.getContent();
            // 设置高亮信息
            Map<String, List<String>> highlightFields = hit.getHighlightFields();
            highlightFields.forEach((k, v) -> {
                // v 即是每个信息中高亮的数组属性,做非空判断
                if (v != null && v.size() > 0) {
                    // 如果k与域名相同,那么才设置高亮
                    if ("goodsName".equals(k)) {
                        esGoods.setGoodsName(v.get(0));
                    }
                }
            });
            return esGoods;
        }).collect(Collectors.toList());

        // 12. 从查询结果中获取聚合信息
        Aggregations aggregations = search.getAggregations();
        // 从指定的域中获取聚合的信息
        Terms brandName = aggregations.get("brandAgg");
        Terms cat3name = aggregations.get("cat3Agg");
        // 获取桶
        List<? extends Terms.Bucket> brandNameBuckets = brandName.getBuckets();
        List<? extends Terms.Bucket> cat3nameBuckets = cat3name.getBuckets();
        // 遍历获取
        List<String> brandNameList = brandNameBuckets.stream().map(item -> (String) item.getKey()).collect(Collectors.toList());
        pageResultVO.setBrandNameList(brandNameList);
        List<String> cat3NameList = cat3nameBuckets.stream().map(item -> (String) item.getKey()).collect(Collectors.toList());
        pageResultVO.setCat3NameList(cat3NameList);

        // 设置每页信息
        pageResultVO.setData(esGoodsList);
        return pageResultVO;
    }

	 // 设置高亮字段
    private HighlightBuilder getHighlightBuilder(String... fields) {
        // 高亮条件
        HighlightBuilder highlightBuilder = new HighlightBuilder(); //生成高亮查询器
        for (String field : fields) {
            highlightBuilder.field(field);//高亮查询字段
        }
        highlightBuilder.requireFieldMatch(false);     //如果要多个字段高亮,这项要为false
        highlightBuilder.preTags("<span style=\"color:red\">");   //高亮设置
        highlightBuilder.postTags("</span>");
        //下面这两项,如果你要高亮如文字内容等有很多字的字段,必须配置,不然会导致高亮不全,文章内容缺失等
        highlightBuilder.fragmentSize(800000); //最大高亮分片数
        highlightBuilder.numOfFragments(0); //从第一个分片获取高亮片段

        return highlightBuilder;
    }

至此,一个ElasticSearch的基本使用完成,你学废了么?

  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2021-10-16 19:42:31  更:2021-10-16 19:45:03 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 1:11:29-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码