谷粒商城篇章5 ---- P173-P192 ---- 检索服务【分布式高级篇二】

目录

1 检索服务 

1.1 搭建页面环境

1.1.1 引入依赖

1.1.2 将检索页面放到gulimall-search的src/main/resources/templates/目录下

1.1.3 调整搜索页面

1.1.4 将静态资源放到linux的nginx相关映射目录下/root/docker/nginx/html/static/ search/

1.1.5 SwitchHosts配置域名转发

1.1.6 测试

1.1.7 nginx配置

1.1.8 网关配置

1.1.9 重启测试

1.2 调整页面跳转

1.2.1 引入spring-boot-devtools依赖

1.2.2 关闭thymeleaf缓存

1.2.3 修改页面能够跳转到商城首页

1.2.4 修改index.html文件名

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

1.3.1 检索条件分析

1.3.2 查询参数封装

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

1.5 检索DSL测试

1.5.1 DSL查询部分

1.5.1.1 查询部分DSL

1.5.1.2 查询部分+排序+分页+高亮DSL

1.5.2 聚合部分

1.5.2.1 聚合时出现的问题Use doc values instead

1.5.2.1.1 报错原因

1.5.2.1.2 解决方案

1.5.2.2 聚合部分DSL

1.5.3 总的DSL(查询+聚合)

1.6 SearchRequest构建

1.6.1 检索、排序、分页、高亮、聚合

1.6.1.1 controller层

1.6.1.2 service层

1.6.1.3 EsConstant.java(Es常量类)

1.6.2 测试

1.7 SearchResponse分析&封装

1.8 验证结果封装正确性

1.9 渲染检索页面【P184-192】

1.9.1 检索页面完整代码

1.9.2 检索服务后端相关代码

1.9.2.1 引入依赖

1.9.2.2 vo

1.9.2.3 controller

1.9.2.4 service

1.9.2.5 远程调用接口

1.9.3 远程服务相关接口

1.9.3.1 attrInfo接口

1.9.3.2 brandsInfo接口


1 检索服务 

1.1 搭建页面环境

1. 引入依赖
2. 将页面复制到gulimall-search的src/main/resources/templates/目录下
3. 调整搜索页面
    1)引入thymeleaf
        xmlns:th="http://www.thymeleaf.org"
    2)修改页面静态资源的引入,加上/static/search
        eg: 
            由
            
            改为:
            
4. 将静态资源放到linux的nginx相关映射目录下/root/docker/nginx/html/static/search/
5. SwitchHosts配置域名转发
    1)以管理员身份运行SwitchHosts;
    2)让所有的search.gulimall.com可以定位到linux的nginx服务
6. 测试
    1)浏览器访问http://localhost:12000
    2)浏览器访问http://search.gulimall.com/
7. nginx配置
8. 网关配置
9. 测试 http://search.gulimall.com/

1.1.1 引入依赖



	org.springframework.boot
	spring-boot-starter-thymeleaf



	org.springframework.boot
	spring-boot-devtools
	true

1.1.2 将检索页面放到gulimall-search的src/main/resources/templates/目录下

谷粒商城篇章5 ---- P173-P192 ---- 检索服务【分布式高级篇二】_第1张图片

1.1.3 调整搜索页面

        引入thymeleaf,修改引入静态资源的路径以/static/search开始。以下为了举例,list.html完整代码见 1.9.1 检索页面完整代码




    
    
    
    
    
    
    
    Document

...

1.1.4 将静态资源放到linux的nginx相关映射目录下/root/docker/nginx/html/static/ search/

谷粒商城篇章5 ---- P173-P192 ---- 检索服务【分布式高级篇二】_第2张图片

1.1.5 SwitchHosts配置域名转发

    (1)以管理员身份运行SwitchHosts;
    (2)让所有的search.gulimall.com可以定位到linux的nginx服务

谷粒商城篇章5 ---- P173-P192 ---- 检索服务【分布式高级篇二】_第3张图片

1.1.6 测试

    (1)浏览器访问http://localhost:12000

谷粒商城篇章5 ---- P173-P192 ---- 检索服务【分布式高级篇二】_第4张图片
    (2)浏览器访问http://search.gulimall.com/

谷粒商城篇章5 ---- P173-P192 ---- 检索服务【分布式高级篇二】_第5张图片

1.1.7 nginx配置

谷粒商城篇章5 ---- P173-P192 ---- 检索服务【分布式高级篇二】_第6张图片

(1)修改gulimall.conf,将 server_name 由 gulimall.com 改为 *.gulimall.com
(2)保存gulimall.conf并重启nginx。
# 进入/root/docker/nginx/conf/conf.d/
cd /root/docker/nginx/conf/conf.d/

# 进入gulimall.conf,修改server_name
vi gulimall.conf

# 重启nginx
docker restart nginx

# 查看nginx是否启动成功
docker ps -a

谷粒商城篇章5 ---- P173-P192 ---- 检索服务【分布式高级篇二】_第7张图片

谷粒商城篇章5 ---- P173-P192 ---- 检索服务【分布式高级篇二】_第8张图片

1.1.8 网关配置

- id: gulimall_host_route
  uri: lb://gulimall-product
  predicates:
    # 由以下的主机域名访问转发到商品服务
    - Host=gulimall.com

- id: gulimall_search_route
  uri: lb://gulimall-search
  predicates:
    # 由以下的主机域名访问转发到搜索服务
    - Host=search.gulimall.com

1.1.9 重启测试

http://search.gulimall.com/

谷粒商城篇章5 ---- P173-P192 ---- 检索服务【分布式高级篇二】_第9张图片

 注意静态资源的路径是否正确;有些图片可能缺失,影响不大。

1.2 调整页面跳转

1.2.1 引入spring-boot-devtools依赖

        前面已经引入。

1.2.2 关闭thymeleaf缓存

页面修改后同过Ctrl+Shift+F9对html页面进行重新构建,无需重启项目

spring:
  thymeleaf:
    cache: false

1.2.3 修改页面能够跳转到商城首页

效果要求:点击搜索页面左上角的谷粒商城首页或谷粒商城都能跳转到商城首页。如下图: 

谷粒商城篇章5 ---- P173-P192 ---- 检索服务【分布式高级篇二】_第10张图片

