【谷粒商城高级篇】商城业务:商品检索

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

在这里插入图片描述

谷粒商城笔记合集

分布式基础篇分布式高级篇高可用集群篇
===简介&环境搭建======Elasticsearch===
项目简介与分布式概念(第一、二章)Elasticsearch:全文检索(第一章)
基础环境搭建(第三章)===商品服务开发===
===整合SpringCloud===商品服务 & 商品上架(第二章)
整合SpringCloud、SpringCloud alibaba(第四、五章)===商城首页开发===
===前端知识===商城业务:首页整合、Nginx 域名访问、性能优化与压力测试 (第三、四、五章)
前端开发基础知识(第六章)缓存与分布式锁(第六章)
===商品服务开发======商城检索开发===
商品服务开发:基础概念、三级分类(第七、八章)商城业务:商品检索(第七章)
商品服务开发:品牌管理(第九章)
商品服务开发:属性分组、平台属性(第十、十一章)
商品服务:商品维护(第十二、十三章)
===仓储服务开发===
仓储服务:仓库维护(第十四章)
基础篇总结(第十五章)

七、商城业务 & 商品检索⚠️

7.1 整合页面:Thymeleaf⚠️

在这里插入图片描述

在这里插入图片描述

  1. 添加本机的域名映射规则并 清除DNS缓存 :C:\Windows\System32\drivers\etc\hosts

    192.168.10.10 bilimall.com
    192.168.10.10 search.bilimall.com
    #清除DNS缓存内容
    PS C:\Users\ZW L> ipconfig /flushdns
    
    Windows IP 配置
    
    已成功刷新 DNS 解析缓存。
    #显示DNS缓存内容
    PS C:\Users\ZW L> ipconfig /displaydns
    
  2. 引入 Thymeleaf 依赖

    <!-- thymeleaf模板引擎 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    
  3. 检索服务 中关闭 Thymeleaf 缓存application.yaml

    spring:
      thymeleaf:
        cache: false
    
  4. 拷贝检索首页到 检索服务 中:bilimall-search/src/main/resources/templates/index.html

    在这里插入图片描述

  5. 检索服务 中修改 检索首页 :静态资源路径、thymeleaf命名空间、html文件格式等

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
        <!-- 修改为: -->
        <link rel="stylesheet" href="/static/search/css/index.css">
        <script src="/static/search/js/jquery-1.12.4.js"></script>
        <img src="/static/search/image/down-@1x.png" />
        <img src="/static/search/img/01.png" />
        ...
    
  6. 拷贝静态资源到 nginx 中修改 nginx 配置文件并重新启动 nginx 服务:F:\software\Nginx\conf\nginx.conf

    在这里插入图片描述

    ...
    http {
    	...
        server {
            listen       80;
            server_name  *.bilimall.com;
            ...
        }
        ...
    }
    
  7. 网关服务 中添加 检索系统 的路由规则:application.yaml

    spring:
      cloud:
        nacos:
          discovery:
            server-addr: 114.132.162.129:8848
        gateway:
          routes:
            - id: bilimall_route
              uri: lb://bilimall-product
              predicates:
                - Host=bilimall.com
            - id: bilimall_search_route
              uri: lb://bilimall-search
              predicates:
                - Host=search.bilimall.com
    

7.2 整合 dev-tools⚠️

  1. 在检索服务的 pom.xml 中引入dev-tools依赖

    <!-- devtools -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <optional>true</optional>
    </dependency>
    
  2. 让页面修改实时生效:CTRL+F9、CTRL+SHIFT+F9

