Elasticsearch 全文搜索实战:构建企业级搜索引擎

Elasticsearch 全文搜索实战:构建企业级搜索引擎

别叫我大神,叫我 Alex 就好。搜索是用户体验的关键,Elasticsearch 让全文搜索变得简单而强大。

一、Elasticsearch 基础配置

1.1 Spring Data Elasticsearch 配置

java 复制代码
@Configuration
@EnableElasticsearchRepositories(basePackages = "com.example.repository")
public class ElasticsearchConfig extends AbstractElasticsearchConfiguration {

    @Override
    @Bean
    public RestHighLevelClient elasticsearchClient() {
        ClientConfiguration clientConfiguration = ClientConfiguration.builder()
            .connectedTo("localhost:9200")
            .withConnectTimeout(Duration.ofSeconds(5))
            .withSocketTimeout(Duration.ofSeconds(30))
            .withBasicAuth("elastic", "password")
            .build();

        return RestClients.create(clientConfiguration).rest();
    }

    @Bean
    public ElasticsearchOperations elasticsearchTemplate() {
        return new ElasticsearchRestTemplate(elasticsearchClient());
    }
}

1.2 文档映射

java 复制代码
@Document(indexName = "products", createIndex = false)
@Setting(settingPath = "elasticsearch/products-settings.json")
@Mapping(mappingPath = "elasticsearch/products-mapping.json")
public class Product {

    @Id
    private String id;

    @Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
    private String name;

    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String description;

    @Field(type = FieldType.Keyword)
    private String category;

    @Field(type = FieldType.Double)
    private BigDecimal price;

    @Field(type = FieldType.Integer)
    private Integer stock;

    @Field(type = FieldType.Date, format = DateFormat.date_time)
    private LocalDateTime createTime;

    @Field(type = FieldType.Nested)
    private List<ProductAttribute> attributes;

    // Getters and setters
}

// 嵌套对象
public class ProductAttribute {

    @Field(type = FieldType.Keyword)
    private String name;

    @Field(type = FieldType.Keyword)
    private String value;
}

1.3 索引设置

json 复制代码
// products-settings.json
{
  "index": {
    "number_of_shards": 3,
    "number_of_replicas": 1,
    "analysis": {
      "analyzer": {
        "ik_custom": {
          "type": "custom",
          "tokenizer": "ik_max_word",
          "filter": ["lowercase", "synonym_filter"]
        }
      },
      "filter": {
        "synonym_filter": {
          "type": "synonym",
          "synonyms": ["手机,移动电话,智能手机", "电脑,计算机,笔记本"]
        }
      }
    }
  }
}

// products-mapping.json
{
  "properties": {
    "name": {
      "type": "text",
      "analyzer": "ik_max_word",
      "fields": {
        "keyword": {
          "type": "keyword",
          "ignore_above": 256
        },
        "suggest": {
          "type": "completion"
        }
      }
    },
    "description": {
      "type": "text",
      "analyzer": "ik_max_word"
    },
    "category": {
      "type": "keyword"
    },
    "price": {
      "type": "scaled_float",
      "scaling_factor": 100
    },
    "attributes": {
      "type": "nested"
    }
  }
}

二、搜索功能实现

2.1 基础搜索

java 复制代码
@Repository
public interface ProductRepository extends ElasticsearchRepository<Product, String> {

    // 简单查询
    List<Product> findByNameContaining(String name);

    // 分页查询
    Page<Product> findByCategory(String category, Pageable pageable);

    // 范围查询
    List<Product> findByPriceBetween(BigDecimal minPrice, BigDecimal maxPrice);

    // 多字段查询
    @Query("{\"multi_match\": {\"query\": \"?0\", \"fields\": [\"name^3\", \"description\"]}}")
    List<Product> searchByKeyword(String keyword);
}

2.2 高级搜索服务

java 复制代码
@Service
public class ProductSearchService {

