当前位置: 首页 > news >正文

谷粒商城:检索服务

目录

检索服务

构建DSL

DSL

代码实现

封装Result

面包屑导航

条件筛选


检索服务

  1. 浏览器向http://search.gulimall.com/请求检索页list.html,
  2. search服务构建DSL语句,从elasticSearch中获取结果
  3. 将检索结果封装好,装入model,页面由themleaf渲染。
    @RequestMapping("/list.html")public String listPage(SearchParam searchParam, Model model){SearchResult searchResult = mallSearchSearchService.search(searchParam);model.addAttribute("result",searchResult);return "list";}

构建DSL

DSL

依据请求参数,构建DSL检索语句

keyword=Huawei&brandId=1&catalog3Id=225&attrs=2_麒麟9000&attrs=5_1080p

  1. keyword匹配skuTitle标题,命中的词条高亮显示highLight
  2. 属性attrs为复杂对象数组,需标明为nested类型
  3. 聚合分析命中结果中 所有品牌,分类,属性。先依据id聚合,再子聚合获取name与value
  4. 分页与排序
GET /gulimall_product/_search
{"from": 0,"size": 4,"query": {"bool": {"must": [{"match": {"skuTitle":"Huawei"}}],"filter": [{"term": {"catalogId": 225}},{"term": {"brandId": 1}},{"nested": {"path": "attrs","query": {"bool": {"must": [{"term": {"attrs.attrId": {"value": "5"}}},{"terms": {"attrs.attrValue": ["1080p"]}}]}}}},{"nested": {"path": "attrs","query": {"bool": {"must": [{"term": {"attrs.attrId": {"value": "2"}}},{"terms": {"attrs.attrValue": ["麒麟9000"]}}]}}}}]}},"aggregations": {"brand_agg": {"terms": {"field": "brandId"},"aggregations": {"brand_name_agg": {"terms": {"field": "brandName"}},"brand_img_agg": {"terms": {"field": "brandImg"}}}},"catalog_agg": {"terms": {"field": "catalogId"},"aggregations": {"catalog_name_agg": {"terms": {"field": "catalogName"}}}},"attr_agg": {"nested": {"path": "attrs"},"aggregations": {"attr_id_agg": {"terms": {"field": "attrs.attrId"},"aggregations": {"attr_name_agg": {"terms": {"field": "attrs.attrName"}},"attr_value_agg": {"terms": {"field": "attrs.attrValue"}}}}}}},"highlight": {"pre_tags": ["<b style='color:red'>"],"post_tags": ["</b>"],"fields": {"skuTitle": {}}}
}