7.3 商城首页(跳转)检索首页

  1. 修改 商城系统 中点击三级分类的跳转地址:F:\software\Nginx\html\static\index\js\catalogLoader.js

    var cata3link = $("<a href=\"http://search.bilimall.com/list.html?catalog3Id="+ctg3.id+"\" 
    

    在这里插入图片描述

  2. 在 检索服务 中修改首页文件名称并创建首页控制器:list.html、cn.lzwei.bilimall.search.controller.SearchController

    @Controller
    public class SearchController {
    
        @GetMapping(value = {"/","/list.html"})
        public String index(){
            return "list";
        }
    }
    
  3. 修改 商城系统 中检索商品的跳转地址:bilimall-product/src/main/resources/templates/index.html

    function search() {
        var keyword=$("#searchText").val()
        window.location.href="http://search.gulimall.com/list.html?keyword="+keyword;
    }
    

    在这里插入图片描述

  4. 在 检索服务 中修改检索页点击商城首页的跳转地址

    在这里插入图片描述

    <!--头部-->
    <div class="header_head">
        <div class="header_head_box">
            <b class="header_head_p">
                <div style="overflow: hidden">
                    <a href="http://bilimall.com" class="header_head_p_a1" style="width:73px;">
                        谷粒商城首页
                    </a>
    <!--搜索导航-->
    <div class="header_sous">
        <div class="logo">
            <a href="http://bilimall.com">...</a>
        </div>
    

7.4 检索业务分析💡

7.4.1 检索条件VO分析💡

打个比例吧 你肯定上过京东、淘宝买过东西吧? 那么你想要购买什么东西你需要在搜索框中搜索你想要购买的物品那么系统就会给你响应

我在京东搜索 手机 他会显示出相对应的产品

在这里插入图片描述

我们分析可能存在的检索条件并创建对应的VO类:cn.lzwei.bilimall.search.vo.SearchParamVo

/**
 * 封装页面所有可能传递过来的查询条件
 */
@Data
public class SearchParamVo {
    //页面传递过来的全文匹配关键字
    private String keyword;

    //三级分类id
    private Long catalog3Id;

    /**
     * sort=saleCout_asc/desc
     * sort=skuPrice_asc/desc
     * sort=hotScore_asc/desc
     */
    //排序条件
    private String sort;

    /**
     * hasStock=0/1
     * skuPrice=1_500/_500/500_
     * brandId=1
     * attrs=1_红色:黑色&attrs=2_5寸:8寸&
     */
    private Integer hasStock;//是否有库存:1-有库存、0-无库存
    private String skuPrice;//价格区间查询
    private List<Long> brandId;//按照品牌进行查询可以多选:&1_5寸:8寸&
    private List<String> attrs;//按照属性进行筛选
    private Integer pageNum = 1;//页码
}

7.4.2 检索结果VO分析💡

那么返回的数据我们是不是也要创建一个 VO 用来返回页面的数据?借鉴京东的实例来做参考

在这里插入图片描述

抽取出结果VO类:cn.lzwei.bilimall.search.vo.SearchResultVo

/**
 * 检索结果返回
 */
@Data
public class SearchResultVo {
    /**
     * 查询到所有商品的商品信息
     */
    private List<SpuUpTo> products;

    /**
     * 以下是分页信息
     */
    private Integer pageNum;//当前页码
    private Long total;//总共记录数
    private Integer totalPages;//总页码

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

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

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

    /**
     * 页码
     */
    private List<Integer> pageNavs;

    //==================以上是要返回给页面的所有信息
    /**
     * 品牌信息
     */
    @Data
    public static class BrandVo {

        private Long brandId; //品牌id
        private String brandName; //品牌名字
        private String brandImg; //品牌图片
    }
    /**
     * 分类信息
     */
    @Data
    public static class CatalogVo {

        private Long catalogId; //分类id
        private String CatalogName; //品牌名字
    }
    /**
     * 属性信息
     */
    @Data
    public static class AttrVo {
        
        private Long attrId; //属性id
        private String attrName; //属性名字
        private List<String> attrValue; //属性值
    }
}

7.4.3 检索语句分析:DSL

那么这个 DSL 编写我们就在 Kibana 中测试

#GET gulimall_product/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "skuTitle": "华为"
          }
        }
      ],
      "filter": [
        {
          "term": {
            "catalogId": "225"
          }
        },
        {
          "terms": {
            "brandId": [
              "7",
              "8",
              "9"
            ]
          }
        },
        {
          "nested": {
            "path": "attrs",
            "query": {
              "bool": {
                "must": [
                  {
                    "term": {
                      "attrs.attrId": {
                        "value": "3"
                      }
                    }
                  },
                  {
                    "terms": {
                      "attrs.attrValue": [
                        "2019"
                      ]
                    }
                  }
                ]
              }
            }
          }
        },
        {
          "term": { 
            "hasStock": {
              "value": "true"
            }
          }
        },
        {
          "range": {
            "skuPrice": {
              "gte": 0,
              "lte": 7000
            }
          }
        }
      ]
    }
  },
  "sort": [
    {
      "skuPrice": {
        "order": "desc"
      }
    }
  ],
  "from": 0,
  "size": 5,
  "highlight": { 
    "fields": {"skuTitle": {}},
    "pre_tags": "<b style=color:red>",
    "post_tags": "</b>"
  },
  "aggs": {
    "brand_agg": { 
      "terms": {
        "field": "brandId",
        "size": 10
      },
      "aggs": {
        "brand_name_agg": { 
          "terms": {
            "field": "brandName",
            "size": 1
          }
        },
        "brand_img_agg": { 
          "terms": {
            "field": "brandImg",
            "size": 1
          }
        }
      }
    },
    "catalog_agg": { 
      "terms": {
        "field": "catalogId",
        "size": 10
      },
      "aggs": {
        "catalog_name_agg": { 
          "terms": {
            "field": "catalogName",
            "size": 10
          }
        }
      }
    },
    "attr_agg":{
      "nested": {
        "path": "attrs"
      },
      "aggs": { 
        "attr_id_agg": {
          "terms": {
            "field": "attrs.attrId",
            "size": 10
          },
          "aggs": {
            "attr_name_agg": { 
              "terms": {
                "field": "attrs.attrName",
                "size": 10
              }
            },
            "attr_value_agg":{ 
              "terms": {
                "field": "attrs.attrValue",
                "size": 10
              }
            }
          }
        }
      }
    }
  }
}

