public class EsConstant { //经常用的常量 抽取出来 static是全局的 可以让访问变得很方便,而且不会被修改。一般可以放配置信息,还有一些状态码的定义。 //其他的补充: static修饰的对象
public class EsConstant {
//经常用的常量 抽取出来 static是全局的 可以让访问变得很方便,而且不会被修改。一般可以放配置信息,还有一些状态码的定义。
//其他的补充: static修饰的对象是放在引用的根下的,意味着几乎不会被回收
public static final String PRODUCT_INDEX ="product";//sku在es中索引
}
商品上架 传入 商品id 封装SkuEsModel upProducts
调用远程
searchFeignService.productStatusUp(upProducts);
商品上架
productStatusUp(List<SkuEsModel> skuEsModels)
@Override
public Boolean productStatusUp(List<SkuEsModel> skuEsModels) throws IOException {
//保存到es中
//1.建议es索引 product 建立映射关系
//bulk 批量操作 传入BulkRequest bulkRequest, RequestOptions options
BulkRequest bulkRequest = new BulkRequest();
for (SkuEsModel model : skuEsModels) {
//new 一个索引 保存product
IndexRequest indexRequest = new IndexRequest(EsConstant.PRODUCT_INDEX);
//指定唯一di
indexRequest.id(model.getSkuId().toString());
String s = JSON.toJSONString(model);
indexRequest.source(s, XContentType.JSON);
bulkRequest.add(indexRequest);
}
BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
//todo 感知错误并处理
boolean b = bulk.hasFailures();
List<String> collect = Arrays.stream(bulk.getItems()).map(item -> {
return item.getId();
}).collect(Collectors.toList());
log.info("商品上架wancheng:{},返回数据{}",collect,bulk.toString());
return b;
}
查询检索
/**
* @param param 传入SearchParam
* @param model
* @return 返回SearchReult
*/
@GetMapping({"/", "list.html"})
public String listPage(SearchParam param, Model model, HttpServletRequest request) {
//HttpServletRequest 中有拿到前端传参路径
param.set_queryString(request.getQueryString());
//1. 根据传递来的页面的查询参数,去es中检索商品
SearchResult result =mallSearchService.search(param);
model.addAttribute("result",result);
return "list";
}
@Override
public SearchResult search(SearchParam param) {
//动态构建DSL语句
SearchResult result = null;//返回
//1、准备检索请求 从新创建一个私有方法
SearchRequest searchRequest = buildSearchRequest(param);
try {
//2.执行检索请求
SearchResponse response = client.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
//3. 分析响应数据封装成我们需要的格式
result = buildSearchResult(response, param);
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
/**
* 构建结果数据
* 模糊匹配,过滤(按照属性、分类、品牌,价格区间,库存),完成排序、分页、高亮,聚合分析功能
*/
private SearchResult buildSearchResult(SearchResponse response, SearchParam param) {
SearchResult result = new SearchResult();
SearchHits hits = response.getHits();//获取 命中的记录
ArrayList<SkuEsModel> esModels = new ArrayList<>();
//1、返回的所有查询到的商品
//命中hits的hits 不为空并且有数值
if (hits.getHits() != null && hits.getHits().length > 0) {
//每一条命中的记录
for (SearchHit hit : hits.getHits()) {
//得到命中中的_Source 以字符串形势 转换成SkuEsModell
String sourceAsString = hit.getSourceAsString();
//用阿里巴巴JSON.parseObject 转换成SkuEsModel.class)
SkuEsModel esModel = JSON.parseObject(sourceAsString, SkuEsModel.class);
//判断是否按关键字检索,若是就显示高亮,否则不显示
if (!StringUtils.isEmpty(param.getKeyword())) {
//拿到高亮信息显示标题
HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");
String skuTitleValue = skuTitle.getFragments()[0].string();
esModel.setSkuTitle(skuTitleValue);
}
esModels.add(esModel); //定义ArrayList esModels放入集合中
}
}
result.setProducts(esModels);
//4、当前商品涉及到的所有分类信息
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();
//2、得到属性的名字
ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attr_name_agg");
String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
//3、得到属性的所有值
ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attr_value_agg");
List<String> attrValues =
attrValueAgg.getBuckets().stream().map(item -> item.getKeyAsString()).collect(Collectors.toList());
attrVo.setAttrId(attrId);
attrVo.setAttrName(attrName);
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类型的
long brandId = bucket.getKeyAsNumber().longValue();
brandVo.setBrandId(brandId);
//2、得到品牌的名字
ParsedStringTerms brandNameAgg = (ParsedStringTerms) bucket.getAggregations().get("brand_name_agg");
String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();
//3、得到品牌的图片
ParsedStringTerms brandImgAgg = (ParsedStringTerms) bucket.getAggregations().get("brand_img_agg");
String brandImg = brandImgAgg.getBuckets().get(0).getKeyAsString();
brandVo.setBrandImg(brandImg);
brandVo.setBrandName(brandName);
brandVos.add(brandVo);
}
result.setBrands(brandVos);
//2、当前商品涉及到的所有属性信息 聚合的类型是 0=ParsedLongTerms@8429 直接转换ParsedLongTerms
List<SearchResult.CatalogVo> catalogVos = new ArrayList<>();
ParsedLongTerms catalog_agg = response.getAggregations().get("catalog_agg");//拿到分类聚合信息.get 获取名字
List<? extends Terms.Bucket> buckets = catalog_agg.getBuckets();
//遍历buckets
for (Terms.Bucket bucket : buckets) {
//拿到分类的id 和分类的名字
SearchResult.CatalogVo catalogVo = new SearchResult.CatalogVo();
String keyAsString = bucket.getKeyAsString();
catalogVo.setCatalogId(Long.parseLong(keyAsString));
//得到分类名 子聚合
ParsedStringTerms catalogNameAgg = bucket.getAggregations().get("catalog_name_agg");
String catalog_name = catalogNameAgg .getBuckets().get(0).getKeyAsString();
catalogVo.setCatalogName(catalog_name);
catalogVos.add(catalogVo);
}
result.setCatalogs(catalogVos);
//===============以上可以从聚合信息中获取====================//
//5、分页信息-页码 来源param
result.setPageNum(param.getPageNum());
//总记录数
long total = hits.getTotalHits().value; //获取总记录数的value值
result.setTotal(total);//保存总记录数
//总页码 计算得到 中记录数/ 每页大小;先判断有没有余数 ==0 没有余数
int totalPages = (int) total % EsConstant.PRODUCT_PAGESIEZE == 0 ?
(int) total / EsConstant.PRODUCT_PAGESIEZE : ((int) total / EsConstant.PRODUCT_PAGESIEZE + 1);
result.setTotalPages(totalPages);
//可遍历的页码
List<Integer> pageNavs = new ArrayList<>();
for (int i = 1; i <= totalPages; i++) {
pageNavs.add(i);
}
result.setPageNavs(pageNavs);
//6、构建面包屑导航
if (param.getAttrs() != null && param.getAttrs().size() > 0) {
List<SearchResult.NavVo> collect = param.getAttrs().stream().map(attr -> {
//1、分析每一个attrs传过来的参数值
SearchResult.NavVo navVo = new SearchResult.NavVo();
String[] s = attr.split("_");
navVo.setNavValue(s[1]);
R r = productFeignService.attrInfo(Long.parseLong(s[0]));
if (r.getCode() == 0) {
AttrResponseVo data = r.getData("attr", new TypeReference<AttrResponseVo>() {
});
navVo.setNavName(data.getAttrName());
} else {
navVo.setNavName(s[0]);
}
//2、取消了这个面包屑以后,我们要跳转到哪个地方,将请求的地址url里面的当前置空
//拿到所有的查询条件,去掉当前
String encode = null;
try {
encode = URLEncoder.encode(attr,"UTF-8");
encode = encode.replace("+","%20"); //浏览器对空格的编码和Java不一样,差异化处理
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
String replace = param.get_queryString().replace("&attrs=" + encode, "");
navVo.setLink("http://search.gulimall.com/list.html?" + replace);
return navVo;
}).collect(Collectors.toList());
result.setNavs(collect);
}
return result;
}
private SearchRequest buildSearchRequest(SearchParam param) {
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();//所有检索请求构建DSL语句
/**
* 模糊匹配,过滤(按照属性,分类,品牌,价格区间,库存)
*/
//1.构建bool -must-query
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//1.1 构建 must 判断 must 如果传过来keyword 不为空 构建must模糊匹配
if (!StringUtils.isEmpty(param.getKeyword())) {
//在must中继续构建匹配match 按照名字[]和数值[param 传过来的]进行匹配
boolQuery.must(QueryBuilders.matchQuery("skuTitle", param.getKeyword()));
}
//1.2 构建 bool -must-filter
//1.2.1 bool -must-filter-item 三级分类id
if (param.getCatalog3Id() != null) {
boolQuery.filter(QueryBuilders.termQuery("catalogId", param.getCatalog3Id()));
}
//1.2.2 bool -must-filter-items品牌id brandId id 判断brandId集合不为空 且大小大于0
if (param.getBrandId() != null && param.getBrandId().size() > 0) {
boolQuery.filter(QueryBuilders.termsQuery("brandId", param.getBrandId()));
}
//1.2.3 属性查询 是嵌入式
if (param.getAttrs() != null && param.getAttrs().size() > 0) {
//attr lattrs= 1_5寸:8寸&attrs=2_16G:8G
for (String attrStr : param.getAttrs()) {
BoolQueryBuilder nestedBoolQuery = QueryBuilders.boolQuery();
//先分割属性
String[] s = attrStr.split("_");
String attrId = s[0];//检索书属性id
//在分割属属性里面的多个数值 attrValues 检索属性值
String[] attrValues = s[1].split(":");
nestedBoolQuery.must(QueryBuilders.termQuery("attrs.attrId", attrId));
nestedBoolQuery.must(QueryBuilders.termsQuery("attrs.attrValue", attrValues));
//是嵌入式 用nestedQuery 传入三个值 每一一个必须都得生成一个nested查询
NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", nestedBoolQuery, ScoreMode.None);
boolQuery.filter(nestedQuery);
}
}
//1.2.4 按照是否有库存 如果getHasStock等于1 就是ture
if (null != param.getHasStock()) {
boolQuery.filter(QueryBuilders.termQuery("hasStock", param.getHasStock() == 1));
}
//1.2. 按价格区间
if (!StringUtils.isEmpty(param.getSkuPrice())) {
RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("skuPrice");
String[] s = param.getSkuPrice().split("_");//分割价格
//长度为2 是一个区间
if (s.length == 2 && s[0] != "") {
//大于等于s[0] 第一个 小于等于s[1]第二个
rangeQuery.gte(null).lte(s[1]);
} else if (s.length == 2) {
//大于等于s[0] 第一个 小于等于s[1]第二个
rangeQuery.gte(s[0]).lte(s[1]);
} else if (s.length == 1) {
//如果长度等他1 是有两种情况 _500 和500_
if (param.getSkuPrice().startsWith("_")) {
rangeQuery.lte(s[0]);
}
if (param.getSkuPrice().endsWith("_")) {
rangeQuery.gte(s[0]);
}
}
boolQuery.filter(rangeQuery);
}
//把以前的所有条件都拿来进行封装
sourceBuilder.query(boolQuery);
/**
* 排序,分页,高亮
*/
//2.1 排序
if (!StringUtils.isEmpty(param.getSort())) {
//sort=hostSore_asc/desc
String[] s = param.getSort().split("_");
//构建排序 不区分大小写
SortOrder sortOrder = "asc".equalsIgnoreCase(s[1]) ? SortOrder.ASC : SortOrder.DESC;
sourceBuilder.sort(s[0], sortOrder);
}
//2.2分页
//2.2.分页pageSize:5
// pageNum:1 from:0 size:5 [0,1,2,3,4]
// pageNum:2 from:5 size:5
//from = (pageNum- 1)*size
sourceBuilder.from((param.getPageNum() - 1) * EsConstant.PRODUCT_PAGESIEZE);
sourceBuilder.size(EsConstant.PRODUCT_PAGESIEZE);
//2.3 高亮设置 只有传入参数才计算
if (!StringUtils.isEmpty(param.getKeyword())) {
HighlightBuilder builder = new HighlightBuilder();
builder.field("skuTitle");//设置哪个高亮
builder.preTags("<b style=color:red>");
builder.postTags("</b>");
sourceBuilder.highlighter(builder);
}
/**
* 聚合分析
*/
//品牌聚合
TermsAggregationBuilder brand_agg = AggregationBuilders.terms("brand_agg");
brand_agg.field("brandId").size(50);
//品牌聚合 名字图片子聚合
brand_agg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(1));
brand_agg.subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg").size(1));
sourceBuilder.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));
sourceBuilder.aggregation(catalog_agg);
//2. 按照属性信息进行聚合 嵌入聚合
NestedAggregationBuilder attr_agg = AggregationBuilders.nested("attr_agg", "attrs");
//2.1 按照属性ID进行聚合 子聚合 AggregationBuilders
TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId");
attr_agg.subAggregation(attr_id_agg);
//2.1.1 子聚合的子聚合subAggregation
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));
sourceBuilder.aggregation(attr_agg);
String s = sourceBuilder.toString();
System.out.println("构建DSL" + s);
log.debug("构建的DSL语句 {}", sourceBuilder.toString());
SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX}, sourceBuilder);
return searchRequest;
}
}