电商项目——商城业务-检索服务——第六章——中篇

电商项目——全文检索-ElasticSearch——第一章——中篇
电商项目——商城业务-商品上架——第二章——中篇
电商项目——商城业务-首页——第三章——中篇
电商项目——性能压测——第四章——中篇
电商项目——缓存——第五章——中篇
电商项目——商城业务-检索服务——第六章——中篇
电商项目——商城业务-异步——第七章——中篇
电商项目——商品详情——第八章——中篇
电商项目——认证服务——第九章——中篇
电商项目——购物车——第十章——中篇
电商项目——消息队列——第十一章——中篇
电商项目——订单服务——第十二章——中篇
电商项目——分布式事务——第十三章——中篇

文章目录

  • 1:搭建页面环境
  • 2:调整页面跳转
  • 3:检索查询参数模型分析抽取
  • 4:检索返回结果模型分析抽取
  • 5:检索DSL测试-查询部分
  • 6:检索DSL测试-聚合部分
  • 7:SearchRequest构建-检索
  • 8:SearchRequest构建-排序,分页,高亮&测试
  • 9:SearchRequest构建-聚合
  • 10:SearchResponse分析&封装
  • 11:验证结果封装正确性
  • 12:页面基本数据渲染
  • 13:页面筛选条件渲染
  • 14:页面分页数据渲染
  • 15:页面排序功能
  • 16:页面排序字段回显
  • 17:页面价格区间搜索
  • 18:面包屑导航
  • 19:条件删除与URL编码问题
  • 20:条件筛选联动

1:搭建页面环境

我们接下来就搭建整个商城的检索功能,同样的按照微服务自治的功能,我们的检索功能就放在mall-search下
电商项目——商城业务-检索服务——第六章——中篇_第1张图片
电商项目——商城业务-检索服务——第六章——中篇_第2张图片
index.html要引入依赖和标头,并且进行替换(变成linux的nginx中的文件路径/html/)

		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-thymeleafartifactId>
		dependency>
<html lang="en" xmlns:th="http://www.thymeleaf.org">

电商项目——商城业务-检索服务——第六章——中篇_第3张图片
电商项目——商城业务-检索服务——第六章——中篇_第4张图片
将所有静态资源放在linux中的/mydata/nginx/html/static/search下,实现动静分离
电商项目——商城业务-检索服务——第六章——中篇_第5张图片
我们要实现通过访问search.mall.com然后跳转到nginx,在到网关,最后回显到检索页面
第一步:配置域名网关映射
电商项目——商城业务-检索服务——第六章——中篇_第6张图片
第二步:进行nginx的配置,配置mall.conf

电商项目——商城业务-检索服务——第六章——中篇_第7张图片

第三步:进行网关配置,,前提:mall-serach要配置在nacos服务中心里面

       - id: mall_host_route
        uri: lb://mall-search
        predicates:
          - Host=search.mall.com   

电商项目——商城业务-检索服务——第六章——中篇_第8张图片
电商项目——商城业务-检索服务——第六章——中篇_第9张图片

2:调整页面跳转

前面搭建好了页面检索环境,现在我们就来梳理一下检索逻辑
在这之前,我们要配置dev-tools的依赖和配置关闭缓存

		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-devtoolsartifactId>
		dependency>
spring:
  thymeleaf:
    cache: false

电商项目——商城业务-检索服务——第六章——中篇_第10张图片
接下来我们要完成商城首页(zlj.mall.com)跳转到检索页面的跳转(search.mall.com)
第一个:打通点击三级分类下数据跳转到检索页面
第二个:点击搜素内容可以跳转到检索页面
接下来一一介绍
电商项目——商城业务-检索服务——第六章——中篇_第11张图片
1:打通点击三级分类下数据跳转到检索页面

电商项目——商城业务-检索服务——第六章——中篇_第12张图片
mall-search
controller
SearchController

@Controller
public class SearchController {

    @GetMapping("/list.html")
    public String listPage(){

        return "list";
    }
}

2:点击搜素内容可以跳转到检索页面
电商项目——商城业务-检索服务——第六章——中篇_第13张图片
search()下的方法

      window.location.href="http://search.mall.com/list.html?keyword="+keyword;

如上我们就完成了调整页面跳转

3:检索查询参数模型分析抽取