7.4.4 /product/_mapping

#PUT product
{
  "mappings":{
    "properties":{
      "skuId":{
        "type":"long"
      },
       "spuId":{
        "type":"keyword"
      },
       "skuTitle":{
        "type":"text",
        "analyzer": "ik_smart"
      },
       "skuPrice":{
        "type":"keyword"
      },
       "skuImg":{
        "type":"text",
        "analyzer": "ik_smart"
      },
       "saleCount":{
        "type":"long"
      },
       "hasStock":{
        "type":"boolean"
      },
      "hotScore":{
        "type":"long"
      },
      "brandId":{
        "type":"long"
      },
      "catelogId":{
        "type":"long"
      },
      "brandName":{
        "type":"keyword"
      },
      "brandImg":{
        "type":"keyword"
      },
      "catalogName":{
        "type":"keyword"
      },
      "attrs":{
        "type":"nested",
        "properties": {
          "attrId":{
            "type":"long"
          },
          "attrName":{
            "type":"keyword"
          },
          "attrValue": {
            "type":"keyword"
          }
        }
      }
    }
  }
}

7.5 API:构建检索请求条件💡

7.5.1 代码编写

代码对照着 7.4.3小节 生成的DSL检索条件进行编写

term 和 terms 不要调用错误

在这里插入图片描述

  1. cn.lzwei.bilimall.search.constant.EsConstant

    public class EsConstant {
        //商品索引
        public static final String PRODUCT_INDEX="product";
        //页大小
        public static final int SEARCH_PAGESIZE=12;
    }
    
  2. cn.lzwei.bilimall.search.controller.SearchController

    /**
     * 检索系统首页控制器
     */
    @Controller
    public class SearchController {
    
        @Resource
        SearchService searchService;
    
        @GetMapping(value = {"/","/list.html"})
        public String index(SearchParamVo searchParamVo, Model model){
            //在返回页面中设置检索结果
            SearchResultVo result=searchService.search(searchParamVo);
            model.addAttribute("result",result);
            return "list";
        }
    }
    
  3. cn.lzwei.bilimall.search.service.SearchService

    /**
     * 检索业务
     */
    public interface SearchService {
        /**
         * 返回检索的结果
         */
        SearchResultVo search(SearchParamVo searchParamVo);
    }
    
  4. cn.lzwei.bilimall.search.service.impl.SearchServiceImpl

    /**
     * 检索业务
     */
    @Service(value = "searchService")
    public class SearchServiceImpl implements SearchService {
    
        @Resource
        RestHighLevelClient restHighLevelClient;
        /**
         * 返回检索的结果
         */
        @Override
        public SearchResultVo search(SearchParamVo searchParamVo) {
            //1.构建请求
            SearchRequest searchRequest = buildSearchRequest(searchParamVo);
            System.out.println("构建的请求"+searchRequest.source().toString());
            //2.执行请求
            SearchResponse response=null;
            try {
                response = restHighLevelClient.search(searchRequest, BilimallElasticsearchConfig.COMMON_OPTIONS);
            } catch (IOException e) {
                e.printStackTrace();
            }
            //3.封装返回结果
            SearchResultVo searchResultVo=buildSearchResultVo(response,searchParamVo);
            return searchResultVo;
        }
        //构建请求
        private SearchRequest buildSearchRequest(SearchParamVo searchParamVo) {
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            /**
             * 查询
             */
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            //1.检索关键字
            if(!StringUtils.isEmpty(searchParamVo.getKeyword())){
                boolQuery.must(QueryBuilders.matchQuery("skuTitle",searchParamVo.getKeyword()));
            }
            //2.过滤三级分类
            if(searchParamVo.getCatalog3Id()!=null){
                boolQuery.filter(QueryBuilders.termQuery("catalogId",searchParamVo.getCatalog3Id()));
            }
            //3.过滤是否有库存
            if(searchParamVo.getHasStock()!=null){
                boolQuery.filter(QueryBuilders.termQuery("hasStock",searchParamVo.getHasStock()==1));
            }
            //4.过滤价格区间:_500/1_500/500_
            if(!StringUtils.isEmpty(searchParamVo.getSkuPrice())){
                String skuPrice = searchParamVo.getSkuPrice();
                String[] s = searchParamVo.getSkuPrice().split("_");
                if(s.length==1){
                    if(skuPrice.startsWith("_")){
                        boolQuery.filter(QueryBuilders.rangeQuery("skuPrice").lte(s[0]));
                    }
                    if(skuPrice.endsWith("_")){
                        boolQuery.filter(QueryBuilders.rangeQuery("skuPrice").gte(s[0]));
                    }
                }else {
                    boolQuery.filter(QueryBuilders.rangeQuery("skuPrice").gte(s[0]).lte(s[1]));
                }
            }
            //5.过滤品牌
            if(searchParamVo.getBrandId()!=null){
                boolQuery.filter(QueryBuilders.termsQuery("brandId",searchParamVo.getBrandId()));
            }
            //6.过滤\属性
            if(searchParamVo.getAttrs()!=null && searchParamVo.getAttrs().size()>0){
                for (String attr : searchParamVo.getAttrs()) {
                    String[] attrInfo = attr.split("_");
                    BoolQueryBuilder attrBoolQuery = QueryBuilders.boolQuery();
                    //6.1、属性id
                    attrBoolQuery.must(QueryBuilders.termQuery("attrs.attrId",attrInfo[0]));
                    //6.2、属性值
                    attrBoolQuery.must(QueryBuilders.termsQuery("attrs.attrValue",attrInfo[1].split(":")));
                    boolQuery.filter(QueryBuilders.nestedQuery("attrs",attrBoolQuery, ScoreMode.None));
                }
            }
            searchSourceBuilder.query(boolQuery);
            /**
             * 排序
             * sort=saleCount_asc/desc
             * sort=skuPrice_asc/desc
             * sort=hotScore_asc/desc
             * 分页
             */
            //7.排序
            if(!StringUtils.isEmpty(searchParamVo.getSort())){
                String[] sortInfo = searchParamVo.getSort().split("_");
                if(sortInfo[0].equalsIgnoreCase("skuPrice")){
                    if(sortInfo[1].equalsIgnoreCase(SortOrder.ASC.toString())){
                        searchSourceBuilder.sort("skuPrice", SortOrder.ASC);
                    }
                    if(sortInfo[1].equalsIgnoreCase(SortOrder.DESC.toString())){
                        searchSourceBuilder.sort("skuPrice", SortOrder.DESC);
                    }
                }
            }
            //8.页码
            if(searchParamVo.getPageNum()!=null){
                searchSourceBuilder.from((searchParamVo.getPageNum()-1)*EsConstant.SEARCH_PAGESIZE);
                searchSourceBuilder.size(EsConstant.SEARCH_PAGESIZE);
            }
            /**
             * 高亮、聚合
             */
            //9.高亮
            if (!StringUtils.isEmpty(searchParamVo.getKeyword())){
                HighlightBuilder highlightBuilder = new HighlightBuilder();
                highlightBuilder.field("skuTitle").preTags("<b style=color:red>").postTags("</b>");
                searchSourceBuilder.highlighter(highlightBuilder);
            }
            //10.聚合
            //10.1、品牌
            TermsAggregationBuilder brandIdAgg = AggregationBuilders.terms("brand_agg").field("brandId").size(50);//品牌id
            brandIdAgg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(1));//品牌名
            brandIdAgg.subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg").size(1));//品牌图片
            searchSourceBuilder.aggregation(brandIdAgg);
            //10.2、分类
            TermsAggregationBuilder catalogIdAgg = AggregationBuilders.terms("catalog_agg").field("catalogId").size(50);//分类id
            catalogIdAgg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(1));//分类名
            searchSourceBuilder.aggregation(catalogIdAgg);
            //10.3、属性
            NestedAggregationBuilder attrsNestedAgg = AggregationBuilders.nested("attr_agg", "attrs");
            TermsAggregationBuilder attrAgg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId").size(50);
            attrAgg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(1));
            attrAgg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(50));
            attrsNestedAgg.subAggregation(attrAgg);
            searchSourceBuilder.aggregation(attrsNestedAgg);
    
            SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX},searchSourceBuilder);
            return searchRequest;
        }
        //封装返回结果
        private SearchResultVo buildSearchResultVo(SearchResponse response) {
            SearchResultVo searchResultVo = new SearchResultVo();
            return searchResultVo;
        }
    }
    
    

