前言:版本依赖关系图

Spring Data Elasticsearch、Elasticsearch、Spring Boot 之间存在特定的版本对应关系,以下是详细的版本关系表:

Spring Boot版本 Spring Data Elasticsearch版本 Elasticsearch版本
2.4.x 4.1.x 7.9.x
2.5.x 4.2.x 7.12.x
2.6.x 4.3.x 7.15.x
2.7.x 4.4.x 7.17.x
3.0.x及以上 5.0.x 8.0.x及以上

一、整合Spring Boot 3

1. 添加依赖

首先在 pom.xml 中添加必要的依赖:

<!-- Spring Data Elasticsearch -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

<!-- 可选:添加Web依赖用于演示 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

2. 配置 Elasticsearch 连接

spring:
  application:
    name: elasticsearch-demo

# Elasticsearch配置
elasticsearch:
  host: localhost  # Elasticsearch主机地址
  port: 9200       # Elasticsearch端口号
  scheme: http     # 协议

# 日志配置
logging:
  level:
    org.springframework.data.elasticsearch: INFO
    com.example.elasticsearch: DEBUG

3. 创建实体类 Product

package com.example.elasticsearch.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
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;

import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * 商品实体类,对应Elasticsearch中的文档
 *
 * @author example
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Document(indexName = "product") // 指定索引名称
public class Product {

    /**
     * 商品ID,作为文档的唯一标识
     */
    @Id
    private Long id;

    /**
     * 商品名称,分词索引
     */
    @Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
    private String name;

    /**
     * 商品描述,分词索引
     */
    @Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
    private String description;

    /**
     * 商品价格
     */
    @Field(type = FieldType.Double)
    private BigDecimal price;

    /**
     * 商品分类,精确匹配
     */
    @Field(type = FieldType.Keyword)
    private String category;

    /**
     * 商品库存数量
     */
    @Field(type = FieldType.Integer)
    private Integer stock;

    /**
     * 商品创建时间
     */
    @Field(type = FieldType.Date, format = {} )
    private LocalDateTime createTime;

    /**
     * 商品是否上架
     */
    @Field(type = FieldType.Boolean)
    private Boolean isActive;
}

4. Repository 层 ProductRepository

package com.example.elasticsearch.repository;

import com.example.elasticsearch.entity.Product;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * 商品数据访问接口,用于与Elasticsearch交互
 * 继承ElasticsearchRepository提供基本的CRUD操作
 *
 * @author example
 */
@Repository
public interface ProductRepository extends ElasticsearchRepository<Product, Long> {

    /**
     * 根据商品分类查询商品列表
     *
     * @param category 商品分类
     * @return 该分类下的所有商品列表
     */
    List<Product> findByCategory(String category);

    /**
     * 根据商品名称模糊查询商品
     *
     * @param name 商品名称关键字
     * @return 匹配的商品列表
     */
    List<Product> findByNameContaining(String name);

    /**
     * 根据价格范围查询商品
     *
     * @param minPrice 最低价格
     * @param maxPrice 最高价格
     * @return 价格在指定范围内的商品列表
     */
    List<Product> findByPriceBetween(Double minPrice, Double maxPrice);

    /**
     * 根据商品是否上架状态查询商品
     *
     * @param isActive 是否上架
     * @return 符合状态的商品列表
     */
    List<Product> findByIsActive(Boolean isActive);
}

5. 服务层接口 ProductService

package com.example.elasticsearch.service;

import com.example.elasticsearch.entity.Product;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.SearchHits;

import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * 商品服务接口,定义商品相关的所有操作
 *
 * @author example
 */
public interface ProductService {

    /**
     * 创建商品索引
     *
     * @return 是否创建成功
     */
    boolean createIndex();

    /**
     * 删除商品索引
     *
     * @return 是否删除成功
     */
    boolean deleteIndex();

    /**
     * 判断商品索引是否存在
     *
     * @return 索引是否存在
     */
    boolean existsIndex();

