From bddb2beaa72f75bf33b8faaaffbe8470aee29b51 Mon Sep 17 00:00:00 2001 From: zhh Date: Wed, 27 Jun 2018 17:36:00 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=81=9A=E5=90=88=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E5=95=86=E5=93=81=E5=93=81=E7=89=8C=E3=80=81=E5=88=86?= =?UTF-8?q?=E7=B1=BB=E3=80=81=E5=B1=9E=E6=80=A7=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 13 +- .../controller/EsProductController.java | 19 +++ .../macro/mall/search/domain/EsProduct.java | 8 +- .../domain/EsProductAttributeValue.java | 64 +++++++++ .../search/domain/EsProductRelatedInfo.java | 67 +++++++++ .../mall/search/service/EsProductService.java | 11 ++ .../service/impl/EsProductServiceImpl.java | 131 +++++++++++++++++- .../src/main/resources/dao/EsProductDao.xml | 18 ++- .../search/MallSearchApplicationTests.java | 4 +- 9 files changed, 317 insertions(+), 18 deletions(-) create mode 100644 mall-search/src/main/java/com/macro/mall/search/domain/EsProductAttributeValue.java create mode 100644 mall-search/src/main/java/com/macro/mall/search/domain/EsProductRelatedInfo.java diff --git a/README.md b/README.md index 1b9a65f..5ef976a 100644 --- a/README.md +++ b/README.md @@ -51,10 +51,9 @@ JWT登录、注册、获取token | ✔ JTA事务处理 | ✔ 集成单元测试 | ✔ OSS上传功能 | ✔ -优化po和dto的定义使用 | SpringSecurity权限管理功能 | Elasticsearch搜索功能 | -MongoDb 日志存储功能 | +Elasticsearch日志收集功能 | 数字型ID生成 | ### 后台功能 @@ -240,9 +239,19 @@ MongoDb 日志存储功能 | - 排序:按新品、销量、价格进行排序 - 搜索返回结果:商品ID、商品图片、名称、副标题、价格、商品销量、新品、商品的参数、品牌名称、分类名称 - 接口:从数据库中查询相关数据并导入es,插入(修改)数据接口,删除数据接口 +- 品牌分类筛选:根据搜索结果聚合返回品牌、分类及属性 > **商品推荐功能** + +- 推荐某商品的相关商品、根据该商品的品牌(10)、分类(6)、名称(8)、关键字(2)、副标题(2) +- 根据用户一周浏览记录推荐商品,根据用户搜索记录推荐商品 + > **商品热搜功能** + +- 根据用户搜索记录聚合生成热搜词 + > **商品搜索提示功能** + + diff --git a/mall-search/src/main/java/com/macro/mall/search/controller/EsProductController.java b/mall-search/src/main/java/com/macro/mall/search/controller/EsProductController.java index d737867..a28cd26 100644 --- a/mall-search/src/main/java/com/macro/mall/search/controller/EsProductController.java +++ b/mall-search/src/main/java/com/macro/mall/search/controller/EsProductController.java @@ -2,6 +2,7 @@ package com.macro.mall.search.controller; import com.macro.mall.search.domain.CommonResult; import com.macro.mall.search.domain.EsProduct; +import com.macro.mall.search.domain.EsProductRelatedInfo; import com.macro.mall.search.service.EsProductService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; @@ -84,4 +85,22 @@ public class EsProductController { Page esProductPage = esProductService.search(keyword, brandId, productCategoryId, pageNum, pageSize, sort); return new CommonResult().pageSuccess(esProductPage); } + + @ApiOperation(value = "根据商品id推荐商品") + @RequestMapping(value = "/recommend/{id}",method = RequestMethod.GET) + @ResponseBody + public Object recommend(@PathVariable Long id, + @RequestParam(required = false, defaultValue = "0") Integer pageNum, + @RequestParam(required = false, defaultValue = "5") Integer pageSize){ + Page esProductPage = esProductService.recommend(id, pageNum, pageSize); + return new CommonResult().pageSuccess(esProductPage); + } + + @ApiOperation(value = "获取搜索的相关品牌、分类及筛选属性") + @RequestMapping(value = "/search/relate",method = RequestMethod.GET) + @ResponseBody + public Object searchRelatedInfo(@RequestParam(required = false) String keyword){ + EsProductRelatedInfo productRelatedInfo = esProductService.searchRelatedInfo(keyword); + return new CommonResult().success(productRelatedInfo); + } } diff --git a/mall-search/src/main/java/com/macro/mall/search/domain/EsProduct.java b/mall-search/src/main/java/com/macro/mall/search/domain/EsProduct.java index bfc20ef..6c77287 100644 --- a/mall-search/src/main/java/com/macro/mall/search/domain/EsProduct.java +++ b/mall-search/src/main/java/com/macro/mall/search/domain/EsProduct.java @@ -1,6 +1,5 @@ package com.macro.mall.search.domain; -import com.macro.mall.model.PmsProductAttributeValue; import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; @@ -42,7 +41,8 @@ public class EsProduct implements Serializable { private Integer stock; private Integer promotionType; private Integer sort; - private List attrValueList; + @Field(type =FieldType.Nested) + private List attrValueList; public Long getId() { return id; @@ -172,11 +172,11 @@ public class EsProduct implements Serializable { this.sort = sort; } - public List getAttrValueList() { + public List getAttrValueList() { return attrValueList; } - public void setAttrValueList(List attrValueList) { + public void setAttrValueList(List attrValueList) { this.attrValueList = attrValueList; } diff --git a/mall-search/src/main/java/com/macro/mall/search/domain/EsProductAttributeValue.java b/mall-search/src/main/java/com/macro/mall/search/domain/EsProductAttributeValue.java new file mode 100644 index 0000000..11cd251 --- /dev/null +++ b/mall-search/src/main/java/com/macro/mall/search/domain/EsProductAttributeValue.java @@ -0,0 +1,64 @@ +package com.macro.mall.search.domain; + +import org.springframework.data.elasticsearch.annotations.Field; +import org.springframework.data.elasticsearch.annotations.FieldIndex; +import org.springframework.data.elasticsearch.annotations.FieldType; + +import java.io.Serializable; + +/** + * 搜索中的商品属性信息 + * Created by macro on 2018/6/27. + */ +public class EsProductAttributeValue implements Serializable { + private static final long serialVersionUID = 1L; + private Long id; + private Long productAttributeId; + //属性值 + @Field(index = FieldIndex.not_analyzed, type = FieldType.String) + private String value; + //属性参数:0->规格;1->参数 + private Integer type; + //属性名称 + @Field(index = FieldIndex.not_analyzed, type = FieldType.String) + private String name; + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getProductAttributeId() { + return productAttributeId; + } + + public void setProductAttributeId(Long productAttributeId) { + this.productAttributeId = productAttributeId; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public Integer getType() { + return type; + } + + public void setType(Integer type) { + this.type = type; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/mall-search/src/main/java/com/macro/mall/search/domain/EsProductRelatedInfo.java b/mall-search/src/main/java/com/macro/mall/search/domain/EsProductRelatedInfo.java new file mode 100644 index 0000000..c4ee1d0 --- /dev/null +++ b/mall-search/src/main/java/com/macro/mall/search/domain/EsProductRelatedInfo.java @@ -0,0 +1,67 @@ +package com.macro.mall.search.domain; + +import java.util.List; + +/** + * 搜索相关商品品牌名称,分类名称及属性 + * Created by macro on 2018/6/27. + */ +public class EsProductRelatedInfo { + private List brandNames; + private List productCategoryNames; + private List productAttrs; + + public List getBrandNames() { + return brandNames; + } + + public void setBrandNames(List brandNames) { + this.brandNames = brandNames; + } + + public List getProductCategoryNames() { + return productCategoryNames; + } + + public void setProductCategoryNames(List productCategoryNames) { + this.productCategoryNames = productCategoryNames; + } + + public List getProductAttrs() { + return productAttrs; + } + + public void setProductAttrs(List productAttrs) { + this.productAttrs = productAttrs; + } + + public static class ProductAttr{ + private Long attrId; + private String attrName; + private List attrValues; + + public Long getAttrId() { + return attrId; + } + + public void setAttrId(Long attrId) { + this.attrId = attrId; + } + + public List getAttrValues() { + return attrValues; + } + + public void setAttrValues(List attrValues) { + this.attrValues = attrValues; + } + + public String getAttrName() { + return attrName; + } + + public void setAttrName(String attrName) { + this.attrName = attrName; + } + } +} diff --git a/mall-search/src/main/java/com/macro/mall/search/service/EsProductService.java b/mall-search/src/main/java/com/macro/mall/search/service/EsProductService.java index 7e01826..4878578 100644 --- a/mall-search/src/main/java/com/macro/mall/search/service/EsProductService.java +++ b/mall-search/src/main/java/com/macro/mall/search/service/EsProductService.java @@ -1,6 +1,7 @@ package com.macro.mall.search.service; import com.macro.mall.search.domain.EsProduct; +import com.macro.mall.search.domain.EsProductRelatedInfo; import org.springframework.data.domain.Page; import java.util.List; @@ -39,4 +40,14 @@ public interface EsProductService { * 根据关键字搜索名称或者副标题复合查询 */ Page search(String keyword, Long brandId, Long productCategoryId, Integer pageNum, Integer pageSize,Integer sort); + + /** + * 根据商品id推荐相关商品 + */ + Page recommend(Long id, Integer pageNum, Integer pageSize); + + /** + * 获取搜索词相关品牌、分类、属性 + */ + EsProductRelatedInfo searchRelatedInfo(String keyword); } diff --git a/mall-search/src/main/java/com/macro/mall/search/service/impl/EsProductServiceImpl.java b/mall-search/src/main/java/com/macro/mall/search/service/impl/EsProductServiceImpl.java index 158dd11..5c2e6a3 100644 --- a/mall-search/src/main/java/com/macro/mall/search/service/impl/EsProductServiceImpl.java +++ b/mall-search/src/main/java/com/macro/mall/search/service/impl/EsProductServiceImpl.java @@ -2,20 +2,31 @@ package com.macro.mall.search.service.impl; import com.macro.mall.search.dao.EsProductDao; import com.macro.mall.search.domain.EsProduct; +import com.macro.mall.search.domain.EsProductRelatedInfo; import com.macro.mall.search.repository.EsProductRepository; import com.macro.mall.search.service.EsProductService; +import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder; import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders; +import org.elasticsearch.search.aggregations.AbstractAggregationBuilder; +import org.elasticsearch.search.aggregations.Aggregation; +import org.elasticsearch.search.aggregations.AggregationBuilders; +import org.elasticsearch.search.aggregations.bucket.filter.InternalFilter; +import org.elasticsearch.search.aggregations.bucket.terms.LongTerms; +import org.elasticsearch.search.aggregations.bucket.terms.StringTerms; +import org.elasticsearch.search.aggregations.bucket.terms.Terms; import org.elasticsearch.search.sort.SortBuilders; import org.elasticsearch.search.sort.SortOrder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.stereotype.Service; @@ -25,6 +36,7 @@ import org.springframework.util.StringUtils; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.Map; /** @@ -33,11 +45,13 @@ import java.util.List; */ @Service public class EsProductServiceImpl implements EsProductService { + private static final Logger LOGGER = LoggerFactory.getLogger(EsProductServiceImpl.class); @Autowired private EsProductDao productDao; @Autowired private EsProductRepository productRepository; - private static final Logger LOGGER = LoggerFactory.getLogger(EsProductServiceImpl.class); + @Autowired + private ElasticsearchTemplate elasticsearchTemplate; @Override public int importAll() { List esProductList = productDao.getAllEsProductList(null); @@ -106,12 +120,13 @@ public class EsProductServiceImpl implements EsProductService { //搜索 FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery() .add(QueryBuilders.matchQuery("name", keyword), - ScoreFunctionBuilders.weightFactorFunction(1000)) + ScoreFunctionBuilders.weightFactorFunction(10)) .add(QueryBuilders.matchQuery("subTitle", keyword), - ScoreFunctionBuilders.weightFactorFunction(500)) + ScoreFunctionBuilders.weightFactorFunction(5)) .add(QueryBuilders.matchQuery("keywords", keyword), - ScoreFunctionBuilders.weightFactorFunction(200)) - .scoreMode("sum").setMinScore(10f); + ScoreFunctionBuilders.weightFactorFunction(2)) + .scoreMode("sum") + .setMinScore(2); if (StringUtils.isEmpty(keyword)) { nativeSearchQueryBuilder.withQuery(QueryBuilders.matchAllQuery()); } else { @@ -137,7 +152,111 @@ public class EsProductServiceImpl implements EsProductService { nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort().order(SortOrder.DESC)); nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort().order(SortOrder.DESC)); NativeSearchQuery searchQuery = nativeSearchQueryBuilder.build(); -// LOGGER.info("DSL:{}", searchQuery.getQuery().toString()); + LOGGER.info("DSL:{}", searchQuery.getQuery().toString()); return productRepository.search(searchQuery); } + + @Override + public Page recommend(Long id, Integer pageNum, Integer pageSize) { + Pageable pageable = new PageRequest(pageNum, pageSize); + List esProductList = productDao.getAllEsProductList(id); + if (esProductList.size() > 0) { + EsProduct esProduct = esProductList.get(0); + String keyword = esProduct.getName(); + Long brandId = esProduct.getBrandId(); + Long productCategoryId = esProduct.getProductCategoryId(); + //根据商品标题、品牌、分类进行搜索 + FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery() + .add(QueryBuilders.matchQuery("name",keyword),ScoreFunctionBuilders.weightFactorFunction(8)) + .add(QueryBuilders.matchQuery("subTitle",keyword),ScoreFunctionBuilders.weightFactorFunction(2)) + .add(QueryBuilders.matchQuery("keywords",keyword),ScoreFunctionBuilders.weightFactorFunction(2)) + .add(QueryBuilders.termQuery("brandId",brandId),ScoreFunctionBuilders.weightFactorFunction(10)) + .add(QueryBuilders.matchQuery("productCategoryId",productCategoryId),ScoreFunctionBuilders.weightFactorFunction(6)) + .scoreMode("sum") + .setMinScore(2); + NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder(); + builder.withQuery(functionScoreQueryBuilder); + builder.withPageable(pageable); + NativeSearchQuery searchQuery = builder.build(); + LOGGER.info("DSL:{}", searchQuery.getQuery().toString()); + return productRepository.search(searchQuery); + } + return new PageImpl<>(null); + } + + @Override + public EsProductRelatedInfo searchRelatedInfo(String keyword) { + NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder(); + //搜索条件 + if(StringUtils.isEmpty(keyword)){ + builder.withQuery(QueryBuilders.matchAllQuery()); + }else{ + builder.withQuery(QueryBuilders.multiMatchQuery(keyword,"name","subTitle","keywords")); + } + //聚合搜索品牌名称 + builder.addAggregation(AggregationBuilders.terms("brandNames").field("brandName")); + //集合搜索分类名称 + builder.addAggregation(AggregationBuilders.terms("productCategoryNames").field("productCategoryName")); + //聚合搜索商品属性,去除type=1的属性 + AbstractAggregationBuilder aggregationBuilder = AggregationBuilders.nested("allAttrValues") + .path("attrValueList") + .subAggregation(AggregationBuilders.filter("productAttrs") + .filter(QueryBuilders.termQuery("attrValueList.type",1)) + .subAggregation(AggregationBuilders.terms("attrIds") + .field("attrValueList.productAttributeId") + .subAggregation(AggregationBuilders.terms("attrValues") + .field("attrValueList.value")) + .subAggregation(AggregationBuilders.terms("attrNames") + .field("attrValueList.name")))); + builder.addAggregation(aggregationBuilder); + NativeSearchQuery searchQuery = builder.build(); + return elasticsearchTemplate.query(searchQuery, response -> { + LOGGER.info("DSL:{}",searchQuery.getQuery().toString()); + return convertProductRelatedInfo(response); + }); + } + + /** + * 将返回结果转换为对象 + */ + private EsProductRelatedInfo convertProductRelatedInfo(SearchResponse response) { + EsProductRelatedInfo productRelatedInfo = new EsProductRelatedInfo(); + Map aggregationMap = response.getAggregations().getAsMap(); + //设置品牌 + Aggregation brandNames = aggregationMap.get("brandNames"); + List brandNameList = new ArrayList<>(); + for(int i = 0; i<((StringTerms) brandNames).getBuckets().size(); i++){ + brandNameList.add(((StringTerms) brandNames).getBuckets().get(i).getKeyAsString()); + } + productRelatedInfo.setBrandNames(brandNameList); + //设置分类 + Aggregation productCategoryNames = aggregationMap.get("productCategoryNames"); + List productCategoryNameList = new ArrayList<>(); + for(int i=0;i<((StringTerms) productCategoryNames).getBuckets().size();i++){ + productCategoryNameList.add(((StringTerms) productCategoryNames).getBuckets().get(i).getKeyAsString()); + } + productRelatedInfo.setProductCategoryNames(productCategoryNameList); + //设置参数 + Aggregation productAttrs = aggregationMap.get("allAttrValues"); + List attrIds = ((LongTerms) ((InternalFilter)productAttrs.getProperty("productAttrs")).getAggregations().getProperty("attrIds")).getBuckets(); + List attrList = new ArrayList<>(); + for (Terms.Bucket attrId : attrIds) { + EsProductRelatedInfo.ProductAttr attr = new EsProductRelatedInfo.ProductAttr(); + attr.setAttrId((Long) attrId.getKey()); + List attrValueList = new ArrayList<>(); + List attrValues = ((StringTerms) attrId.getAggregations().get("attrValues")).getBuckets(); + List attrNames = ((StringTerms) attrId.getAggregations().get("attrNames")).getBuckets(); + for (Terms.Bucket attrValue : attrValues) { + attrValueList.add(attrValue.getKeyAsString()); + } + attr.setAttrValues(attrValueList); + if(!CollectionUtils.isEmpty(attrNames)){ + String attrName = attrNames.get(0).getKeyAsString(); + attr.setAttrName(attrName); + } + attrList.add(attr); + } + productRelatedInfo.setProductAttrs(attrList); + return productRelatedInfo; + } } diff --git a/mall-search/src/main/resources/dao/EsProductDao.xml b/mall-search/src/main/resources/dao/EsProductDao.xml index 24e5d52..4bfbf95 100644 --- a/mall-search/src/main/resources/dao/EsProductDao.xml +++ b/mall-search/src/main/resources/dao/EsProductDao.xml @@ -3,7 +3,12 @@ - + + + + + +