7.5.2 测试

使用PostMan发送请求获取检索条件

在这里插入图片描述

验证检索条件

在这里插入图片描述

7.6 API:封装检索结果💡

7.6.1 代码编写

在Debug模式下对照着 resultVO 对返回的结果进行封装

在这里插入图片描述

  1. cn.lzwei.bilimall.search.constant.EsConstant

    public class EsConstant {
        //商品索引
        public static final String PRODUCT_INDEX="product";
        //页大小
        public static final int SEARCH_PAGESIZE=12;
    }
    
  2. cn.lzwei.bilimall.search.controller.SearchController:返回检索页面

    /**
     * 检索系统首页控制器
     */
    @Controller
    public class SearchController {
    
        @Resource
        SearchService searchService;
    
        @GetMapping(value = {"/","/list.html"})
        public String index(SearchParamVo searchParamVo, Model model){
            //在返回页面中设置检索结果
            SearchResultVo result=searchService.search(searchParamVo);
            model.addAttribute("result",result);
            return "list";
        }
    }
    
  3. cn.lzwei.bilimall.search.service.SearchService:检索并封装结果

    /**
     * 检索业务
     */
    public interface SearchService {
        /**
         * 返回检索的结果
         */
        SearchResultVo search(SearchParamVo searchParamVo);
    }
    
  4. cn.lzwei.bilimall.search.service.impl.SearchServiceImpl:检索并封装结果

    /**
     * 检索业务
     */
    @Service(value = "searchService")
    public class SearchServiceImpl implements SearchService {
    
        @Resource
        RestHighLevelClient restHighLevelClient;
        /**
         * 返回检索的结果
         */
        @Override
        public SearchResultVo search(SearchParamVo searchParamVo) {
            //1.构建请求
            SearchRequest searchRequest = buildSearchRequest(searchParamVo);
            System.out.println("构建的请求"+searchRequest.source().toString());
            //2.执行请求
            SearchResponse response=null;
            try {
                response = restHighLevelClient.search(searchRequest, BilimallElasticsearchConfig.COMMON_OPTIONS);
            } catch (IOException e) {
                e.printStackTrace();
            }
            //3.封装返回结果
            SearchResultVo searchResultVo=buildSearchResultVo(response,searchParamVo);
            return searchResultVo;
        }
        //构建请求
        private SearchRequest buildSearchRequest(SearchParamVo searchParamVo) {
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            ...
            return searchRequest;
        }
        //封装返回结果
        private SearchResultVo buildSearchResultVo(SearchResponse response,SearchParamVo searchParamVo) {
            SearchResultVo searchResultVo = new SearchResultVo();
            /**
             * 所有商品信息
             */
            //1.封装商品信息
            List<SpuUpTo> products=new ArrayList<>();
            SearchHit[] hits = response.getHits().getHits();
            for (SearchHit hit : hits) {
                String sourceAsString = hit.getSourceAsString();
                SpuUpTo sourceParseObject = JSON.parseObject(sourceAsString, new TypeReference<SpuUpTo>() {
                });
                HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");
                if(skuTitle!=null && !StringUtils.isEmpty(searchParamVo.getKeyword())){
                    sourceParseObject.setSkuTitle(skuTitle.getFragments()[0].string());
                }
                products.add(sourceParseObject);
            }
            searchResultVo.setProducts(products);
            /**
             * 涉及到的所有品牌
             */
            List<SearchResultVo.BrandVo> brands=new ArrayList<>();
            ParsedLongTerms brand_agg = response.getAggregations().get("brand_agg");
            for (Terms.Bucket bucket : brand_agg.getBuckets()) {
                SearchResultVo.BrandVo brandVo = new SearchResultVo.BrandVo();
                brandVo.setBrandId(bucket.getKeyAsNumber().longValue());//设置品牌id
                ParsedStringTerms brand_img_agg = bucket.getAggregations().get("brand_img_agg");
                brandVo.setBrandImg(brand_img_agg.getBuckets().get(0).getKeyAsString());//设置品牌图片
                ParsedStringTerms brand_name_agg = bucket.getAggregations().get("brand_name_agg");
                brandVo.setBrandName(brand_name_agg.getBuckets().get(0).getKeyAsString());//设置品牌名
                brands.add(brandVo);
            }
            searchResultVo.setBrands(brands);
            /**
             * 涉及到的所有分类
             */
            List<SearchResultVo.CatalogVo> catalogs=new ArrayList<>();
            ParsedLongTerms catalog_agg = response.getAggregations().get("catalog_agg");
            for (Terms.Bucket bucket : catalog_agg.getBuckets()) {
                SearchResultVo.CatalogVo catalogVo = new SearchResultVo.CatalogVo();
                catalogVo.setCatalogId(bucket.getKeyAsNumber().longValue());//设置分类id
                ParsedStringTerms catalog_name_agg = bucket.getAggregations().get("catalog_name_agg");
                catalogVo.setCatalogName(catalog_name_agg.getBuckets().get(0).getKeyAsString());//设置分类名
                catalogs.add(catalogVo);
            }
            searchResultVo.setCatalogs(catalogs);
            /**
             * 涉及到的所有属性
             */
            List<SearchResultVo.AttrVo> attrs=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()) {
                SearchResultVo.AttrVo attrVo = new SearchResultVo.AttrVo();
                attrVo.setAttrId((Long) bucket.getKey());//设置属性id
                ParsedStringTerms attr_name_agg = bucket.getAggregations().get("attr_name_agg");
                attrVo.setAttrName(attr_name_agg.getBuckets().get(0).getKeyAsString());//设置属性名
                ParsedStringTerms attr_value_agg = bucket.getAggregations().get("attr_value_agg");
                List<String> attrValues = attr_value_agg.getBuckets().stream().map(item -> item.getKeyAsString()
                ).collect(Collectors.toList());
                attrVo.setAttrValue(attrValues);//设置属性值
                attrs.add(attrVo);
            }
            searchResultVo.setAttrs(attrs);
            /**
             * 分页信息
             */
            long total = response.getHits().getTotalHits().value;//获取总记录数
            //设置总记录数
            searchResultVo.setTotal(total);
            //设置总页码
            Integer totalPage=(int) (total%EsConstant.SEARCH_PAGESIZE==0?(total/EsConstant.SEARCH_PAGESIZE):(total/EsConstant.SEARCH_PAGESIZE+1));
            searchResultVo.setTotalPages(totalPage);
            //设置当前页码
            searchResultVo.setPageNum(searchParamVo.getPageNum());
            //设置页码列表
            List<Integer> pageNavs=new ArrayList<>();
            for (int i = 1; i <= totalPage; i++) {
                pageNavs.add(i);
         }
            searchResultVo.setPageNavs(pageNavs);
    
            return searchResultVo;
        }
    }
    

