SpringBoot + Meilisearch实现商品搜索:从设计到实战的完整攻略
传统搜索的痛点
在我们的日常开发工作中,经常会遇到这样的场景:
- 用户搜索"iPhone 15",结果却是各种苹果汁和苹果派
- 搜索响应时间超过3秒,用户早就流失了
- 没有智能纠错功能,错别字导致搜索无结果
- 无法处理同义词,"手机"和"mobile"是两个概念
传统的数据库LIKE查询不仅性能差,用户体验也糟糕。今天我们就用Meilisearch来解决这些问题。
为什么选择Meilisearch
相比Elasticsearch,Meilisearch有以下优势:
- 开箱即用:无需复杂配置,安装即可使用
- 中文支持好:默认支持中文分词
- 性能优异:查询速度快,资源消耗少
- 易用性强:API简单,学习成本低
解决方案思路
今天我们要解决的,就是如何用SpringBoot + Meilisearch构建一个高效的商品搜索系统。
核心思路是:
- 实时索引:商品数据变更时同步更新搜索索引
- 智能搜索:支持模糊匹配、同义词、拼写纠错
- 个性化排序:根据销量、评分等因素排序
- 性能优化:缓存热门搜索,提升响应速度
Meilisearch环境搭建
1. 安装Meilisearch
# Docker方式安装
docker run -it --rm -p 7700:7700 -v $(pwd)/data.ms:/data.ms getmeili/meilisearch:latest
2. 基础配置
# application.yml
meilisearch:
host-url: http://localhost:7700
api-key: master_key
index:
products: products_index
SpringBoot集成实现
1. 依赖配置
<dependency>
<groupId>com.meilisearch</groupId>
<artifactId>meilisearch-java</artifactId>
<version>0.7.0</version>
</dependency>
2. 配置类
@Configuration
@ConfigurationProperties(prefix = "meilisearch")
@Data
public class MeilisearchConfig {
private String hostUrl;
private String apiKey;
private String index;
@Bean
public MeilisearchClient meilisearchClient() {
return new MeilisearchClient(hostUrl, apiKey);
}
}
3. 搜索服务类
@Service
public class ProductSearchService {
@Autowired
private MeilisearchClient meilisearchClient;
private static final String PRODUCT_INDEX = "products";
/**
* 搜索商品
*/
public SearchResult searchProducts(String query, SearchRequest searchRequest) {
Index index = meilisearchClient.index(PRODUCT_INDEX);
SearchQuery searchQuery = new SearchQuery()
.setQuery(query)
.setPage(searchRequest.getPage())
.setHitsPerPage(searchRequest.getSize())
.setFacets(searchRequest.getFacets())
.setAttributesToRetrieve(searchRequest.getAttributes());
return index.search(searchQuery);
}
/**
* 添加商品到索引
*/
public void addProduct(Product product) {
Index index = meilisearchClient.index(PRODUCT_INDEX);
index.addDocuments(Collections.singletonList(product));
}
/**
* 批量添加商品
*/
public void addProducts(List<Product> products) {
Index index = meilisearchClient.index(PRODUCT_INDEX);
index.addDocuments(products);
}
/**
* 更新商品信息
*/
public void updateProduct(Product product) {
Index index = meilisearchClient.index(PRODUCT_INDEX);
index.updateDocuments(Collections.singletonList(product));
}
/**
* 删除商品
*/
public void deleteProduct(String productId) {
Index index = meilisearchClient.index(PRODUCT_INDEX);
index.deleteDocument(productId);
}
}
搜索功能增强
1. 智能搜索配置
@Service
public class AdvancedSearchService {
public SearchQuery configureSmartSearch(String query, ProductSearchCriteria criteria) {
SearchQuery searchQuery = new SearchQuery()
.setQuery(query)
.setShowMatchesPosition(true)
.setShowRankingScoreDetails(true);
// 添加过滤条件
if (criteria.getMinPrice() != null || criteria.getMaxPrice() != null) {
String priceFilter = buildPriceFilter(criteria);
searchQuery.setFilter(priceFilter);
}
// 添加分类过滤
if (CollectionUtils.isNotEmpty(criteria.getCategories())) {
String categoryFilter = buildCategoryFilter(criteria.getCategories());
searchQuery.setFilter(categoryFilter);
}
// 设置排序
if (StringUtils.hasText(criteria.getSortBy())) {
searchQuery.setSort(Collections.singletonList(
criteria.getSortBy() + ":" + criteria.getSortOrder()));
}
return searchQuery;
}
private String buildPriceFilter(ProductSearchCriteria criteria) {
StringBuilder filter = new StringBuilder();
if (criteria.getMinPrice() != null) {
filter.append("price >= ").append(criteria.getMinPrice());
}
if (criteria.getMaxPrice() != null) {
if (filter.length() > 0) {
filter.append(" AND ");
}
filter.append("price <= ").append(criteria.getMaxPrice());
}
return filter.toString();
}
}
2. 搜索建议功能
@Service
public class SearchSuggestionService {
/**
* 获取搜索建议
*/
public List<String> getSuggestions(String query, int limit) {
// 使用Meilisearch的facet搜索功能
SearchQuery searchQuery = new SearchQuery()
.setQuery(query)
.setAttributesToSearchOn(Arrays.asList("name", "brand"))
.setHitsPerPage(limit);
SearchResult result = meilisearchClient.index(PRODUCT_INDEX).search(searchQuery);
// 提取相关词汇作为建议
return result.getHits().stream()
.map(hit -> (String) hit.get("name"))
.distinct()
.limit(limit)
.collect(Collectors.toList());
}
/**
* 记录热门搜索
*/
public void recordSearchKeyword(String keyword) {
// 使用Redis记录搜索热度
String key = "search_hot_keywords";
redisTemplate.opsForZSet().incrementScore(key, keyword, 1);
// 设置过期时间
redisTemplate.expire(key, Duration.ofDays(7));
}
}
数据同步策略
1. 实时同步
@Service
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public class ProductSyncListener {
@Autowired
private ProductSearchService searchService;
public void handleProductCreated(ProductCreatedEvent event) {
Product product = event.getProduct();
searchService.addProduct(product);
}
public void handleProductUpdated(ProductUpdatedEvent event) {
Product product = event.getProduct();
searchService.updateProduct(product);
}
public void handleProductDeleted(ProductDeletedEvent event) {
String productId = event.getProductId();
searchService.deleteProduct(productId);
}
}
2. 批量同步
@Component
public class BatchSyncScheduler {
@Scheduled(fixedRate = 3600000) // 每小时执行一次
public void syncProducts() {
// 分批同步商品数据到搜索索引
int pageSize = 1000;
int currentPage = 0;
while (true) {
Pageable pageable = PageRequest.of(currentPage, pageSize);
Page<Product> productPage = productService.findAll(pageable);
if (productPage.isEmpty()) {
break;
}
searchService.addProducts(productPage.getContent());
currentPage++;
// 避免一次性处理过多数据
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
性能优化
1. 搜索结果缓存
@Service
public class CachedSearchService {
@Cacheable(value = "searchResults", key = "#query + '_' + #page + '_' + #size")
public SearchResult searchWithCache(String query, int page, int size) {
return productSearchService.searchProducts(query,
SearchRequest.builder()
.page(page)
.size(size)
.build());
}
@CacheEvict(value = "searchResults", allEntries = true)
public void evictSearchCache() {
// 当商品数据发生变化时清除缓存
}
}
2. 索引优化配置
@Service
public class IndexOptimizationService {
public void configureIndexSettings() {
Index index = meilisearchClient.index(PRODUCT_INDEX);
// 设置搜索属性
Settings settings = new Settings()
.setSearchableAttributes(Arrays.asList("name", "description", "brand", "category"))
.setDisplayedAttributes(Arrays.asList("id", "name", "price", "image", "brand", "category"))
.setSortableAttributes(Arrays.asList("price", "sales", "rating", "createTime"))
.setTypoTolerance(new TypoTolerance().setEnabled(true))
.setPagination(new Pagination().setMaxTotalHits(10000));
index.updateSettings(settings);
}
}
实际应用效果
通过SpringBoot + Meilisearch的组合,我们可以实现:
- 毫秒级搜索:复杂查询也能在100ms内返回结果
- 智能纠错:自动纠正用户输入错误
- 相关性排序:按销量、评分等因素智能排序
- 多维度筛选:支持价格、品牌、分类等多种筛选条件
注意事项
在使用Meilisearch时,需要注意以下几点:
- 数据一致性:确保数据库和搜索索引的数据同步
- 索引大小:定期清理无用索引,控制存储空间
- 安全配置:生产环境要配置适当的API密钥
- 性能监控:监控搜索响应时间和资源使用情况
总结
通过SpringBoot + Meilisearch的集成,我们可以快速构建一个功能强大、性能优异的商品搜索系统。这种方案不仅开发效率高,而且用户体验好,是电商项目中不可或缺的重要组件。
希望这篇文章对你有所帮助!如果你觉得有用,欢迎关注【服务端技术精选】公众号,获取更多后端技术干货。
原文首发于 www.jiangyi.space
转载请注明出处
标题:SpringBoot + Meilisearch实现商品搜索:从设计到实战的完整攻略
作者:jiangyi
地址:http://jiangyi.space/articles/2026/01/19/1768799761365.html
0 评论