1. 修改页面代码:(跳转路径 http://gulimall.com)

谷粒商城篇章5 ---- P173-P192 ---- 检索服务【分布式高级篇二】_第11张图片

谷粒商城篇章5 ---- P173-P192 ---- 检索服务【分布式高级篇二】_第12张图片

2. 测试:

点击谷粒商城首页或谷粒商城

谷粒商城篇章5 ---- P173-P192 ---- 检索服务【分布式高级篇二】_第13张图片

3. 解决商城首页显示问题

server_name gulimall.com *.gulimall.com;

相关命令:

# 进入gulimall.conf,修改server_name
vi gulimall.conf

# 重启nginx
docker restart nginx

重启nginx,访问 gulimall.com:

1.2.4 修改index.html文件名

1. gulimall-search下的index.html重命名为list.html

原因:在首页点击搜索跳转的链接为list.html

2. 新增由/list.html能跳转到list.thml页面的方法gulimall-search/src/main/java/com/wen/gulimall/search/controller/SearchController.java

@Controller
public class SearchController {

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

3. 将gulimall-product的index.html页面中的search()方法的调用由img标签放到a标签

谷粒商城篇章5 ---- P173-P192 ---- 检索服务【分布式高级篇二】_第14张图片

 4. 重启服务测试

谷粒商城篇章5 ---- P173-P192 ---- 检索服务【分布式高级篇二】_第15张图片

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

1.3.1 检索条件分析

  • 全文检索:skuTitle-》keyword
  • 排序:saleCount(销量)、hotScore(热度分)、skuPrice(价格)
  • 过滤:hasStock、skuPrice区间、brandId、catalog3Id、attrs
  • 聚合:attrs

完整查询参数

keyword=小米&sort=saleCount_desc/asc&hasStock=0/1&skuPrice=400_1900&brandId=1&catalog3Id=1&at trs=1_3G:4G:5G&attrs=2_骁龙845&attrs=4_高清屏

1.3.2 查询参数封装

gulimall-search/src/main/java/com/wen/gulimall/search/vo/SearchParam.java

/**
 * @author W
 * @createDate 2023/7/17 16:10
 * @description 封装页面所有可能传递过来的查询条件
 *  catalog3Id=255&keyword=小米&sort=saleCount_asc&hasStock=0/1&bandId=1&bandId=2
 */
@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=0/1
     *  skuPrice=1_500/_500/500_
     *  bandId=1
     *  attrs=2_5寸:6寸
     */
    private Integer hasStock; // 是否只显示有货
    private String skuPrice;// 价格区间查询
    private List brandId;// 按照品牌进行查询,可以多选
    private List attrs;// 按照属性进行筛选
    private Integer pageNum;// 页码

}

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

gulimall-search/src/main/java/com/wen/gulimall/search/vo/SearchResult.java

@Data
public class SearchResult {
    // 查询到的所有商品信息
    private List products;

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

    private List brands; // 当前查询到的结果,所有涉及到的品牌
    private List catalogs; // 当前查询到的结果,所有涉及到的所有分类
    private List 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 List attrValue;
    }
    
}

1.5 检索DSL测试

1.5.1 DSL查询部分

  • 属性数据类型为嵌入式,即"type"="nested",查询、过滤也需要嵌入式。可参照官网:Nested query | Elasticsearch Guide [7.4] | Elastic
  • 价格区间使用range gte lte.

1.5.1.1 查询部分DSL

# 查询部分
GET product/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "skuTitle": "华为"
          }
        }
      ],
      "filter": [
        {
          "term": {
            "catalogId": 225
          }
        },
        {
          "terms": {
            "brandId": [
              "1",
              "2",
              "7"
            ]
          }
        },
        {
          "nested": {
            "path": "attrs",
            "query": {
              "bool": {
                "must": [
                  {
                    "term": {
                      "attrs.attrId": {
                        "value": "12"
                      }
                    }
                  },
                  {
                    "terms": {
                      "attrs.attrValue": [
                        "HUAWEI Kirin 980",
                        "A13"
                      ]
                    }
                  }
                ]
              }
            }
          }
        },
        {
          "term": {
            "hasStock": {
              "value": "false"
            }
          }
        },
        {
          "range": {
            "skuPrice": {
              "gte": 0,
              "lte": 6000
            }
          }
        }
      ]
    }
  }
}

1.5.1.2 查询部分+排序+分页+高亮DSL

模糊匹配,过滤(按照属性,分类,品牌,价格区间,库存),排序,分页,高亮,聚合分析

# 模糊匹配,过滤(按照属性,分类,品牌,价格区间,库存),排序,分页,高亮,聚合分析
GET product/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "skuTitle": "华为"
          }
        }
      ],
      "filter": [
        {
          "term": {
            "catalogId": 225
          }
        },
        {
          "terms": {
            "brandId": [
              "1",
              "2",
              "7"
            ]
          }
        },
        {
          "nested": {
            "path": "attrs",
            "query": {
              "bool": {
                "must": [
                  {
                    "term": {
                      "attrs.attrId": {
                        "value": "12"
                      }
                    }
                  },
                  {
                    "terms": {
                      "attrs.attrValue": [
                        "HUAWEI Kirin 980",
                        "A13"
                      ]
                    }
                  }
                ]
              }
            }
          }
        },
        {
          "term": {
            "hasStock": {
              "value": "false"
            }
          }
        },
        {
          "range": {
            "skuPrice": {
              "gte": 0,
              "lte": 6000
            }
          }
        }
      ]
    }
  },
  "sort": [
    {
      "skuPrice": {
        "order": "desc"
      }
    }
  ],
  "from": 0,
  "size": 1,
  "highlight": {
    "fields": {"skuTitle": {}},
    "pre_tags": "",
    "post_tags": ""
  }
}

1.5.2 聚合部分

1.5.2.1 聚合时出现的问题Use doc values instead

1.5.2.1.1 报错原因

谷粒商城篇章5 ---- P173-P192 ---- 检索服务【分布式高级篇二】_第16张图片