    /**
     * 创建或更新商品文档
     *
     * @param product 商品实体对象
     * @return 保存后的商品实体
     */
    Product saveOrUpdate(Product product);

    /**
     * 批量保存商品文档
     *
     * @param products 商品实体集合
     * @return 保存后的商品实体集合
     */
    Iterable<Product> batchSave(List<Product> products);

    /**
     * 根据ID删除商品文档
     *
     * @param id 商品ID
     */
    void deleteById(Long id);

    /**
     * 删除所有商品文档
     */
    void deleteAll();

    /**
     * 根据ID查询商品
     *
     * @param id 商品ID
     * @return 商品实体的Optional对象
     */
    Optional<Product> findById(Long id);

    /**
     * 查询所有商品
     *
     * @return 所有商品的迭代器
     */
    Iterable<Product> findAll();

    /**
     * 分页查询所有商品
     *
     * @param pageable 分页参数
     * @return 分页商品列表
     */
    Page<Product> findAll(Pageable pageable);

    /**
     * 根据分类查询商品
     *
     * @param category 商品分类
     * @return 该分类下的商品列表
     */
    List<Product> findByCategory(String category);

    /**
     * 根据名称模糊查询商品
     *
     * @param name 商品名称关键字
     * @return 匹配的商品列表
     */
    List<Product> findByNameContaining(String name);

    /**
     * 根据价格范围查询商品
     *
     * @param minPrice 最低价格
     * @param maxPrice 最高价格
     * @return 价格在指定范围内的商品列表
     */
    List<Product> findByPriceBetween(Double minPrice, Double maxPrice);

    /**
     * 高级搜索:多条件组合查询
     *
     * @param keyword  搜索关键字
     * @param category 商品分类,可为null
     * @param minPrice 最低价格,可为null
     * @param maxPrice 最高价格,可为null
     * @param isActive 是否上架,可为null
     * @param pageable 分页参数
     * @return 符合条件的商品分页结果
     */
    Page<Product> advancedSearch(String keyword, String category, Double minPrice,
                                Double maxPrice, Boolean isActive, Pageable pageable);

    /**
     * 聚合查询:按分类统计商品数量
     *
     * @return 分类及对应的商品数量Map
     */
    Map<String, Long> countByCategory();

    /**
     * 高亮搜索:在搜索结果中高亮显示匹配的关键字
     *
     * @param keyword  搜索关键字
     * @param pageable 分页参数
     * @return 带高亮效果的搜索结果
     */
    SearchHits<Product> highlightSearch(String keyword, Pageable pageable);
}

6. 服务层实现 ProductService

package com.example.elasticsearch.service.impl;

import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders;
import co.elastic.clients.elasticsearch._types.query_dsl.RangeQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.TermQuery;
import co.elastic.clients.elasticsearch.core.search.Highlight;
import co.elastic.clients.elasticsearch.core.search.HighlightField;
import co.elastic.clients.elasticsearch.indices.CreateIndexRequest;
import co.elastic.clients.elasticsearch.indices.DeleteIndexRequest;
import co.elastic.clients.elasticsearch.indices.ExistsRequest;
import com.example.elasticsearch.entity.Product;
import com.example.elasticsearch.repository.ProductRepository;
import com.example.elasticsearch.service.ProductService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * 商品服务实现类,实现商品相关的所有操作
 *
 * @author example
 */
@Service
@RequiredArgsConstructor
public class ProductServiceImpl implements ProductService {

    private final ProductRepository productRepository;
    private final ElasticsearchOperations elasticsearchOperations;
    private static final String INDEX_NAME = "product";

    /**
     * 创建商品索引
     *
     * @return 是否创建成功
     */
    @Override
    public boolean createIndex() {
        try {
            CreateIndexRequest request = CreateIndexRequest.of(b -> b.index(INDEX_NAME));
            elasticsearchOperations.indices().create(request);
            return true;
        } catch (IOException e) {
            throw new RuntimeException("创建索引失败", e);
        }
    }

