<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-cacheartifactId>
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;
重启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": "",
"post_tags": ""
}
}
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": "",
"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
}
}
}
}
}
}
}
}
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("");
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;
}
/**
* 封装检索结果
* 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.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
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;
}
此处前端就不做笔记了
附上代码链接
测试