    @Autowired
    private ElasticsearchRestTemplate elasticsearchTemplate;

    public SearchHits<Product> advancedSearch(ProductSearchRequest request) {
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        // 关键词搜索
        if (StringUtils.hasText(request.getKeyword())) {
            MultiMatchQueryBuilder multiMatchQuery = QueryBuilders
                .multiMatchQuery(request.getKeyword(), "name^3", "description", "category")
                .type(MultiMatchQueryBuilder.Type.BEST_FIELDS)
                .fuzziness(Fuzziness.AUTO);

            boolQuery.must(multiMatchQuery);
        }

        // 分类过滤
        if (StringUtils.hasText(request.getCategory())) {
            boolQuery.filter(QueryBuilders.termQuery("category", request.getCategory()));
        }

        // 价格范围
        if (request.getMinPrice() != null || request.getMaxPrice() != null) {
            RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("price");
            if (request.getMinPrice() != null) {
                rangeQuery.gte(request.getMinPrice());
            }
            if (request.getMaxPrice() != null) {
                rangeQuery.lte(request.getMaxPrice());
            }
            boolQuery.filter(rangeQuery);
        }

        // 属性过滤
        if (request.getAttributes() != null && !request.getAttributes().isEmpty()) {
            for (Map.Entry<String, String> attr : request.getAttributes().entrySet()) {
                NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery(
                    "attributes",
                    QueryBuilders.boolQuery()
                        .must(QueryBuilders.termQuery("attributes.name", attr.getKey()))
                        .must(QueryBuilders.termQuery("attributes.value", attr.getValue())),
                    ScoreMode.None
                );
                boolQuery.filter(nestedQuery);
            }
        }

        // 构建查询
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder()
            .withQuery(boolQuery)
            .withPageable(PageRequest.of(request.getPage(), request.getSize()));

        // 排序
        if (StringUtils.hasText(request.getSortField())) {
            SortOrder sortOrder = "desc".equalsIgnoreCase(request.getSortOrder())
                ? SortOrder.DESC : SortOrder.ASC;
            queryBuilder.withSort(SortBuilders.fieldSort(request.getSortField()).order(sortOrder));
        }

        // 高亮
        queryBuilder.withHighlightFields(
            new HighlightBuilder.Field("name").preTags("<em>").postTags("</em>"),
            new HighlightBuilder.Field("description").preTags("<em>").postTags("</em>")
        );

        // 聚合
        queryBuilder.addAggregation(
            AggregationBuilders.terms("categories").field("category").size(20)
        );
        queryBuilder.addAggregation(
            AggregationBuilders.histogram("price_ranges").field("price").interval(100)
        );

        NativeSearchQuery searchQuery = queryBuilder.build
相关推荐
喜欢打篮球的普通人25 分钟前
LLVM Backend Lowering 从入门到实战:把 IR 变成机器码的完整链路
android·java·数据库
FL16238631291 小时前
室内易燃物识别易燃评估室内易燃程度识别分割数据集labelme格式1015张85类别
java·服务器·前端
jufeng13071 小时前
【系列:TDengine 工业物联网实战:从零搭起可运行系统 · 第 8 篇】
java·spring boot·时序数据库·tdengine
程序员贺加贝2 小时前
轻制造SaaS的生产闭环建模-BOM工单领料报工质检与入库
java·设计模式·架构
敲个大西瓜3 小时前
Spring Could Alibaba 核心面试题
java·后端·spring
骇客野人3 小时前
Java SSO 统一认证方案
java·开发语言
START_GAME4 小时前
MSSQL$SQL2016
java·服务器·前端
ydd1001004 小时前
字符串转换整数
java
会编程的吕洞宾4 小时前
DeepAgents In Action学习(Second)
android·java·学习
梦梦代码精4 小时前
基于UniApp+Vue3+ThinkPHP 8,这套知识付费系统的架构设计有点东西
java·低代码·docker·uni-app·开源·php