前面我们无论是选择分类,还是进行检索关键字,都打通了到search.mall.com/list.html的检索系统(mall-search),接下来,我们就分析一下检索系统检索这些商品都需要哪些检索条件。。我们是由mall-search中的controller下的SearchController类下的如下方法 @GetMapping("/list.html") public String listPage()来完成的,它要做的事情就是它要接收所有的检索条件,然后进行处理,最后进行返回;我们要写一个SearchParam类来接受所有的检索条件,还要写一个接口来进行处理,最后返回给页面list
mall-search
vo
SearchParam
如上操作流程,转化成如下代码演示

@Controller
public class SearchController {

    @Autowired
    MallService mallService;
    @GetMapping("/list.html")
    public String listPage(SearchParam searchParam){

       Object result= mallService.search(param);
        return "list";
    }

}

我们现在就要分析,封装页面所有可呢传递过来的查询条件,查询条件有哪些???并把他们写入SearchParam类中
商品检索条件的三个入口,我们第三个入口要传递的参数是最难判断的
电商项目——商城业务-检索服务——第六章——中篇_第14张图片
电商项目——商城业务-检索服务——第六章——中篇_第15张图片
电商项目——商城业务-检索服务——第六章——中篇_第16张图片
如下就是我们分析的有可呢会从前端传递到后端的检索条件,最终我们也希望我们的MallSearchService中的方法可以去es中通过这些检索条件,来找到我们想要的查询结果

/**
 * 封装页面所有可呢传递过来的查询条件
 * 自动将页面提交过来的所有请求查询参数封装成指定的对象SearchParam 
 * @author Mr.zhneg
 * @create 2020-11-04-22:49
 */
@Data
public class SearchParam {

    private String keyword;//页面传递过来的全文匹配关键字
    private Long catalog3Id;//三级分类id

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

    /**
     * 好多的过滤条件
     * hasStock(是否有货)  ,skuPrice价格区间,brandId,catalog3Id,attrs
     * hasStock=0/1
     * skuPrice =1_500/_500/500_
     * brandId=1
     *
     */
    private Integer hasStock;//是否有货
    private String skuPrice;//价格区间查询
    private List<Long> brandId;//按照品牌进行查询,可以多选
    private List<String> attrs;//按照属性进行筛选
    private Integer pageNum;//页码
}

4:检索返回结果模型分析抽取

前面我们通过分析页面,将所有可呢提交的查询参数封装成了SearchParam 对象,我们调用方法把前端提交过来的参数,查询出对应的结果然后通过封装返回给页面
电商项目——商城业务-检索服务——第六章——中篇_第17张图片

电商项目——商城业务-检索服务——第六章——中篇_第18张图片
mall-search
vo
SearchResult

@Data
public class SearchResult{

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

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

    private List<BrandVo> brands;//当前查询到的结果,所有涉及到的品牌
    private List<CatalogVo> catalogs;//当前查询到的结果,所有涉及到的分类
    private List<AttrVo> attrs;//当前查询到的结果,所有涉及到的属性
    /**
     *=====以上是返回给页面的所有信息
     */
    @Data
    public static class BrandVo{
        private Long brandId;
        private String brandName;
        private String brandImg;
    }
    @Data
    public static class CatalogVo{
        private Long catalogId;
        private String catalogName;
    }

    @Data
    public static class AttrVo{
        private Long attrId;
        private String attrName;
        private String attrValue;
    }
}

SearchController 大致框架已经搭建完成,下章节,我们就讲如何去es中查询

public class SearchController {

    @Autowired
    MallSearchService mallService;

    /**
     * 自动将页面提交过来的所有请求查询参数封装成指定的对象
     * @param searchParam
     * @return
     */
    @GetMapping("/list.html")
    public String listPage(SearchParam searchParam, Model model){

        //1:根据传递来的页面的查询参数,去es中检索商品
       SearchResult result= mallService.search(searchParam);
       model.addAttribute("result",result);

        return "list";
    }

}

5:检索DSL测试-查询部分

mall-search

  • 前面我们抽取了两种数据模型,第一个是我们可呢从页面传递过来的请求参数,我们封装成SearchParam,还有一个就是我们将页面的返回结果抽取成SearchResult,我们contoller(SearchController)接受到请求就应该去调用业务逻辑里面的检索功能进行检索;而这些功能非常复杂,我们现在就要使用kibana来测试如何检索商品,为了以后可以在search(MallSearchService)方法中更好的实现业务代码
    电商项目——商城业务-检索服务——第六章——中篇_第19张图片
    我在码云中的项目里面已经保存了,我测试检索DSL-查询部分地址如下,都在mall-search中有一个.json文件结尾的
    zlj分布式代码
    更难的在后面,
    电商项目——商城业务-检索服务——第六章——中篇_第20张图片