代码实现

            /*** 构建DSL语句* 模糊匹配,过滤(按照属性,分类,品牌,价格区间,库存),排序,分页,高亮,聚合分析* @param searchParam* @return*/private SearchRequest buildSearchRequest(SearchParam searchParam) {SearchSourceBuilder searchSourceBuilder =new SearchSourceBuilder();BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();/*** 模糊匹配*/if(!StringUtils.isEmpty(searchParam.getKeyword())){boolQueryBuilder.must(QueryBuilders.matchQuery("skuTitle",searchParam.getKeyword()));}if(searchParam.getCatalog3Id()!=null){boolQueryBuilder.filter(QueryBuilders.termQuery("catalogId",searchParam.getCatalog3Id()));}if(searchParam.getBrandId()!=null&& !searchParam.getBrandId().isEmpty()){boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId",searchParam.getBrandId()));}if(searchParam.getHasStock()!=null){boolQueryBuilder.filter(QueryBuilders.termQuery("hasStock",searchParam.getHasStock()==1));}/*** 价格区间*/if(!StringUtils.isEmpty(searchParam.getSkuPrice())){RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("skuPrice");String[] price = searchParam.getSkuPrice().trim().split("_");if(price.length==2){//这里 skuPrice=_1000 会传 空字符串rangeQueryBuilder.gte(StringUtils.isEmpty(price[0])? null:price[0]).lte(price[1]);}else if(price.length == 1){if(searchParam.getSkuPrice().startsWith("_")){rangeQueryBuilder.lte(price[0]);}if(searchParam.getSkuPrice().endsWith("_")){rangeQueryBuilder.gte(price[0]);}}boolQueryBuilder.filter(rangeQueryBuilder);}/*** 属性* 使用nested嵌套查询*/if(searchParam.getAttrs() != null && !searchParam.getAttrs().isEmpty()){searchParam.getAttrs().forEach(item -> {BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();//attrs=1_5寸:8寸&2_16G:8GString[] s = item.split("_");String attrId=s[0];String[] attrValues = s[1].split(":");//这个属性检索用的值boolQuery.must(QueryBuilders.termQuery("attrs.attrId",attrId));boolQuery.must(QueryBuilders.termsQuery("attrs.attrValue",attrValues));NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery("attrs",boolQuery, ScoreMode.None);boolQueryBuilder.filter(nestedQueryBuilder);});}searchSourceBuilder.query(boolQueryBuilder);/*** 排序,分页,高亮*///排序 形式为sort=hotScore_asc/descif(!StringUtils.isEmpty(searchParam.getSort())){String sort = searchParam.getSort();String[] sortFields = sort.split("_");SortOrder sortOrder="asc".equalsIgnoreCase(sortFields[1])? SortOrder.ASC:SortOrder.DESC;searchSourceBuilder.sort(sortFields[0],sortOrder);}//分页searchSourceBuilder.from((searchParam.getPageNum()-1) *EsConstant.PRODUCT_PAGESIZE);searchSourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);//高亮if(!StringUtils.isEmpty(searchParam.getKeyword())){HighlightBuilder highlightBuilder = new HighlightBuilder();highlightBuilder.field("skuTitle");highlightBuilder.preTags("<b style='color:red'>");highlightBuilder.postTags("</b>");searchSourceBuilder.highlighter(highlightBuilder);}/*** 聚合分析*///1. 按照品牌进行聚合TermsAggregationBuilder brand_agg = AggregationBuilders.terms("brand_agg");brand_agg.field("brandId").size(50);//1.1 品牌的子聚合-品牌名聚合brand_agg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(1));//1.2 品牌的子聚合-品牌图片聚合brand_agg.subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg").size(1));searchSourceBuilder.aggregation(brand_agg);//2. 按照分类信息进行聚合TermsAggregationBuilder catalog_agg = AggregationBuilders.terms("catalog_agg");catalog_agg.field("catalogId").size(20);catalog_agg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(1));searchSourceBuilder.aggregation(catalog_agg);//2. 按照属性信息进行聚合NestedAggregationBuilder attr_agg = AggregationBuilders.nested("attr_agg", "attrs");//2.1 按照属性ID进行聚合TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId");attr_agg.subAggregation(attr_id_agg);//2.1.1 在每个属性ID下,按照属性名进行聚合attr_id_agg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(1));//2.1.1 在每个属性ID下,按照属性值进行聚合attr_id_agg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(50));searchSourceBuilder.aggregation(attr_agg);System.out.println("DSL:");System.out.println(searchSourceBuilder.toString());return new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX},searchSourceBuilder);}

        封装Result

        将elasticsearch返回的结果封装成Result

            /*** 构建响应数据* 1.商品.标题高亮* 2.品牌,分类,页码* @param searchResponse* @return*/private SearchResult buildSearchResult(SearchResponse searchResponse,SearchParam searchParam) {SearchResult result = new SearchResult();/*** 1.将source转为sku* 2.替换标题,高亮* 3.将sku商品集合装入result*/SearchHits hits = searchResponse.getHits();List<SkuEsModel> products =  new ArrayList<>();for (SearchHit hit:hits.getHits()) {SkuEsModel skuEsModel = JSON.parseObject(hit.getSourceAsString(), SkuEsModel.class);if(!StringUtils.isEmpty(searchParam.getKeyword())){String skuTitle = hit.getHighlightFields().get("skuTitle").getFragments()[0].string();skuEsModel.setSkuTitle(skuTitle);}products.add(skuEsModel);}result.setProduct(products);/*** 页码*/result.setTotal(hits.getTotalHits().value);result.setTotalPages((int)hits.getTotalHits().value%EsConstant.PRODUCT_PAGESIZE == 0?(int)hits.getTotalHits().value/EsConstant.PRODUCT_PAGESIZE:(int)hits.getTotalHits().value/EsConstant.PRODUCT_PAGESIZE+1);result.setPageNum(searchParam.getPageNum());List<Integer> pageNavs = new ArrayList<>();for (int i = 1; i <= result.getTotalPages(); i++) {pageNavs.add(i);}result.setPageNavs(pageNavs);Aggregations aggregations = searchResponse.getAggregations();/*** 品牌*/HashMap<Long, SearchResult.BrandVo> brandVoHashMap = new HashMap<>();Terms brand_agg =  aggregations.get("brand_agg");List<SearchResult.BrandVo> brands =new ArrayList<>();for(Terms.Bucket bucket:brand_agg.getBuckets()){SearchResult.BrandVo brand = new SearchResult.BrandVo();brand.setBrandId(bucket.getKeyAsNumber().longValue());//品牌名Terms brand_name_agg = bucket.getAggregations().get("brand_name_agg");brand.setBrandName(brand_name_agg.getBuckets().get(0).getKeyAsString());//品牌imgTerms brand_img_agg = bucket.getAggregations().get("brand_img_agg");brand.setBrandImg(brand_img_agg.getBuckets().get(0).getKeyAsString());brands.add(brand);brandVoHashMap.put(brand.getBrandId(),brand);}result.setBrands(brands);/*** 分类*/Terms catalog_agg =  aggregations.get("catalog_agg");List<SearchResult.CatalogVo> catalogs =new ArrayList<>();for(Terms.Bucket bucket:catalog_agg.getBuckets()){SearchResult.CatalogVo catalog = new SearchResult.CatalogVo();catalog.setCatalogId(bucket.getKeyAsNumber().longValue());//分类名Terms catalog_name_agg = bucket.getAggregations().get("catalog_name_agg");catalog.setCatalogName(catalog_name_agg.getBuckets().get(0).getKeyAsString());catalogs.add(catalog);}result.setCatalogs(catalogs);/*** 当前商品涉及到的所有属性信息*/List<SearchResult.AttrVo> attrVos = new ArrayList<>();HashMap<Long,SearchResult.AttrVo> attrVoHashMap = new HashMap<>();//获取属性信息的聚合Nested attr_agg = aggregations.get("attr_agg");Terms attr_id_agg = attr_agg.getAggregations().get("attr_id_agg");for (Terms.Bucket bucket : attr_id_agg.getBuckets()) {SearchResult.AttrVo attrVo = new SearchResult.AttrVo();//1、得到属性的idlong attrId = bucket.getKeyAsNumber().longValue();attrVo.setAttrId(attrId);//2、得到属性的名字Terms attr_name_agg = bucket.getAggregations().get("attr_name_agg");String attrName = attr_name_agg.getBuckets().get(0).getKeyAsString();attrVo.setAttrName(attrName);//3、得到属性的所有值Terms attr_value_agg = bucket.getAggregations().get("attr_value_agg");List<String> attrValues = attr_value_agg.getBuckets().stream().map(item -> item.getKeyAsString()).collect(Collectors.toList());attrVo.setAttrValue(attrValues);attrVos.add(attrVo);/*这里用hashMap存一下属性,就不用远程调用了,太麻烦了*/attrVoHashMap.put(attrId,attrVo);}result.setAttrs(attrVos);//6、构建面包屑导航if (searchParam.getAttrs() != null && !searchParam.getAttrs().isEmpty()) {List<SearchResult.NavVo> collect = searchParam.getAttrs().stream().map(attr -> {//1、分析每一个attrs传过来的参数值SearchResult.NavVo navVo = new SearchResult.NavVo();String[] s = attr.split("_");navVo.setNavValue(s[1]);navVo.setNavId(Long.parseLong(s[0]));navVo.setParamName("attrs");String navName;if (!StringUtils.isEmpty(navName=attrVoHashMap.get(Long.parseLong(s[0])).getAttrName())) {navVo.setNavName(navName);} else {navVo.setNavName(s[0]);}return navVo;}).collect(Collectors.toList());result.setNavs(collect);}if(searchParam.getBrandId()!=null && !searchParam.getBrandId().isEmpty()){List<SearchResult.NavVo> navs = result.getNavs();for (Long brandId:searchParam.getBrandId()) {SearchResult.NavVo navVo = new SearchResult.NavVo();navVo.setParamName("brandId");navVo.setNavId(brandId);navVo.setNavName("品牌");String navValue;if (!StringUtils.isEmpty(navValue=brandVoHashMap.get(brandId).getBrandName())) {navVo.setNavValue(navValue);} else {navVo.setNavValue(String.valueOf(brandId));}navs.add(0,navVo);}}System.out.println("searchResult:");System.out.println(result);return result;}

        面包屑导航

        <!-- 遍历面包屑功能 -->
        <a href="/static/search/#"th:href="${'javascript:removeNav(&quot;'+nav.paramName+'&quot;,&quot;'+nav.navId+'&quot;,'+'&quot;'+nav.navValue+'&quot;)'}"th:each="nav:${result.navs}"><span th:text="${nav.navName}"></span>:<span th:text="${nav.navValue}"></span> x</a>

        result中封装了navs,前端依据navs来渲染,可以直观地看到筛选条件。

        (不封装navs,前端通过解析url中的param,应该也能实现)

        为了封装navs,在聚合分析时,顺便将聚合结果存储在hashMap中,以便取值,避免调用远程服务

        移除一个筛选条件时,将url中对应的param移除即可

        url中带有中文时,浏览器url会解码中文。因此 进行正则匹配时,也要解码paramVal

            function removeNav(paramName,navId,navValue){if(paramName==="attrs"){location.href = removeParamVal(paramName,navId+"_"+navValue);}else{location.href = removeParamVal(paramName,navId);}}function removeParamVal(paramName,paramVal) {var oUrl = location.href.toString();//处理url编码中文const encodedVal = encodeURIComponent(paramVal);var re = eval('/&?'+'('+ paramName +'='+ encodedVal+ ')/gi');var nUrl = oUrl.replace(re,"");console.log(nUrl);return nUrl?nUrl:oUrl;}

        条件筛选

        将选中的条件 进行拼串或替换

            function searchByKeyword() {//搜索时,清除拼串location.href = "http://search.gulimall.com/list.html?" + "keyword=" + $("#keyword_input").val();}function searchProducts(name, value,forceAdd) {//原來的页面location.href = replaceParamVal(location.href,name,value,forceAdd)}function replaceParamVal(url, paramName, replaceVal,forceAdd) {var oUrl = url.toString();var nUrl;if (oUrl.indexOf(paramName) != -1) {if( forceAdd ) {if (oUrl.indexOf("?") != -1) {nUrl = oUrl + "&" + paramName + "=" + replaceVal;} else {nUrl = oUrl + "?" + paramName + "=" + replaceVal;}} else {var re = eval('/(' + paramName + '=)([^&]*)/gi');nUrl = oUrl.replace(re, paramName + '=' + replaceVal);}} else {if (oUrl.indexOf("?") != -1) {nUrl = oUrl + "&" + paramName + "=" + replaceVal;} else {nUrl = oUrl + "?" + paramName + "=" + replaceVal;}}return nUrl;}

        其余内容,不做赘述

        http://www.dtcms.com/a/321499.html

        相关文章:

      1. WSL 安装 Ubuntu
      2. 50系显卡ubuntu20.04安装显卡驱动,解决gazebo不调用显卡的问题
      3. 接口自动化-YAML
      4. 【其他分类】Showrunner AI版的Netflix 互动故事创作平台 进行动画生成与微调、角色场景创建
      5. A100用transformers推理gpt-oss
      6. 【无标题】无名管道
      7. (第二篇)spring cloud之Eureka注册中心
      8. JDK、eclipse的安装,配置JDK、Tomcat并使用eclipse创建项目
      9. SpringBoot 处理 RESTful 服务中的异常与错误
      10. 我和 ChatGPT:一次用 AI 反观自己的技术成长之旅
      11. Android 中解决 Button 按钮背景色设置无效的问题
      12. Redis 7主从复制与哨兵模式搭建
      13. k8s-nfs实现创建sc的两种方式
      14. ConcurrentDictionary 详解:.NET 中的线程安全字典
      15. 并发编程(五)ThreadLocal
      16. 生产环境Tomcat运行一段时间后,如何测试其性能是否满足后续使用
      17. Rust语言序列化和反序列化vec<u8>,serde库Serialize, Deserialize,bincode库(2025年最新解决方案详细使用)
      18. AI 智能体框架:LlamaIndex
      19. 国内如何使用体验到GPT-5呢?附GPT快速升级Plus计划保姆级教程
      20. 大模型量化上溢及下溢解析
      21. 达梦DMFLDR导出和导入的方法
      22. 以任务为中心的智能推荐系统架构设计:原理、实现与挑战分析
      23. 深入理解Java集合框架:核心接口、实现类与实战选择
      24. Vue2中,Promise.all()调用多个接口的用法
      25. Numpy科学计算与数据分析:Numpy文件操作入门之数组数据的读取和保存
      26. 智慧社区(十)——声明式日志记录与小区地图功能实现
      27. 解决MinIO上传图片后返回URL无法访问的问题
      28. Linux 启动流程实战:Device Tree 全解析与驱动绑定机制
      29. 【LLM实战】RAG高级
      30. 从0到1开发剧本杀小程序:全流程指南与避坑指南