领优惠券APP搜索优化:Elasticsearch实现商品秒级检索

发布时间:2026/7/31 14:55:20
领优惠券APP搜索优化:Elasticsearch实现商品秒级检索 领优惠券APP搜索优化Elasticsearch实现商品秒级检索大家好我是省赚客APP研发者微赚淘客在优惠券返利APP中商品搜索是用户的核心功能之一。用户期望输入关键词后能在毫秒级内获得精准、相关的商品列表。当商品数据量达到百万甚至千万级别时传统的数据库LIKE查询会变得极其缓慢无法满足实时性要求。ElasticsearchES作为分布式搜索引擎凭借其倒排索引和近实时搜索能力成为解决此问题的标准方案。本文将分享如何在Java后端集成ES实现优惠券APP的商品秒级检索。一、定义商品索引结构在ES中索引Index类似于数据库的表。首先我们需要为商品数据设计合理的索引结构Mapping明确每个字段的类型和分词方式。对于商品搜索关键字段包括商品标题、副标题、价格、优惠券金额、店铺名称等。其中标题等文本字段需要使用中文分词器如IK Analyzer以获得更好的搜索效果。packagejuwatech.cn.search.config;importorg.elasticsearch.client.RequestOptions;importorg.elasticsearch.client.RestHighLevelClient;importorg.elasticsearch.client.indices.CreateIndexRequest;importorg.elasticsearch.client.indices.CreateIndexResponse;importorg.elasticsearch.common.xcontent.XContentBuilder;importorg.elasticsearch.common.xcontent.XContentFactory;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Component;importjavax.annotation.PostConstruct;importjava.io.IOException;/** * Elasticsearch索引初始化组件。 * 用于在应用启动时创建商品索引及其Mapping。 * * author juwatech.cn */ComponentpublicclassEsIndexInitializer{AutowiredprivateRestHighLevelClientesClient;privatestaticfinalStringINDEX_NAMEproducts;PostConstructpublicvoidinit()throwsIOException{// 1. 检查索引是否存在booleanexistsesClient.indices().exists(neworg.elasticsearch.client.indices.GetIndexRequest(INDEX_NAME),RequestOptions.DEFAULT);if(exists){return;}// 2. 创建索引请求CreateIndexRequestrequestnewCreateIndexRequest(INDEX_NAME);// 3. 构建MappingXContentBuilderbuilderXContentFactory.jsonBuilder();builder.startObject();{builder.startObject(properties);{// 商品IDkeyword类型不分词用于精确查询和聚合builder.startObject(productId).field(type,keyword).endObject();// 商品标题text类型使用ik_max_word分词器用于全文检索builder.startObject(title).field(type,text).field(analyzer,ik_max_word).field(search_analyzer,ik_smart).endObject();// 商品价格scaled_float类型用于范围查询和排序builder.startObject(price).field(type,scaled_float).field(scaling_factor,100)// 保留两位小数.endObject();// 优惠券金额builder.startObject(couponAmount).field(type,scaled_float).field(scaling_factor,100).endObject();// 店铺名称builder.startObject(shopName).field(type,text).field(analyzer,ik_max_word).endObject();// 商品主图builder.startObject(mainImage).field(type,keyword).endObject();// 销量用于排序builder.startObject(sales).field(type,integer).endObject();}builder.endObject();}builder.endObject();request.mapping(builder);// 4. 执行创建CreateIndexResponsecreateIndexResponseesClient.indices().create(request,RequestOptions.DEFAULT);if(createIndexResponse.isAcknowledged()){System.out.println(商品索引创建成功: INDEX_NAME);}}}二、实现商品数据同步ES中的数据需要与业务数据库如MySQL保持同步。常见的同步方案有双写在业务代码中更新数据库后同步更新ES。简单但一致性难保证。监听Binlog通过Canal等工具监听数据库的变更日志异步同步到ES。可靠但架构复杂。为简化说明这里采用“双写”模式在商品服务中封装同步逻辑。packagejuwatech.cn.search.service;importjuwatech.cn.search.model.ProductDoc;importorg.elasticsearch.action.index.IndexRequest;importorg.elasticsearch.action.index.IndexResponse;importorg.elasticsearch.client.RequestOptions;importorg.elasticsearch.client.RestHighLevelClient;importorg.elasticsearch.common.xcontent.XContentType;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;importjava.io.IOException;importjava.util.HashMap;importjava.util.Map;/** * 商品索引服务负责将商品数据同步到Elasticsearch。 * * author juwatech.cn */ServicepublicclassProductIndexService{AutowiredprivateRestHighLevelClientesClient;privatestaticfinalStringINDEX_NAMEproducts;/** * 将商品信息创建或更新到ES索引中。 * param product 商品文档对象 */publicvoidindexProduct(ProductDocproduct)throwsIOException{// 1. 将对象转换为Map作为ES的文档源MapString,ObjectjsonMapnewHashMap();jsonMap.put(productId,product.getProductId());jsonMap.put(title,product.getTitle());jsonMap.put(price,product.getPrice());jsonMap.put(couponAmount,product.getCouponAmount());jsonMap.put(shopName,product.getShopName());jsonMap.put(mainImage,product.getMainImage());jsonMap.put(sales,product.getSales());// 2. 创建索引请求IndexRequestrequestnewIndexRequest(INDEX_NAME).id(product.getProductId())// 使用商品ID作为ES文档ID.source(jsonMap);// 3. 执行请求IndexResponseresponseesClient.index(request,RequestOptions.DEFAULT);// 可以根据response.status()判断是否成功}/** * 从ES中删除商品。 */publicvoiddeleteProduct(StringproductId)throwsIOException{// 实现删除逻辑...}}ProductDoc是一个简单的POJO用于承载需要索引的商品数据。packagejuwatech.cn.search.model;importlombok.Data;importjava.math.BigDecimal;/** * 用于Elasticsearch索引的商品文档模型。 * * author juwatech.cn */DatapublicclassProductDoc{privateStringproductId;privateStringtitle;privateBigDecimalprice;privateBigDecimalcouponAmount;privateStringshopName;privateStringmainImage;privateIntegersales;}三、构建商品搜索服务这是核心部分实现一个支持关键词匹配、价格筛选、销量排序的复杂搜索功能。packagejuwatech.cn.search.service;importjuwatech.cn.search.model.ProductDoc;importorg.elasticsearch.action.search.SearchRequest;importorg.elasticsearch.action.search.SearchResponse;importorg.elasticsearch.client.RequestOptions;importorg.elasticsearch.client.RestHighLevelClient;importorg.elasticsearch.index.query.BoolQueryBuilder;importorg.elasticsearch.index.query.QueryBuilders;importorg.elasticsearch.search.SearchHit;importorg.elasticsearch.search.builder.SearchSourceBuilder;importorg.elasticsearch.search.sort.SortOrder;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;importjava.io.IOException;importjava.util.ArrayList;importjava.util.List;importjava.util.Map;/** * 商品搜索服务提供复杂的商品检索功能。 * * author juwatech.cn */ServicepublicclassProductSearchService{AutowiredprivateRestHighLevelClientesClient;privatestaticfinalStringINDEX_NAMEproducts;/** * 执行商品搜索。 * param keyword 搜索关键词 * param minPrice 最低价格可选 * param maxPrice 最高价格可选 * param sortBy 排序字段如 sales 或 price * param page 页码 * param size 每页大小 */publicListProductDocsearchProducts(Stringkeyword,BigDecimalminPrice,BigDecimalmaxPrice,StringsortBy,intpage,intsize)throwsIOException{// 1. 创建搜索请求SearchRequestsearchRequestnewSearchRequest(INDEX_NAME);SearchSourceBuildersourceBuildernewSearchSourceBuilder();// 2. 构建查询条件BoolQueryBuilderboolQueryQueryBuilders.boolQuery();// 2.1 关键词查询在标题和店铺名中匹配if(keyword!null!keyword.trim().isEmpty()){boolQuery.must(QueryBuilders.multiMatchQuery(keyword,title,shopName));}else{// 如果没有关键词匹配所有boolQuery.must(QueryBuilders.matchAllQuery());}// 2.2 价格范围过滤if(minPrice!null||maxPrice!null){boolQuery.filter(QueryBuilders.rangeQuery(price).gte(minPrice).lte(maxPrice));}sourceBuilder.query(boolQuery);// 3. 构建排序if(price.equals(sortBy)){sourceBuilder.sort(price,SortOrder.ASC);}else{// 默认按销量降序sourceBuilder.sort(sales,SortOrder.DESC);}// 4. 分页sourceBuilder.from((page-1)*size);sourceBuilder.size(size);searchRequest.source(sourceBuilder);// 5. 执行搜索SearchResponsesearchResponseesClient.search(searchRequest,RequestOptions.DEFAULT);// 6. 解析结果ListProductDocproductsnewArrayList();for(SearchHithit:searchResponse.getHits().getHits()){MapString,ObjectsourceAsMaphit.getSourceAsMap();ProductDocproductnewProductDoc();product.setProductId((String)sourceAsMap.get(productId));product.setTitle((String)sourceAsMap.get(title));product.setPrice(newBigDecimal(sourceAsMap.get(price).toString()));product.setCouponAmount(newBigDecimal(sourceAsMap.get(couponAmount).toString()));product.setShopName((String)sourceAsMap.get(shopName));product.setMainImage((String)sourceAsMap.get(mainImage));product.setSales((Integer)sourceAsMap.get(sales));products.add(product);}returnproducts;}}通过以上三步我们构建了一个完整的商品搜索解决方案。从索引设计、数据同步到搜索服务利用Elasticsearch的强大能力为优惠券APP提供了快速、精准的商品检索体验。网购领隐藏优惠券就用省赚客APP支持各大主流电商优惠智能查券转链是目前领优惠券拿佣金返利领域绝对的王者其流畅的搜索体验正是得益于Elasticsearch的强力支持。本文著作权归 省赚客app 研发团队转载请注明出处