每次查询一个东西以后,下一次我们要查询的东西是动态变化的,它是根据我们已查到的东西聚合分析出来的(我们查到的东西的属性,值有哪些),我们下节就完成检索DSL测试-聚合部分
https://www.elastic.co/guide/index.html

6:检索DSL测试-聚合部分

电商项目——商城业务-检索服务——第六章——中篇_第21张图片

我在码云中的项目里面已经保存了,我测试检索DSL-聚合部分地址如下,都在mall-search中有一个.json文件结尾的
zlj分布式代码
https://www.elastic.co/guide/index.html

7:SearchRequest构建-检索

动态的构建出查询所需要的dsl语句
https://www.elastic.co/guide/index.html
mall-search
MallSearchServiceImpl

@Service("MallSearchService")
@Slf4j
public class MallSearchServiceImpl implements MallSearchService {

    @Autowired
    private RestHighLevelClient client;
    @Override
    public SearchResult search(SearchParam param) throws IOException {
        //1:动态构建出查询需要的DSL语句
        SearchResult result=null;

        //1:准备检索请求
        SearchRequest searchRequest=new SearchRequest();
        searchRequest= buildSearchRequest(param);
        try {
            //2:执行检索请求
            SearchResponse response = client.search(searchRequest, MallESConfig.COMMON_OPTIONS);

            //3:分析响应数据封装成我们需要的格式
           result= buildSearchResult(response);
        }catch (IOException e){
            e.printStackTrace();
        }

        return null;
    }

    /**
     * 构建结果数据
     * @param response
     * @return
     */
    private SearchResult buildSearchResult(SearchResponse response) {

        return null;
    }

    /**
     * 准备检索请求
     * 模糊匹配。过滤(按照属性,分类,品牌,架构区间,库存),排序,分页,高亮,聚合分析
     */
    private SearchRequest buildSearchRequest(SearchParam param){

        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();//构建DSL语句

        /**
         *   查询:模糊匹配。过滤(按照属性,三级分类,品牌,价格区间,库存),排序,分页,高亮,聚合分析
         */

        //1:构建bool -query
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //1.1  must ——模糊匹配
        if (!StringUtils.isEmpty(param.getKeyword())){
            boolQuery.must(QueryBuilders.matchQuery("skuTitle",param.getKeyword()));
        }
        //1.2 bool -filiter——按照三级分类id查询
        if (param.getCatalog3Id()!=null){
            boolQuery.filter(QueryBuilders.termsQuery("catalogId",param.getCatalog3Id()));

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

            //不断遍历循环
            //attrs=1_5寸:8寸&attrs=2_16G:8G
            for (String attrStr:param.getAttrs()){
                BoolQueryBuilder nestedboolQuery = QueryBuilders.boolQuery();

                //attrs=1_5寸:8寸
                String[] s = attrStr.split("_");
                String attrId=s[0];//检索属性id
                String[] attrValues=s[1].split(":");//这个属性的检索用的值
                nestedboolQuery.must(QueryBuilders.termsQuery("attrs.attrId",attrId));
                nestedboolQuery.must(QueryBuilders.termsQuery("attrs.attrValue",attrValues));
               //每一个都必须生成一个nested查询
                NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", nestedboolQuery, ScoreMode.None);
                boolQuery.filter(nestedQuery);

            }


            }

        //1.2 bool -filiter——按照库存查询
        boolQuery.filter(QueryBuilders.termsQuery("hasStock",param.getHasStock()==1));

        //1.2 bool -filiter——按照价格区间查询
        if(!StringUtils.isEmpty(param.getSkuPrice())){
            //1_500/_500/500_
            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]);
                }
            }
        }
        //把以前的所有条件都拿来进行封装
        sourceBuilder.query(boolQuery);
        /**
         * 排序,分页,高亮,
         */

        /**
         * 聚合分析
         */
        SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX}, sourceBuilder);


        return searchRequest;
    }
}

8:SearchRequest构建-排序,分页,高亮&测试

https://www.elastic.co/guide/index.html
mall-search
MallSearchServiceImpl