7.6.2 测试

在这里插入图片描述

7.7 前端:检索页面渲染

7.7.1 渲染:商品&筛选条件

在这里插入图片描述

7.7.2 检索:增加筛选条件

<!--品牌-->
<li th:each="brand:${result.brands}">
    <a href="/static/search/#" th:href="${'javascript:searchProducts(&quot;brandId&quot;,'+brand.brandId+')'}">
        <img th:src="${brand.brandImg}" alt="">
        <div th:text="${brand.brandName}">
            华为(HUAWEI)
        </div>
    </a>
</li>
<!--分类-->
<li th:each="catalog:${result.catalogs}">
    <a href="/static/search/#" th:href="${'javascript:searchProducts(&quot;catalog3Id&quot;,'+catalog.catalogId+')'}">
        <div th:text="${catalog.catalogName}">
            手机
        </div>
    </a>
</li>
<!--其他属性-->
<li th:each="value:${attr.attrValue}">
    <a href="/static/search/#"
       th:href="${'javascript:searchProducts(&quot;attrs&quot;,&quot;'+attr.attrId+'_'+value+'&quot;)'}" 
       th:text="${value}">
        5.56英寸及以上
    </a>
</li>
<script>
    function searchProducts(name, value) {
        //原來的页面
        location.href = replaceParamVal(location.href,name,value,true)
    };