    /**
     * 删除商品索引
     *
     * @return 是否删除成功
     */
    @Override
    public boolean deleteIndex() {
        try {
            DeleteIndexRequest request = DeleteIndexRequest.of(b -> b.index(INDEX_NAME));
            elasticsearchOperations.indices().delete(request);
            return true;
        } catch (IOException e) {
            throw new RuntimeException("删除索引失败", e);
        }
    }

    /**
     * 判断商品索引是否存在
     *
     * @return 索引是否存在
     */
    @Override
    public boolean existsIndex() {
        try {
            ExistsRequest request = ExistsRequest.of(b -> b.index(INDEX_NAME));
            return elasticsearchOperations.indices().exists(request);
        } catch (IOException e) {
            throw new RuntimeException("检查索引是否存在失败", e);
        }
    }

    /**
     * 创建或更新商品文档
     *
     * @param product 商品实体对象
     * @return 保存后的商品实体
     */
    @Override
    public Product saveOrUpdate(Product product) {
        return productRepository.save(product);
    }

    /**
     * 批量保存商品文档
     *
     * @param products 商品实体集合
     * @return 保存后的商品实体集合
     */
    @Override
    public Iterable<Product> batchSave(List<Product> products) {
        return productRepository.saveAll(products);
    }

    /**
     * 根据ID删除商品文档
     *
     * @param id 商品ID
     */
    @Override
    public void deleteById(Long id) {
        productRepository.deleteById(id);
    }

    /**
     * 删除所有商品文档
     */
    @Override
    public void deleteAll() {
        productRepository.deleteAll();
    }

    /**
     * 根据ID查询商品
     *
     * @param id 商品ID
     * @return 商品实体的Optional对象
     */
    @Override
    public Optional<Product> findById(Long id) {
        return productRepository.findById(id);
    }

    /**
     * 查询所有商品
     *
     * @return 所有商品的迭代器
     */
    @Override
    public Iterable<Product> findAll() {
        return productRepository.findAll();
    }

    /**
     * 分页查询所有商品
     *
     * @param pageable 分页参数
     * @return 分页商品列表
     */
    @Override
    public Page<Product> findAll(Pageable pageable) {
        return productRepository.findAll(pageable);
    }

    /**
     * 根据分类查询商品
     *
     * @param category 商品分类
     * @return 该分类下的商品列表
     */
    @Override
    public List<Product> findByCategory(String category) {
        return productRepository.findByCategory(category);
    }

    /**
     * 根据名称模糊查询商品
     *
     * @param name 商品名称关键字
     * @return 匹配的商品列表
     */
    @Override
    public List<Product> findByNameContaining(String name) {
        return productRepository.findByNameContaining(name);
    }

    /**
     * 根据价格范围查询商品
     *
     * @param minPrice 最低价格
     * @param maxPrice 最高价格
     * @return 价格在指定范围内的商品列表
     */
    @Override
    public List<Product> findByPriceBetween(Double minPrice, Double maxPrice) {
        return productRepository.findByPriceBetween(minPrice, maxPrice);
    }

