org.springframework.boot
spring-boot-devtools
true
org.springframework.boot
spring-boot-starter-cache
1.1 上传静态文件至/mydata/nginx/html/static/search目录下
1.2 将搜索页主页放在gulimall-search的templates目录下
1.3 修改index.html中的文件引入路径
1.4 SwitchHosts配置
# gulimall 192.168.157.128 gulimall.com #search 192.168.157.128 search.gulimall.com1.5 测试一
访问http://localhost:12000/
1.6 nginx配置访问http://search.gulimall.com/
server_name : gulimall.com->*.gulimall.com
server {
listen 80;
server_name *.gulimall.com;
location /static/ {
root /usr/share/nginx/html;
}
location / {
proxy_set_header Host $host;
proxy_pass http://gulimall;
}
}
1.7 配置网关
#将主机地址为search.gulimall.com转发至gulimall-search
- id: gulimall_serach_host
uri: lb://gulimall-search
predicates:
- Host=search.gulimall.com
#将主机地址为**.gulimall.com转发至gulimall-product
- id: gulimall_host
uri: lb://gulimall-product
predicates:
- Host=**.gulimall.com
1.8 重启服务并测试
http://search.gulimall.com/
访问gulimall.com
1.9 修改index.html文件名解决:
server_name gulimall.com *.gulimall.com;重启nginx服务
访问gulimall.com
修改index.html为list.html
原因:在首页点击搜索跳转的链接为list.html
gulimall-search/src/main/java/site/zhourui/gilimall/search/controller 新增listpage方法
package site.zhourui.gilimall.search.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class SearchController {
@GetMapping("/list.html")
public String listPage(){
return "list";
}
}
重启服务测试
http://search.gulimall.com/list.html
2 检索 2.1 检索条件分析-
全文检索:skuTitle-》keyword
-
排序:saleCount(销量)、hotScore(热度分)、skuPrice(价格)
-
过滤:hasStock、skuPrice区间、brandId、catalog3Id、attrs
-
聚合:attrs
2.2 查询参数封装完整查询参数
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_高清屏
gulimall-search/src/main/java/site/zhourui/gilimall/search/vo/SearchParam.java
package site.zhourui.gilimall.search.vo;
import lombok.Data;
import java.util.List;
@Data
public class SearchParam {
private String keyword;//页面传递过来的全文匹配关键字 v
private Long catalog3Id;//三级分类 id v
private String sort;//排序条件
private Integer hasStock;//是否只显示有货 0(无库存) 1(有库存)
private String skuPrice;//价格区间查询
private List brandId;//按照品牌进行查询, 可以多选
private List attrs;//按照属性进行筛选
private Integer pageNum = 1;//页码
private String _queryString;//原生的所有查询条件
}
2.3 相应结果封装
gulimall-search/src/main/java/site/zhourui/gilimall/search/vo/SearchResult.java
package site.zhourui.gilimall.search.vo;
import lombok.Data;
import site.zhourui.common.to.es.SkuEsModel;
import java.util.ArrayList;
import java.util.List;
@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;
}
}
2.4 检索DSL
2.4.1 聚合时出现的问题 Use doc values instead
2.4.1.1 问题原因
index: 默认 true, 如果为 false, 表示该字段不会被索引, 但是检索结果里面有, 但字段本身不能 当做检索条件。
doc_values:默认 true, 设置为 false, 表示不可以做排序、 聚合以及脚本操作, 这样更节省磁盘空间。 还可以通过设定 doc_values 为true, index 为 false 来让字段不能被搜索但可以用于排序、 聚 合以及脚本操作:
2.4.1.2 解决办法需要修改索引映射中的doc
2.4.1.2.1 新建索引 gulimall_product解决办法:删除映射中的属性
“index” : false,
index: 默认 true, 如果为 false, 表示该字段不会被索引, 但是检索结果里面有, 但字段本身不能 当做检索条件。
“doc_values” : false
默认 true, 设置为 false, 表示不可以做排序、 聚合以及脚本操作, 这样更节省磁盘空间。 还可以通过设定 doc_values 为true, index 为 false 来让字段不能被搜索但可以用于排序、 聚 合以及脚本操作:
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.4.1.2.2 将product索引的数据迁移到gulimall_product
POST _reindex
{
"source": {
"index": "product"
},
"dest": {
"index": "gulimall_product"
}
}
迁移成功
2.4.1.2.3 修改索引常量名2.4.2 DSL查询部分 2.4.2.1 注:嵌入式属性type=nested,查询、过滤的时候也要使用嵌入式gulimall-common/src/main/java/site/zhourui/common/constant/EsConstant.java
官方文档
2.4.2.2 查询部分DSL注意点:
- nested查询
- 价格区间 gte,lte
#查询部分
GET gulimall_product/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"skuTitle": "手机"
}
}
],
"filter": [
{
"term": {
"catalogId": "225"
}
},
{
"terms": {
"brandId": [
"3",
"5"
]
}
},
{
"nested": {
"path": "attrs",
"query": {
"bool": {
"must": [
{
"term": {
"attrs.attrId": {
"value": "6"
}
}
},
{
"terms": {
"attrs.attrValue": [
"海思(Hisilicon)",
"以官网信息为准"
]
}
}
]
}
}
}
},
{
"term": {
"hasStock": {
"value": "true"
}
}
},
{
"range": {
"skuPrice": {
"gte": 0,
"lte": 7000
}
}
}
]
}
}
}
2.4.2.3 查询部分+排序+分页+高亮DSL
模糊匹配,过滤(按照属性,分类,品牌,价格区间,库存),排序,分页,高亮 ,聚合分析【分析所有可选的规格、分类、品牌】
#查询部分+排序+分页+高亮
GET gulimall_product/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"skuTitle": "手机"
}
}
],
"filter": [
{
"term": {
"catalogId": "225"
}
},
{
"terms": {
"brandId": [
"1",
"2",
"9"
]
}
},
{
"nested": {
"path": "attrs",
"query": {
"bool": {
"must": [
{
"term": {
"attrs.attrId": {
"value": "6"
}
}
},
{
"terms": {
"attrs.attrValue": [
"海思(Hisilicon)",
"以官网信息为准"
]
}
}
]
}
}
}
},
{
"term": {
"hasStock": {
"value": "true"
}
}
},
{
"range": {
"skuPrice": {
"gte": 0,
"lte": 7000
}
}
}
]
}
},
"sort": [
{
"skuPrice": {
"order": "desc"
}
}
],
"from": 0,
"size": 2,
"highlight": {
"fields": {
"skuTitle": {}
},
"pre_tags": "",
"post_tags": ""
}
}
2.4.2.4 聚合部分DSL
1、可以在聚合内聚合,就可以根据上一次聚合的结果 作为条件再聚合查【上一次聚合的结果只是没显示,可以作为内聚合的条件继续聚合,例如根据brandId聚合,再使用内部聚合可以聚合brandName】
2、并且嵌入式属性,聚合的时候也要使用嵌入式
#聚合部分
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
}
}
}
}
}
}
}
}
2.4.2.5 查询部分+排序+分页+高亮+聚合DSL
GET gulimall_product/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"skuTitle": "手机"
}
}
],
"filter": [
{
"term": {
"catalogId": "225"
}
},
{
"terms": {
"brandId": [
"3",
"5"
]
}
},
{
"nested": {
"path": "attrs",
"query": {
"bool": {
"must": [
{
"term": {
"attrs.attrId": {
"value": "6"
}
}
},
{
"terms": {
"attrs.attrValue": [
"海思(Hisilicon)",
"以官网信息为准"
]
}
}
]
}
}
}
},
{
"term": {
"hasStock": {
"value": "true"
}
}
},
{
"range": {
"skuPrice": {
"gte": 0,
"lte": 7000
}
}
}
]
}
},
"sort": [
{
"skuPrice": {
"order": "desc"
}
}
],
"from": 0,
"size": 2,
"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
}
}
}
}
}
}
}
}
2.4.3 使用java构建检索语句(将2.4.2检索语句翻译为java)
gulimall-search/src/main/java/site/zhourui/gilimall/search/controller/SearchController.java
package site.zhourui.gilimall.search.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import site.zhourui.gilimall.search.vo.SearchParam;
import site.zhourui.gilimall.search.vo.SearchResult;
import javax.servlet.http.HttpServletRequest;
@Controller
public class SearchController {
@Autowired
MallSearchService mallSearchService;
@GetMapping("/list.html")
public String listPage(SearchParam param, Model model, HttpServletRequest request) {
param.set_queryString(request.getQueryString());
SearchResult result = mallSearchService.search(param);
model.addAttribute("result", result);
return "list";
}
}
gulimall-search/src/main/java/site/zhourui/gilimall/search/service/MallSearchService.java
package site.zhourui.gilimall.search.service;
import site.zhourui.gilimall.search.vo.SearchParam;
import site.zhourui.gilimall.search.vo.SearchResult;
public interface MallSearchService {
SearchResult search(SearchParam param);
}
gulimall-search/src/main/java/site/zhourui/gilimall/search/service/impl/MallSearchServiceImpl.java
下面这部分代码是核心,请仔细阅读
package site.zhourui.gilimall.search.service.impl;
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import site.zhourui.common.constant.EsConstant;
import site.zhourui.common.to.es.SkuEsModel;
import site.zhourui.gilimall.search.config.GulimallElasticSearchConfig;
import site.zhourui.gilimall.search.service.MallSearchService;
import site.zhourui.gilimall.search.vo.SearchParam;
import site.zhourui.gilimall.search.vo.SearchResult;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class MallSearchServiceImpl implements MallSearchService {
@Autowired
RestHighLevelClient client;
public SearchResult search(SearchParam param) {
SearchResult result = null;
// 1、动态构建检索需要的DSL语句
// 1、准备检索请求
SearchRequest searchRequest = buildSearchRequest(param);
try {
// 2、执行检索请求
SearchResponse searchResponse = client.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
// 3、分析响应数据,封装成需要的格式
result = buildSearchResponse(param, searchResponse);
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
private SearchRequest buildSearchRequest(SearchParam param) {
// 构建DSL语句对象
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// 1、构建bool - query
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
// 1.1、must【得分】
// 分词匹配skuTitle
if (!StringUtils.isEmpty(param.getKeyword())) {
boolQueryBuilder.must(QueryBuilders.matchQuery("skuTitle", param.getKeyword()));
}
// 1.2、filter【无得分】
// 三级分类
if (param.getCatalog3Id() != null) {
boolQueryBuilder.filter(QueryBuilders.termQuery("catalogId", param.getCatalog3Id()));
}
// 1.3、品牌
if (!CollectionUtils.isEmpty(param.getBrandId())) {
boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId", param.getBrandId()));
}
// 1.4、属性
if (!CollectionUtils.isEmpty(param.getAttrs())) {
for (String attr : param.getAttrs()) {
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
String[] s = attr.split("_");
String attrId = s[0];
String[] attrValues = s[1].split(":");
// must中的必须是同时满足的,如果boolQuery不是内层的
// 那么boolQuery会拼接多个must(attrs.attrId),例如id = 6时,同时必须id = 7,没有此attr
boolQuery.must(QueryBuilders.termQuery("attrs.attrId", attrId));
boolQuery.must(QueryBuilders.termsQuery("attrs.attrValue", attrValues));
// 每一个属性都要生成一个嵌入式查询
// 商品必须包含每一个 传入的属性
NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", boolQuery, ScoreMode.None);
boolQueryBuilder.filter(nestedQuery);
}
}
// 1.5、库存
if (param.getHasStock() != null) {
boolQueryBuilder.filter(QueryBuilders.termQuery("hasStock", param.getHasStock() == 1));
}
// 1.6、价格区间 0_500 _500 500_
if (!StringUtils.isEmpty(param.getSkuPrice())) {
RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("skuPrice");
String[] s = param.getSkuPrice().split("_");
if (s.length == 2) {
rangeQuery.gte(s[0]).lte(s[1]);
}else if (s.length == 1) {
if (param.getSkuPrice().startsWith("_")) {
rangeQuery.lte(s[0]);
}
if (param.getSkuPrice().endsWith("_")) {
rangeQuery.gte(s[0]);
}
}
boolQueryBuilder.filter(rangeQuery);
}
// 1、END 封装查询条件
sourceBuilder.query(boolQueryBuilder);
// 2.1、排序
if (!StringUtils.isEmpty(param.getSort())) {
String[] s = param.getSort().split("_");
SortOrder order = s[1].equalsIgnoreCase("asc") ? SortOrder.ASC : SortOrder.DESC;
sourceBuilder.sort(s[0], order);
}
// 2.2、分页
sourceBuilder.from((param.getPageNum()-1) * EsConstant.PRODUCT_PAGESIZE);
sourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);
// 2.3、高亮
if (!StringUtils.isEmpty(param.getKeyword())) {
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("skuTitle");
highlightBuilder.preTags("");
highlightBuilder.postTags("");
sourceBuilder.highlighter(highlightBuilder);
}
// 3.1、品牌聚合
TermsAggregationBuilder brand_agg = AggregationBuilders.terms("brand_agg").field("brandId").size(10);
// 子聚合,获得品牌name和图片
brand_agg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(1));
brand_agg.subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg").size(1));
// 构建DSL
// TODO 聚合品牌
sourceBuilder.aggregation(brand_agg);
// 3.2、分类聚合
TermsAggregationBuilder catalog_agg = AggregationBuilders.terms("catalog_agg").field("catalogId").size(20);
// 子聚合,获得分类名
catalog_agg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(1));
// 构建DSL
// TODO 聚合分类
sourceBuilder.aggregation(catalog_agg);
// 3.3、规格聚合【嵌入式】
NestedAggregationBuilder attr_agg = AggregationBuilders.nested("attr_agg", "attrs");
// 根据id分组
TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId").size(10);
// 子聚合=》在id分组内部,继续按照 attr_name分组
attr_id_agg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(1));
// 子聚合=》在id分组内部,继续按照 attr_value分组
attr_id_agg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(50));
// 嵌入式聚合
attr_agg.subAggregation(attr_id_agg);
// 构建DSL
// TODO 聚合属性规格
sourceBuilder.aggregation(attr_agg);
SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX}, sourceBuilder);
// 打印DSL
System.out.println(sourceBuilder.toString());
return searchRequest;
}
private SearchResult buildSearchResponse(SearchParam param, SearchResponse searchResponse) {
SearchResult result = new SearchResult();
SearchHits hits = searchResponse.getHits();
// 1、返回所有查询到的商品
List products = new ArrayList<>();
if (!ArrayUtils.isEmpty(hits.getHits())) {
for (SearchHit hit : hits.getHits()) {
String jsonStr = hit.getSourceAsString();
SkuEsModel model = JSON.parseObject(jsonStr, SkuEsModel.class);
// 设置高亮信息
if (!StringUtils.isEmpty(param.getKeyword())) {
HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");
model.setSkuTitle(skuTitle.getFragments()[0].string());
}
products.add(model);
}
}
result.setProducts(products);
// 2、当前所有商品涉及到的所有属性信息
List attrVos = new ArrayList<>();
ParsedNested attr_agg = searchResponse.getAggregations().get("attr_agg");
ParsedLongTerms attr_id_agg = attr_agg.getAggregations().get("attr_id_agg");
List extends Terms.Bucket> attrBuckets = attr_id_agg.getBuckets();
for (Terms.Bucket bucket : attrBuckets) {
SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
// 提取属性ID
attrVo.setAttrId(bucket.getKeyAsNumber().longValue());
// 提取属性名字
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 attrValues = attr_value_agg.getBuckets().stream().map(item -> {
return ((Terms.Bucket) item).getKeyAsString();
}).collect(Collectors.toList());
attrVo.setAttrValue(attrValues);
attrVos.add(attrVo);
}
result.setAttrs(attrVos);
// 3、当前所有商品涉及到的所有品牌信息
List brandVos = new ArrayList<>();
ParsedLongTerms brand_agg = searchResponse.getAggregations().get("brand_agg");
List extends Terms.Bucket> brandBuckets = brand_agg.getBuckets();
for (Terms.Bucket bucket : brandBuckets) {
SearchResult.BrandVo brandVo = new SearchResult.BrandVo();
// 提取品牌ID
brandVo.setBrandId(bucket.getKeyAsNumber().longValue());
// 提取品牌名字
ParsedStringTerms brand_name_agg = bucket.getAggregations().get("brand_name_agg");
brandVo.setBrandName(brand_name_agg.getBuckets().get(0).getKeyAsString());
// 提取品牌图片
ParsedStringTerms brand_img_agg = bucket.getAggregations().get("brand_img_agg");
brandVo.setBrandImg(brand_img_agg.getBuckets().get(0).getKeyAsString());
brandVos.add(brandVo);
}
result.setBrands(brandVos);
// 4、当前所有商品涉及到的所有分类信息
List catalogVos = new ArrayList<>();
ParsedLongTerms catalog_agg = searchResponse.getAggregations().get("catalog_agg");
List extends Terms.Bucket> catalogBuckets = catalog_agg.getBuckets();
for (Terms.Bucket bucket : catalogBuckets) {
SearchResult.CatalogVo catalogVo = new SearchResult.CatalogVo();
// 提取分类Id
catalogVo.setCatalogId(Long.parseLong(bucket.getKeyAsString()));
// 提取分类名字
ParsedStringTerms catalog_name_agg = bucket.getAggregations().get("catalog_name_agg");
catalogVo.setCatalogName(catalog_name_agg.getBuckets().get(0).getKeyAsString());
catalogVos.add(catalogVo);
}
result.setCatalogs(catalogVos);
// 5、分页信息 pageNum:当前页码 、total:总记录数 、totalPages: 总页码
long total = hits.getTotalHits().value;
int totalPages = (int)total % EsConstant.PRODUCT_PAGESIZE == 0 ? (int)total/EsConstant.PRODUCT_PAGESIZE : ((int)total/EsConstant.PRODUCT_PAGESIZE + 1);
result.setPageNum(param.getPageNum());
result.setTotal(total);
result.setTotalPages(totalPages);
return result;
}
}
测试
测试结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jFzeZ5zO-1638070778109)(http://zr.zhourui.site/img/image-20211117215359879.png)]
2.4.4 面包屑导航2.4.4.1 构建面包屑导航功能gulimall-search/src/main/java/site/zhourui/gilimall/search/service/impl/MallSearchServiceImpl.java
// 7、构建面包屑导航功能
if (param.getAttrs() != null && param.getAttrs().size() > 0) {
List collect = param.getAttrs().stream().map(attr -> {
//1、分析每一个attrs传过来的参数值
SearchResult.NavVo navVo = new SearchResult.NavVo();
// attrs=2_5寸:6寸
String[] s = attr.split("_");
navVo.setNavValue(s[1]);
R r = productFeignService.attrInfo(Long.parseLong(s[0]));
// 根据请求构造面包屑 规格属性Id集合,这个集合包含的属性规格不显示【前端会遍历每个参数显示】
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、取消了这个面包屑以后,我们要跳转到哪个地方,将请求的地址url里面的当前置空
//拿到所有的查询条件,去掉当前
String replace = replaceQueryString(param, attr, "attrs");
navVo.setlink("http://search.gulimall.com/list.html?" + replace);
return navVo;
}).collect(Collectors.toList());
result.setNavs(collect);
}
// 品牌、分类
if (param.getBrandId() != null && param.getBrandId().size() > 0) {
List navs = result.getNavs();
SearchResult.NavVo navVo = new SearchResult.NavVo();
navVo.setNavName("品牌");
// TODO 远程查询所有品牌
R r = productFeignService.brandsInfo(param.getBrandId());
if (r.getCode() == 0) {
List brand = r.getData("brand", new TypeReference>() {
});
StringBuffer sb = new StringBuffer();
String replace = "";
for (BrandVo brandVo : brand) {
sb.append(brandVo.getName()+";");
replace = replaceQueryString(param, brandVo.getBrandId()+"", "brandId");
}
navVo.setNavValue(sb.toString());
navVo.setlink("http://search.gulimall.com/list.html?" + replace);
}
navs.add(navVo);
}
2.4.4.2 解决浏览器对空格的编码和Java不一样,差异化处理
浏览器对空格的编码和Java不一样,差异化处理
gulimall-search/src/main/java/site/zhourui/gilimall/search/service/impl/MallSearchServiceImpl.java
//浏览器对空格的编码和Java不一样,差异化处理
private String replaceQueryString(SearchParam param, String value, String key) {
String encode = null;
try {
encode = URLEncoder.encode(value, "UTF-8");
encode = encode.replace("+", "%20"); //浏览器对空格的编码和Java不一样,差异化处理
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
// 就是点了X之后,应该跳转的地址
// 这里要判断一下,attrs是不是第一个参数,因为第一个参数 没有&符号
// TODO BUG,第一个参数不带&
return param.get_queryString().replace("&"+ key + "=" + encode, "");
}
2.4.4.3 编写需要远程调用的接口
2.4.4.3.1 导入依赖
gulimall-search/pom.xml
2.4.4.3.2 开启feign@EnableFeignClients 2.4.4.3.3 编写远程调用接口org.springframework.cloud spring-cloud-starter-openfeign
gulimall-search/src/main/java/site/zhourui/gilimall/search/feign/ProductFeignService.java
package site.zhourui.gilimall.search.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import site.zhourui.common.utils.R;
import java.util.List;
@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);
}
3.4.4.3.4 新增实体类
gulimall-search/src/main/java/site/zhourui/gilimall/search/vo/AttrResponseVo.java
package site.zhourui.gilimall.search.vo;
import lombok.Data;
@Data
public class AttrResponseVo {
private Long attrId;
private String attrName;
private Integer searchType;
private Integer valueType;
private String icon;
private String valueSelect;
private Integer attrType;
private Long enable;
private Long catelogId;
private Integer showDesc;
private Long attrGroupId;
private String catelogName;
private String groupName;
private Long[] catelogPath;
}
gulimall-search/src/main/java/site/zhourui/gilimall/search/vo/BrandVo.java
package site.zhourui.gilimall.search.vo;
import lombok.Data;
@Data
public class BrandVo {
private Long brandId;
private String name;
}
3.4.4.3.5 远程新增接口
3.4.4.3.5.1 attrInfo接口
gulimall-product/src/main/java/site/zhourui/gulimall/product/app/AttrController.java
@RequestMapping("/info/{attrId}")
//@RequiresPermissions("product:attr:info")
public R info(@PathVariable("attrId") Long attrId){
//AttrEntity attr = attrService.getById(attrId);
AttrRespVo respVo = attrService.getAttrInfo(attrId);
return R.ok().put("attr", respVo);
}
gulimall-product/src/main/java/site/zhourui/gulimall/product/service/AttrService.java
AttrRespVo getAttrInfo(Long attrId);
gulimall-product/src/main/java/site/zhourui/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 respVo = new AttrRespVo();
BeanUtils.copyProperties(attrEntity, respVo);
if (attrEntity.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_base.getCode()) {
// 设置分组信息
AttrAttrgroupRelationEntity relationEntity = relationDao.selectOne(new QueryWrapper().eq("attr_id", attrId));
if (relationEntity != null) {
respVo.setAttrGroupId(relationEntity.getAttrGroupId());
AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(relationEntity.getAttrGroupId());
// 可能没有设置分组,relationEntity.getAttrGroupId()是null,导致attrGroupEntity也是null
if (attrGroupEntity != null) {
respVo.setGroupName(attrGroupEntity.getAttrGroupName());
}
}
}
// 设置分类信息
Long[] catelogPath = categoryService.findCatelogPath(attrEntity.getCatelogId());
respVo.setCatelogPath(catelogPath);
CategoryEntity categoryEntity = categoryDao.selectById(attrEntity.getCatelogId());
if (categoryEntity != null) {
respVo.setCatelogName(categoryEntity.getName());
}
return respVo;
}
3.4.4.3.5.1 brandsInfo接口
gulimall-product/src/main/java/site/zhourui/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/site/zhourui/gulimall/product/service/BrandService.java
ListgetBrandsByIds(List brandIds);
gulimall-product/src/main/java/site/zhourui/gulimall/product/service/impl/BrandServiceImpl.java
@Override
public List getBrandsByIds(List brandIds) {
return baseMapper.selectList(new QueryWrapper().in("brand_id", brandIds));
}
需要调用商品服务根据数据id查询属性名
gulimall-product/src/main/java/site/zhourui/gulimall/product/app/AttrController.java
@RequestMapping("/info/{attrId}")
//@RequiresPermissions("product:attr:info")
public R info(@PathVariable("attrId") Long attrId){
//AttrEntity attr = attrService.getById(attrId);
AttrRespVo respVo = attrService.getAttrInfo(attrId);
return R.ok().put("attr", respVo);
}
gulimall-product/src/main/java/site/zhourui/gulimall/product/service/AttrService.java
AttrRespVo getAttrInfo(Long attrId);
gulimall-product/src/main/java/site/zhourui/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 respVo = new AttrRespVo();
BeanUtils.copyProperties(attrEntity, respVo);
if (attrEntity.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_base.getCode()) {
// 设置分组信息
AttrAttrgroupRelationEntity relationEntity = relationDao.selectOne(new QueryWrapper().eq("attr_id", attrId));
if (relationEntity != null) {
respVo.setAttrGroupId(relationEntity.getAttrGroupId());
AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(relationEntity.getAttrGroupId());
// 可能没有设置分组,relationEntity.getAttrGroupId()是null,导致attrGroupEntity也是null
if (attrGroupEntity != null) {
respVo.setGroupName(attrGroupEntity.getAttrGroupName());
}
}
}
// 设置分类信息
Long[] catelogPath = categoryService.findCatelogPath(attrEntity.getCatelogId());
respVo.setCatelogPath(catelogPath);
CategoryEntity categoryEntity = categoryDao.selectById(attrEntity.getCatelogId());
if (categoryEntity != null) {
respVo.setCatelogName(categoryEntity.getName());
}
return respVo;
}
2.4.5 前端代码
此处前端就不做笔记了
附上代码链接
测试



