Springboot+ES 项目实战(1、Springboot整合ES 2、商品上架数据保存到ES 3、商品的检索服务 )

1、Springboot整合ES


1、导入elasticsearch依赖

 <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>7.4.2</version>
        </dependency>

2、配置elasticsearch

@Configuration
public class EsConfig {

    public static final RequestOptions COMMON_OPTIONS;

    static {
        RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
//    builder.addHeader("Authorization", "Bearer " + TOKEN);
//    builder.setHttpAsyncResponseConsumerFactory(
//        new HttpAsyncResponseConsumerFactory
//            .HeapBufferedResponseConsumerFactory(30 * 1024 * 1024 * 1024));
        COMMON_OPTIONS = builder.build();
    }
     
   //RestHighLevelClient 加入容器以后我们就用RestHighLevelClient 操作ES
    @Bean
    public RestHighLevelClient esRestClient(){

        RestClientBuilder builder=null;
         //ES连接地址及其端口号                                   
         builder = RestClient.builder(new HttpHost("127.0.0.1", 9200, "http"));
        RestHighLevelClient client =new RestHighLevelClient(builder);

              return client;
    }

}

2、商品上架数据保存到ES


1、建立SkuEsModel实体类,用于保存商品信息方便接收前端传送的数据,BizCodeEnume枚举类封装返回给前端的数据信息

@Data
public class SkuEsModel {

    private Long skuId;// 销售属性id
    private Long spuId; //基本属性id
    private String skuTitle;/
    private BigDecimal skuPrice; 
    private String skuImg; 
    private Long saleCount; 
    private Boolean hasStock; //库存
    private Long hotScore;//热度评分
    private Long brandId;//品牌id
    private Long catalogId;//菜单分类id
    private String brandName;//品牌名
    private String brandImg;//品牌图片
    private String catalogName;//分类名
    private List<Attrs> attrs;//属性
    @Data       //属性内部类
    public static class Attrs{

        private Long attrId;
        private String attrName;
        private String attrValue;
    }
}

BizCodeEnume枚举类

public enum BizCodeEnume {
    UNKNOW_EXCEPTION(10000,"系统未知异常"),
    PRODUCT_UP_EXCEPTION(11000,"商品上架异常"),
    VAILD_EXCEPTION(10001,"参数格式校验失败");


    private int code;
    private String msg;
    BizCodeEnume(int code,String msg){
        this.code = code;
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}


2、保存商品到es实现上架接口

@Slf4j
@RequestMapping("/search/save")
@RestController
public class EsSaveController {

    @Autowired
    ProductSaveService productSaveService;

    @RequestMapping("/product")              
    public R productStatusUp(@RequestBody List<SkuEsModel> skuEsModels){

        Boolean b=false;
        try {                     //商品上架到ES
            b = productSaveService.productStatusUp(skuEsModels);
        } catch (IOException e) {
            log.error("商品上架错误:",e);
        return R.error(BizCodeEnume.PRODUCT_UP_EXCEPTION.getCode(),BizCodeEnume.PRODUCT_UP_EXCEPTION.getMsg());
        }
       // b = productSaveService.productStatusUp(skuEsModels);返回值为false证明数据存到ES中没有发生错误
        if(!b){
        return R.ok(); }

        else{

            return R.error(BizCodeEnume.PRODUCT_UP_EXCEPTION.getCode(),BizCodeEnume.PRODUCT_UP_EXCEPTION.getMsg());
        }

    }
}


常量数据类EsConstant

public class EsConstant {