</script>

检索首页

在这里插入图片描述

筛选 手机、iphone

在这里插入图片描述

筛选 上市日期、入网型号

在这里插入图片描述

7.7.3 检索:分页&关键字

分页

在这里插入图片描述

关键字

在这里插入图片描述

<!--关键字-->
<div class="header_form">
    <input type="text"  id="keyword_input" placeholder="手机" th:value="${param.keyword}"/>
    <a href="javascript:searchByKeyword();">搜索</a>
</div>
<!--分页-->
<div class="page_wrap">
    <span class="page_span1">
        <a class="page_a" th:attr="pn=${result.pageNum - 1}" th:if="${result.pageNum > 1}">
            < 上一页
        </a>
        <a class="page_a" th:each="nav:${result.pageNavs}" 
           th:attr="pn=${nav},style=${nav == result.pageNum?'border:0;color:#ee2222;background: #fff':''}">
            [[${nav}]]
        </a>
        <a class="page_a" th:attr="pn=${result.pageNum + 1}"
           th:if="${result.pageNum < result.totalPages}">
            下一页 >
        </a>
    </span>
    <span class="page_span2">
        <em><b>[[${result.totalPages}]]</b>&nbsp;&nbsp;到第</em>
        <input type="number" value="1">
        <em></em>
        <a href="#">确定</a>
    </span>