1. mapping映射参数index、doc_values使用说明: 

  •  index:index选项控制是否对字段值进行索引。它接受true或false,默认为true。如果为 false, 表示该字段不会被索引, 但是检索结果里面有, 但字段本身不能当做检索条件。
  • doc_values:默认为true。如果为false,表示字段不需要进行排序、聚合、或者使用脚本访问字段值,这样可以节省磁盘空间。还可以通过设定doc_values为true,index为false 来让字段不能被检索但是可以用于排序、聚合以及脚本操作。

        具体可以参照官网:mapping parameters

2. 错误原因:

        brandName属性的doc_values为false不可以进行聚合。

1.5.2.1.2 解决方案

        更新映射,删除映射中的index和doc_values参数。

(1)创建新的索引指定映射

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

(2)迁移数据

# 数据迁移
POST _reindex
{
  "source": {
    "index": "product"
  },
  "dest": {
    "index": "gulimall_product"
  }
}

迁移成功

谷粒商城篇章5 ---- P173-P192 ---- 检索服务【分布式高级篇二】_第17张图片

 (3)修改索引常量名

由product改为gulimall_product

public class EsConstant {

    public static final String PRODUCT_INDEX = "gulimall_product";
}

1.5.2.2 聚合部分DSL

(1)DSL聚合部分

GET gulimall_product/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "brand_agg": {
      "terms": {
        "field": "brandId",
        "size": 10
      },
      "aggs": {
        "brand_name_agg": {
          "terms": {
            "field": "brandName",
            "size": 10
          }
        },
        "brand_img_agg":{
          "terms": {
            "field": "brandImg",
            "size": 10
          }
        }
      }
    },
    "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
              }
            }
          }
        }
      }
    }
  }
}

1.5.3 总的DSL(查询+聚合)

GET gulimall_product/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "skuTitle": "华为"
          }
        }
      ],
      "filter": [
        {
          "term": {
            "catalogId": 225
          }
        },
        {
          "terms": {
            "brandId": [
              "1",
              "2",
              "7"
            ]
          }
        },
        {
          "nested": {
            "path": "attrs",
            "query": {
              "bool": {
                "must": [
                  {
                    "term": {
                      "attrs.attrId": {
                        "value": "12"
                      }
                    }
                  },
                  {
                    "terms": {
                      "attrs.attrValue": [
                        "HUAWEI Kirin 980",
                        "A13"
                      ]
                    }
                  }
                ]
              }
            }
          }
        },
        {
          "term": {
            "hasStock": {
              "value": "false"
            }
          }
        },
        {
          "range": {
            "skuPrice": {
              "gte": 0,
              "lte": 6000
            }
          }
        }
      ]
    }
  },
  "sort": [
    {
      "skuPrice": {
        "order": "desc"
      }
    }
  ],
  "from": 0,
  "size": 1,
  "highlight": {
    "fields": {"skuTitle": {}},
    "pre_tags": "",
    "post_tags": ""
  },
  "aggs": {
    "brand_agg": {
      "terms": {
        "field": "brandId",
        "size": 10
      },
      "aggs": {
        "brand_name_agg": {
          "terms": {
            "field": "brandName",
            "size": 10
          }
        },
        "brand_img_agg":{
          "terms": {
            "field": "brandImg",
            "size": 10
          }
        }
      }
    },
    "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
              }
            }
          }
        }
      }
    }
  }
}

1.6 SearchRequest构建

1.6.1 检索、排序、分页、高亮、聚合

1.6.1.1 controller层

gulimall-search/src/main/java/com/wen/gulimall/search/controller/SearchController.java

@Controller
public class SearchController {

    @Resource
    private MallSearchService mallSearchService;

    /**
     * 自动将页面提交过来的所有请求查询参数封装成指定的对象
     * @param searchParam
     * @return
     */
    @GetMapping("/list.html")
    public String listPage(SearchParam searchParam, Model model){
        // 1. 根据传递来的页面的查询参数,去es中检索商品
        SearchResult result = mallSearchService.search(searchParam);
        model.addAttribute("result", result);
        return "list";
    }
}

1.6.1.2 service层

gulimall-search/src/main/java/com/wen/gulimall/search/service/MallSearchService.java

public interface MallSearchService {
    /**
     *
     * @param searchParam 检索的所有参数
     * @return 返回检索的结果,里面包含页面需要的所有信息
     */
    SearchResult search(SearchParam searchParam);
}

gulimall-search/src/main/java/com/wen/gulimall/search/service/impl/MallSearchServiceImpl.java

谷粒商城篇章5 ---- P173-P192 ---- 检索服务【分布式高级篇二】_第18张图片

@Service
public class MallSearchServiceImpl implements MallSearchService {
    @Resource
    private RestHighLevelClient restHighLevelClient;
    // 根据条件去es中检索
    @Override
    public SearchResult search(SearchParam searchParam) {
        // 动态的构建出查询所需要的DSL
        SearchResult result = null;
        // 1. 准备检索请求
        SearchRequest searchRequest = buildSearchRequest(searchParam);
        try {
            // 2. 执行检索请求

            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, GulimallElasticsearchConfig.COMMON_OPTIONS);
            // 3. 分析响应数据,封装成需要的格式
            result = buildSearchResponse(searchParam,searchResponse);
        } catch (IOException e) {
            e.printStackTrace();
        }