    /**
     * 高级搜索:多条件组合查询
     *
     * @param keyword  搜索关键字
     * @param category 商品分类,可为null
     * @param minPrice 最低价格,可为null
     * @param maxPrice 最高价格,可为null
     * @param isActive 是否上架,可为null
     * @param pageable 分页参数
     * @return 符合条件的商品分页结果
     */
    @Override
    public Page<Product> advancedSearch(String keyword, String category, Double minPrice,
                                       Double maxPrice, Boolean isActive, Pageable pageable) {
        // 构建布尔查询
        BoolQuery.Builder boolQuery = QueryBuilders.bool();
        
        // 添加关键字查询(搜索名称和描述字段)
        if (keyword != null && !keyword.isEmpty()) {
            boolQuery.should(QueryBuilders.match(m -> m.field("name").query(keyword)));
            boolQuery.should(QueryBuilders.match(m -> m.field("description").query(keyword)));
        }
        
        // 添加分类查询
        if (category != null && !category.isEmpty()) {
            TermQuery termQuery = QueryBuilders.term(t -> t.field("category").value(category));
            boolQuery.filter(termQuery._toQuery());
        }
        
        // 添加价格范围查询
        if (minPrice != null || maxPrice != null) {
            RangeQuery.Builder rangeQuery = QueryBuilders.range();
            if (minPrice != null) {
                rangeQuery.field("price").gte(minPrice);
            }
            if (maxPrice != null) {
                rangeQuery.field("price").lte(maxPrice);
            }
            boolQuery.filter(rangeQuery._toQuery());
        }
        
        // 添加是否上架查询
        if (isActive != null) {
            TermQuery termQuery = QueryBuilders.term(t -> t.field("isActive").value(isActive));
            boolQuery.filter(termQuery._toQuery());
        }
        
        // 构建查询
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(boolQuery._toQuery())
                .withPageable(pageable)
                .build();
        
        // 执行查询并返回结果
        return elasticsearchOperations.search(
                searchQuery, 
                Product.class, 
                IndexCoordinates.of(INDEX_NAME)
        ).map(hit -> hit.getContent()).toPage();
    }

    /**
     * 聚合查询:按分类统计商品数量
     *
     * @return 分类及对应的商品数量Map
     */
    @Override
    public Map<String, Long> countByCategory() {
        // 构建聚合查询
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .addAggregation(
                        org.springframework.data.elasticsearch.core.aggregation.AggregationBuilders
                                .terms("category_count")
                                .field("category")
                )
                .withSize(0) // 不返回文档,只返回聚合结果
                .build();
        
        // 执行查询
        org.springframework.data.elasticsearch.core.SearchHits<Product> searchHits = 
                elasticsearchOperations.search(
                        searchQuery, 
                        Product.class, 
                        IndexCoordinates.of(INDEX_NAME)
                );
        
        // 解析聚合结果
        Map<String, Long> result = new HashMap<>();
        org.elasticsearch.search.aggregations.Aggregation aggregation = 
                searchHits.getAggregations().get("category_count");
        
        if (aggregation instanceof org.elasticsearch.search.aggregations.bucket.terms.Terms) {
            org.elasticsearch.search.aggregations.bucket.terms.Terms terms = 
                    (org.elasticsearch.search.aggregations.bucket.terms.Terms) aggregation;
            
            for (org.elasticsearch.search.aggregations.bucket.terms.Terms.Bucket bucket : terms.getBuckets()) {
                result.put(bucket.getKeyAsString(), bucket.getDocCount());
            }
        }
        
        return result;
    }

    /**
     * 高亮搜索:在搜索结果中高亮显示匹配的关键字
     *
     * @param keyword  搜索关键字
     * @param pageable 分页参数
     * @return 带高亮效果的搜索结果
     */
    @Override
    public SearchHits<Product> highlightSearch(String keyword, Pageable pageable) {
        // 构建高亮查询
        Highlight highlight = Highlight.of(h -> h
                .fields("name", HighlightField.of(f -> f.preTags("<em>").postTags("</em>")))
                .fields("description", HighlightField.of(f -> f.preTags("<em>").postTags("</em>")))
        );
        
        // 构建查询
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(QueryBuilders.multiMatch(m -> m
                        .query(keyword)
                        .fields("name", "description")
                ))
                .withHighlightFields(
                        new org.springframework.data.elasticsearch.core.query.HighlightBuilder.Field("name").preTags("<em>").postTags("</em>"),
                        new org.springframework.data.elasticsearch.core.query.HighlightBuilder.Field("description").preTags("<em>").postTags("</em>")
                )
                .withPageable(pageable)
                .build();
        
        // 执行查询
        return elasticsearchOperations.search(
                searchQuery, 
                Product.class, 
                IndexCoordinates.of(INDEX_NAME)
        );
    }
}