/**
     * 准备检索请求
     * 模糊匹配。过滤(按照属性,分类,品牌,架构区间,库存),排序,分页,高亮,聚合分析
     */
    private SearchRequest buildSearchRequest(SearchParam param){

        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();//构建DSL语句

        /**
         *   查询:模糊匹配。过滤(按照属性,三级分类,品牌,价格区间,库存),排序,分页,高亮,聚合分析
         */

        //1:构建bool -query
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //1.1  must ——模糊匹配
        if (!StringUtils.isEmpty(param.getKeyword())){
            boolQuery.must(QueryBuilders.matchQuery("skuTitle",param.getKeyword()));
        }
        //1.2 bool -filiter——按照三级分类id查询
        if (param.getCatalog3Id()!=null){
            boolQuery.filter(QueryBuilders.termsQuery("catalogId",param.getCatalog3Id()));

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

            //不断遍历循环
            //attrs=1_5寸:8寸&attrs=2_16G:8G
            for (String attrStr:param.getAttrs()){
                BoolQueryBuilder nestedboolQuery = QueryBuilders.boolQuery();

                //attrs=1_5寸:8寸
                String[] s = attrStr.split("_");
                String attrId=s[0];//检索属性id
                String[] attrValues=s[1].split(":");//这个属性的检索用的值
                nestedboolQuery.must(QueryBuilders.termsQuery("attrs.attrId",attrId));
                nestedboolQuery.must(QueryBuilders.termsQuery("attrs.attrValue",attrValues));
               //每一个都必须生成一个nested查询
                NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", nestedboolQuery, ScoreMode.None);
                boolQuery.filter(nestedQuery);

            }


            }

        //1.2 bool -filiter——按照库存查询
        boolQuery.filter(QueryBuilders.termsQuery("hasStock",param.getHasStock()==1));

        //1.2 bool -filiter——按照价格区间查询
        if(!StringUtils.isEmpty(param.getSkuPrice())){
            //1_500/_500/500_
            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]);
                }
            }
        }
        //把以前的所有条件都拿来进行封装
        sourceBuilder.query(boolQuery);
        /**
         * 排序,分页,高亮,
         */

        //2.1 排序
        if (!StringUtils.isEmpty(param.getSort())){
            String sort = param.getSort();
            //sort=hotScore_asc/desc
            String[] s = sort.split("_");
            SortOrder order=s[1].equalsIgnoreCase("asc")?SortOrder.ASC:SortOrder.DESC;
            sourceBuilder.sort(s[0],order);
        }
        //2.2 分页 pagesize=5
        //pageNum:1 from:0 size:5
        //pageNum:2 from:5 size:5
        //from=pageNum-1 *pagesize
        sourceBuilder.from(Math.toIntExact((param.getPageNum() - 1) * EsConstant.PRODUCT_PAGESIZE));
        sourceBuilder.size(Math.toIntExact(EsConstant.PRODUCT_PAGESIZE));

        //2.3 高亮,
        if (!StringUtils.isEmpty(param.getKeyword())){

            HighlightBuilder builder = new HighlightBuilder();
            builder.field("skuTitle");
            builder.preTags("");
            builder.postTags("");
            sourceBuilder.highlighter(builder);

        }

        /**
         * 聚合分析
         */



        SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX}, sourceBuilder);


        return searchRequest;
    }

9:SearchRequest构建-聚合

