This article describes how to implement the product search function in Elasticsearch

Chinese word divider

  • Elasticsearch has a default word splitter, the default word splitter just separates Chinese word by word, which is not what we want.

    get hanzo/_analyze
    {
      "text": "Xiaomi Phone"."tokenizer": "standard"
    }
    Copy the code
  • You need to install the same IK word splitter as Elasticsearch, ik word splitter divides mi phones into Mi and phone, which meets our requirements.

get hanzo/_analyze
{
  "text": "Xiaomi Phone"."tokenizer": "ik_max_word"
}
Copy the code

Used in SpringBoot

Annotate @document, @field and so on in commodity information entity class. For fields that require Chinese word segmentation, we directly use the @field attribute to set it to ik_max_word.

/ * * *@AuthorHao yu QAQ *@Date2020/6/4 then *@Description: Product information in search */
@Document(indexName = "hanzo", type = "product",shards = 1,replicas = 0)
@Data
public class EsProduct implements Serializable {
    private static final long serialVersionUID = -1L;
    @Id
    private Long goodsId;
    @Field(analyzer = "ik_max_word",type = FieldType.Text)
    private String goodsName;
    @Field(analyzer = "ik_max_word",type = FieldType.Text)
    private String goodsIntro;

    private Long goodsCategoryId;

    private String goodsCoverImg;

    private String goodsCarousel;

    private Integer originalPrice;

    private Integer sellingPrice;

    private Integer stockNum;

    private String tag;

    private Byte goodsSellStatus;

    private Integer createUser;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date createTime;

    private Integer updateUser;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date updateTime;
    @Field(type = FieldType.Keyword)
    private String goodsDetailContent;
}
Copy the code

Simple Product Search

