电商平台的搜索功能是用户找到商品最直接、最高效的途径,其性能和相关性直接影响用户体验和转化率。传统的数据库LIKE查询在海量数据面前性能低下且功能有限。Elasticsearch(简称ES)是一个功能强大、基于Lucene的开源搜索引擎,它提供了近实时的搜索、强大的聚合分析能力和水平扩展性。本章我们将构建一个专门的搜索服务cloudmall-search,将商品数据同步到ES中,并实现复杂的商品检索功能。


25.1 Elasticsearch核心概念

内容讲解

为了更好地使用ES,我们需要理解它的一些核心概念,并将其与关系型数据库进行类比:

Elasticsearch 关系型数据库 (MySQL)
Index (索引) Database (数据库)
Type (类型) Table (表) - 在ES 7.x后被弱化,一个Index只有一个_doc类型
Document (文档) Row (行) - ES中的数据单元,以JSON格式表示
Field (字段) Column (列) - Document中的一个字段
Mapping (映射) Schema (表结构) - 定义Index中字段的类型、分词器等属性
Inverted Index (倒排索引) 无直接对应 - ES实现快速搜索的核心机制

倒排索引 (Inverted Index)
这是ES实现全文检索的关键。传统的关系型数据库是“文档 -> 词”的正向索引,而倒排索引是“词 -> 文档”的结构。它记录了每个词(Term)出现在哪些文档(Document)中,以及出现的位置和频率。当用户搜索一个词时,ES可以直接通过倒排索引找到所有包含该词的文档,从而极大地提高了搜索速度。

倒排索引 (Elasticsearch)
正向索引 (数据库)
文档1
小米
文档2
华为
文档1, 文档2
手机
小米
文档1: 小米手机很好用
手机
华为
文档2: 华为手机很强大
手机

25.2 商品数据同步到ES

内容讲解

要实现搜索,首先需要将商品数据从MySQL数据库同步到Elasticsearch中。这个过程称为“索引数据”。

实现思路:

  1. 创建cloudmall-search服务: 引入spring-boot-starter-data-elasticsearch依赖。
  2. 配置ES连接: 在application.yml中配置ES集群的地址。
  3. 定义ES数据模型: 创建一个ProductEsModel.java类,其字段对应我们需要在ES中存储和检索的商品信息。使用@Document注解标记这是一个ES文档模型。
  4. 编写数据同步接口: 在cloudmall-search服务中提供一个HTTP接口,当调用该接口时,它会:
    a. 通过Feign远程调用cloudmall-product服务,获取所有已上架(status=1)的SKU信息。
    b. 将查询到的SKU列表转换成ProductEsModel列表。
    c. 使用ElasticsearchRestTemplatesave方法,将数据批量保存到ES中。
代码示例

1. pom.xml依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

2. ProductEsModel.java

package com.cloudmall.search.vo;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

@Data
@Document(indexName = "product") // 指定索引名
public class ProductEsModel {
    @Id
    private Long skuId;

    @Field(type = FieldType.Text, analyzer = "ik_smart") // 使用IK分词器
    private String skuTitle;

    @Field(type = FieldType.Keyword) // Keyword类型不分词
    private String skuImg;

    @Field(type = FieldType.Double)
    private Double skuPrice;

    // ... 其他需要检索或展示的字段 ...
}

3. 数据同步Controller (ElasticSaveController.java)

package com.cloudmall.search.controller;

import com.cloudmall.search.service.ProductSaveService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/search/save")
public class ElasticSaveController {

    @Autowired
    private ProductSaveService productSaveService;

    // 上架商品,将商品数据保存到ES
    @PostMapping("/product")
    public Result productStatusUp(@RequestBody List<ProductEsModel> models) {
        boolean success = productSaveService.productStatusUp(models);
        if (success) {
            return Result.ok();
        } else {
            return Result.error(BizCodeEnum.PRODUCT_UP_EXCEPTION);
        }
    }
}

25.3 实现商品检索功能

内容讲解

有了数据之后,我们就可以构建一个复杂的搜索接口,支持多种查询条件。

搜索条件分析:
一个典型的电商搜索页包含:

  • 关键字查询: 在商品标题、副标题中进行全文检索。
  • 分类过滤: 按商品的三级分类进行筛选。
  • 品牌过滤: 按品牌进行筛选。
  • 属性过滤: 按商品的销售属性(如颜色、内存)进行筛选。
  • 价格区间过滤: 按指定的价格范围筛选。
  • 排序: 按销量、价格等字段排序。
  • 分页: 返回指定页码的数据。

使用Bool Query构建复合查询
ES的bool查询是构建复杂查询的利器,它包含四个子句:

  • must: 必须匹配,贡献算分。
  • filter: 必须匹配,但不贡献算分(性能更高,用于精确匹配过滤)。
  • should: 可选匹配,匹配上会增加算分。
  • must_not: 必须不匹配。

我们可以将关键字查询放入must子句,将分类、品牌、属性、价格等过滤条件放入filter子句,从而构建出高效且功能强大的搜索请求。

代码示例 (SearchServiceImpl.java)
package com.cloudmall.search.service.impl;

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.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private RestHighLevelClient client;

    @Override
    public SearchResult search(SearchParam param) {
        // 1. 动态构建查询DSL语句
        SearchRequest request = buildSearchRequest(param);

        // 2. 执行检索
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        // 3. 将结果封装成VO
        SearchResult result = buildSearchResult(response, param);
        return result;
    }

    private SearchRequest buildSearchRequest(SearchParam param) {
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        // 1.1 must - 关键字查询
        if (StringUtils.isNotEmpty(param.getKeyword())) {
            boolQuery.must(QueryBuilders.matchQuery("skuTitle", param.getKeyword()));
        }

        // 1.2 filter - 分类过滤
        if (param.getCatalog3Id() != null) {
            boolQuery.filter(QueryBuilders.termQuery("catalogId", param.getCatalog3Id()));
        }

        // 1.3 filter - 品牌过滤 (可多选)
        if (param.getBrandId() != null && param.getBrandId().size() > 0) {
            boolQuery.filter(QueryBuilders.termsQuery("brandId", param.getBrandId()));
        }

        // ... 其他过滤、排序、分页、高亮、聚合等逻辑 ...

        sourceBuilder.query(boolQuery);
        SearchRequest request = new SearchRequest(new String[]{"product"}, sourceBuilder);
        return request;
    }
}

本章总结

本章我们引入了强大的搜索引擎Elasticsearch来解决电商平台的核心需求——商品检索。我们首先学习了ES的基本概念,特别是其核心的倒排索引机制。接着,我们从零开始搭建了cloudmall-search服务,设计了商品在ES中的数据模型,并实现了从MySQL到ES的数据同步功能。最核心的部分是,我们分析了复杂的搜索场景,并利用ES的bool查询,将关键字匹配、多维度过滤、排序、分页等需求组合在一起,构建了动态、高效的检索DSL。通过本章的学习,我们不仅掌握了ES的基本使用,更重要的是学会了如何将它与业务场景结合,构建生产级的搜索引擎服务。

资源下载

本章的示例代码可以从以下链接下载:
微服务开发课程第16-26章的源码

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