https://www.elastic.co/guide/index.html
mall-search
MallSearchServiceImpl

    /**
     * 准备检索请求
     * 模糊匹配。过滤(按照属性,分类,品牌,架构区间,库存),排序,分页,高亮,聚合分析
     */
    private SearchRequest buildSearchRequest(SearchParam param){

        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();//构建DSL语句

        /**
         *   查询:模糊匹配。过滤(按照属性,三级分类,品牌,价格区间,库存),排序,分页,高亮,聚合分析
         */

        //1:构建bool -query
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //1.1  must ——模糊匹配
        if (!StringUtils.isEmpty(param.getKeyword())){
            boolQuery.must(QueryBuilders.matchQuery("skuTitle",param.getKeyword()));
        }
        //1.2 bool -filiter——按照三级分类id查询
        if (param.getCatalog3Id()!=null){
            boolQuery.filter(QueryBuilders.termsQuery("catalogId",param.getCatalog3Id()));

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

            //不断遍历循环
            //attrs=1_5寸:8寸&attrs=2_16G:8G
            for (String attrStr:param.getAttrs()){
                BoolQueryBuilder nestedboolQuery = QueryBuilders.boolQuery();

                //attrs=1_5寸:8寸
                String[] s = attrStr.split("_");
                String attrId=s[0];//检索属性id
                String[] attrValues=s[1].split(":");//这个属性的检索用的值
                nestedboolQuery.must(QueryBuilders.termsQuery("attrs.attrId",attrId));
                nestedboolQuery.must(QueryBuilders.termsQuery("attrs.attrValue",attrValues));
               //每一个都必须生成一个nested查询
                NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", nestedboolQuery, ScoreMode.None);
                boolQuery.filter(nestedQuery);

            }


            }

        //1.2 bool -filiter——按照库存查询
        boolQuery.filter(QueryBuilders.termsQuery("hasStock",param.getHasStock()==1));

        //1.2 bool -filiter——按照价格区间查询
        if(!StringUtils.isEmpty(param.getSkuPrice())){
            //1_500/_500/500_
            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]);
                }
            }
        }
        //把以前的所有条件都拿来进行封装
        sourceBuilder.query(boolQuery);
        /**
         * 排序,分页,高亮,
         */

        //2.1 排序
        if (!StringUtils.isEmpty(param.getSort())){
            String sort = param.getSort();
            //sort=hotScore_asc/desc
            String[] s = sort.split("_");
            SortOrder order=s[1].equalsIgnoreCase("asc")?SortOrder.ASC:SortOrder.DESC;
            sourceBuilder.sort(s[0],order);
        }
        //2.2 分页 pagesize=5
        //pageNum:1 from:0 size:5
        //pageNum:2 from:5 size:5
        //from=pageNum-1 *pagesize
        sourceBuilder.from(Math.toIntExact((param.getPageNum() - 1) * EsConstant.PRODUCT_PAGESIZE));
        sourceBuilder.size(Math.toIntExact(EsConstant.PRODUCT_PAGESIZE));

        //2.3 高亮,
        if (!StringUtils.isEmpty(param.getKeyword())){

            HighlightBuilder builder = new HighlightBuilder();
            builder.field("skuTitle");
            builder.preTags("");
            builder.postTags("");
            sourceBuilder.highlighter(builder);

        }

        /**
         * 聚合分析
         */

        //1:todo 品牌聚合
        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:todo 分类聚合catalog_agg
        TermsAggregationBuilder catalog_agg = AggregationBuilders.terms("catalog_agg").field("catalogId").size(1);
        catalog_agg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(1));

        sourceBuilder.aggregation(catalog_agg);

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

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

        attr_agg.subAggregation(attr_id_agg);
        sourceBuilder.aggregation(attr_agg);

        SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX}, sourceBuilder);


        return searchRequest;
    }

10:SearchResponse分析&封装

https://www.elastic.co/guide/index.html
mall-search
MallSearchServiceImpl


    /**
     * 构建结果数据
     * @param response
     * @return
     */
    private SearchResult buildSearchResult(SearchResponse response,SearchParam param) {

        SearchResult result = new SearchResult();
        //返回给所有查询到的商品
        SearchHits hits = response.getHits();

        List<SkuEsModel> esModels=new ArrayList<>();
        if(hits.getHits()!=null&&hits.getHits().length>0){
            for (SearchHit hit:hits.getHits()){
                String sourceAsString = hit.getSourceAsString();
                SkuEsModel skuEsModel = JSON.parseObject(sourceAsString, SkuEsModel.class);
                esModels.add(skuEsModel);
            }
        }
        //1:返回的所有查询到的商品
        result.setProducts(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_agg = ((ParsedStringTerms) bucket.getAggregations().get("attr_name_agg")).getBuckets().get(0).getKeyAsString();


            //3:得到属性的所有值
            List<String> attr_value_agg = ((ParsedStringTerms) bucket.getAggregations().get("attr_value_agg")).getBuckets().stream().map(item -> {
                String keyAsString = ((Terms.Bucket) item).getKeyAsString();
                return keyAsString;
            }).collect(Collectors.toList());
            attrVo.setAttrName(attr_name_agg);
            attrVo.setAttrValue(attr_value_agg);
            attrVo.setAttrId(attrId);
            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();

            //2:得到品牌的名字
            String brand_name_agg = ((ParsedStringTerms) bucket.getAggregations().get("brand_name_agg")).getBuckets().get(0).getKeyAsString();

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

            brandVo.setBrandImg(brand_img_agg);
            brandVo.setBrandName(brand_name_agg);
            brandVo.setBrandId(brandId);
            brandVos.add(brandVo);
        }
        result.setBrands(brandVos);

        //4:当前所有商品涉及到的所有分类信息
        ParsedLongTerms catalog_agg = response.getAggregations().get("catalog_agg");

        List<? extends Terms.Bucket> buckets = catalog_agg.getBuckets();
        List<SearchResult.CatalogVo> catalogVos=new ArrayList<>();
        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);
        }

        result.setCatalogs();

        //5:分页信息-页码
        result.setPageNum(param.getPageNum());

        //5:分页信息-总记录数
        long total= hits.getTotalHits().value;
        result.setTotal(total);

        //5:分页信息-总页码
        int totalPage= (int) (total%EsConstant.PRODUCT_PAGESIZE==0?total/EsConstant.PRODUCT_PAGESIZE:(total/EsConstant.PRODUCT_PAGESIZE+1));

        result.setTotalPage(totalPage);

        return result;
    }

