赞
踩
<!--devtools-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!--Spring Cache-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
# gulimall
192.168.157.128 gulimall.com
#search
192.168.157.128 search.gulimall.com
访问http://localhost:12000/
访问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; } }
#将主机地址为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
http://search.gulimall.com/
访问gulimall.com
解决:
server_name gulimall.com *.gulimall.com;
- 1
重启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; /** * @author zr * @date 2021/11/17 14:35 */ @Controller public class SearchController { @GetMapping("/list.html") public String listPage(){ return "list"; } }
重启服务测试
http://search.gulimall.com/list.html
全文检索: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_高清屏
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; /** * @author zr * @date 2021/11/17 15:02 */ @Data public class SearchParam { private String keyword;//页面传递过来的全文匹配关键字 v private Long catalog3Id;//三级分类 id v /** * 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 * attrs=2_5 存:6 寸 */ private Integer hasStock;//是否只显示有货 0(无库存) 1(有库存) private String skuPrice;//价格区间查询 private List<Long> brandId;//按照品牌进行查询, 可以多选 private List<String> attrs;//按照属性进行筛选 private Integer pageNum = 1;//页码 private String _queryString;//原生的所有查询条件 }
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; /** * @author zr * @date 2021/11/17 15:06 */ @Data public class SearchResult { //查询到的所有商品信息 private List<SkuEsModel> products; /** * 以下是分页信息 */ private Integer pageNum;//当前页码 private Long total;//总记录数 private Integer totalPages;//总页码 private List<Integer> pageNavs; private List<BrandVo> brands;//当前查询到的结果, 所有涉及到的品牌 private List<CatalogVo> catalogs;//当前查询到的结果, 所有涉及到的所有分类 private List<AttrVo> attrs;//当前查询到的结果, 所有涉及到的所有属性 //==========以上是返回给页面的所有信息============ //面包屑导航数据 private List<NavVo> navs = new ArrayList<>(); private List<Long> 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<String> attrValue; } }
index: 默认 true, 如果为 false, 表示该字段不会被索引, 但是检索结果里面有, 但字段本身不能 当做检索条件。
doc_values:默认 true, 设置为 false, 表示不可以做排序、 聚合以及脚本操作, 这样更节省磁盘空间。 还可以通过设定 doc_values 为true, index 为 false 来让字段不能被搜索但可以用于排序、 聚 合以及脚本操作:
需要修改索引映射中的doc
解决办法:删除映射中的属性
“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" } } } } } }
POST _reindex
{
"source": {
"index": "product"
},
"dest": {
"index": "gulimall_product"
}
}
迁移成功
gulimall-common/src/main/java/site/zhourui/common/constant/EsConstant.java
type=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 } } } ] } } }
模糊匹配,过滤(按照属性,分类,品牌,价格区间,库存),排序,分页,高亮 ,聚合分析【分析所有可选的规格、分类、品牌】
#查询部分+排序+分页+高亮 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": "<b style='color:red'>", "post_tags": "</b>" } }
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 } } } } } } } }
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": "<b style='color:red'>", "post_tags": "</b>" }, "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 } } } } } } } }
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; /** * @author zr * @date 2021/11/17 14:35 */ @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;
/**
* @author zr
* @date 2021/11/17 18:06
*/
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; /** * @author zr * @date 2021/11/17 18:06 */ @Service public class MallSearchServiceImpl implements MallSearchService { @Autowired RestHighLevelClient client; /** * 在es检索 * * @param param 检索的所有参数 * @return 返回的结果 */ 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; } /** * 准备检所请求 * 模糊匹配,过滤(按照属性,分类,品牌,价格区间,库存),排序,分页,高亮 ,聚合分析【分析所有可选的规格、分类、品牌】 * * 1、创建查询请求 * SearchRequest searchRequest = new SearchRequest("newbank") * 2、创建构建DSL检索条件对象 * SearchSourceBuilder sourceBuilder = new SearchSourceBuilder() * 3、创建各种条件 * QueryBuilder boolQuery = QueryBuilders.boolQuery() * QueryBuilder matchQuery = QueryBuilders.matchAllQuery() * * 4、组合检索条件 * sourceBuilder.sort(); * sourceBuilder.from(); * sourceBuilder.size(); * sourceBuilder.aggregation(); * sourceBuilder.query(QueryBuilder); * sourceBuilder.query(boolQuery); * 5、请求绑定条件 * searchRequest.source(sourceBuilder); */ 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("<b style='color:red'>"); highlightBuilder.postTags("</b>"); 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; } /** * 封装检索结果 * 1、返回所有查询到的商品 * 2、当前所有商品涉及到的所有属性信息 * 3、当前所有商品涉及到的所有品牌信息 * 4、当前所有商品涉及到的所有分类信息 * 5、分页信息 pageNum:当前页码 total:总记录数 totalPages: 总页码 */ private SearchResult buildSearchResponse(SearchParam param, SearchResponse searchResponse) { SearchResult result = new SearchResult(); SearchHits hits = searchResponse.getHits(); // 1、返回所有查询到的商品 List<SkuEsModel> 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<SearchResult.AttrVo> 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<String> 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<SearchResult.BrandVo> 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<SearchResult.CatalogVo> 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)]
gulimall-search/src/main/java/site/zhourui/gilimall/search/service/impl/MallSearchServiceImpl.java
// 7、构建面包屑导航功能 if (param.getAttrs() != null && param.getAttrs().size() > 0) { List<SearchResult.NavVo> 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<AttrResponseVo>() { }); // 设置属性名 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<SearchResult.NavVo> navs = result.getNavs(); SearchResult.NavVo navVo = new SearchResult.NavVo(); navVo.setNavName("品牌"); // TODO 远程查询所有品牌 R r = productFeignService.brandsInfo(param.getBrandId()); if (r.getCode() == 0) { List<BrandVo> brand = r.getData("brand", new TypeReference<List<BrandVo>>() { }); 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); }
浏览器对空格的编码和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, "");
}
gulimall-search/pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
@EnableFeignClients
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; /** * @author zr * @date 2021/11/22 21:53 */ @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<Long> brandIds); }
gulimall-search/src/main/java/site/zhourui/gilimall/search/vo/AttrResponseVo.java
package site.zhourui.gilimall.search.vo; import lombok.Data; /** * @author zr * @date 2021/11/22 22:02 */ @Data public class AttrResponseVo { /** * 属性id */ private Long attrId; /** * 属性名 */ private String attrName; /** * 是否需要检索[0-不需要,1-需要] */ private Integer searchType; /** * 单选 多选[0 1] */ private Integer valueType; /** * 属性图标 */ 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; /** * 属性分类Id,vo独有字段 */ 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;
/**
* @author zr
* @date 2021/11/22 22:06
*/
@Data
public class BrandVo {
private Long brandId;
private String name;
}
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
/** * 获取属性信息{属性分组信息+商品分类信息} * * @param attrId * @return */ @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<AttrAttrgroupRelationEntity>().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; }
gulimall-product/src/main/java/site/zhourui/gulimall/product/app/BrandController.java
@GetMapping("/infos")
public R info(@RequestParam("brandIds") List<Long> brandIds) {
List<BrandEntity> brand = brandService.getBrandsByIds(brandIds);
return R.ok().put("brand", brand);
}
gulimall-product/src/main/java/site/zhourui/gulimall/product/service/BrandService.java
List<BrandEntity> getBrandsByIds(List<Long> brandIds);
gulimall-product/src/main/java/site/zhourui/gulimall/product/service/impl/BrandServiceImpl.java
@Override
public List<BrandEntity> getBrandsByIds(List<Long> brandIds) {
return baseMapper.selectList(new QueryWrapper<BrandEntity>().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
再加上缓存
/** * 获取属性信息{属性分组信息+商品分类信息} * * @param attrId * @return */ @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<AttrAttrgroupRelationEntity>().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; }
此处前端就不做笔记了
测试
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。