1.ES 数据迁移
为什么要做数据迁移呢?
因为barandName的doc_value设置为false,因属性中设置doc_values 为false导致keyword类型不支持,导致agg聚合语句失败
-
新的mapping
//PUT gulimall_product { "mappings": { "properties": { "skuId": { "type": "long" }, "spuId": { "type": "long" }, "skuTitle": { "type": "text", "analyzer": "ik_smart" }, "skuPrice": { "type": "keyword" }, "skuImg": { "type": "keyword" }, "saleCount": { "type": "long" }, "hosStock": { "type": "boolean" }, "hotScore": { "type": "long" }, "brandId": { "type": "long" }, "catelogId": { "type": "long" }, "brandName": { "type": "keyword" }, "brandImg": { "type": "keyword" }, "catalogName": { "type": "keyword" }, "attrs": { "type": "nested", "properties": { "attrId": { "type": "long" }, "attrName": { "type": "keyword" }, "attrValue": { "type": "keyword" } } } } } }
-
dsl数据迁移数据
POST _reindex # 源index { "source": { "index": "product" }, # 目标index "dest": { "index": "dreammall_product" } }
2.业务实现搜索功能
-
前端页面可以通过分类或者标题进行es搜索
-
根据分类搜索结果
-
面包屑导航功能实现
-
后端代码实现
// service @Override public SearchResult search(SearchParam param) { SearchResult result = new SearchResult(); //1、准备检索请求 SearchRequest searchRequest = buildSearchRequest(param); try { SearchResponse response = esRestClient.search(searchRequest, RequestOptions.DEFAULT); result = buildSearchResponse(response, param); } catch (IOException e) { log.error("查询es失败"); } return result; }
// 检索条件代码 private SearchRequest buildSearchRequest(SearchParam param) { SearchSourceBuilder builder = SearchSourceBuilder.searchSource(); // 布尔查询 BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); // skuTitle if (StrUtil.isNotBlank(param.getKeyword())) { boolQuery.must(QueryBuilders.matchQuery("skuTitle", param.getKeyword())); } // catalogId term if (ObjectUtil.isNotNull(param.getCatalog3Id())) { boolQuery.filter(QueryBuilders.termQuery("catalogId", param.getCatalog3Id())); } // brandId terms if (CollUtil.isNotEmpty(param.getBrandId())) { boolQuery.filter(QueryBuilders.termsQuery("brandId", param.getBrandId())); } // attrs attrs=1_5寸:8寸&2_16G:8G // nested 嵌入查询 attrId term attrValue terms if (CollUtil.isNotEmpty(param.getAttrs())) { // 先获取id for (String attr : param.getAttrs()) { String[] attrStr = attr.split("_"); // attrId term BoolQueryBuilder query = QueryBuilders.boolQuery(); query.must(QueryBuilders.termQuery("attrs.attrId", attrStr[0])); // value String[] attValue = attrStr[1].split(":"); query.must(QueryBuilders.termsQuery("attrs.attrValue", attValue)); NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", query, ScoreMode.None); boolQuery.filter(nestedQuery); } } // hasStock term if (ObjectUtil.isNotNull(param.getHasStock())) { boolQuery.filter(QueryBuilders.termQuery("hasStock", param.getHasStock() == 1)); } // skuPrice range if (StrUtil.isNotBlank(param.getSkuPrice())) { String[] price = param.getSkuPrice().split("_"); // 0_100 _500 500_ 价格范围索引的三种格式 if (param.getSkuPrice().startsWith("_")) { boolQuery.filter(QueryBuilders.rangeQuery("skuPrice").lte(price[1])); } else if (param.getSkuPrice().endsWith("_")) { boolQuery.filter(QueryBuilders.rangeQuery("skuPrice").gte(price[0])); } else { boolQuery.filter(QueryBuilders.rangeQuery("skuPrice").gte(price[0]).lte(price[1])); } } builder.query(boolQuery); // 排序 sort=hotscore_desc/asc if (StrUtil.isNotBlank(param.getSort())) { String[] sortValue = param.getSort().split("_"); builder.sort(sortValue[0], sortValue[1].equals("desc") ? SortOrder.DESC : SortOrder.ASC); } // 分页 int page = param.getPageNum() - 1; page = page * EsConstant.PRODUCT_PAGE_SIZE; builder.from(page); builder.size(EsConstant.PRODUCT_PAGE_SIZE); // 高亮 builder.highlighter(new HighlightBuilder().field("skuTitle").preTags("<b style='color:red'>").postTags("</b>")); // 聚合操作 // brand_agg TermsAggregationBuilder brandAgg = AggregationBuilders.terms("brand_agg").field("brandId").size(50); // brand_img aggs brandAgg.subAggregation(new TermsAggregationBuilder("brand_img_agg").field("brandImg").size(1)); brandAgg.subAggregation(new TermsAggregationBuilder("brand_name_agg").field("brandName").size(1)); builder.aggregation(brandAgg); // catalog_agg TermsAggregationBuilder catalogAgg = AggregationBuilders.terms("catalog_agg").field("catalogId").size(50); catalogAgg.subAggregation(new TermsAggregationBuilder("catalog_name_agg").field("catalogName").size(50)); builder.aggregation(catalogAgg); // attrs NestedAggregationBuilder nested = AggregationBuilders.nested("attr_agg", "attrs"); // attr_id_agg TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId").size(50); nested.subAggregation(attrIdAgg); // attr_name_agg attrIdAgg.subAggregation(new TermsAggregationBuilder("attr_name_agg").field("attrs.attrName").size(50)); // attr_value_agg attrIdAgg.subAggregation(new TermsAggregationBuilder("attr_value_agg").field("attrs.attrValue").size(50)); builder.aggregation(nested); System.out.println(builder); return new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX}, builder); }
// 封装返回结果 private SearchResult buildSearchResponse(SearchResponse response, SearchParam param) { SearchResult result = new SearchResult(); //1、返回的所有查询到的商品 SearchHits hits = response.getHits(); List<SkuEsModel> esModels = new ArrayList<>(); //遍历所有商品信息 if (hits.getHits() != null && hits.getHits().length > 0) { for (SearchHit hit : hits.getHits()) { String sourceAsString = hit.getSourceAsString(); SkuEsModel esModel = JSON.parseObject(sourceAsString, SkuEsModel.class); //判断是否按关键字检索,若是就显示高亮,否则不显示 if (StrUtil.isNotBlank(param.getKeyword())) { //拿到高亮信息显示标题 HighlightField skuTitle = hit.getHighlightFields().get("skuTitle"); String skuTitleValue = skuTitle.getFragments()[0].string(); esModel.setSkuTitle(skuTitleValue); } esModels.add(esModel); } } result.setProduct(esModels); //2、当前商品涉及到的所有属性信息 List<SearchResult.AttrVo> attrVos = new ArrayList<>(); //获取属性信息的聚合 ParsedNested attrsAgg = response.getAggregations().get("attr_agg"); ParsedLongTerms attrIdAgg = attrsAgg.getAggregations().get("attr_id_agg"); for (Terms.Bucket bucket : attrIdAgg.getBuckets()) { SearchResult.AttrVo attrVo = new SearchResult.AttrVo(); //1、得到属性的id long attrId = bucket.getKeyAsNumber().longValue(); attrVo.setAttrId(attrId); //2、得到属性的名字 ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attr_name_agg"); String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString(); attrVo.setAttrName(attrName); //3、得到属性的所有值 ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attr_value_agg"); List<String> attrValues = attrValueAgg.getBuckets().stream().map(item -> item.getKeyAsString()).collect(Collectors.toList()); attrVo.setAttrValue(attrValues); attrVos.add(attrVo); } result.setAttrs(attrVos); //3、当前商品涉及到的所有品牌信息 List<SearchResult.BrandVo> brandVos = new ArrayList<>(); //获取到品牌的聚合 ParsedLongTerms brandAgg = response.getAggregations().get("brand_agg"); for (Terms.Bucket bucket : brandAgg.getBuckets()) { SearchResult.BrandVo brandVo = new SearchResult.BrandVo(); //1、得到品牌的id long brandId = bucket.getKeyAsNumber().longValue(); brandVo.setBrandId(brandId); //2、得到品牌的名字 ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brand_name_agg"); String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString(); brandVo.setBrandName(brandName); //3、得到品牌的图片 ParsedStringTerms brandImgAgg = bucket.getAggregations().get("brand_img_agg"); String brandImg = brandImgAgg.getBuckets().get(0).getKeyAsString(); brandVo.setBrandImg(brandImg); brandVos.add(brandVo); } result.setBrands(brandVos); //4、当前商品涉及到的所有分类信息 //获取到分类的聚合 List<SearchResult.CatalogVo> catalogVos = new ArrayList<>(); ParsedLongTerms catalogAgg = response.getAggregations().get("catalog_agg"); for (Terms.Bucket bucket : catalogAgg.getBuckets()) { SearchResult.CatalogVo catalogVo = new SearchResult.CatalogVo(); //得到分类id String keyAsString = bucket.getKeyAsString(); catalogVo.setCatalogId(Long.parseLong(keyAsString)); //得到分类名 ParsedStringTerms catalogNameAgg = bucket.getAggregations().get("catalog_name_agg"); String catalogName = catalogNameAgg.getBuckets().get(0).getKeyAsString(); catalogVo.setCatalogName(catalogName); catalogVos.add(catalogVo); } result.setCatalogs(catalogVos); //===============以上可以从聚合信息中获取====================// //5、分页信息-页码 result.setPageNum(param.getPageNum()); //5、1分页信息、总记录数 long total = hits.getTotalHits().value; result.setTotal(total); //5、2分页信息-总页码-计算 int totalPages = (int) total % EsConstant.PRODUCT_PAGE_SIZE == 0 ? (int) total / EsConstant.PRODUCT_PAGE_SIZE : ((int) total / EsConstant.PRODUCT_PAGE_SIZE + 1); result.setTotalPages(totalPages); List<Integer> pageNavs = new ArrayList<>(); for (int i = 1; i <= totalPages; i++) { pageNavs.add(i); } result.setPageNavs(pageNavs); // attrs=1_5寸:8寸&2_16G:8G Map<Long, String> map = result.getAttrs().stream().collect(Collectors.toMap(SearchResult.AttrVo::getAttrId, SearchResult.AttrVo::getAttrName)); // 构建面包屑导航功能 if (CollUtil.isNotEmpty(param.getAttrs())) { List<SearchResult.NavVO> navVOList = param.getAttrs().stream().map(att -> { SearchResult.NavVO navVO = new SearchResult.NavVO(); String[] split = att.split("_"); navVO.setNavValue(split[1]); navVO.setNavName(map.get(Long.parseLong(split[0]))); result.getAttrIds().add(Long.parseLong(split[0])); String replace = replaceQueryString(param, att, "attrs"); navVO.setLink("http://search.dreammall.com/list.html?" + replace); return navVO; }).collect(Collectors.toList()); result.setNavs(navVOList); } Map<Long, String> brandMap = result.getBrands().stream().collect(Collectors.toMap(SearchResult.BrandVo::getBrandId, SearchResult.BrandVo::getBrandName)); // 品牌面包屑功能 if (CollUtil.isNotEmpty(param.getBrandId())) { List<SearchResult.NavVO> navs = result.getNavs(); SearchResult.NavVO navVO = new SearchResult.NavVO(); navVO.setNavName("品牌"); StringBuffer buffer = new StringBuffer(); String replace = ""; for (Long brandId : param.getBrandId()) { String value = brandMap.get(brandId); replace = replaceQueryString(param, brandId.toString(), "brandId"); buffer.append(value + ";"); } navVO.setNavValue(buffer.toString()); navVO.setLink("http://search.dreammall.com/list.html?" + replace); navs.add(navVO); } return result; } private String replaceQueryString(SearchParam param, String value, String key) { // att 编码utf-8 String encode = null; try { encode = URLEncoder.encode(value, "UTF-8"); encode = encode.replace("+", "%20"); //浏览器对空格的编码和Java不一样,差异化处理 } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return param.getQueryUrlString().replace("&" + key + "=" + encode, ""); }