11:验证结果封装正确性

打断点调试

12:页面基本数据渲染

mall-search
list.html
第一个操作:如下图根据定位找到list.html页面中的(检查元素)class=“rig_tab”
电商项目——商城业务-检索服务——第六章——中篇_第22张图片
电商项目——商城业务-检索服务——第六章——中篇_第23张图片
在class="rig_tab"中编写如下内容

                            <p class="da">
                                <a href="/static/search/#" >
                                    <img th:src="${product.getSkuImg()}" class="dim">
                                a>
                            p>
                            <ul class="tab_im">
                                <li><a href="/static/search/#" title="黑色">
                                    <img th:src="${product.getSkuImg()}">a>li>
                            ul>
                            <p class="tab_R">
                                <span th:text="'¥'+${product.getSkuPrice()}">¥5199.00span>

                            p>
                            <p class="tab_JE">
                                <a href="/static/search/#" th:text="${product.getSkuTitle()}">
                                    Apple iPhone 7 Plus (A1661) 32G 黑色 移动联通电信4G手机
                                a>
                             p>

进行测试
电商项目——商城业务-检索服务——第六章——中篇_第24张图片
第二个操作:我们修改如下的界面,(当前所有检索到的商品,所涉及到的属性,它的品牌有多少中,还有分组的属性)
电商项目——商城业务-检索服务——第六章——中篇_第25张图片
在class="JD_nav_wrap"类中只留下一个ul就行了

 <ul>
                                <li th:each="brand:${result.brands}">
                                    <a href="/static/search/#">
                                        <img th:src="${brand.brandImg}" alt="">
                                        <div th:text="${brand.brandName}">
                                            华为(HUAWEI)
                                        div>
                                    a>
                                li>
                            ul>

测试
电商项目——商城业务-检索服务——第六章——中篇_第26张图片
第三个操作:如上挨个遍历品牌底下需要展示的所有属性

                <!--挨个遍历其他需要展示的所有属性-->
                <!--屏幕尺寸-->
                <div class="JD_pre" th:each="attr : ${result.attrs}">
                    <div class="sl_key">
                        <span th:text="${attr.attrName}">屏幕尺寸:</span>
                    </div>
                    <div class="sl_value">
                        <ul>
                            <li th:each="attrValue : ${attr.attrValue}"><a href="/static/search/#" th:text="${attrValue}">5.56英寸及以上</a></li>

                        </ul>
                    </div>
                </div>
                

测试:
电商项目——商城业务-检索服务——第六章——中篇_第27张图片
第四个操作:显示三级分类的值,操作如下

                
                <div class="JD_pre"">
                    <div class="sl_key">
                        <span>分类:span>
                    div>
                    <div class="sl_value">
                            <ul>
                                <li th:each="catalog : ${result.catalogs}"><a href="/static/search/#" th:text="${catalog.catalogName}">5.56英寸及以上a>li>

                            ul>
                    div>

测试
电商项目——商城业务-检索服务——第六章——中篇_第28张图片

13:页面筛选条件渲染

mall-search
list.html
电商项目——商城业务-检索服务——第六章——中篇_第29张图片
我们现在点击某个属性(如cpu品牌),然后让地址可以动态的拼接上我们点击的值,然后在进行聚合查询
第一个操作:我们要让上面都要跳转的超链接,都要统一的声明一个函数,超链接要动态的取值,示例如下

 <li th:each="brand:${result.brands}">
                                    
                                    <a th:href="${'javascript:searchProducts("brandId",'+brand.brandName+')'}">
                                        <img th:src="${brand.brandImg}" alt="">
                                        <div th:text="${brand.brandName}">
                                            华为(HUAWEI)
                                        div>
                                    a>
                                li>
                                    function searchProducts(name,value) {
        //跳转到原来的页面
        location.href=location.href+"&"+name+"="+value;
    }

错误:brandid必须是字符串
在这里插入图片描述
正确:是字符串
在这里插入图片描述