First to achieve a simple commodity search function, search including commodity name, commodity introduction, commodity label contains the specified keywords of the commodity and a direct according to the classification of commodity query commodity function

  • Implementation in SpringBoot, using Elasticsearch Repositories derived query to search;

    public interface EsProductRepository extends ElasticsearchRepository<EsProduct.Long> {
        /** * derivative search keyword query **@paramGoodsName Product name *@paramGoodsIntro Product Profile *@paramTag Indicates the product tag@paramPage page information *@return* /
        Page<EsProduct> findByGoodsNameOrGoodsIntroOrTag(String goodsName, String goodsIntro, String tag,Pageable page);
        
       /** * derivative search product classification query **@paramGoodsCategoryId Indicates the product category *@paramPage page information *@return* /
        Page<EsProduct> findByGoodsCategoryId(Long goodsCategoryId, Pageable page);
    
    }
    Copy the code
  • A derived Query is a simple way to convert a method with a specified method name into a Elasticsearch Query DSL statement.

Integrated product search

Next, let’s implement a complex product search that involves filtering, matching weights for different fields, and sorting.

  • First of all, let’s talk about our requirements. Search the product name, product introduction and product label according to the input keyword, and sort by relevance by default.
  • Here we have some special requirements, such as the product name matching the keyword of the product we think better match the search criteria, followed by the product profile and product label, then need to usefunction_scoreThe query;
  • Elasticsearch finds document relevance by_scoreField, document_scoreThe higher the field value is, the more it matches the search criteriafunction_scoreQueries can be influenced by setting weights_scoreField value, using it we can achieve the above requirements;
  • In SpringBoot implementation, using Elasticsearch Repositories search method to achieve, but need to customize query conditions QueryBuilder;
 @Override
    public Page<EsProduct> sortSearch(String keyword, Integer pageNum, Integer pageSize, Integer sort) {
        Pageable pageable = PageRequest.of(pageNum, pageSize);
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        / / paging
        nativeSearchQueryBuilder.withPageable(pageable);
        / / filter
        
        / / search
        if (StringUtils.isEmpty(keyword)) {
            nativeSearchQueryBuilder.withQuery(QueryBuilders.matchAllQuery());
        } else {
            List<FunctionScoreQueryBuilder.FilterFunctionBuilder> filterFunctionBuilders = new ArrayList<>();
            filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("goodsName", keyword),
                    ScoreFunctionBuilders.weightFactorFunction(10)));
            filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("goodsIntro", keyword),
                    ScoreFunctionBuilders.weightFactorFunction(5)));
            filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("tag", keyword),
                    ScoreFunctionBuilders.weightFactorFunction(2)));
            FunctionScoreQueryBuilder.FilterFunctionBuilder[] builders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[filterFunctionBuilders.size()];
            filterFunctionBuilders.toArray(builders);
            FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(builders)
                    .scoreMode(FunctionScoreQuery.ScoreMode.SUM)
                    .setMinScore(2);
            nativeSearchQueryBuilder.withQuery(functionScoreQueryBuilder);
        }
        / / sorting
        if(sort==1) {// From new to old
            nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("goodsId").order(SortOrder.DESC));
        }else if(sort==2) {// From high to low by inventory
            nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("stockNum").order(SortOrder.DESC));
        }else if(sort==3) {// From low to high by price
            nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("sellingPrice").order(SortOrder.ASC));
        }else if(sort==4) {// From high to low by price
            nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("sellingPrice").order(SortOrder.DESC));
        }else{
            // By relevance
            nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort().order(SortOrder.DESC));
        }
        nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort().order(SortOrder.DESC));
        NativeSearchQuery searchQuery = nativeSearchQueryBuilder.build();
        log.info("DSL:{}", searchQuery.getQuery().toString());
        return productRepository.search(searchQuery);
    }
Copy the code

Related products recommendation

When we look at the related items, there will be some product recommendations at the bottom.

  • First of all, let’s talk about our requirements. We can find related goods according to the ID of the specified goods.

  • Here our implementation principle is as follows: first, obtain the specified product information according to the ID, and then search the product by the name of the specified product, product introduction, product label and classification, and filter out the current product, adjust the weight of the search conditions to obtain the best matching degree;

  • In SpringBoot implementation, using Elasticsearch Repositories search method to achieve, but need to customize query conditions QueryBuilder;

    @Override
        public Page<EsProduct> recommend(Long id, Integer pageNum, Integer pageSize) {
            Pageable pageable = PageRequest.of(pageNum, pageSize);
            List<EsProduct> esProductList = productDao.getAllEsProductList(id);
            if (esProductList.size() > 0) {
                EsProduct esProduct = esProductList.get(0);
                String keyword = esProduct.getGoodsName();
                Long goodsCategoryId = esProduct.getGoodsCategoryId();
                // Search by product name, product description, product label and classification
                List<FunctionScoreQueryBuilder.FilterFunctionBuilder> filterFunctionBuilders = new ArrayList<>();
                filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("goodsName", keyword),
                        ScoreFunctionBuilders.weightFactorFunction(8)));
                filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("goodsIntro", keyword),
                        ScoreFunctionBuilders.weightFactorFunction(2)));
                filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("tag", keyword),
                        ScoreFunctionBuilders.weightFactorFunction(2)));
                filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("goodsCategoryId", goodsCategoryId),
                        ScoreFunctionBuilders.weightFactorFunction(6)));
                FunctionScoreQueryBuilder.FilterFunctionBuilder[] builders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[filterFunctionBuilders.size()];
                filterFunctionBuilders.toArray(builders);
                FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(builders)
                        .scoreMode(FunctionScoreQuery.ScoreMode.SUM)
                        .setMinScore(2);
                NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
                builder.withQuery(functionScoreQueryBuilder);
                builder.withPageable(pageable);
                NativeSearchQuery searchQuery = builder.build();
                log.info("DSL:{}", searchQuery.getQuery().toString());
                return productRepository.search(searchQuery);
            }
            return new PageImpl<>(null);
        }
    Copy the code

Reference code and interface documentation

Github

Swagger UI document

conclusion

Knowledge is valuable only if it is shared. If you have any questions, please feel free to contact me through my email on my page.