电商项目-基于ElasticSearch实现商品搜索功能(一)

本系列文章主要介绍基于 Spring Data Elasticsearch 实现商品搜索的后端代码,介绍代码逻辑和代码实现。
主要实现功能:根据搜索关键字查询、条件筛选、规格过滤、价格区间搜索、搜索查询分页、搜索查询排序、高亮查询。

主要应用技术:canal,Eureka,微服务架构(Microservices Architecture),Spring Data Elasticsearch

一、根据关键字查询商品

(1) shangcheng_service_search项目创建SearchService接口

public interface SearchService {/**
     * 全文检索
     * @param paramMap  查询参数
     * @return
     */
    public Map search(Map<String, String> paramMap) throws Exception;
}

(2) shangcheng_service_search项目创建SearchService接口实现类SearchServiceImpl

@Service
public class SearchServiceImpl implements SearchService {@Autowired
    private ElasticsearchTemplate esTemplate;
​
​
​
    //设置每页查询条数据
    public final static Integer PAGE_SIZE = 20;@Override
    public Map search(Map<String, String> searchMap) throws Exception {
        Map<String, Object> resultMap = new HashMap<>();//有条件才查询Es
        if (null != searchMap) {
            //组合条件对象
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            //0:关键词
            if (!StringUtils.isEmpty(searchMap.get("keywords"))) {
                boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
               
            }//4. 原生搜索实现类
            NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
            nativeSearchQueryBuilder.withQuery(boolQuery);
            
            //10: 执行查询, 返回结果对象
            AggregatedPage<SkuInfo> aggregatedPage = esTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class, new SearchResultMapper() {
                @Override
                public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {List<T> list = new ArrayList<>();SearchHits hits = searchResponse.getHits();
                    if (null != hits) {
                        for (SearchHit hit : hits) {
                            SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
​
                            list.add((T) skuInfo);
                        }
                    }
                    return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations());
                }
            });//11. 总条数
            resultMap.put("total", aggregatedPage.getTotalElements());
            //12. 总页数
            resultMap.put("totalPages", aggregatedPage.getTotalPages());
            //13. 查询结果集合
            resultMap.put("rows", aggregatedPage.getContent());return resultMap;
        }
        return null;
    }}

(3) shangcheng_service_search项目创建SearchController

@RestController
@RequestMapping("/sku_search")
public class SearchController {@Autowired
    private EsManagerService esManagerService;@Autowired
    private SearchService searchService;
    
     //对搜索入参带有特殊符号进行处理
    public void handlerSearchMap(Map<String,String> searchMap){if(null != searchMap){
            Set<Map.Entry<String, String>> entries = searchMap.entrySet();
            for (Map.Entry<String, String> entry : entries) {
                if(entry.getKey().startsWith("spec_")){
                    searchMap.put(entry.getKey(),entry.getValue().replace("+","%2B"));
                }
            }
        }}/**
     * 全文检索
     * @return
     */
    @GetMapping
    public Map search(@RequestParam Map<String, String> paramMap) throws Exception {
        //特殊符号处理
        handlerSearchMap(searchMap);
        Map resultMap = searchService.search(paramMap);
        return resultMap;
    }
}

(4) 测试

使用postmain访问 http://localhost:9009/sku_search?keywords=手机

二、品牌筛选

用户有可能会根据分类搜索、品牌搜索,还有可能根据规格搜索,以及价格搜索和排序操作。根据分类和品牌搜索的时候,可以直接根据指定域搜索,而规格搜索的域数据是不确定的,价格是一个区间搜索,所以我们可以先实现分类、品牌搜素,再实现规格搜索,然后实现价格区间搜索。

在这里插入图片描述

2.1 需求分析

页面每次向后台传入对应的分类和品牌,后台据分类和品牌进行条件过滤。

2.2 代码实现

修改搜索微服务com.shangcheng.service.SearchServiceImpl的搜索方法,添加品牌过滤,代码如下:

代码如下:

@Override
public Map search(Map<String, String> searchMap) throws Exception {
    Map<String, Object> resultMap = new HashMap<>();//有条件才查询Es
    if (null != searchMap) {
        //组合条件对象
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //0:关键词
        if (StringUtils.isNotEmpty(searchMap.get("keywords"))) {
            boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
        }
        //1:条件 品牌
        if (StringUtils.isNotEmpty(searchMap.get("brand"))) {
            boolQuery.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand")));
        }
        
        //4. 原生搜索实现类
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        nativeSearchQueryBuilder.withQuery(boolQuery);//6. 品牌聚合(分组)查询
        String skuBrand = "skuBrand";
nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName"));//10: 执行查询, 返回结果对象
        AggregatedPage<SkuInfo> aggregatedPage = esTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class, new SearchResultMapper() {
            @Override
            public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {List<T> list = new ArrayList<>();SearchHits hits = searchResponse.getHits();
                if (null != hits) {
                    for (SearchHit hit : hits) {
                        SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
​
                        list.add((T) skuInfo);
                    }
                }
                return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations());
            }
        });//11. 总条数
        resultMap.put("total", aggregatedPage.getTotalElements());
        //12. 总页数
        resultMap.put("totalPages", aggregatedPage.getTotalPages());
        //13. 查询结果集合
        resultMap.put("rows", aggregatedPage.getContent());//14. 获取品牌聚合结果
        StringTerms brandTerms = (StringTerms) aggregatedPage.getAggregation(skuBrand);
        List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
        resultMap.put("brandList", brandList);return resultMap;
    }
    return null;
}
 

2.3 测试

测试:

访问地址:http://localhost:9009/sku_search?keywords=手机

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值