对应b站视频 视频
视频使用的API为ES7.x的API,本人使用ES8.x 链式风格JAVA API
折腾了挺久的,基本弄明白链式API的用法了,实际上这里就是根据业务写出es DSL语句,再把DSL语句翻译成Java 语言的写法。
参考资料:
- https://blog.csdn.net/pyd1040201698/article/details/108354264
- https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/java-client-javadoc.html
- https://blog.csdn.net/Oaklkm/article/details/130992152
给出DSL语句
//GET glmall_product/_search
GET /glmall_product/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"skuTitle": "小米"
}
}
],
"filter": [
{
"term": {
"catalogId": "225"
}
},
{
"terms": {
"brandId": [
"11"
]
}
},
{
"nested": {
"path": "attrs",
"query": {
"bool": {
"must": [
{
"term": {
"attrs.attrId": {
"value": "9"
}
}
},
{
"terms": {
"attrs.attrValue": [
"红色",
"以官网信息为准"
]
}
}
]
}
}
}
},
{
"nested": {
"path": "attrs",
"query": {
"bool": {
"must": [
{
"term": {
"attrs.attrId": {
"value": "23"
}
}
},
{
"terms": {
"attrs.attrValue": [
"3"
]
}
}
]
}
}
}
},
{
"nested": {
"path": "attrs",
"query": {
"bool": {
"must": [
{
"term": {
"attrs.attrId": {
"value": "8"
}
}
},
{
"terms": {
"attrs.attrValue": [
"是"
]
}
}
]
}
}
}
},
{
"term": {
"hasStock": {
"value": "true"
}
}
},
{
"range": {
"skuPrice": {
"gte": 0,
"lte": 6000
}
}
}
]
}
},
"sort": [
{
"skuPrice": {
"order": "desc"
}
}
],
"from": 0,
"size": 2,
"highlight": {
"fields": {
"skuTitle": {}
},
"pre_tags": "<b style='color: red'>",
"post_tags": "</b>"
},
"aggs": {
"brand_agg": {
"terms": {
"field": "brandId",
"size": 10
},
"aggs": {
"brand_name_agg": {
"terms": {
"field": "brandName",
"size": 10
}
},
"brand_img_agg": {
"terms": {
"field": "brandImg",
"size": 10
}
}
}
},
"catalog_agg": {
"terms": {
"field": "catalogId",
"size": 10
},
"aggs": {
"catalog_name_agg": {
"terms": {
"field": "catalogName",
"size": 10
}
}
}
},
"attr_agg": {
"nested": {
"path": "attrs"
},
"aggs": {
"attr_id_agg": {
"terms": {
"field": "attrs.attrId",
"size": 10
},
"aggs": {
"attr_name_agg": {
"terms": {
"field": "attrs.attrName",
"size": 10
}
},
"attr_value_agg": {
"terms": {
"field": "attrs.attrValue",
"size": 10
}
}
}
}
}
}
}
}
Controller层
@Controller
public class ListController {
@Resource
MallESService mallESService;
@GetMapping(value = {"/list.html"})
private String search(SearchParam searchParam, Model model) {
SearchResponseVo result = mallESService.search(searchParam);
model.addAttribute("result", result);
return "list";
}
}
Service层
public interface MallESService {
/**
* @param searchParam 检索的参数
* @return 检索出来的结果
*/
SearchResponseVo search(SearchParam searchParam);
}
@Service("mallESService")
public class MallESServiceImpl implements MallESService {
@Resource
ElasticsearchClient client;
@Override
public SearchResponseVo search(SearchParam searchParam) {
Function<SearchRequest.Builder, ObjectBuilder<SearchRequest>> searchFunction = buildSearchFunction(searchParam);
SearchResponse<SkuEsTo> searchResponse = null;
try {
System.out.println("dsl function");
System.out.println(searchFunction);
searchResponse = client.search(searchFunction, SkuEsTo.class);
} catch (IOException e) {
throw new RuntimeException(e);
}
SearchResponseVo searchResponseVo = buildSearchResult(searchResponse, searchParam);
return searchResponseVo;
}
/**
* 将es的响应结果封装成我们的SearchResponseVo的格式
*
* @param searchResponse
* @param searchParam
* @return
*/
private SearchResponseVo buildSearchResult(SearchResponse<SkuEsTo> searchResponse, SearchParam searchParam) {
SearchResponseVo searchResponseVo = new SearchResponseVo();
// 1. 返回查询到的所有商品
List<Hit<SkuEsTo>> hits = searchResponse.hits().hits();
List<SkuEsTo> products = hits.stream().map(Hit::source).collect(Collectors.toList());
searchResponseVo.setProducts(products);
// 命中的总记录数量
assert searchResponse.hits().total() != null;
long totalHit = searchResponse.hits().total().value();
// 总共页码数量计算 page = (totalCount + pageSize - 1) / pageSize
searchResponseVo.setTotalPages( (totalHit+EsConstant.PRODUCT_PAGESIZE-1) / EsConstant.PRODUCT_PAGESIZE);
searchResponseVo.setPageNum(Long.valueOf(searchParam.getPageNo()));
searchResponseVo.setTotal(totalHit);
// 2. 当前商品的所有属性值
Map<String, Aggregate> aggregations = searchResponse.aggregations();
Aggregate attrAgg = aggregations.get("attr_agg");
List<SearchResponseVo.AttrVO> arrtVoList = attrAgg.nested().aggregations().get("attr_id_agg").lterms().buckets().array().stream().map(innerBucket -> {
SearchResponseVo.AttrVO attrVO = new SearchResponseVo.AttrVO();
Map<String, Aggregate> bucketAgg = innerBucket.aggregations();
String attrName = bucketAgg.get("attr_name_agg").sterms().buckets().array().get(0).key().stringValue();
Aggregate attrValueAgg = bucketAgg.get("attr_value_agg");
List<StringTermsBucket> bucketList = attrValueAgg.sterms().buckets().array();
String attrValue = bucketList.get(0).key().stringValue();
attrVO.setAttrId(innerBucket.key());
attrVO.setAttrName(attrName);
assert attrValue != null;
attrVO.setAttrValue(Arrays.asList(attrValue.split(";")));
return attrVO;
}).collect(Collectors.toList());
searchResponseVo.setAttrs(arrtVoList);
// 3. 设置品牌信息
Aggregate brandAgg = aggregations.get("brand_agg");
List<SearchResponseVo.BrandVO> brandVOList = brandAgg.lterms().buckets().array().stream().map(longTermsBucket -> {
long brandId = longTermsBucket.key();
SearchResponseVo.BrandVO brandVO = new SearchResponseVo.BrandVO();
Aggregate brandNameAgg = longTermsBucket.aggregations().get("brand_name_agg");
String brandName = brandNameAgg.sterms().buckets().array().get(0).key().stringValue();
Aggregate brandImgAgg = longTermsBucket.aggregations().get("brand_img_agg");
String imgName = brandImgAgg.sterms().buckets().array().get(0).key().stringValue();
brandVO.setBrandName(brandName);
brandVO.setBrandId(brandId);
brandVO.setBrandImg(imgName);
return brandVO;
}).collect(Collectors.toList());
searchResponseVo.setBrands(brandVOList);
// 4. 设置分类信息
Aggregate catalogAgg = aggregations.get("catalog_agg");
List<SearchResponseVo.CatelogVO> catelogVOList = catalogAgg.lterms().buckets().array().stream().map(longTermsBucket -> {
long catalogId = longTermsBucket.key();
Aggregate catalogNameAgg = longTermsBucket.aggregations().get("catalog_name_agg");
String catalogName = catalogNameAgg.sterms().buckets().array().get(0).key().stringValue();
SearchResponseVo.CatelogVO catelogVO = new SearchResponseVo.CatelogVO();
catelogVO.setCatelogName(catalogName);
catelogVO.setCatelogId(catalogId);
return catelogVO;
}).collect(Collectors.toList());
searchResponseVo.setCatelogs(catelogVOList);
return searchResponseVo;
}
/**
* 将检索的参数转化为es请求的查询、聚合条件
*
* @param searchParam
* @return
*/
private Function<SearchRequest.Builder, ObjectBuilder<SearchRequest>> buildSearchFunction(SearchParam searchParam) {
return builder -> {
// 查询功能 模糊匹配,过滤(属性 分类 品牌 价格区间 库存)
Query byKeyWord;
if (!StringUtils.isNullOrEmpty(searchParam.getKeyword())) {
byKeyWord = MatchQuery.of(m -> m.field("skuTitle").query(searchParam.getKeyword()))._toQuery();
} else {
byKeyWord = null;
}
//
SearchRequest.Builder requestBuilder = builder.index(EsConstant.PRODUCT_INDEX)
.query(q ->
q.bool(b -> {
// 一、 构造查询条件 布尔查询
if (byKeyWord != null) b.must(Collections.singletonList(byKeyWord));
if (searchParam.getCatelog3Id() != null) {
// 1. 三级分类id
b.filter(
TermQuery.of(t ->
t.field("catalogId") // es中的分类id
.value(searchParam.getCatelog3Id()))._toQuery()
);
}
if (searchParam.getBrandId() != null && searchParam.getBrandId().size() > 0) {
// 2. 按照品牌id匹配
b.filter(TermsQuery.of(ts ->
ts.field("brandId") // es中的品牌id
.terms(new TermsQueryField.Builder()
.value(searchParam.getBrandId().stream().map(FieldValue::of).collect(Collectors.toList())) // 将Long类型的品牌id转化为FieldValue类型
.build()))._toQuery()
);
}
if (searchParam.getStock() != null && searchParam.getStock().equals(1)) {
// 3. 按照有无库存查询。 为1查询有库存的;如果为0就把有库存和没库存的都查询出来,不拼接本条语句
b.filter(TermQuery.of(t -> t.field("hasStock").value(true))
._toQuery());
}
// 4. 按照价格区间查询 价格区间:price=0_400 或者 price=_200 或者 price=200_
if (!StringUtils.isNullOrEmpty(searchParam.getSkuPrice())) {
String skuPrice = searchParam.getSkuPrice().trim();
b.filter(RangeQuery.of(
r -> {
r.field("skuPrice");
String[] priceSpilt = skuPrice.split("_");
if (priceSpilt.length == 2){
r.gte(JsonData.of(priceSpilt[0])).lte(JsonData.of(priceSpilt[1]));
}else if (priceSpilt.length == 1){
// _500 means <=500
if (skuPrice.startsWith("_")) r.lte(JsonData.of(priceSpilt[0]));
// 100_ means >=100
else if (skuPrice.endsWith("_")) r.gte(JsonData.of(priceSpilt[0]));
else throw new RuntimeException("检查请求参数skuPrice的格式");
}else throw new RuntimeException("检查请求参数skuPrice的格式");
return r;
} )
._toQuery());
}
// 5. 按照指定属性的条件进行查询 attrs=1_3G:4G
if(searchParam.getAttrs() != null && searchParam.getAttrs().size() > 0){
List<String> attrs = searchParam.getAttrs();
attrs.forEach(attr->{
List<Query> attrQuery = new ArrayList<>();
String[] idString = attr.split("_");
long attrId = Long.parseLong(idString[0]);
List<String> attrValue = Arrays.asList(idString[1].split(":"));
attrQuery.add(TermQuery.of(termQuery -> termQuery.field("attrs.attrId").value(attrId))._toQuery());
attrQuery.add( TermsQuery.of(termsQuery->termsQuery.field("attrs.attrValue").terms(new TermsQueryField.Builder()
.value(attrValue.stream().map(FieldValue::of).collect(Collectors.toList())).build()))._toQuery() );
// 对于每一个要查询的属性都要生成一个filter
b.filter(NestedQuery.of(
n -> n.path("attrs")
.query(attr_q ->
attr_q.bool(attr_b -> attr_b.must(attrQuery))))._toQuery());
});
}
return b;
}
));
// 二、 构造排序条件
/**
* sort = saleCount_asc/desc
* sort = skuPrice_asc/desc
* sort = hotScore_asc/desc
*/
if (!StringUtils.isNullOrEmpty(searchParam.getSort())){
String[] split = searchParam.getSort().split("_");
assert split.length == 2;
String field = split[0];
String rule = split[1];
SortOrder sortOrder = Objects.equals(rule, "asc") ? SortOrder.Asc : SortOrder.Desc;
builder.sort(f->f.field(o->o.field(field).order(sortOrder)));
}
// 三、构造分页条件
int pageNo = searchParam.getPageNo()==null ? 1 : searchParam.getPageNo();
// int pageSize = searchParam.getPageSize()==null ? EsConstant.PRODUCT_PAGESIZE : searchParam.getPageSize();
int pageSize = EsConstant.PRODUCT_PAGESIZE; // 暂时先固定为常量
// (页码-1)* 页大小 = from
builder.from((pageNo-1)* pageSize );
builder.size(pageSize);
// 四、高亮查询条件
if (!StringUtils.isNullOrEmpty(searchParam.getKeyword())){
// 高亮查询的keyword
builder.highlight(h->h.fields("skuTitle", new HighlightField.Builder().build()).preTags("<b style='color: red'>").postTags("</b>"));
}
// 五、聚合分析
// 5.1 brand_agg
builder.aggregations("brand_agg", aggregation -> aggregation
.terms(termsAggregation->termsAggregation.field("brandId").size(50))
.aggregations("brand_name_agg", innerAggregation->innerAggregation.terms(terms->terms.field("brandName").size(1)))
.aggregations("brand_img_agg", innerAggregation->innerAggregation.terms(terms->terms.field("brandImg").size(1))));
// 5.2 catalog_agg
builder.aggregations("catalog_agg", aggregation -> aggregation
.terms(termsAggregation->termsAggregation.field("catalogId").size(30))
.aggregations("catalog_name_agg", innerAggregation->innerAggregation.terms(terms->terms.field("catalogName").size(1))));
// 5.3 attr_agg
builder.aggregations("attr_agg", aggregation -> aggregation
.nested(nest-> nest.path("attrs"))
.aggregations("attr_id_agg", attrIdAggretation -> attrIdAggretation
.terms(term->term.field("attrs.attrId").size(50))
.aggregations("attr_name_agg", attrNameAggregation -> attrNameAggregation.terms(term->term.field("attrs.attrName").size(1)))
.aggregations("attr_value_agg", attrValueAggregation -> attrValueAggregation.terms(term->term.field("attrs.attrValue").size(50)))
));
return requestBuilder;
};
}
}
请求参数VO
@Data
public class SearchParam {
/**
* 检索关键字
*/
String keyword;
/**
* 品牌id,可以多选
*/
List<Long> brandId;
/**
* 分类id
*/
Long catelog3Id;
/**
* sort = saleCount_asc/desc
* sort = skuPrice_asc/desc
* sort = hotScore_asc/desc
*/
String sort;
/**
* 价格区间:price=0_400 或者 price=_200 或者 price=200_
*/
String skuPrice;
/**
* 是否有库存: stock=0/1;1只显示有货,0或者不传都会显示
*/
Integer stock;
/**
* 可以传入多个; attrs=1_3G:4G;1号属性值为3G或者4G
*/
List<String> attrs;
/**
* 页码
*/
Integer pageNo;
/**
* 页面大小
*/
Integer pageSize;
}