SpringBoot + Meilisearch实现商品搜索:从设计到实战的完整攻略

传统搜索的痛点

在我们的日常开发工作中,经常会遇到这样的场景:

  • 用户搜索"iPhone 15",结果却是各种苹果汁和苹果派
  • 搜索响应时间超过3秒,用户早就流失了
  • 没有智能纠错功能,错别字导致搜索无结果
  • 无法处理同义词,"手机"和"mobile"是两个概念

传统的数据库LIKE查询不仅性能差,用户体验也糟糕。今天我们就用Meilisearch来解决这些问题。

为什么选择Meilisearch

相比Elasticsearch,Meilisearch有以下优势:

  • 开箱即用:无需复杂配置,安装即可使用
  • 中文支持好:默认支持中文分词
  • 性能优异:查询速度快,资源消耗少
  • 易用性强:API简单,学习成本低

解决方案思路

今天我们要解决的,就是如何用SpringBoot + Meilisearch构建一个高效的商品搜索系统。

核心思路是:

  1. 实时索引:商品数据变更时同步更新搜索索引
  2. 智能搜索:支持模糊匹配、同义词、拼写纠错
  3. 个性化排序:根据销量、评分等因素排序
  4. 性能优化:缓存热门搜索,提升响应速度

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时,需要注意以下几点:

  1. 数据一致性:确保数据库和搜索索引的数据同步
  2. 索引大小:定期清理无用索引,控制存储空间
  3. 安全配置:生产环境要配置适当的API密钥
  4. 性能监控:监控搜索响应时间和资源使用情况

总结

通过SpringBoot + Meilisearch的集成,我们可以快速构建一个功能强大、性能优异的商品搜索系统。这种方案不仅开发效率高,而且用户体验好,是电商项目中不可或缺的重要组件。

希望这篇文章对你有所帮助!如果你觉得有用,欢迎关注【服务端技术精选】公众号,获取更多后端技术干货。


原文首发于 www.jiangyi.space

转载请注明出处


标题:SpringBoot + Meilisearch实现商品搜索:从设计到实战的完整攻略
作者:jiangyi
地址:http://www.jiangyi.space/articles/2026/01/19/1768799761365.html

    0 评论
avatar