        return result;
    }

    /**
     * 准备检索请求
     * # 模糊匹配,过滤(按照属性,分类,品牌,价格区间,库存),排序,分页,高亮,聚合分析
     * @return
     */
    private SearchRequest buildSearchRequest(SearchParam searchParam) {
        // 构建DSL语句对象
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        /**
         * 查询:模糊匹配,过滤(按照属性,分类,品牌,价格区间,库存)
         */
        // 1. 构建bool - query
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        // 1.1 bool - must 模糊匹配
        if(StrUtil.isNotEmpty(searchParam.getKeyword())){
            boolQuery.must(QueryBuilders.matchQuery("skuTitle",searchParam.getKeyword()));
        }

        // 1.2 bool - filter - 按照三级分类id查询
        if(searchParam.getCatalog3Id() != null){
            boolQuery.filter(QueryBuilders.termQuery("catalogId",searchParam.getCatalog3Id()));
        }
        // 1.2 bool -filter - 按照品牌id查询
        if(CollectionUtil.isNotEmpty(searchParam.getBrandId())){
            boolQuery.filter(QueryBuilders.termsQuery("brandId",searchParam.getBrandId()));
        }
        // 1.2 bool - filter - 按照所有指定的属性进行查询
        if(CollectionUtil.isNotEmpty(searchParam.getAttrs())){
            // attrs=1_5寸:6寸&attrs=2_8G:16G
            for (String attrStr : searchParam.getAttrs()) {
                String[] s = attrStr.split("_");
                String attrId = s[0]; // 检索的属性id
                String[] attrValues = s[1].split(":"); // 这个属性检索所需要的值
                BoolQueryBuilder nestedBoolQuery = QueryBuilders.boolQuery();
                nestedBoolQuery.must(QueryBuilders.termQuery("attrs.attrId",attrId));
                nestedBoolQuery.must(QueryBuilders.termsQuery("attrs.attrValue",attrValues));
                // ScoreMode.None 不参与评分
                // 每一个必须都得生成一个nested查询
                NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", nestedBoolQuery, ScoreMode.None);
                boolQuery.filter(nestedQuery);
            }
        }
        // 1.2 bool -filter - 按照库存是否有进行查询
        if(searchParam.getHasStock() != null) {
            boolQuery.filter(QueryBuilders.termQuery("hasStock", searchParam.getHasStock() == 1));
        }
        // 1.2 bool -filter - 按照价格区间
        if(StrUtil.isNotEmpty(searchParam.getSkuPrice())){
            String[] s = searchParam.getSkuPrice().split("_");
            RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("skuPrice");
            if(s.length == 2){
                rangeQuery.gte(s[0]).lte(s[1]);
                boolQuery.filter(rangeQuery);
            }else if(s.length == 1){
                if(searchParam.getSkuPrice().startsWith("_")){
                    rangeQuery.lte(s[0]);
                    boolQuery.filter(rangeQuery);
                }
                if(searchParam.getSkuPrice().endsWith("_")){
                    rangeQuery.gte(s[0]);
                    boolQuery.filter(rangeQuery);
                }
            }
        }

        // 把以上所有的条件都拿来进行封装
        sourceBuilder.query(boolQuery);

        /**
         * 排序,分页,高亮
         */
        // 2.1 排序
        if(StrUtil.isNotEmpty(searchParam.getSort())){
            // sort=saleCount_asc/desc
            String[] s = searchParam.getSort().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   form:5 size:5
        // from = (pageNum - 1)*size
        sourceBuilder.from((searchParam.getPageNum()-1)*EsConstant.PRODUCT_PAGESIZE);
        sourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);

        // 2.3 高亮
        if(StrUtil.isNotEmpty(searchParam.getKeyword())) {
            HighlightBuilder builder = new HighlightBuilder();
            builder.field("skuTitle");
            builder.preTags("");
            builder.postTags("");
            sourceBuilder.highlighter(builder);
        }

        /**
         * 聚合分析
         */
        // 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));
        // TODO 1.聚合brand
        sourceBuilder.aggregation(brand_agg);
        // 2. 分类聚合 catalog_agg
        TermsAggregationBuilder catalog_agg = AggregationBuilders.terms("catalog_agg").field("catalogId").size(20);
        // 分类聚合的子聚合
        catalog_agg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(1));
        // TODO 1.聚合catalog
        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").size(10);
        // 聚合出当前所有的attr_id对应的名字
        attr_id_agg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(1));
        // 聚合出当前所有的attr_id对应的所有可能的属性值attrValue
        attr_id_agg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(50));
        attr_agg.subAggregation(attr_id_agg);
        // TODO 1.聚合attr
        sourceBuilder.aggregation(attr_agg);

        System.out.println("构建的DSL语句"+sourceBuilder.toString());
        SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX},sourceBuilder);
        return searchRequest;
    }

    /**
     * 封装检索结果
     * @param searchParam
     * @param searchResponse
     * @return
     */
    private SearchResult buildSearchResponse(SearchParam searchParam, SearchResponse searchResponse) {
        return null;
    }
}

 buildSearchResponse(searchParam,searchResponse)方法具体实现见 1.7 SearchResponse分析&封装

1.6.1.3 EsConstant.java(Es常量类)

gulimall-search/src/main/java/com/wen/gulimall/search/constant/EsConstant.java

public class EsConstant {

    public static final String PRODUCT_INDEX = "gulimall_product";
    public static final Integer PRODUCT_PAGESIZE = 2;

}

1.6.2 测试

谷粒商城篇章5 ---- P173-P192 ---- 检索服务【分布式高级篇二】_第19张图片

        根据代码中System.out.println("构建的DSL语句"+sourceBuilder.toString());输出的DSL语句,在Kibana中进行测试,看输出结果是否正确。

1.7 SearchResponse分析&封装

对search()接口进行debug,根据debug确定聚合的具体类型。如下图,以分类的聚合为例:

谷粒商城篇章5 ---- P173-P192 ---- 检索服务【分布式高级篇二】_第20张图片 gulimall-search/src/main/java/com/wen/gulimall/search/service/impl/MallSearchServiceImpl.java

/**
 * 封装检索结果
 * @param searchParam
 * @param searchResponse
 * @return
 */