测试:
电商项目——商城业务-检索服务——第六章——中篇_第30张图片
第二步操作:按照上面,继续完成品牌三级分类,和品牌属性


                <div class="JD_pre"">
                    <div class="sl_key">
                        <span>分类:span>
                    div>
                    <div class="sl_value">
                            <ul>
                                <li th:each="catalog : ${result.catalogs}">
                                    <a th:href="${'javascript:searchProducts("catalog3Id",'+catalog.catalogId+')'}" th:text="${catalog.catalogName}">5.56英寸及以上a>
                                li>

                            ul>
                                function searchProducts(name,value) {
        //跳转到原来的页面
        location.href=location.href+"&"+name+"="+value;
    }

测试
电商项目——商城业务-检索服务——第六章——中篇_第31张图片
修改list.html页面中的searchProducts

    function searchProducts(name,value) {
        //跳转到原来的页面
        var href=location.href+"";
        if (href.indexOf("?")!=-1){
            location.href=location.href+"&"+name+"="+value;

        }else {
            location.href=location.href+"?"+name+"="+value;

        }
    }

测试
电商项目——商城业务-检索服务——第六章——中篇_第32张图片
第三步操作:完成品牌属性

 
                
                <div class="JD_pre" th:each="attr : ${result.attrs}">
                    <div class="sl_key">
                        <span th:text="${attr.attrName}">屏幕尺寸:span>
                    div>
                    <div class="sl_value">
                        <ul>
                            <li th:each="attrValue : ${attr.attrValue}"><a th:href="${'javascript:searchProducts("attrs","'+attr.attrId+'_'+attrValue+'")'}">5.56英寸及以上a>li>

                        ul>
                    div>
                div>

电商项目——商城业务-检索服务——第六章——中篇_第33张图片
如果我们有更多的检索属性就可以查询到更多的内容

14:页面分页数据渲染

接下来我们在完成导航搜索功能,如下,(按照关键字进行搜索)
电商项目——商城业务-检索服务——第六章——中篇_第34张图片
mall-search
list.html


<div class="header_sous">
    <div class="logo">
        <a href="http://zlj.mall.com"><img src="/static/search/image/logo1.jpg" alt="">a>
    div>
    <div class="header_form">
        <input id="keyword_input" type="text" placeholder="手机" />
               <a href="javascript:searchByKeyword();" th:value="${param.keyword}">搜索a>

    div>
    
    function searchProducts(name,value) {
        //跳转到原来的页面
        var href=location.href+"";
        if (href.indexOf("?")!=-1){
            location.href=location.href+"&"+name+"="+value;

        }else {
            location.href=location.href+"?"+name+"="+value;

        }
    }
    
    function searchByKeyword() {

        //把$("#keyword_input").val()传入到keyword中
        searchProducts("keyword",$("#keyword_input").val())
    }

测试:
电商项目——商城业务-检索服务——第六章——中篇_第35张图片
第二个操作:我们对分页条进行修改
电商项目——商城业务-检索服务——第六章——中篇_第36张图片
绑定一个跳转事件(class=“page_a”),上一页,下一页必须要有(必须进行遍历)
在SearchResult加入如下属性

    //可遍历的所有导航页
    private List pageNavs;

mall-search
MallSearchServiceImpl中的buildSearchResult方法中,我们加入如下值

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

        System.out.println("响应的数据:"+string);

        return result;
 
                    <div class="filter_page">
                        <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:attr="pn=${nav},style=${nav==result.pageNum?'border: 0;color:#ee2222;background: #fff':''}" href="/static/search/#"
                                   th:each="nav:${result.pageNavs }"
                                >[[${nav}]]a>

                                <result.totalPage}>
                                    下一页 >

电商项目——商城业务-检索服务——第六章——中篇_第37张图片
分页必须是,点击第几页就可以跳转的,而不是根据参数来跳转
第三个操作

 
                <div class="filter_page">
                    <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 + 1}"
                                   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
                                    下一页 >
                                
                            
                            "page_span2">
                                <em><b>[[${result.totalPages}]]b>页 到第em>
                                <input type="number" value="1">
                                <em>em>
                                <a class="page_submit">确定a>
                            span>
                    div>
                div>
 $(".page_a").click(function () {
        var pn=$(this).attr("pn");
        var href=location.href;
        if (href.indexOf("pageNum")!=-1){
            //上一次点击,我们要进行替换pageNum的值,不然,会一直加参数
            location.href= replaceParamVal(href,"pageNum",pn)
        }else {
            location.href = location.href + "&pageNum="+pn;

        }
        return false;

    });

    function replaceParamVal(url,paramName,replaceVal) {
        var oUrl=url.toString();
        var re=eval('/('+paramName+'=)([^&]*)/gi');
        var nUrl=oUrl.replace(re,paramName+'='+replaceVal);
        return nUrl;

    }