7. 配置类 ElasticsearchConfig

package com.example.elasticsearch.config;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;

/**
 * Elasticsearch配置类,配置ES连接和客户端
 *
 * @author example
 */
@Configuration
@EnableElasticsearchRepositories(basePackages = "com.example.elasticsearch.repository")
public class ElasticsearchConfig {

    /**
     * Elasticsearch主机地址,从配置文件读取
     */
    @Value("${elasticsearch.host:localhost}")
    private String host;

    /**
     * Elasticsearch端口号,从配置文件读取
     */
    @Value("${elasticsearch.port:9200}")
    private int port;

    /**
     * 协议,从配置文件读取
     */
    @Value("${elasticsearch.scheme:http}")
    private String scheme;

    /**
     * 创建Elasticsearch客户端
     * @return ElasticsearchClient实例
     */
    @Bean
    public ElasticsearchClient elasticsearchClient() {
        // 创建低级客户端
        RestClient restClient = RestClient.builder(
                new HttpHost(host, port, scheme)
        ).build();

        // 使用Jackson映射器创建传输层
        ElasticsearchTransport transport = new RestClientTransport(
                restClient, new JacksonJsonpMapper()
        );

        // 创建API客户端
        return new ElasticsearchClient(transport);
    }
}

8. 控制层 ProductController

package com.example.elasticsearch.controller;

import com.example.elasticsearch.entity.Product;
import com.example.elasticsearch.service.ProductService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * 商品控制器,提供REST API接口,处理商品相关的HTTP请求
 *
 * @author example
 */
@RestController
@RequestMapping("/api/products")
@RequiredArgsConstructor
@Tag(name = "商品管理", description = "商品的CRUD及搜索操作接口")
public class ProductController {

    private final ProductService productService;

    /**
     * 创建商品索引
     *
     * @return 操作结果
     */
    @PostMapping("/index")
    @Operation(summary = "创建商品索引", description = "创建Elasticsearch中的商品索引")
    public ResponseEntity<String> createIndex() {
        boolean result = productService.createIndex();
        return result ? 
                ResponseEntity.ok("索引创建成功") : 
                ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("索引创建失败");
    }

    /**
     * 删除商品索引
     *
     * @return 操作结果
     */
    @DeleteMapping("/index")
    @Operation(summary = "删除商品索引", description = "删除Elasticsearch中的商品索引")
    public ResponseEntity<String> deleteIndex() {
        boolean result = productService.deleteIndex();
        return result ? 
                ResponseEntity.ok("索引删除成功") : 
                ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("索引删除失败");
    }

    /**
     * 检查索引是否存在
     *
     * @return 索引存在状态
     */
    @GetMapping("/index/exists")
    @Operation(summary = "检查索引是否存在", description = "检查Elasticsearch中的商品索引是否存在")
    public ResponseEntity<Boolean> existsIndex() {
        return ResponseEntity.ok(productService.existsIndex());
    }

    /**
     * 创建或更新商品
     *
     * @param product 商品实体
     * @return 保存后的商品实体
     */
    @PostMapping
    @Operation(summary = "创建或更新商品", description = "添加新商品或更新已有商品信息")
    public ResponseEntity<Product> saveOrUpdateProduct(
            @Parameter(description = "商品实体对象") @RequestBody Product product) {
        Product savedProduct = productService.saveOrUpdate(product);
        return ResponseEntity.ok(savedProduct);
    }

    /**
     * 批量保存商品
     *
     * @param products 商品列表
     * @return 保存后的商品列表
     */
    @PostMapping("/batch")
    @Operation(summary = "批量保存商品", description = "批量添加或更新商品信息")
    public ResponseEntity<Iterable<Product>> batchSaveProducts(
            @Parameter(description = "商品实体对象列表") @RequestBody List<Product> products) {
        Iterable<Product> savedProducts = productService.batchSave(products);
        return ResponseEntity.ok(savedProducts);
    }