private SearchResult buildSearchResponse(SearchParam searchParam, SearchResponse searchResponse) {
    SearchResult result = new SearchResult();
    // 1. 返回所有查询到的商品
    SearchHit[] hits = searchResponse.getHits().getHits();
    List skuEsModels = new ArrayList<>();
    if(ArrayUtil.isNotEmpty(hits)) {
        for (SearchHit hit : hits) {
            String sourceAsString = hit.getSourceAsString();
            SkuEsModel skuEsModel = JSON.parseObject(sourceAsString, SkuEsModel.class);
            // keyword非空设置高亮
            if(StrUtil.isNotEmpty(searchParam.getKeyword())) {
                String skuTitle = hit.getHighlightFields().get("skuTitle").getFragments()[0].string();
                skuEsModel.setSkuTitle(skuTitle);
            }
            skuEsModels.add(skuEsModel);
        }
    }
    result.setProducts(skuEsModels);
     2. 当前所有商品涉及到的所有属性信息
    List attrVos = new ArrayList<>();
    ParsedNested attr_agg = searchResponse.getAggregations().get("attr_agg");
    ParsedLongTerms attr_id_agg = attr_agg.getAggregations().get("attr_id_agg");
    for (Terms.Bucket bucket : attr_id_agg.getBuckets()) {
        // 1. 获取属性id
        long attrId = bucket.getKeyAsNumber().longValue();
        // 2. 获取属性的名字
        String attrName = ((ParsedStringTerms) bucket.getAggregations().get("attr_name_agg")).getBuckets().get(0).getKeyAsString();
        // 3. 获取属性的值
        List attrValues = ((ParsedStringTerms) bucket.getAggregations().get("attr_value_agg")).getBuckets().stream().map(item -> {
            return ((Terms.Bucket) item).getKeyAsString();
        }).collect(Collectors.toList());

        SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
        attrVo.setAttrId(attrId);
        attrVo.setAttrName(attrName);
        attrVo.setAttrValue(attrValues);

        attrVos.add(attrVo);
    }
    result.setAttrs(attrVos);
     3. 当前所有商品涉及到的所有品牌信息
    List brandVos = new ArrayList<>();
    ParsedLongTerms brand_agg = searchResponse.getAggregations().get("brand_agg");
    for (Terms.Bucket bucket : brand_agg.getBuckets()) {
        // 1. 获取品牌的id
        long brandId = bucket.getKeyAsNumber().longValue();
        // 2. 获取品牌的名字
        String brandName = ((ParsedStringTerms) bucket.getAggregations().get("brand_name_agg")).getBuckets().get(0).getKeyAsString();
        // 3. 获取平品牌的图片
        String brandImg = ((ParsedStringTerms) bucket.getAggregations().get("brand_img_agg")).getBuckets().get(0).getKeyAsString();
        SearchResult.BrandVo brandVo = new SearchResult.BrandVo();
        brandVo.setBrandId(brandId);
        brandVo.setBrandName(brandName);
        brandVo.setBrandImg(brandImg);
        brandVos.add(brandVo);
    }
    result.setBrands(brandVos);
     4. 当前所有商品涉及到的所有分类信息
    ParsedLongTerms catalog_agg = searchResponse.getAggregations().get("catalog_agg");
    List catalogVos = new ArrayList<>();
    List buckets = catalog_agg.getBuckets();
    if(CollectionUtil.isNotEmpty(buckets)){
        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(catalogVos);
    //
     5. 分页信息 - 当前页码
    result.setPageNum(searchParam.getPageNum());
     6. 分页信息 - 总记录数
    long total = searchResponse.getHits().getTotalHits().value;
    result.setTotal(total);
     7. 分页信息 - 总页数
    Integer totalPages = (int)total%EsConstant.PRODUCT_PAGESIZE == 0?(int)total/EsConstant.PRODUCT_PAGESIZE:((int)total/EsConstant.PRODUCT_PAGESIZE + 1);
    result.setTotalPages(totalPages);
    return result;
}

参数keyword不为空时,skuTitle中keyword的值设置高亮。

1.8 验证结果封装正确性

谷粒商城篇章5 ---- P173-P192 ---- 检索服务【分布式高级篇二】_第21张图片

通过debug查看结果的封装。

1.9 渲染检索页面【P184-192】

(1)修改SearchParam类中库存属性hasStock不给默认值,buildSearchRequest()方法中加上库存判断,有库存,才拼接库存查询。(注意:上面的hasStock和buildSearchRequest()已经修改)
(2)要判断当前location.href是否有参数,没有以?拼接,有以&拼接。可以通过字符串的indexOf()方法进行判断,也可以通过includes()方法进行判断。具体可以参考W3C文档:https://www.w3school.com.cn/jsref/jsref_obj_string.asp
includes() :返回字符串是否包含指定值。
indexOf() :返回值在字符串中第一次出现的位置。
(3)搜索框回显,可以使用 th:value="${param.keyword}"完成回显功能。${param.keyword}可以获取请求参数keyword的值。
(4)分页功能完善,输入页码,点击确定,跳转到具体页面。
面包屑导航:
(1)SearchResult中添加面包屑相关内容
(2)buildSearchResponse()构建查询结果里返回面包屑相关数据
(3)构建属性面包屑导航功能,因为需要属性名,所以需要远程调用gulimall-product服务
    1)修改search服务的pom.xml,添加spring-cloud,引入openfeign
    2)开启openfeign
    3)编写远程调用接口
        @GetMapping("/product/attr/info/{attrId}")
        public R attrInfo(@PathVariable("attrId") Long attrId);
(5)浏览器对空格的编码和Java不一样,差异化处理:
private String replaceQueryString(SearchParam searchParam, String value, String key) {
    String encode = null;
    try {
        encode = URLEncoder.encode(value, "UTF-8");
        // 前端传递过来的空格被解码成+,替换成%20
        encode = encode.replace("+","%20"); //浏览器对空格的编码和Java不一样,差异化处理
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }
    String replace = searchParam.get_queryString().replace("&"+key+"=" + encode, "");
    return replace;
}
(6)构建品牌面包屑导航功能,因为需要品牌名称,所以需要远程调用gulimall-product服务
    1)编写gulimall-product服务所需品牌的接口
        @GetMapping("/infos")
        public R info(@RequestParam("brandIds") List brandIds){
            List brand = brandService.getBrandsByIds(brandIds);

            return R.ok().put("brand", brand);
        }
    2)编写远程调用接口
        @GetMapping("/product/brand/infos")
        public R brandsInfo(@RequestParam("brandIds") List brandIds);
(7)注意th:if的优先级比th:with高,可以参照 usingthymeleaf.pdf -》 Attribute Precedence

1.9.1 检索页面完整代码




    
    
    
    
    
    
    
    Document



购物车中还没有商品,赶紧选购吧!


  • ¥2998.00
  • ¥88.00
  • ¥199.00
  • ¥799.00
  • ¥599.00
  • ¥699.00
  • ¥715.00

1.9.2 检索服务后端相关代码

1.9.2.1 引入依赖

        用于面包屑导航,远程调用商品服务,根据ID获取名称。注意要引入spring-cloud依赖管理,以及版本控制。


	org.springframework.cloud
	spring-cloud-starter-openfeign

1.9.2.2 vo

包 gulimall-search/src/main/java/com/wen/gulimall/search/vo/

