前言
在项目中涉及到商品搜索的模块,通过学习ES的知识,便尝试着将ES应用到了项目当中,这篇博客主要叙述我在项目中是如何使用ES的。由于我们一起买的阿里云服务器已经到期了,这里就不演示了。以后工作一定要买台好的服务器
如果大家也想安装一下ES和Kibana,可以按照我之前的博客来搭建
传送门<----点这
ES中数据的模型
想要从ES中查数据,首先需要把数据存入到ES当中,当时和同学商量着直接在Kibana中批量导入这些数据,但是想想,这样太麻烦了,后来想到同学写的后台管理有商品上架的功能,然后就从这里入手了。
模型的设计
来看下我们设计的模型:
@Data
public class SkuEsModel {
//具体商品的id
private Long skuId;
//某类商品的id
private Long spuId;
//销售商品的标题
private String skuTitle;
//销售商品的价格
private BigDecimal skuPrice;
//销售商品的图片
private String skuImg;
//商品的销量
private Long saleCount;
//商品是否有库存
private Boolean hasStock;
//商品的评分
private Long hotScore;
//商品所属品牌的id
private Long brandId;
//商品所属分类的id
private Long catalogId;
//商品所属品牌的名字
private String brandName;
//商品所属品牌的图片
private String brandImg;
//商品所属分类的名字
private String catalogName;
//商品所对应的属性
private List<Attrs> attrs;
@Data
public static class Attrs {
//属性id
private Long attrId;
//属性的名字
private String attrName;
//属性值
private String attrValue;
}
}
为什么这样设计
首先先聊聊商城都有什么业务用到了es:
- 通过商城首页的三级列表的三级目录进入到搜索页面
- 通过商城首页的搜索框输入要搜索商品的名字,通过查es返回数据
在三级目录点击进入的话会传一个分类id的参数,所以模型中应该有分类id提供搜索,然后就是通过搜索框输入的话,会传一个String的参数,它去匹配sku商品的标题。
那么通过这些搜索,我在详情页需要提供给前端什么数据去渲染呢?
以上截图来源京东,我的同学根据京东的页面模仿出部分功能,看完图中的标注,就应该会明白为什么模型会那么设计了。但是,还有一些其他的字段,那是涉及到商品详情页面的字段了。我们先说一说这个搜索页面吧。
搜索页面,中间这些属性、品牌是需要根据不同的搜索给出不同的答案的,所以,通过es中的聚合操作来实现的。
现在就是还有一个讨论的话题:是如何通过聚合得到这些信息的呢?
首先,后天管理员点击上架,会有对应的skuId,以及所属spu的信息,在上架的时候,我们会去数据库中查,将这些信息查到并且封装到SkuModel里,通过远程调用搜索服务,将这些数据存到es中。
如何存进ES的
接下来就看下es的索引映射的设计:
PUT product
{
"mappings": {
"properties": {
"skuId": {
"type": "long"
},
"spuId": {
"type": "long"
},
"skuTitle": {
"type": "text",//支持全文搜索的
"analyzer": "ik_smart"//采用ik分词器
},
"skuPrice": {
"type": "keyword"//必须输入精确值才能搜索
},
"skuImg": {
"type": "keyword",//必须输入精确值才能搜索
"index": false,//不能被索引
"doc_values": false
},
"saleCount": {
"type": "long"
},
"hosStock": {
"type": "boolean"
},
"hotScore": {
"type": "long"
},
"brandId": {
"type": "long"
},
"catelogId": {
"type": "long"
},
"brandName": {
"type": "keyword",
"index": false,
"doc_values": false
},
"brandImg": {
"type": "keyword",
"index": false,
"doc_values": false
},
"catelogName": {
"type": "keyword",
"index": false,
"doc_values": false
},
"attrs": {
"type": "nested",//es的嵌套模型
"properties": {
"attrId": {
"type": "long"
},
"attrName": {
"type": "keyword",
"index": false,
"doc_values": false
},
"attrValue": {
"type": "keyword"
}
}
}
}
}
}
通过java操作ES的api:RestHighLevelClient来操作es的增删改查,接下来看看是如何存进去的
@Override
public boolean productStatusUp(List<SkuEsModel> skuEsModels) throws IOException {
//1.在es中建立索引,建立号映射关系
//2. 在ES中保存这些数据
BulkRequest bulkRequest = new BulkRequest();
for (SkuEsModel skuEsModel : skuEsModels) {
//构造保存请求
IndexRequest indexRequest = new IndexRequest(EsConstant.PRODUCT_INDEX);
indexRequest.id(skuEsModel.getSkuId().toString());
String jsonString = JSON.toJSONString(skuEsModel);
indexRequest.source(jsonString, XContentType.JSON);
bulkRequest.add(indexRequest);
}
BulkResponse bulk = esRestClient.bulk(bulkRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
//TODO 如果批量错误
boolean hasFailures = bulk.hasFailures();
List<String> collect = Arrays.asList(bulk.getItems()).stream().map(item -> {
return item.getId();
}).collect(Collectors.toList());
log.info("商品上架完成:{}",collect);
return hasFailures;
}
如何从ES中查数据
查询的参数,我们也设计了一个模型来封装这些参数
@Data
public class SearchParam {
/**
* 页面传递过来的全文匹配关键字
*/
private String keyword;
/**
* 品牌id,可以多选
*/
private List<Long> brandId;
/**
* 三级分类id
*/
private Long catalog3Id;
/**
* 排序条件:sort=price/salecount/hotscore_desc/asc
*/
private String sort;
/**
* 是否显示有货
*/
private Integer hasStock;
/**
* 价格区间查询
*/
private String skuPrice;
/**
* 按照属性进行筛选
*/
private List<String> attrs;
/**
* 页码
*/
private Integer pageNum = 1;
/**
* 原生的所有查询条件
*/
private String _queryString;
}
通过这些参数,然后去es中找到对应的索引下进行拼装查询,总结一下我在查询的步骤:
- 动态构建出查询需要的DSL语句
- 准备检索请求:
1
、模糊匹配,过滤(按照属性,分类,品牌,价格区间,库存),排序,分页,高亮,聚合分析2
、聚合:品牌聚合、分类聚合、属性聚合 - 执行检索请求
- 分析响应数据,封装成我们需要的格式
- 返回