</div>
<script>
    <!--分页函数-->
    $(".page_a").click(function () {
        var pn=$(this).attr("pn");
        location.href=replaceParamVal(location.href,"pageNum",pn,false);
        console.log(replaceParamVal(location.href,"pageNum",pn,false))
    });
    <!--关键字-->
    function searchByKeyword() {
        location.href = replaceParamVal(location.href,"keyword", $("#keyword_input").val(),false);
    };
    /**
     * @param url 目前的url
     * @param paramName 需要替换的参数属性名
     * @param replaceVal 需要替换的参数的新属性值
     * @param forceAdd 该参数是否可以重复查询(attrs=1_3G:4G:5G&attrs=2_骁龙845&attrs=4_高清屏)
     * @returns {string} 替换或添加后的url
     */
    function replaceParamVal(url, paramName, replaceVal,forceAdd) {
        var oUrl = url.toString();
        var nUrl;
        if (oUrl.indexOf(paramName) != -1) {
            if( forceAdd && oUrl.indexOf(paramName+"="+replaceVal)==-1) {
                if (oUrl.indexOf("?") != -1) {
                    nUrl = oUrl + "&" + paramName + "=" + replaceVal;
                } else {
                    nUrl = oUrl + "?" + paramName + "=" + replaceVal;
                }
            } else {
                var re = eval('/(' + paramName + '=)([^&]*)/gi');
                nUrl = oUrl.replace(re, paramName + '=' + replaceVal);
            }
        } else {
            if (oUrl.indexOf("?") != -1) {
                nUrl = oUrl + "&" + paramName + "=" + replaceVal;
            } else {
                nUrl = oUrl + "?" + paramName + "=" + replaceVal;
            }
        }
        return nUrl;
    };
</script>

7.7.4 检索:排序

在这里插入图片描述