@Data
public class AttrResponseVo {
    private Long attrId;
    /**
     * 属性名
     */
    private String attrName;
    /**
     * 是否需要检索[0-不需要,1-需要]
     */
    private Integer searchType;
    /**
     * 属性图标
     */
    private String icon;
    /**
     * 可选值列表[用逗号分隔]
     */
    private String valueSelect;
    /**
     * 属性类型[0-销售属性,1-基本属性,2-既是销售属性又是基本属性]
     */
    private Integer attrType;
    /**
     * 启用状态[0 - 禁用,1 - 启用]
     */
    private Long enable;
    /**
     * 所属分类
     */
    private Long catelogId;
    /**
     * 快速展示【是否展示在介绍上;0-否 1-是】,在sku中仍然可以调整
     */
    private Integer showDesc;
    /**
     * 值类型[0-为单个值,1-可以选择多个值]
     */
    private Integer valueType;
    /**
     * 所属分组
     */
    private Long attrGroupId;

    /**
     * 所属分组名称
     */
    private String groupName;
    /**
     * 所属分类名称
     */
    private String catelogName;
    /**
     * 所属分类的路径
     */
    private Long[] catelogPath;
}
@Data
public class BrandVo {
    /**
     * "brandId": 0,
     * 		"brandName": "string",
     */
    private Long brandId;
    private String name;
}
@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=0/1
     *  skuPrice=1_500/_500/500_
     *  bandId=1
     *  attrs=2_5寸:6寸
     */
    private Integer hasStock; // 是否只显示有货,0(无库存)1(有库存)
    private String skuPrice;// 价格区间查询
    private List brandId;// 按照品牌进行查询,可以多选
    private List attrs;// 按照属性进行筛选
    private Integer pageNum = 1;// 页码

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

}
@Data
public class SearchResult {
    // 查询到的所有商品信息
    private List products;

    /**
     * 以下是分页信息
     */
    private Integer pageNum; // 当前页码
    private Long total; // 总记录数
    private Integer totalPages; // 总页码
    private List pageNavs; // 页码导航栏

    private List brands; // 当前查询到的结果,所有涉及到的品牌
    private List catalogs; // 当前查询到的结果,所有涉及到的所有分类
    private List attrs; // 当前查询到的结果,所有涉及到的所有属性

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

    // 面包屑导航数据
    private List navs = new ArrayList<>();
    private List attrIds = 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 CatalogVo{
        private Long catalogId;
        private String catalogName;
    }

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

}

1.9.2.3 controller

@Controller
public class SearchController {

    @Resource
    private MallSearchService mallSearchService;

    /**
     * 自动将页面提交过来的所有请求查询参数封装成指定的对象
     * @param searchParam
     * @return
     */
    @GetMapping("/list.html")
    public String listPage(SearchParam searchParam, Model model, HttpServletRequest request){
        searchParam.set_queryString(request.getQueryString());
        // 1. 根据传递来的页面的查询参数,去es中检索商品
        SearchResult result = mallSearchService.search(searchParam);
        model.addAttribute("result", result);
        return "list";
    }
}

1.9.2.4 service

@Service
public class MallSearchServiceImpl implements MallSearchService {
    @Resource
    private RestHighLevelClient restHighLevelClient;
    @Resource
    private ProductFeignService productFeignService;
    // 根据条件去es中检索
    @Override
    public SearchResult search(SearchParam searchParam) {
        // 动态的构建出查询所需要的DSL
        SearchResult result = null;
        // 1. 准备检索请求
        SearchRequest searchRequest = buildSearchRequest(searchParam);
        try {
            // 2. 执行检索请求

            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, GulimallElasticsearchConfig.COMMON_OPTIONS);
            // 3. 分析响应数据,封装成需要的格式
            result = buildSearchResponse(searchParam,searchResponse);
        } catch (IOException e) {
            e.printStackTrace();
        }