    public static final String PRODUCT_INDEX ="gulimall_product";//sku数据在ES中的索引
    public static final Integer PRODUCT_PAGESIZE =16;//sku数据在ES中的索引

}


Service服务(productStatusUp

 @Autowired
    RestHighLevelClient client;
    @Override
    public Boolean productStatusUp(List<SkuEsModel> skuEsModels) throws IOException {

        //数据保存到ES
        //1、给ES建立索引,建立好映射关系
        //2、给ES中保存数据

        BulkRequest bulkRequest = new BulkRequest();

        for (SkuEsModel model : skuEsModels) {
            //建立索引
            IndexRequest indexRequest = new IndexRequest(EsConstant.PRODUCT_INDEX);
            indexRequest.id(model.getSkuId().toString());
            //保存到ES的数据必须是JSON格式,这里转换格式
            String s = JSON.toJSONString(model);
            indexRequest.source(s, XContentType.JSON);
            bulkRequest.add(indexRequest);
        }
        //执行保存到ES EsConfig.COMMON_OPTIONS指定检索数
        BulkResponse bulk = client.bulk(bulkRequest, EsConfig.COMMON_OPTIONS);
        //是否有上价失败的返回true
        boolean b = bulk.hasFailures();

        List<String> collect = Arrays.stream(bulk.getItems()).map(item -> {
            return item.getId();
        }).collect(Collectors.toList());

        //打印上架成功的商品
        log.info("商品上架成功:{},返回数据{}",collect,bulk.toString());

        return b;
    }

3、商品的检索服务

1、建立SearchParamsSearchResult两个VO类用于封装接收前端数据和给前端返回数据。

SearchParams

@Data
public class SearchParams {
    /**
     * 页面传递过来的全文匹配关键字
     */
    private String keyword;

    /**
     * 品牌id,可以多选
     */
    private List<Long> brandId;

    /**
     * 三级分类id
     */
    private Long catalog3Id;

    /**
     * 排序条件:sort=price/salecount/hotscore_desc/asc
     */
    private String sort;

    /**
     * 是否显示有货
     */
    private Integer hasStock;

    /**
     * 价格区间查询
     */
    private String skuPrice;

    /**
     * 按照属性进行筛选
     */
    private List<String> attrs;

    /**
     * 页码
     */
    private Integer pageNum = 1;

    /**
     * 原生的所有查询条件
     */
    private String _queryString;


}

SearchResult

@Data
public class SearchResult {

    /**
     * 查询到的所有商品信息
     */
    private List<SkuEsModel> product;


    /**
     * 当前页码
     */
    private Integer pageNum;

    /**
     * 总记录数
     */
    private Long total;

    /**
     * 总页码
     */
    private Integer totalPages;

    private List<Integer> pageNavs;

    /**
     * 当前查询到的结果,所有涉及到的品牌
     */
    private List<BrandVo> brands;

    /**
     * 当前查询到的结果,所有涉及到的所有属性
     */
    private List<AttrVo> attrs;

    /**
     * 当前查询到的结果,所有涉及到的所有分类
     */
    private List<CatalogVo> catalogs;


    //===========================以上是返回给页面的所有信息============================//


    /* 面包屑导航数据 */
    private List<NavVo> navs=new ArrayList<>();

    @Data
    public static class NavVo {
        private String navName;
        private String navValue;
        private String link;
    }


    @Data
    public static class BrandVo {

        private Long brandId;

        private String brandName;

        private String brandImg;
    }


    @Data
    public static class AttrVo {

        private Long attrId;

        private String attrName;

        private List<String> attrValue;
    }


    @Data
    public static class CatalogVo {

        private Long catalogId;

        private String catalogName;
    }
}


2、接收前端页面数据接口

  @GetMapping("/list.html")
    public String listPage(SearchParams param, Model model, HttpServletRequest request) {

        param.set_queryString(request.getQueryString());

        //根据传递来的页面的查询参数,去es中检索商品
        SearchResult result = mallSearchService.search(param);

       //将数据存在request域中,便于HTML页面获取值
        model.addAttribute("result",result);
        return "list";
    }


3、Service服务(search)

 public SearchResult search(SearchParams param) {

        SearchResult result = null;
        //1、准备检索请求(buildSearchRequest)
        SearchRequest searchRequest =buildSearchRequest(param);

        try {
            //2、执行检索请求
            SearchResponse response = client.search(searchRequest, EsConfig.COMMON_OPTIONS);

            //3、分析response 响应数据并封装
           result= buildSearchResult(response,param);
        } catch (IOException e) {
            e.printStackTrace();
        }

        System.out.println(result);
        return result;
    }


buildSearchRequest(返回封装好的searchRequest检索请求)

 private SearchRequest buildSearchRequest(SearchParams param) {

        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        //1、构建了bool  query
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        //1.1 bool  must 模糊查询
        if(!StringUtils.isEmpty(param.getKeyword())){
            boolQuery.must(QueryBuilders.matchQuery("skuTitle",param.getKeyword()));
        }

        //1.2 bool  Filter -按照3级分类Id查询
        if(param.getCatalog3Id()!=null){          boolQuery.filter(QueryBuilders.termQuery("catalogId",param.getCatalog3Id()));

        }
        //1.2 bool  Filter -按照品牌Id查询
        if (param.getBrandId()!=null&&param.getBrandId().size()>0){            boolQuery.filter(QueryBuilders.termsQuery("brandId",param.getBrandId()));
        }

        //1.2 bool  Filter -按照所以指定的属性进行查询
        if(param.getAttrs()!=null&&param.getAttrs().size()>0){
            BoolQueryBuilder nestedboolQuery = QueryBuilders.boolQuery();
            for (String attr : param.getAttrs()) {
                String[] s = attr.split("_");
                String attrId=s[0];
                String[] attrValues = s[1].split(":");
       //Attrs数据内部类所以这里使用nest查询
             nestedboolQuery.must(QueryBuilders.termQuery("attrs.attrId",attrId));
                nestedboolQuery.must(QueryBuilders.termQuery("attrs.attrValue",attrValues));
                //每个属性必须都得生成一个nested查询
                NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", nestedboolQuery, ScoreMode.None);
                boolQuery.filter(nestedQuery);
            }

        }

        //1.2 bool  Filter -按照是否拥有库存进行查询
        if(null != param.getHasStock()){
            boolQuery.filter(QueryBuilders.termQuery("hasStock",param.getHasStock() == 1));
        }

        //1.2 bool  Filter -按照价格区间进行查询
        if(!StringUtils.isEmpty(param.getSkuPrice())){
            RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("skuPrice");
            String[] s = param.getSkuPrice().split("_");
            if(s.length==2){
                rangeQuery.gte(s[0]).lte(s[1]);
            }
            else if(s.length==1){
                if(param.getSkuPrice().startsWith("_")){
                    rangeQuery.lte(s[0]);
                }
                if(param.getSkuPrice().endsWith("_")){
                    rangeQuery.gte(s[0]);
                }
            }
            boolQuery.filter(rangeQuery);
        }
        //把以前的所有条件封装在一起
        sourceBuilder.query(boolQuery);
        //2.1  排序
        if(!StringUtils.isEmpty(param.getSort())){
            String sort = param.getSort();
            String[] s = sort.split("_");
            SortOrder sortOrder =s[1].equalsIgnoreCase("asc")?SortOrder.ASC:SortOrder.DESC;
            sourceBuilder.sort(s[0],sortOrder);
        }
        //2.2  分页
        //from = (pageNum-1)*size
        sourceBuilder.from((param.getPageNum()-1)*EsConstant.PRODUCT_PAGESIZE);
        sourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);
        //2.3  高亮

        if(!StringUtils.isEmpty(param.getKeyword())){
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("skuTitle");
            highlightBuilder.preTags("<b style='color:red'>");
            highlightBuilder.postTags("</b>");

        }
        //聚合分析
         //1、品牌聚合
        TermsAggregationBuilder brand_agg = AggregationBuilders.terms("brand_agg");
        brand_agg.field("brandId").size(50);
        //品牌聚合的子聚合
               brand_agg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(1));
        brand_agg.subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg").size(1));
        sourceBuilder.aggregation(brand_agg);