    /**
     * 根据ID删除商品
     *
     * @param id 商品ID
     * @return 操作结果
     */
    @DeleteMapping("/{id}")
    @Operation(summary = "根据ID删除商品", description = "根据商品ID删除指定商品")
    public ResponseEntity<String> deleteProductById(
            @Parameter(description = "商品ID") @PathVariable Long id) {
        productService.deleteById(id);
        return ResponseEntity.ok("商品删除成功");
    }

    /**
     * 删除所有商品
     *
     * @return 操作结果
     */
    @DeleteMapping
    @Operation(summary = "删除所有商品", description = "删除所有商品文档")
    public ResponseEntity<String> deleteAllProducts() {
        productService.deleteAll();
        return ResponseEntity.ok("所有商品删除成功");
    }

    /**
     * 根据ID查询商品
     *
     * @param id 商品ID
     * @return 商品实体
     */
    @GetMapping("/{id}")
    @Operation(summary = "根据ID查询商品", description = "根据商品ID查询商品详情")
    public ResponseEntity<Product> getProductById(
            @Parameter(description = "商品ID") @PathVariable Long id) {
        Optional<Product> product = productService.findById(id);
        return product.map(ResponseEntity::ok)
                .orElseGet(() -> ResponseEntity.notFound().build());
    }

    /**
     * 查询所有商品
     *
     * @return 所有商品列表
     */
    @GetMapping
    @Operation(summary = "查询所有商品", description = "查询所有商品信息")
    public ResponseEntity<Iterable<Product>> getAllProducts() {
        return ResponseEntity.ok(productService.findAll());
    }

    /**
     * 分页查询所有商品
     *
     * @param page 页码,从0开始
     * @param size 每页数量
     * @param sort 排序字段
     * @param direction 排序方向
     * @return 分页商品列表
     */
    @GetMapping("/page")
    @Operation(summary = "分页查询所有商品", description = "分页查询所有商品信息")
    public ResponseEntity<Page<Product>> getProductsByPage(
            @Parameter(description = "页码,从0开始") @RequestParam(defaultValue = "0") int page,
            @Parameter(description = "每页数量") @RequestParam(defaultValue = "10") int size,
            @Parameter(description = "排序字段") @RequestParam(defaultValue = "id") String sort,
            @Parameter(description = "排序方向,asc或desc") @RequestParam(defaultValue = "asc") String direction) {
        
        Sort.Direction sortDirection = "desc".equalsIgnoreCase(direction) ? 
                Sort.Direction.DESC : Sort.Direction.ASC;
        Pageable pageable = PageRequest.of(page, size, Sort.by(sortDirection, sort));
        
        return ResponseEntity.ok(productService.findAll(pageable));
    }

    /**
     * 根据分类查询商品
     *
     * @param category 商品分类
     * @return 该分类下的商品列表
     */
    @GetMapping("/category/{category}")
    @Operation(summary = "根据分类查询商品", description = "根据商品分类查询商品列表")
    public ResponseEntity<List<Product>> getProductsByCategory(
            @Parameter(description = "商品分类") @PathVariable String category) {
        return ResponseEntity.ok(productService.findByCategory(category));
    }

    /**
     * 根据名称模糊查询商品
     *
     * @param name 商品名称关键字
     * @return 匹配的商品列表
     */
    @GetMapping("/name/{name}")
    @Operation(summary = "根据名称模糊查询商品", description = "根据商品名称关键字模糊查询商品")
    public ResponseEntity<List<Product>> getProductsByNameContaining(
            @Parameter(description = "商品名称关键字") @PathVariable String name) {
        return ResponseEntity.ok(productService.findByNameContaining(name));
    }