        return result;
    }

    /**
     * 准备检索请求
     * # 模糊匹配,过滤(按照属性,分类,品牌,价格区间,库存),排序,分页,高亮,聚合分析
     * @return
     */
    private SearchRequest buildSearchRequest(SearchParam searchParam) {
        // 构建DSL语句对象
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        /**
         * 查询:模糊匹配,过滤(按照属性,分类,品牌,价格区间,库存)
         */
        // 1. 构建bool - query
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        // 1.1 bool - must 模糊匹配
        if(StrUtil.isNotEmpty(searchParam.getKeyword())){
            boolQuery.must(QueryBuilders.matchQuery("skuTitle",searchParam.getKeyword()));
        }

        // 1.2 bool - filter - 按照三级分类id查询
        if(searchParam.getCatalog3Id() != null){
            boolQuery.filter(QueryBuilders.termQuery("catalogId",searchParam.getCatalog3Id()));
        }
        // 1.2 bool -filter - 按照品牌id查询
        if(CollectionUtil.isNotEmpty(searchParam.getBrandId())){
            boolQuery.filter(QueryBuilders.termsQuery("brandId",searchParam.getBrandId()));
        }
        // 1.2 bool - filter - 按照所有指定的属性进行查询
        if(CollectionUtil.isNotEmpty(searchParam.getAttrs())){
            // attrs=1_5寸:6寸&attrs=2_8G:16G
            for (String attrStr : searchParam.getAttrs()) {
                String[] s = attrStr.split("_");
                String attrId = s[0]; // 检索的属性id
                String[] attrValues = s[1].split(":"); // 这个属性检索所需要的值
                BoolQueryBuilder nestedBoolQuery = QueryBuilders.boolQuery();
                nestedBoolQuery.must(QueryBuilders.termQuery("attrs.attrId",attrId));
                nestedBoolQuery.must(QueryBuilders.termsQuery("attrs.attrValue",attrValues));
                // ScoreMode.None 不参与评分
                // 每一个必须都得生成一个nested查询
                NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", nestedBoolQuery, ScoreMode.None);
                boolQuery.filter(nestedQuery);
            }
        }
        // 1.2 bool -filter - 按照库存是否有进行查询
        if(searchParam.getHasStock() != null) {
            boolQuery.filter(QueryBuilders.termQuery("hasStock", searchParam.getHasStock() == 1));
        }
        // 1.2 bool -filter - 按照价格区间
        if(StrUtil.isNotEmpty(searchParam.getSkuPrice())){
            String[] s = searchParam.getSkuPrice().split("_");
            RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("skuPrice");
            if(s.length == 2){
                rangeQuery.gte(s[0]).lte(s[1]);
                boolQuery.filter(rangeQuery);
            }else if(s.length == 1){
                if(searchParam.getSkuPrice().startsWith("_")){
                    rangeQuery.lte(s[0]);
                    boolQuery.filter(rangeQuery);
                }
                if(searchParam.getSkuPrice().endsWith("_")){
                    rangeQuery.gte(s[0]);
                    boolQuery.filter(rangeQuery);
                }
            }
        }

        // 把以上所有的条件都拿来进行封装
        sourceBuilder.query(boolQuery);

        /**
         * 排序,分页,高亮
         */
        // 2.1 排序
        if(StrUtil.isNotEmpty(searchParam.getSort())){
            // sort=saleCount_asc/desc
            String[] s = searchParam.getSort().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   form:5 size:5
        // from = (pageNum - 1)*size
        sourceBuilder.from((searchParam.getPageNum()-1)*EsConstant.PRODUCT_PAGESIZE);
        sourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);

        // 2.3 高亮
        if(StrUtil.isNotEmpty(searchParam.getKeyword())) {
            HighlightBuilder builder = new HighlightBuilder();
            builder.field("skuTitle");
            builder.preTags("");
            builder.postTags("");
            sourceBuilder.highlighter(builder);
        }

        /**
         * 聚合分析
         */
        // 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));
        // TODO 1.聚合brand
        sourceBuilder.aggregation(brand_agg);
        // 2. 分类聚合 catalog_agg
        TermsAggregationBuilder catalog_agg = AggregationBuilders.terms("catalog_agg").field("catalogId").size(20);
        // 分类聚合的子聚合
        catalog_agg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(1));
        // TODO 1.聚合catalog
        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").size(10);
        // 聚合出当前所有的attr_id对应的名字
        attr_id_agg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(1));
        // 聚合出当前所有的attr_id对应的所有可能的属性值attrValue
        attr_id_agg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(50));
        attr_agg.subAggregation(attr_id_agg);
        // TODO 1.聚合attr
        sourceBuilder.aggregation(attr_agg);

        System.out.println("构建的DSL语句"+sourceBuilder.toString());
        SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX},sourceBuilder);
        return searchRequest;
    }

    /**
     * 封装检索结果
     * @param searchParam
     * @param searchResponse
     * @return
     */
    private SearchResult buildSearchResponse(SearchParam searchParam, SearchResponse searchResponse) {
        SearchResult result = new SearchResult();
        // 1. 返回所有查询到的商品
        SearchHit[] hits = searchResponse.getHits().getHits();
        List skuEsModels = new ArrayList<>();
        if(ArrayUtil.isNotEmpty(hits)) {
            for (SearchHit hit : hits) {
                String sourceAsString = hit.getSourceAsString();
                SkuEsModel skuEsModel = JSON.parseObject(sourceAsString, SkuEsModel.class);
                // keyword非空设置高亮
                if(StrUtil.isNotEmpty(searchParam.getKeyword())) {
                    String skuTitle = hit.getHighlightFields().get("skuTitle").getFragments()[0].string();
                    skuEsModel.setSkuTitle(skuTitle);
                }
                skuEsModels.add(skuEsModel);
            }
        }
        result.setProducts(skuEsModels);
         2. 当前所有商品涉及到的所有属性信息
        List attrVos = new ArrayList<>();
        ParsedNested attr_agg = searchResponse.getAggregations().get("attr_agg");
        ParsedLongTerms attr_id_agg = attr_agg.getAggregations().get("attr_id_agg");
        for (Terms.Bucket bucket : attr_id_agg.getBuckets()) {
            // 1. 获取属性id
            long attrId = bucket.getKeyAsNumber().longValue();
            // 2. 获取属性的名字
            String attrName = ((ParsedStringTerms) bucket.getAggregations().get("attr_name_agg")).getBuckets().get(0).getKeyAsString();
            // 3. 获取属性的值
            List attrValues = ((ParsedStringTerms) bucket.getAggregations().get("attr_value_agg")).getBuckets().stream().map(item -> {
                return ((Terms.Bucket) item).getKeyAsString();
            }).collect(Collectors.toList());

            SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
            attrVo.setAttrId(attrId);
            attrVo.setAttrName(attrName);
            attrVo.setAttrValue(attrValues);

            attrVos.add(attrVo);
        }
        result.setAttrs(attrVos);
         3. 当前所有商品涉及到的所有品牌信息
        List brandVos = new ArrayList<>();
        ParsedLongTerms brand_agg = searchResponse.getAggregations().get("brand_agg");
        for (Terms.Bucket bucket : brand_agg.getBuckets()) {
            // 1. 获取品牌的id
            long brandId = bucket.getKeyAsNumber().longValue();
            // 2. 获取品牌的名字
            String brandName = ((ParsedStringTerms) bucket.getAggregations().get("brand_name_agg")).getBuckets().get(0).getKeyAsString();
            // 3. 获取平品牌的图片
            String brandImg = ((ParsedStringTerms) bucket.getAggregations().get("brand_img_agg")).getBuckets().get(0).getKeyAsString();
            SearchResult.BrandVo brandVo = new SearchResult.BrandVo();
            brandVo.setBrandId(brandId);
            brandVo.setBrandName(brandName);
            brandVo.setBrandImg(brandImg);
            brandVos.add(brandVo);
        }
        result.setBrands(brandVos);
         4. 当前所有商品涉及到的所有分类信息
        ParsedLongTerms catalog_agg = searchResponse.getAggregations().get("catalog_agg");
        List catalogVos = new ArrayList<>();
        List buckets = catalog_agg.getBuckets();
        if(CollectionUtil.isNotEmpty(buckets)){
            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(catalogVos);
        //
         5. 分页信息 - 当前页码
        result.setPageNum(searchParam.getPageNum());
         5. 分页信息 - 总记录数
        long total = searchResponse.getHits().getTotalHits().value;
        result.setTotal(total);
         5. 分页信息 - 总页数
        Integer totalPages = (int)total%EsConstant.PRODUCT_PAGESIZE == 0?(int)total/EsConstant.PRODUCT_PAGESIZE:((int)total/EsConstant.PRODUCT_PAGESIZE + 1);
        result.setTotalPages(totalPages);
         5. 分页信息 - 导航栏
        List pageNavs = new ArrayList<>();
        for(int i = 1;i<=totalPages;i++){
            pageNavs.add(i);
        }
        result.setPageNavs(pageNavs);

        // 6. 构建面包屑导航功能 - 属性
        if(CollectionUtil.isNotEmpty(searchParam.getAttrs())) {
            List collect = searchParam.getAttrs().stream().map(attr -> {
                // 1. 分析每一个参数传过来的参数值
                SearchResult.NavVo navVo = new SearchResult.NavVo();
                // attrs=1_5寸:6寸
                String[] s = attr.split("_");
                navVo.setNavValue(s[1]);

                R r = productFeignService.attrInfo(Long.parseLong(s[0]));
                result.getAttrIds().add(Long.parseLong(s[0]));
                if (r.getCode() == 0) {
                    AttrResponseVo data = r.getData("attr", new TypeReference() {
                    });
                    navVo.setNavName(data.getAttrName());
                } else {
                    navVo.setNavName(s[0]);
                }
                // 2. 取消了这个面包屑以后,我们要跳转到哪个地方
                String replace = replaceQueryString(searchParam, attr, "attrs");
                navVo.setLink("http://search.gulimall.com/list.html?" + replace);
                return navVo;
            }).collect(Collectors.toList());
            result.setNavs(collect);
        }

        // 品牌、分类的面包屑
        if(CollectionUtil.isNotEmpty(searchParam.getBrandId())){
            List navs = result.getNavs();
            SearchResult.NavVo navVo = new SearchResult.NavVo();
            navVo.setNavName("品牌");
            // TODO 远程查询所有品牌
            R r = productFeignService.brandsInfo(searchParam.getBrandId());
            if(r.getCode() == 0){
                List brand = r.getData("brand", new TypeReference>() {
                });
                StringBuffer stringBuffer = new StringBuffer();
                String replace = "";
                for (BrandVo brandVo : brand) {
                    stringBuffer.append(brandVo.getName()+";");
                    replace = replaceQueryString(searchParam,brandVo.getBrandId()+"","brandId");
                }
                navVo.setNavValue(stringBuffer.toString());
                navVo.setLink("http://search.gulimall.com/list.html?" + replace);
            }

            navs.add(navVo);
        }

        return result;
    }


    private String replaceQueryString(SearchParam searchParam, String value, String key) {
        String encode = null;
        try {
            encode = URLEncoder.encode(value, "UTF-8");
            // 前端传递过来的空格被解码成+,替换成%20
            encode = encode.replace("+","%20"); //浏览器对空格的编码和Java不一样,差异化处理
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        String replace = searchParam.get_queryString().replace("&"+key+"=" + encode, "");
        return replace;
    }
}

1.9.2.5 远程调用接口

开启openfeign

@EnableFeignClients // 开启openfeign

接口

gulimall-search/src/main/java/com/wen/gulimall/search/feign/ProductFeignService.java

@FeignClient("gulimall-product")
public interface ProductFeignService {

    @GetMapping("/product/attr/info/{attrId}")
    public R attrInfo(@PathVariable("attrId") Long attrId);

    @GetMapping("/product/brand/infos")
    public R brandsInfo(@RequestParam("brandIds") List brandIds);
}

1.9.3 远程服务相关接口

1.9.3.1 attrInfo接口

gulimall-product/src/main/java/com/wen/gulimall/product/app/AttrController.java

/**
 * 修改回显信息
 */
@RequestMapping("/info/{attrId}")

public R info(@PathVariable("attrId") Long attrId){
    //AttrEntity attr = attrService.getById(attrId);
    AttrRespVo attrRespVo = attrService.getAttrInfo(attrId);
    return R.ok().put("attr", attrRespVo);
}

gulimall-product/src/main/java/com/wen/gulimall/product/service/AttrService.java

AttrRespVo getAttrInfo(Long attrId);

 gulimall-product/src/main/java/com/wen/gulimall/product/service/impl/AttrServiceImpl.java

@Cacheable(value = "attr",key = "'attrinfo:'+#root.args[0]")
@Override
public AttrRespVo getAttrInfo(Long attrId) {
    AttrEntity attrEntity = this.getById(attrId);
    AttrRespVo attrRespVo = new AttrRespVo();
    BeanUtils.copyProperties(attrEntity, attrRespVo);
    if (attrEntity.getCatelogId() != null) {
        // 所属分类
        Long[] catelogPath = categoryService.findCatelogPath(attrEntity.getCatelogId());
        attrRespVo.setCatelogPath(catelogPath);
        CategoryEntity categoryEntity = categoryDao.selectById(attrEntity.getCatelogId());
        if (categoryEntity != null) {
            attrRespVo.setCatelogName(categoryEntity.getName());
        }
    }
    if (attrEntity.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()) {
        // 所属分组
        AttrAttrgroupRelationEntity attrAttrgroupRelation = attrAttrgroupRelationDao.selectOne(new QueryWrapper().eq("attr_id", attrId));
        // 可能属性与属性分组没有关联关系
        if (attrAttrgroupRelation != null) {
            attrRespVo.setAttrGroupId(attrAttrgroupRelation.getAttrGroupId());
            AttrGroupEntity groupEntity = attrGroupDao.selectById(attrAttrgroupRelation.getAttrGroupId());
            if (groupEntity != null) {
                attrRespVo.setGroupName(groupEntity.getAttrGroupName());
            }

        }
    }

    return attrRespVo;
}

1.9.3.2 brandsInfo接口

gulimall-product/src/main/java/com/wen/gulimall/product/app/BrandController.java

@GetMapping("/infos")
public R info(@RequestParam("brandIds") List brandIds){
    List brand = brandService.getBrandsByIds(brandIds);

    return R.ok().put("brand", brand);
}

gulimall-product/src/main/java/com/wen/gulimall/product/service/BrandService.java

List getBrandsByIds(List brandIds);

gulimall-product/src/main/java/com/wen/gulimall/product/service/impl/BrandServiceImpl.java

@Override
public List getBrandsByIds(List brandIds) {
    return baseMapper.selectList(new QueryWrapper().in("brand_id",brandIds));
}

你可能感兴趣的:(分布式,springcloud)