        //2、分类聚合
        TermsAggregationBuilder catalog_agg = 
        //聚合名(catalog_agg),过滤类型(catalogId)
        AggregationBuilders.terms("catalog_agg").field("catalogId").size(20);
        catalog_agg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(1));
        sourceBuilder.aggregation(catalog_agg);

        //3、属性聚合
        NestedAggregationBuilder attr_agg = AggregationBuilders.nested("attr_agg", "attrs");

        //聚合出当前所以的attrId
        TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId");
        //attrId对应的attr名字    
            attr_id_agg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(1));
        //查出当前attrId所有可能对应的属性值     
          attr_id_agg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(50));
        attr_agg.subAggregation(attr_id_agg);
        sourceBuilder.aggregation(attr_agg);



        String s = sourceBuilder.toString();
        System.out.println("构造的DSL:"+s);
        SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX}, sourceBuilder);

        return searchRequest;
    }


buildSearchResult(封装结果数据)

  private SearchResult buildSearchResult(SearchResponse response,SearchParams param) {
       //用于封装要返回的数据
        SearchResult result =new SearchResult();

        //查询到的所有商品信息(获取ES中所有被击中的数据)
        SearchHits hits = response.getHits();

      //用SkuEsModel来封装被击中hit中的hit的商品信息数据
        List<SkuEsModel> esModels = new ArrayList<>();
        if(hits.getHits()!=null&&hits.getHits().length>0) {
        //遍历
            for (SearchHit hit : hits.getHits()) {

                String sourceAsString = hit.getSourceAsString();
                //强转封装
                SkuEsModel esModel = JSON.parseObject(sourceAsString, SkuEsModel.class);
                esModels.add(esModel);


            }
        }
        result.setProduct(esModels);

        //2、当前商品涉及的所有属性信息
        List<SearchResult.AttrVo> attrVos = new ArrayList<>();
        ParsedNested attr_agg = response.getAggregations().get("attr_agg");
        ParsedLongTerms attr_id_agg = attr_agg.getAggregations().get("attr_id_agg");
        for (Terms.Bucket bucket : attr_id_agg.getBuckets()) {
            SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
            //1、得到属性的ID
            long attrId = bucket.getKeyAsNumber().longValue();
            //2、得到属性的名字
            String attr_name = ((ParsedStringTerms) bucket.getAggregations().get("attr_name_agg")).getBuckets().get(0).getKeyAsString();
            //3、得到属性的所有值
            List<String> attr_values = ((ParsedStringTerms) bucket.getAggregations().get("attr_value_agg")).getBuckets().stream().map(item -> {
                String keyAsString = ((Terms.Bucket) item).getKeyAsString();
                return keyAsString;
            }).collect(Collectors.toList());

            attrVo.setAttrId(attrId);
            attrVo.setAttrName(attr_name);
            attrVo.setAttrValue(attr_values);
            attrVos.add(attrVo);
        }

        result.setAttrs(attrVos);
        //3、当前所有商品涉及到的品牌信息

        List<SearchResult.BrandVo> brandVos = new ArrayList<>();
        ParsedLongTerms brand_agg = response.getAggregations().get("brand_agg");
        for (Terms.Bucket bucket : brand_agg.getBuckets()) {
            SearchResult.BrandVo brandVo = new SearchResult.BrandVo();
            //1、得到品牌的ID
            long brandId = bucket.getKeyAsNumber().longValue();
            brandVo.setBrandId(brandId);
            //2、得到品牌的名字
            ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brand_name_agg");
            String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();
            brandVo.setBrandName(brandName);

            //3、得到品牌的图片
            ParsedStringTerms brandImgAgg = bucket.getAggregations().get("brand_img_agg");
            String brandImg = brandImgAgg.getBuckets().get(0).getKeyAsString();
            brandVo.setBrandImg(brandImg);



            brandVos.add(brandVo);

        }
        result.setBrands(brandVos);
        //4、当前所有商品涉及到的所有分类信息

        ParsedLongTerms catalog_agg = response.getAggregations().get("catalog_agg");
        List<SearchResult.CatalogVo> catalogVos= new ArrayList<>();
        List<? extends Terms.Bucket> buckets = catalog_agg.getBuckets();
        for (Terms.Bucket bucket : buckets) {

            SearchResult.CatalogVo catalogVo = new SearchResult.CatalogVo();
            //得到分类ID
            String keyAsString = bucket.getKeyAsString();
            catalogVo.setCatalogId(Long.parseLong(keyAsString));
            //得到分类名
            ParsedStringTerms catalog_name_agg = bucket.getAggregations().get("catalog_name_agg");
            String catalog_name = catalog_name_agg.getBuckets().get(0).getKeyAsString();
            catalogVo.setCatalogName(catalog_name);
            catalogVos.add(catalogVo);
        }
        //5.1、获取当前页码
        result.setPageNum(param.getPageNum());
        //5.2、分页信息总记录数
     Long total=hits.getTotalHits().value;
     result.setTotal(total);
        //5.3、总页码
      int totalPages= (int) (total%EsConstant.PRODUCT_PAGESIZE==0?total/EsConstant.PRODUCT_PAGESIZE:(total/EsConstant.PRODUCT_PAGESIZE+1));
      result.setTotalPages(totalPages);

        List<Integer> pageNavs = new ArrayList<>();
        for (int i = 1; i <= totalPages; i++) {
            pageNavs.add(i);
        }
        result.setPageNavs(pageNavs);
      

        return result;
    }

关于ES的一些基础语法链接:ES基础语法

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值