Elasticsearch也可以实现商品智能搜索实战

╰半橙微兮° 2023-01-09 12:35 206阅读 0赞

Elasticsearch也可以实现商品智能搜索

本文主要讲解整合Elasticsearch的过程,以实现商品信息在Elasticsearch中的导入、查询、修改、删除为例。

项目使用框架介绍

Elasticsearch
Elasticsearch 是一个分布式、可扩展、实时的搜索与数据分析引擎。 它能从项目一开始就赋予你的数据以搜索、分析和探索的能力,可用于实现全文搜索和实时数据统计。

  1. package com.macro.mall.search.domain;
  2. import org.springframework.data.annotation.Id;
  3. import org.springframework.data.elasticsearch.annotations.Document;
  4. import org.springframework.data.elasticsearch.annotations.Field;
  5. import org.springframework.data.elasticsearch.annotations.FieldType;
  6. import java.io.Serializable;
  7. import java.math.BigDecimal;
  8. import java.util.List;
  9. @Document(indexName = "pms", type = "product",shards = 1,replicas = 0)
  10. public class EsProduct implements Serializable {
  11. private static final long serialVersionUID = -1L;
  12. @Id
  13. private Long id;
  14. @Field(type = FieldType.Keyword)
  15. private String productSn;
  16. private Long brandId;
  17. @Field(type = FieldType.Keyword)
  18. private String brandName;
  19. private Long productCategoryId;
  20. @Field(type = FieldType.Keyword)
  21. private String productCategoryName;
  22. private String pic;
  23. @Field(analyzer = "ik_max_word",type = FieldType.Text)
  24. private String name;
  25. @Field(analyzer = "ik_max_word",type = FieldType.Text)
  26. private String subTitle;
  27. @Field(analyzer = "ik_max_word",type = FieldType.Text)
  28. private String keywords;
  29. private BigDecimal price;
  30. private Integer sale;
  31. private Integer newStatus;
  32. private Integer recommandStatus;
  33. private Integer stock;
  34. private Integer promotionType;
  35. private Integer sort;
  36. @Field(type =FieldType.Nested)
  37. private List<EsProductAttributeValue> attrValueList;
  38. public Long getId() {
  39. return id;
  40. }
  41. public void setId(Long id) {
  42. this.id = id;
  43. }
  44. public String getProductSn() {
  45. return productSn;
  46. }
  47. public void setProductSn(String productSn) {
  48. this.productSn = productSn;
  49. }
  50. public Long getBrandId() {
  51. return brandId;
  52. }
  53. public void setBrandId(Long brandId) {
  54. this.brandId = brandId;
  55. }
  56. public String getBrandName() {
  57. return brandName;
  58. }
  59. public void setBrandName(String brandName) {
  60. this.brandName = brandName;
  61. }
  62. public Long getProductCategoryId() {
  63. return productCategoryId;
  64. }
  65. public void setProductCategoryId(Long productCategoryId) {
  66. this.productCategoryId = productCategoryId;
  67. }
  68. public String getProductCategoryName() {
  69. return productCategoryName;
  70. }
  71. public void setProductCategoryName(String productCategoryName) {
  72. this.productCategoryName = productCategoryName;
  73. }
  74. public String getPic() {
  75. return pic;
  76. }
  77. public void setPic(String pic) {
  78. this.pic = pic;
  79. }
  80. public String getName() {
  81. return name;
  82. }
  83. public void setName(String name) {
  84. this.name = name;
  85. }
  86. public String getSubTitle() {
  87. return subTitle;
  88. }
  89. public void setSubTitle(String subTitle) {
  90. this.subTitle = subTitle;
  91. }
  92. public BigDecimal getPrice() {
  93. return price;
  94. }
  95. public void setPrice(BigDecimal price) {
  96. this.price = price;
  97. }
  98. public Integer getSale() {
  99. return sale;
  100. }
  101. public void setSale(Integer sale) {
  102. this.sale = sale;
  103. }
  104. public Integer getNewStatus() {
  105. return newStatus;
  106. }
  107. public void setNewStatus(Integer newStatus) {
  108. this.newStatus = newStatus;
  109. }
  110. public Integer getRecommandStatus() {
  111. return recommandStatus;
  112. }
  113. public void setRecommandStatus(Integer recommandStatus) {
  114. this.recommandStatus = recommandStatus;
  115. }
  116. public Integer getStock() {
  117. return stock;
  118. }
  119. public void setStock(Integer stock) {
  120. this.stock = stock;
  121. }
  122. public Integer getPromotionType() {
  123. return promotionType;
  124. }
  125. public void setPromotionType(Integer promotionType) {
  126. this.promotionType = promotionType;
  127. }
  128. public Integer getSort() {
  129. return sort;
  130. }
  131. public void setSort(Integer sort) {
  132. this.sort = sort;
  133. }
  134. public List<EsProductAttributeValue> getAttrValueList() {
  135. return attrValueList;
  136. }
  137. public void setAttrValueList(List<EsProductAttributeValue> attrValueList) {
  138. this.attrValueList = attrValueList;
  139. }
  140. public String getKeywords() {
  141. return keywords;
  142. }
  143. public void setKeywords(String keywords) {
  144. this.keywords = keywords;
  145. }
  146. }