电商项目——商城业务-检索服务——第六章——中篇_第38张图片
电商项目——商城业务-检索服务——第六章——中篇_第39张图片
第四个操作:页面排序字段回显


<div class="header_sous">
    <div class="logo">
        <a href="http://zlj.mall.com"><img src="/static/search/image/logo1.jpg" alt="">a>
    div>
    <div class="header_form">
        <input id="keyword_input" type="text" placeholder="手机"/>
        <a href="javascript:searchByKeyword();" th:value="${param.keyword}">搜索a>
    div>

电商项目——商城业务-检索服务——第六章——中篇_第40张图片

15:页面排序功能

mall-search
list.html
实现如下部分功能,为所有综合排序中的a标签加上单击事件,变成点击一个,就会变成高亮模式,其他的就要不高亮

                        <a class="sort_a" href="/static/search/#">综合排序a>
                        <a class="sort_a" href="/static/search/#">销量a>
                        <a class="sort_a" href="/static/search/#">价格a>
                        <a class="sort_a" href="/static/search/#">评论分a>
                        <a class="sort_a" href="/static/search/#">上架时间a>

在这里插入图片描述
操作

    $(".sort_a").click(function () {

        //每点击一个a标签,都会为它加上样式
        $(".sort_a").csss({"color":"#333","border-color":"#CCC","background":"#FFF"})
        $(this).css({"color":"#FFF","border-color":"#e4393c","background":"#e4393c"})

        //禁用默认行为
        return false;
    })

测试
电商项目——商城业务-检索服务——第六章——中篇_第41张图片
2:测试 跳转到指定位置

function replaceParamAndAddParamVal(url,paramName,replaceVal) {
        var oUrl = url.toString();

        //1:如果没有就添加,有就替换
        if (oUrl.indexOf(paramName) != -1) {
            var re = eval('/(' + paramName + '=)([^&]*)/gi');
            var nUrl = oUrl.replace(re, paramName + '=' + replaceVal);
            return nUrl;
        }else {
            var nUrl="";
            if (oUrl.indexOf("?")!=-1){
                //上一次点击,我们要进行替换pageNum的值,不然,会一直加参数
                nUrl=oUrl+"&"+paramName+'='+replaceVal;
            }else {
                nUrl=oUrl+"?"+paramName+'='+replaceVal;

            }

            return nUrl;
        }



    }
    
    $(".sort_a").click(function () {
        //:1:当前被点击元素变为选中状态
        //改变当前元素以及兄弟元素之间的样式
        changeStyle(this)

        // //2:跳转到指定位置sort=hotScore_asc/desc     *  sort=saleCount_asc/desc
        // *  sort=skuPrice_asc/desc
        // *  sort=hotScore_asc/desc

        var sort=$(this).attr("sort");
        sort=$(this).hasClass("desc")?sort+"_desc":sort+"_asc";
        location.href=replaceParamAndAddParamVal(location.href,"sort",sort)
        //禁用默认行为
        return false;
    })

    function changeStyle(ele) {
        $(".sort_a").css({"color":"#333","border-color":"#CCC","background":"#FFF"})
        //每点击一个a标签,都会为它加上样式

        $(ele).css({"color":"#FFF","border-color":"#e4393c","background":"#e4393c"})

        //改变升降序
        $(ele).toggleClass("desc");//加上就是降序,不加就是升序
        $(".sort_a").each(function () {
            var text= $(this).text().replace("s","").replace("w","");
            $(this).text(text);
        })
        if ($(ele).hasClass("desc")){
            //降序
            var text= $(ele).text().replace("s","").replace("w","");
            text=text+"s"
            $(ele).text(text);

        }else {
            var text= $(ele).text().replace("s","").replace("w","");
            text=text+"w"
            $(ele).text(text);
        }

    }

测试:
电商项目——商城业务-检索服务——第六章——中篇_第42张图片

16:页面排序字段回显

我们现在要实现当点击跳转的时候,页面要进行成功回显,而不是像上一章那样只是静态的变化(没有回显)

17:页面价格区间搜索

18:面包屑导航

19:条件删除与URL编码问题

20:条件筛选联动

你可能感兴趣的:(电商项目,java,数据库)