<!--综合排序、销量排序、价格排序-->
<div class="filter_top_left" th:with="p = ${param.sort}, priceRange = ${param.skuPrice}">
    <a sort="hotScore"
       th:class="${(!#strings.isEmpty(p) && #strings.startsWith(p,'hotScore') && #strings.endsWith(p,'desc')) ? 'sort_a desc' : 'sort_a'}"
       th:attr="style=${(#strings.isEmpty(p) || #strings.startsWith(p,'hotScore')) ?
                                   'color: #fff; border-color: #e4393c; background: #e4393c;':'color: #333; border-color: #ccc; background: #fff;' }">
        综合排序[[${(!#strings.isEmpty(p) && #strings.startsWith(p,'hotScore') && 
        #strings.endsWith(p,'desc')) ?'↓':'↑' }]]
    </a>
    <a sort="saleCount"
       th:class="${(!#strings.isEmpty(p) && #strings.startsWith(p,'saleCount') && #strings.endsWith(p,'desc')) ? 'sort_a desc' : 'sort_a'}"
       th:attr="style=${(!#strings.isEmpty(p) && #strings.startsWith(p,'saleCount')) ? 
                'color: #fff; border-color: #e4393c; background: #e4393c;':'color: #333; border-color: #ccc; background: #fff;' }">
        销量[[${(!#strings.isEmpty(p) && #strings.startsWith(p,'saleCount') && 
        #strings.endsWith(p,'desc'))?'↓':'↑'  }]]
    </a>
    <a sort="skuPrice" 
       th:class="${(!#strings.isEmpty(p) && #strings.startsWith(p,'skuPrice') && #strings.endsWith(p,'desc')) ? 'sort_a desc' : 'sort_a'}" 
       th:attr="style=${(!#strings.isEmpty(p) && #strings.startsWith(p,'skuPrice')) ? 
                'color: #fff; border-color: #e4393c; background: #e4393c;':'color: #333; border-color: #ccc; background: #fff;' }">
        价格[[${(!#strings.isEmpty(p) && #strings.startsWith(p,'skuPrice') && 
        #strings.endsWith(p,'desc'))?'↓':'↑'  }]]
    </a>
</div>
<script>
    /* 筛选排序条件 */
    $(".sort_a").click(function () {
        //1.点击排序条件:改变当前元素以及兄弟元素的样式
        // changeStyle(this);
        //2.跳转到指定位置
        $(this).toggleClass("desc");
        let sort = $(this).attr("sort");
        sort = $(this).hasClass("desc") ? sort + "_desc" : sort + "_asc";
        location.href = replaceParamVal(location.href, "sort", sort,false);
        //禁用默认行为
        return false;
    });
</script>

7.7.5 检索:价格区间&库存

在这里插入图片描述

<!--价格区间-->
<input id="skuPriceFrom" type="number"
       th:value="${#strings.isEmpty(priceRange)?'':#strings.substringBefore(priceRange,'_')}"
       style="width: 100px; margin-left: 30px">
                            -
<input id="skuPriceTo" type="number"
       th:value="${#strings.isEmpty(priceRange)?'':#strings.substringAfter(priceRange,'_')}"
       style="width: 100px">
<button id="skuPriceSearchBtn">确定</button>
<!--是否有货-->
<a th:with="check = ${param.hasStock}">
    <input id="showHasStock" type="checkbox" th:checked="${#strings.equals(check,'1')?true:false}">
    仅显示有货
</a>
<script>
    <!--仅显示有货-->
    $("#showHasStock").click(function () {
        let checked = $(this).prop("checked");
        console.log(checked);
        location.href=replaceParamVal(location.href,"hasStock",checked?1:0,false)
    });
    <!--价格区间-->
    $("#skuPriceSearchBtn").click(function () {
        var skuPriceFrom = $("#skuPriceFrom").val();
        var skuPriceTo = $("#skuPriceTo").val();
        location.href = replaceParamVal(location.href, "skuPrice", skuPriceFrom + "_" + skuPriceTo, false);
    });
</script>

7.8 前后端开发:面包屑导航💡

在这里插入图片描述

后端开发

  1. cn.lzwei.bilimall.search.vo.SearchResultVo:在页面返回数据Vo中添加面包屑集合

    /**
     * 检索结果返回
     */
    @Data
    public class SearchResultVo {
        /**
         * 面包屑
         */
        private List<NavVo> Navs;
    
        //==================以上是要返回给页面的所有信息
    
        /**
         * 面包屑Vo
         */
        @Data
        public static class NavVo{
            private String navName;
            private String navValue;
            private String link;
        }
    }
    
  2. cn.lzwei.bilimall.search.vo.SearchParamVo:在请求参数VO中添加原始请求参数

    @Data
    public class SearchParamVo {
        private String _queryString;//请求参数
    }
    
  3. cn.lzwei.bilimall.search.controller.SearchController:添加请求参数

    @Controller
    public class SearchController {
    
        @GetMapping(value = {"/","/list.html"})
        public String index(SearchParamVo searchParamVo, Model model, HttpServletRequest httpServletRequest){
            searchParamVo.set_queryString(httpServletRequest.getQueryString());//设置原始请求参数
            ...
        }
    }
    
  4. cn.lzwei.bilimall.search.service.impl.SearchServiceImpl:在页面返回数据Vo的封装中添加面包屑的封装

    /**
     * 检索业务
     */
    @Service(value = "searchService")
    public class SearchServiceImpl implements SearchService {
    
        //封装返回结果
        private SearchResultVo buildSearchResultVo(SearchResponse response,SearchParamVo searchParamVo) {
            SearchResultVo searchResultVo = new SearchResultVo();
            
            /**
             * 涉及到的所有属性
             */
            List<SearchResultVo.AttrVo> attrs=new ArrayList<>();
            ParsedNested attr_agg = response.getAggregations().get("attr_agg");
            ParsedLongTerms attr_id_agg = attr_agg.getAggregations().get("attr_id_agg");
            ...
            /**
             * 面包屑
             */
            List<SearchResultVo.NavVo> navs=null;
            if(searchParamVo.getAttrs()!=null && searchParamVo.getAttrs().size()>0){
                navs=searchParamVo.getAttrs().stream().map(attr->{
                    //attrs=1_2018:2019
                    SearchResultVo.NavVo navVo = new SearchResultVo.NavVo();
                    String[] str = attr.split("_");
                    //1.属性名(在es返回中获取)
                    for (Terms.Bucket bucket : attr_id_agg.getBuckets()) {
                        if(bucket.getKey().equals(Long.parseLong(str[0]))){
                            ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attr_name_agg");
                            navVo.setNavName(attrNameAgg.getBuckets().get(0).getKeyAsString());
                        }
                    }
                    //2.属性值
                    navVo.setNavValue(str[1]);
                    //3.导航链接
                    //3.1、获取请求参数
                    String encode=null;
                    try {
                        encode = URLEncoder.encode(attr, "UTF-8");
                        encode.replace("+","%20");//浏览器与java对空格的编码不一致
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    }
                    String queryString = searchParamVo.get_queryString();
                    //3.2、将当前属性替换为空串
                    String replace = queryString.replace("&attrs=" + encode, "");
                    navVo.setLink("http://search.bilimall.com/list.html?"+replace);
                    return navVo;
                }).collect(Collectors.toList());
            }
            searchResultVo.setNavs(navs);
    
            return searchResultVo;
        }
    }
    

前端开发

bilimall-search/src/main/resources/templates/list.html

<div class="JD_ipone_one c">
    <!-- 遍历面包屑功能 -->
    <a th:href="${nav.link}" th:each="nav:${result.navs}">
        <span th:text="${nav.navName}"></span><span th:text="${nav.navValue}"></span> 
        x
    </a>
</div>
阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

“【谷粒商城高级篇】商城业务:商品检索” 的相关文章