搜索中的商品属性信息

  1. package com.macro.mall.search.domain;
  2. import org.springframework.data.elasticsearch.annotations.Field;
  3. import org.springframework.data.elasticsearch.annotations.FieldType;
  4. import java.io.Serializable;
  5. public class EsProductAttributeValue implements Serializable {
  6. private static final long serialVersionUID = 1L;
  7. private Long id;
  8. private Long productAttributeId;
  9. //属性值
  10. @Field(type = FieldType.Keyword)
  11. private String value;
  12. //属性参数:0->规格;1->参数
  13. private Integer type;
  14. //属性名称
  15. @Field(type=FieldType.Keyword)
  16. private String name;
  17. public Long getId() {
  18. return id;
  19. }
  20. public void setId(Long id) {
  21. this.id = id;
  22. }
  23. public Long getProductAttributeId() {
  24. return productAttributeId;
  25. }
  26. public void setProductAttributeId(Long productAttributeId) {
  27. this.productAttributeId = productAttributeId;
  28. }
  29. public String getValue() {
  30. return value;
  31. }
  32. public void setValue(String value) {
  33. this.value = value;
  34. }
  35. public Integer getType() {
  36. return type;
  37. }
  38. public void setType(Integer type) {
  39. this.type = type;
  40. }
  41. public String getName() {
  42. return name;
  43. }
  44. public void setName(String name) {
  45. this.name = name;
  46. }
  47. }

搜索相关商品品牌名称,分类名称及属性

  1. package com.macro.mall.search.domain;
  2. import java.util.List;
  3. public class EsProductRelatedInfo {
  4. private List<String> brandNames;
  5. private List<String> productCategoryNames;
  6. private List<ProductAttr> productAttrs;
  7. public List<String> getBrandNames() {
  8. return brandNames;
  9. }
  10. public void setBrandNames(List<String> brandNames) {
  11. this.brandNames = brandNames;
  12. }
  13. public List<String> getProductCategoryNames() {
  14. return productCategoryNames;
  15. }
  16. public void setProductCategoryNames(List<String> productCategoryNames) {
  17. this.productCategoryNames = productCategoryNames;
  18. }
  19. public List<ProductAttr> getProductAttrs() {
  20. return productAttrs;
  21. }
  22. public void setProductAttrs(List<ProductAttr> productAttrs) {
  23. this.productAttrs = productAttrs;
  24. }
  25. public static class ProductAttr{
  26. private Long attrId;
  27. private String attrName;
  28. private List<String> attrValues;
  29. public Long getAttrId() {
  30. return attrId;
  31. }
  32. public void setAttrId(Long attrId) {
  33. this.attrId = attrId;
  34. }
  35. public List<String> getAttrValues() {
  36. return attrValues;
  37. }
  38. public void setAttrValues(List<String> attrValues) {
  39. this.attrValues = attrValues;
  40. }
  41. public String getAttrName() {
  42. return attrName;
  43. }
  44. public void setAttrName(String attrName) {
  45. this.attrName = attrName;
  46. }
  47. }
  48. }

接口类

  1. package com.macro.mall.search.repository;
  2. import com.macro.mall.search.domain.EsProduct;
  3. import org.springframework.data.domain.Page;
  4. import org.springframework.data.domain.Pageable;
  5. import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
  6. public interface EsProductRepository extends ElasticsearchRepository<EsProduct, Long> {
  7. /** * 搜索查询 * * @param name 商品名称 * @param subTitle 商品标题 * @param keywords 商品关键字 * @param page 分页信息 * @return */
  8. Page<EsProduct> findByNameOrSubTitleOrKeywords(String name, String subTitle, String keywords,Pageable page);
  9. }

