栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 前沿技术 > 大数据 > 大数据系统

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微服务中的依赖

    org.springframework.boot
    spring-boot-starter-web



    com.alibaba.cloud
    spring-cloud-starter-alibaba-nacos-discovery


    com.alibaba.cloud
    spring-cloud-starter-alibaba-nacos-config



    org.springframework.boot
    spring-boot-starter-actuator



    com.alibaba
    druid-spring-boot-starter
    1.1.10



    mysql
    mysql-connector-java



    com.baomidou
    mybatis-plus-boot-starter
    3.4.0



    com.fengmi
    fengmi-entity
    1.0-SNAPSHOT

goods微服务中的mapper层
public interface MallGoodsMapper extends baseMapper {

    // 查询所有审核通过的商品
    public List findAllGoodsAudited();
}
goods微服务中的mapper层映射文件



    
    
        
        
        
        
        
            
            
        
        
            
            
        
        
            
            
        
        
            
            
        
    

    
        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;
    

goods微服务中的service层
public interface IMallGoodsService extends IService {

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

    @Override
    public List findAllGoodsInfo() {
        return this.baseMapper.findAllGoodsAudited();
    }
}
goods微服务中的controller层
@RestController
@RequestMapping("/goods")
public class MallGoodsController {

    @Autowired
    private IMallGoodsService goodsService;

    @GetMapping("findAllGoodsInfo")
    public List 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 findAllGoodsInfo();

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

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

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

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

2.1 开发所需依赖

     org.springframework.boot
     spring-boot-starter-data-elasticsearch
 


 
     com.alibaba.cloud
     spring-cloud-starter-alibaba-nacos-config
 

 
     com.alibaba.cloud
     spring-cloud-starter-alibaba-nacos-discovery
 


 
     org.springframework.boot
     spring-boot-starter-web
 


 
     org.springframework.boot
     spring-boot-starter-actuator
 


 
     com.fengmi
     fengmi-entity
     1.0-SNAPSHOT
 


 
     com.fengmi
     fengmi-api
     1.0-SNAPSHOT
 
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 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 allGoodsInfo = goodsApi.findAllGoodsInfo();

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

        // 遍历 将实体类的映射设置到索引库
        List 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 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 {
    private Long total; // 总记录数

    private Integer pages; // 总页数

    private List data;  // 每页数据

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

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

    @Override
    public PageResultVO 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 search = restTemplate.search(nativeSearchQueryBuilder.build(), ESGoods.class);
        // 9. 设置总条数
        PageResult pageResultVO = new PageResult();
        pageResultVO.setTotal(search.getTotalHits());

        // 10. 获取命中对象
        List> searchHits = search.getSearchHits();
        // 11. 遍历,处理每页数据,和高亮
        List esGoodsList = searchHits.stream().map(hit -> {
            // 获取内容
            ESGoods esGoods = hit.getContent();
            // 设置高亮信息
            Map> 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("");   //高亮设置
        highlightBuilder.postTags("");
        //下面这两项,如果你要高亮如文字内容等有很多字的字段,必须配置,不然会导致高亮不全,文章内容缺失等
        highlightBuilder.fragmentSize(800000); //最大高亮分片数
        highlightBuilder.numOfFragments(0); //从第一个分片获取高亮片段

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

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

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

public PageResultVO 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 search = restTemplate.search(nativeSearchQueryBuilder.build(), ESGoods.class);
        // 9. 设置总条数
        PageResult pageResultVO = new PageResult();
        pageResultVO.setTotal(search.getTotalHits());
        // 10. 获取命中对象
        List> searchHits = search.getSearchHits();
        // 11. 遍历,处理每页数据,和高亮
        List esGoodsList = searchHits.stream().map(hit -> {
            // 获取内容
            ESGoods esGoods = hit.getContent();
            // 设置高亮信息
            Map> 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 brandNameBuckets = brandName.getBuckets();
        List cat3nameBuckets = cat3name.getBuckets();
        // 遍历获取
        List brandNameList = brandNameBuckets.stream().map(item -> (String) item.getKey()).collect(Collectors.toList());
        pageResultVO.setBrandNameList(brandNameList);
        List 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("");   //高亮设置
        highlightBuilder.postTags("");
        //下面这两项,如果你要高亮如文字内容等有很多字的字段,必须配置,不然会导致高亮不全,文章内容缺失等
        highlightBuilder.fragmentSize(800000); //最大高亮分片数
        highlightBuilder.numOfFragments(0); //从第一个分片获取高亮片段

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

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

关键字: 分数

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

回忆一下在kibana中怎么使用过滤的:
过滤在bool复合查询中,使用filters与bool同级,使用term或者terms来设置过滤的字段,该字段是不分词的
所以在代码中使用过滤也是在bool中组合,而非与查询同级
public PageResultVO 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 search = restTemplate.search(nativeSearchQueryBuilder.build(), ESGoods.class);
        // 9. 设置总条数
        PageResult pageResultVO = new PageResult();
        pageResultVO.setTotal(search.getTotalHits());

        // 10. 获取命中对象
        List> searchHits = search.getSearchHits();
        // 11. 遍历,处理每页数据,和高亮
        List esGoodsList = searchHits.stream().map(hit -> {
            // 获取内容
            ESGoods esGoods = hit.getContent();
            // 设置高亮信息
            Map> 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 brandNameBuckets = brandName.getBuckets();
        List cat3nameBuckets = cat3name.getBuckets();
        // 遍历获取
        List brandNameList = brandNameBuckets.stream().map(item -> (String) item.getKey()).collect(Collectors.toList());
        pageResultVO.setBrandNameList(brandNameList);
        List 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("");   //高亮设置
        highlightBuilder.postTags("");
        //下面这两项,如果你要高亮如文字内容等有很多字的字段,必须配置,不然会导致高亮不全,文章内容缺失等
        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 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 search = restTemplate.search(nativeSearchQueryBuilder.build(), ESGoods.class);
        // 9. 设置总条数
        PageResult pageResultVO = new PageResult();
        pageResultVO.setTotal(search.getTotalHits());

        // 10. 获取命中对象
        List> searchHits = search.getSearchHits();
        // 11. 遍历,处理每页数据,和高亮
        List esGoodsList = searchHits.stream().map(hit -> {
            // 获取内容
            ESGoods esGoods = hit.getContent();
            // 设置高亮信息
            Map> 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 brandNameBuckets = brandName.getBuckets();
        List cat3nameBuckets = cat3name.getBuckets();
        // 遍历获取
        List brandNameList = brandNameBuckets.stream().map(item -> (String) item.getKey()).collect(Collectors.toList());
        pageResultVO.setBrandNameList(brandNameList);
        List 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("");   //高亮设置
        highlightBuilder.postTags("");
        //下面这两项,如果你要高亮如文字内容等有很多字的字段,必须配置,不然会导致高亮不全,文章内容缺失等
        highlightBuilder.fragmentSize(800000); //最大高亮分片数
        highlightBuilder.numOfFragments(0); //从第一个分片获取高亮片段

        return highlightBuilder;
    }

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/326129.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号