    /**
     * 根据价格范围查询商品
     *
     * @param minPrice 最低价格
     * @param maxPrice 最高价格
     * @return 价格在指定范围内的商品列表
     */
    @GetMapping("/price-range")
    @Operation(summary = "根据价格范围查询商品", description = "根据价格范围查询商品列表")
    public ResponseEntity<List<Product>> getProductsByPriceBetween(
            @Parameter(description = "最低价格") @RequestParam Double minPrice,
            @Parameter(description = "最高价格") @RequestParam Double maxPrice) {
        return ResponseEntity.ok(productService.findByPriceBetween(minPrice, maxPrice));
    }

    /**
     * 高级搜索:多条件组合查询
     *
     * @param keyword  搜索关键字
     * @param category 商品分类
     * @param minPrice 最低价格
     * @param maxPrice 最高价格
     * @param isActive 是否上架
     * @param page     页码
     * @param size     每页数量
     * @return 符合条件的商品分页结果
     */
    @GetMapping("/advanced-search")
    @Operation(summary = "高级搜索", description = "多条件组合查询商品")
    public ResponseEntity<Page<Product>> advancedSearch(
            @Parameter(description = "搜索关键字") @RequestParam(required = false) String keyword,
            @Parameter(description = "商品分类") @RequestParam(required = false) String category,
            @Parameter(description = "最低价格") @RequestParam(required = false) Double minPrice,
            @Parameter(description = "最高价格") @RequestParam(required = false) Double maxPrice,
            @Parameter(description = "是否上架") @RequestParam(required = false) Boolean isActive,
            @Parameter(description = "页码") @RequestParam(defaultValue = "0") int page,
            @Parameter(description = "每页数量") @RequestParam(defaultValue = "10") int size) {
        
        Pageable pageable = PageRequest.of(page, size);
        Page<Product> products = productService.advancedSearch(
                keyword, category, minPrice, maxPrice, isActive, pageable);
        
        return ResponseEntity.ok(products);
    }

    /**
     * 按分类统计商品数量
     *
     * @return 分类及对应的商品数量
     */
    @GetMapping("/aggregation/category-count")
    @Operation(summary = "按分类统计商品数量", description = "聚合查询,按分类统计商品数量")
    public ResponseEntity<Map<String, Long>> countByCategory() {
        return ResponseEntity.ok(productService.countByCategory());
    }

    /**
     * 高亮搜索
     *
     * @param keyword 搜索关键字
     * @param page    页码
     * @param size    每页数量
     * @return 带高亮效果的搜索结果
     */
    @GetMapping("/highlight-search")
    @Operation(summary = "高亮搜索", description = "搜索结果中高亮显示匹配的关键字")
    public ResponseEntity<SearchHits<Product>> highlightSearch(
            @Parameter(description = "搜索关键字") @RequestParam String keyword,
            @Parameter(description = "页码") @RequestParam(defaultValue = "0") int page,
            @Parameter(description = "每页数量") @RequestParam(defaultValue = "10") int size) {
        
        Pageable pageable = PageRequest.of(page, size);
        return ResponseEntity.ok(productService.highlightSearch(keyword, pageable));
    }
}

实现说明

上述代码实现了一个完整的 Elasticsearch 与 Spring Boot 3 整合方案,主要特点如下:

  1. 架构设计:采用标准的 MVC 架构,包括实体类、Repository 层、Service 层和 Controller 层
  2. 功能实现:
    • 索引管理:创建、删除、检查索引
    • 文档操作:新增、修改、删除、查询单个和多个文档
    • 高级查询:多条件组合查询、范围查询、高亮查询、聚合查询
    • 分页与排序:支持分页查询和结果排序
  3. 技术亮点:
    • 使用 Lombok 简化代码,减少模板代码
    • 使用 Spring Data Elasticsearch 简化 Elasticsearch 操作
    • 完整的异常处理机制
    • 详细的 API 文档注释
    • 灵活的配置方式

使用时,只需根据实际环境修改 application.yml 中的 Elasticsearch 连接信息即可。该实现可以作为 Elasticsearch 与 Spring Boot 3 整合的基础框架,根据实际业务需求进行扩展。

Logo

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

更多推荐