商品搜索管理Service实现类

  1. package com.macro.mall.search.service.impl;
  2. import com.macro.mall.search.dao.EsProductDao;
  3. import com.macro.mall.search.domain.EsProduct;
  4. import com.macro.mall.search.domain.EsProductRelatedInfo;
  5. import com.macro.mall.search.repository.EsProductRepository;
  6. import com.macro.mall.search.service.EsProductService;
  7. import org.elasticsearch.action.search.SearchResponse;
  8. import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
  9. import org.elasticsearch.index.query.BoolQueryBuilder;
  10. import org.elasticsearch.index.query.QueryBuilders;
  11. import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
  12. import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
  13. import org.elasticsearch.search.aggregations.AbstractAggregationBuilder;
  14. import org.elasticsearch.search.aggregations.Aggregation;
  15. import org.elasticsearch.search.aggregations.AggregationBuilders;
  16. import org.elasticsearch.search.aggregations.bucket.filter.InternalFilter;
  17. import org.elasticsearch.search.aggregations.bucket.nested.InternalNested;
  18. import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
  19. import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
  20. import org.elasticsearch.search.aggregations.bucket.terms.Terms;
  21. import org.elasticsearch.search.sort.SortBuilders;
  22. import org.elasticsearch.search.sort.SortOrder;
  23. import org.slf4j.Logger;
  24. import org.slf4j.LoggerFactory;
  25. import org.springframework.beans.factory.annotation.Autowired;
  26. import org.springframework.data.domain.Page;
  27. import org.springframework.data.domain.PageImpl;
  28. import org.springframework.data.domain.PageRequest;
  29. import org.springframework.data.domain.Pageable;
  30. import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
  31. import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
  32. import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
  33. import org.springframework.stereotype.Service;
  34. import org.springframework.util.CollectionUtils;
  35. import org.springframework.util.StringUtils;
  36. import java.util.ArrayList;
  37. import java.util.Iterator;
  38. import java.util.List;
  39. import java.util.Map;
  40. @Service
  41. public class EsProductServiceImpl implements EsProductService {
  42. private static final Logger LOGGER = LoggerFactory.getLogger(EsProductServiceImpl.class);
  43. @Autowired
  44. private EsProductDao productDao;
  45. @Autowired
  46. private EsProductRepository productRepository;
  47. @Autowired
  48. private ElasticsearchTemplate elasticsearchTemplate;
  49. @Override
  50. public int importAll() {
  51. List<EsProduct> esProductList = productDao.getAllEsProductList(null);
  52. Iterable<EsProduct> esProductIterable = productRepository.saveAll(esProductList);
  53. Iterator<EsProduct> iterator = esProductIterable.iterator();
  54. int result = 0;
  55. while (iterator.hasNext()) {
  56. result++;
  57. iterator.next();
  58. }
  59. return result;
  60. }
  61. @Override
  62. public void delete(Long id) {
  63. productRepository.deleteById(id);
  64. }
  65. @Override
  66. public EsProduct create(Long id) {
  67. EsProduct result = null;
  68. List<EsProduct> esProductList = productDao.getAllEsProductList(id);
  69. if (esProductList.size() > 0) {
  70. EsProduct esProduct = esProductList.get(0);
  71. result = productRepository.save(esProduct);
  72. }
  73. return result;
  74. }
  75. @Override
  76. public void delete(List<Long> ids) {
  77. if (!CollectionUtils.isEmpty(ids)) {
  78. List<EsProduct> esProductList = new ArrayList<>();
  79. for (Long id : ids) {
  80. EsProduct esProduct = new EsProduct();
  81. esProduct.setId(id);
  82. esProductList.add(esProduct);
  83. }
  84. productRepository.deleteAll(esProductList);
  85. }
  86. }
  87. @Override
  88. public Page<EsProduct> search(String keyword, Integer pageNum, Integer pageSize) {
  89. Pageable pageable = PageRequest.of(pageNum, pageSize);
  90. return productRepository.findByNameOrSubTitleOrKeywords(keyword, keyword, keyword, pageable);
  91. }
  92. @Override
  93. public Page<EsProduct> search(String keyword, Long brandId, Long productCategoryId, Integer pageNum, Integer pageSize,Integer sort) {
  94. Pageable pageable = PageRequest.of(pageNum, pageSize);
  95. NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
  96. //分页
  97. nativeSearchQueryBuilder.withPageable(pageable);
  98. //过滤
  99. if (brandId != null || productCategoryId != null) {
  100. BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
  101. if (brandId != null) {
  102. boolQueryBuilder.must(QueryBuilders.termQuery("brandId", brandId));
  103. }
  104. if (productCategoryId != null) {
  105. boolQueryBuilder.must(QueryBuilders.termQuery("productCategoryId", productCategoryId));
  106. }
  107. nativeSearchQueryBuilder.withFilter(boolQueryBuilder);
  108. }
  109. //搜索
  110. if (StringUtils.isEmpty(keyword)) {
  111. nativeSearchQueryBuilder.withQuery(QueryBuilders.matchAllQuery());
  112. } else {
  113. List<FunctionScoreQueryBuilder.FilterFunctionBuilder> filterFunctionBuilders = new ArrayList<>();
  114. filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("name", keyword),
  115. ScoreFunctionBuilders.weightFactorFunction(10)));
  116. filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("subTitle", keyword),
  117. ScoreFunctionBuilders.weightFactorFunction(5)));
  118. filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("keywords", keyword),
  119. ScoreFunctionBuilders.weightFactorFunction(2)));
  120. FunctionScoreQueryBuilder.FilterFunctionBuilder[] builders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[filterFunctionBuilders.size()];
  121. filterFunctionBuilders.toArray(builders);
  122. FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(builders)
  123. .scoreMode(FunctionScoreQuery.ScoreMode.SUM)
  124. .setMinScore(2);
  125. nativeSearchQueryBuilder.withQuery(functionScoreQueryBuilder);
  126. }
  127. //排序
  128. if(sort==1){
  129. //按新品从新到旧
  130. nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("id").order(SortOrder.DESC));
  131. }else if(sort==2){
  132. //按销量从高到低
  133. nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("sale").order(SortOrder.DESC));
  134. }else if(sort==3){
  135. //按价格从低到高
  136. nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.ASC));
  137. }else if(sort==4){
  138. //按价格从高到低
  139. nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.DESC));
  140. }else{
  141. //按相关度
  142. nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort().order(SortOrder.DESC));
  143. }
  144. nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort().order(SortOrder.DESC));
  145. NativeSearchQuery searchQuery = nativeSearchQueryBuilder.build();
  146. LOGGER.info("DSL:{}", searchQuery.getQuery().toString());
  147. return productRepository.search(searchQuery);
  148. }
  149. @Override
  150. public Page<EsProduct> recommend(Long id, Integer pageNum, Integer pageSize) {
  151. Pageable pageable = PageRequest.of(pageNum, pageSize);
  152. List<EsProduct> esProductList = productDao.getAllEsProductList(id);
  153. if (esProductList.size() > 0) {
  154. EsProduct esProduct = esProductList.get(0);
  155. String keyword = esProduct.getName();
  156. Long brandId = esProduct.getBrandId();
  157. Long productCategoryId = esProduct.getProductCategoryId();
  158. //根据商品标题、品牌、分类进行搜索
  159. List<FunctionScoreQueryBuilder.FilterFunctionBuilder> filterFunctionBuilders = new ArrayList<>();
  160. filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("name", keyword),
  161. ScoreFunctionBuilders.weightFactorFunction(8)));
  162. filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("subTitle", keyword),
  163. ScoreFunctionBuilders.weightFactorFunction(2)));
  164. filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("keywords", keyword),
  165. ScoreFunctionBuilders.weightFactorFunction(2)));
  166. filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("brandId", brandId),
  167. ScoreFunctionBuilders.weightFactorFunction(10)));
  168. filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("productCategoryId", productCategoryId),
  169. ScoreFunctionBuilders.weightFactorFunction(6)));
  170. FunctionScoreQueryBuilder.FilterFunctionBuilder[] builders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[filterFunctionBuilders.size()];
  171. filterFunctionBuilders.toArray(builders);
  172. FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(builders)
  173. .scoreMode(FunctionScoreQuery.ScoreMode.SUM)
  174. .setMinScore(2);
  175. NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
  176. builder.withQuery(functionScoreQueryBuilder);
  177. builder.withPageable(pageable);
  178. NativeSearchQuery searchQuery = builder.build();
  179. LOGGER.info("DSL:{}", searchQuery.getQuery().toString());
  180. return productRepository.search(searchQuery);
  181. }
  182. return new PageImpl<>(null);
  183. }
  184. @Override
  185. public EsProductRelatedInfo searchRelatedInfo(String keyword) {
  186. NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
  187. //搜索条件
  188. if(StringUtils.isEmpty(keyword)){
  189. builder.withQuery(QueryBuilders.matchAllQuery());
  190. }else{
  191. builder.withQuery(QueryBuilders.multiMatchQuery(keyword,"name","subTitle","keywords"));
  192. }
  193. //聚合搜索品牌名称
  194. builder.addAggregation(AggregationBuilders.terms("brandNames").field("brandName"));
  195. //集合搜索分类名称
  196. builder.addAggregation(AggregationBuilders.terms("productCategoryNames").field("productCategoryName"));
  197. //聚合搜索商品属性,去除type=1的属性
  198. AbstractAggregationBuilder aggregationBuilder = AggregationBuilders.nested("allAttrValues","attrValueList")
  199. .subAggregation(AggregationBuilders.filter("productAttrs",QueryBuilders.termQuery("attrValueList.type",1))
  200. .subAggregation(AggregationBuilders.terms("attrIds")
  201. .field("attrValueList.productAttributeId")
  202. .subAggregation(AggregationBuilders.terms("attrValues")
  203. .field("attrValueList.value"))
  204. .subAggregation(AggregationBuilders.terms("attrNames")
  205. .field("attrValueList.name"))));
  206. builder.addAggregation(aggregationBuilder);
  207. NativeSearchQuery searchQuery = builder.build();
  208. return elasticsearchTemplate.query(searchQuery, response -> {
  209. LOGGER.info("DSL:{}",searchQuery.getQuery().toString());
  210. return convertProductRelatedInfo(response);
  211. });
  212. }
  213. /** * 将返回结果转换为对象 */
  214. private EsProductRelatedInfo convertProductRelatedInfo(SearchResponse response) {
  215. EsProductRelatedInfo productRelatedInfo = new EsProductRelatedInfo();
  216. Map<String, Aggregation> aggregationMap = response.getAggregations().getAsMap();
  217. //设置品牌
  218. Aggregation brandNames = aggregationMap.get("brandNames");
  219. List<String> brandNameList = new ArrayList<>();
  220. for(int i = 0; i<((Terms) brandNames).getBuckets().size(); i++){
  221. brandNameList.add(((Terms) brandNames).getBuckets().get(i).getKeyAsString());
  222. }
  223. productRelatedInfo.setBrandNames(brandNameList);
  224. //设置分类
  225. Aggregation productCategoryNames = aggregationMap.get("productCategoryNames");
  226. List<String> productCategoryNameList = new ArrayList<>();
  227. for(int i=0;i<((Terms) productCategoryNames).getBuckets().size();i++){
  228. productCategoryNameList.add(((Terms) productCategoryNames).getBuckets().get(i).getKeyAsString());
  229. }
  230. productRelatedInfo.setProductCategoryNames(productCategoryNameList);
  231. //设置参数
  232. Aggregation productAttrs = aggregationMap.get("allAttrValues");
  233. List<LongTerms.Bucket> attrIds = ((LongTerms) ((InternalFilter) ((InternalNested) productAttrs).getProperty("productAttrs")).getProperty("attrIds")).getBuckets();
  234. List<EsProductRelatedInfo.ProductAttr> attrList = new ArrayList<>();
  235. for (Terms.Bucket attrId : attrIds) {
  236. EsProductRelatedInfo.ProductAttr attr = new EsProductRelatedInfo.ProductAttr();
  237. attr.setAttrId((Long) attrId.getKey());
  238. List<String> attrValueList = new ArrayList<>();
  239. List<StringTerms.Bucket> attrValues = ((StringTerms) attrId.getAggregations().get("attrValues")).getBuckets();
  240. List<StringTerms.Bucket> attrNames = ((StringTerms) attrId.getAggregations().get("attrNames")).getBuckets();
  241. for (Terms.Bucket attrValue : attrValues) {
  242. attrValueList.add(attrValue.getKeyAsString());
  243. }
  244. attr.setAttrValues(attrValueList);
  245. if(!CollectionUtils.isEmpty(attrNames)){
  246. String attrName = attrNames.get(0).getKeyAsString();
  247. attr.setAttrName(attrName);
  248. }
  249. attrList.add(attr);
  250. }
  251. productRelatedInfo.setProductAttrs(attrList);
  252. return productRelatedInfo;
  253. }
  254. }

进行接口测试

将数据库中数据导入到Elasticsearch

在这里插入图片描述

进行商品搜索

在这里插入图片描述

发表评论

表情:
评论列表 (有 0 条评论,206人围观)

还没有评论,来说两句吧...

相关阅读