1.背景
在使用Easy-Es发现有很多功能可能不是很好使用,或者说我自己不太会使用。
这里分享自己使用EE和原生API一起搭配使用的商品搜索功能,包含keyword指定分词器搜索,商品分类,商品品牌,价格区间,嵌套分组搜索,权重排序,高亮,以及品牌去重等功能。
2.直接上代码
商品实体类
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@IndexName(value = "product", aliasName = "product")
public class Product {
@IndexId(type = IdType.CUSTOMIZE)
private Long id;
private Long brandId;
private String brandLogo;
private Long productCategoryId;
private Long freightTemplateId;
private Long productAttributeCategoryId;
private String channelCode;
private String channelProductCode;
private String name;
private String productType;
private String pic;
private String productCode;
private Integer publishStatus;
private Integer initStatus;
private Integer verifyStatus;
private Integer sort;
private BigDecimal price;
private BigDecimal costPrice;
private BigDecimal crossedPrice;
private BigDecimal profitMargin;
private BigDecimal profitMarginPercent;
private BigDecimal priceProfitMargin;
private BigDecimal priceProfitMarginPercent;
private Integer productRealSales;
private Integer productVirtualSales;
private String subTitle;
private String description;
private BigDecimal originalPrice;
private String albumPics;
private String detailTitle;
private String detailDesc;
private String detailHtml;
private String detailMobileHtml;
private String detailWechatHtml;
private Integer sellType;
private String keywords;
private String note;
private String brandName;
private String channelProductCategoryPath;
private String productCategoryName;
private String productCategoryPath;
private LocalDateTime joinTime;
private Integer blacklist;
private String blackReason;
private Boolean delFlag;
@IndexField(fieldType = FieldType.NESTED, nestedClass = SkuStock.class)
private List<SkuStock> skuInfos;
@IndexField(fieldType = FieldType.NESTED, nestedClass = ProductGroup.class)
private List<ProductGroup> groupInfos;
@IndexField(fieldType = FieldType.DATE, fieldData = true, dateFormat = "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis")
private LocalDateTime createTime;
@IndexField(fieldType = FieldType.DATE, fieldData = true, dateFormat = "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis")
private LocalDateTime updateTime;
}
SKU实体类
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SkuStock {
private Long skuId;
private Long productId;
private String productCode;
private String productType;
private String skuCode;
private String channelCode;
private String channelSkuCode;
private String skuName;
private BigDecimal platformPrice;
private BigDecimal platformSellPrice;
private BigDecimal originalPrice;
private BigDecimal crossedPrice;
private Integer stock;
private LocalDateTime warningTime;
private Integer lowStock;
private String pic;
private Integer orderNum;
private Integer backOrderNum;
private Integer lockStock;
private Integer publishStatus;
private Integer warningStatus;
private Integer initStatus;
private String priceTypeCode;
private String saleAttributes;
private String marker;
private String remark;
private Integer blacklist;
private String channelProductPoolCode;
private Boolean skuDelFlag;
private String productArea;
private Integer lowestBuy;
private String warrantDesc;
private String capacity;
private String weight;
private Integer logisticsType;
private BigDecimal taxRatePercentage;
private Integer deliveryTime;
private String taxCode;
private String unit;
private String wareInfo;
private String albumPics;
private String cubeParam;
private Integer canInvoice;
private Integer saleState;
private Integer returnRuleType;
private Integer noReasonToReturn;
private String specificationAttributes;
private String categoryAttributes;
private Boolean extraDelFlag;
}
搜索的入参
@Data
public class EntProductSearchDTO implements Serializable {
@ApiModelProperty(value = "商品名称")
private String keyword;
@ApiModelProperty(value = "商品价格")
private BigDecimal priceMax;
@ApiModelProperty(value = "商品价格")
private BigDecimal priceMin;
@ApiModelProperty(value = "品牌ID")
private List<Long> brandIds;
@ApiModelProperty(value = "商品分类ID")
private List<Long> productCategoryIds;
@ApiModelProperty(value = "分组ID")
private List<String> groupingIdList;
@ApiModelProperty(value = "商品类型 :real->实物商品;call->话费商品;coupon->卡券;recharge->直充商品")
private String productType;
@ApiModelProperty("发布状态 0-下架 1-上架 2三方下架")
private List<Integer> publishStatus;
@ApiModelProperty("分类地址 ,分割")
private String productCategoryPath;
/**
* 页码
*/
@ApiModelProperty("页码")
private Integer page;
/**
* 每页记录数
*/
@ApiModelProperty("每页记录数")
private Integer size;
@NotNull(message = "sortType cannot be null")
@Min(value = 0, message = "sortType mix is 0")
@Max(value = 3, message = "sortType max is 3")
@ApiModelProperty(value = "排序方式:0->默认排序;1->价格大;2->价格小")
private Integer sortType;
public int getPage() {
return page == null ? 1 : page;
}
public void setPage(int page) {
this.page = page;
}
public int getSize() {
return size == null ? 20 : size;
}
public void setSize(int size) {
this.size = size;
}
/**
* 返回偏移量
*
* @return
*/
public int getOffset() {
int offset = (getPage() - 1) * getSize();
return Math.max(offset, 0);
}
}
3.进入正题,搜索模块
组装查询条件LambdaEsQueryWrapper,包含查询条件,排序,分页,高亮,去重
LambdaEsQueryWrapper<Product> wrapper = new LambdaEsQueryWrapper<>();
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(decorateBp(dto));
//排序
if (dto.getSortType() != null) {
switch (dto.getSortType()) {
case 1:
sourceBuilder.sort(SortBuilders.fieldSort(FieldUtils.val(Product::getPrice)).order(SortOrder.DESC));
break;
case 2:
sourceBuilder.sort(SortBuilders.fieldSort(FieldUtils.val(Product::getPrice)).order(SortOrder.ASC));
break;
case 3:
sourceBuilder.sort(SortBuilders.fieldSort(FieldUtils.val(Product::getSort)).order(SortOrder.DESC));
break;
default:
sourceBuilder.sort("_score", SortOrder.DESC);
sourceBuilder.sort(SortBuilders.fieldSort(FieldUtils.val(Product::getSort)).order(SortOrder.DESC));
}
}
//分页
sourceBuilder.from(dto.getOffset());
sourceBuilder.size(dto.getSize());
//高亮
if (StringUtils.isNotBlank(dto.getKeyword())) {
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field(FieldUtils.val(Product::getName));
highlightBuilder.preTags("<b style='color:red'>");
highlightBuilder.postTags("</b>");
sourceBuilder.highlighter(highlightBuilder);
}
//品牌信息要去重
if (brandCon) {
sourceBuilder.collapse(new CollapseBuilder(FieldUtils.val(Product::getBrandId)));
}
wrapper.setSearchSourceBuilder(sourceBuilder);
log.info("搜索条件{}", brandCon ? "品牌" : "商品");
return wrapper;
这里是搜索的条件组装,封装BoolQueryBuilder文章来源:https://www.toymoban.com/news/detail-584802.html
/**
* 组装搜索条件
*
* @param dto 入参
*/
private BoolQueryBuilder decorateBp(EntSearchDTO dto) {
BoolQueryBuilder bp = QueryBuilders.boolQuery();
//关键字查询
if (StringUtils.isNotBlank(dto.getKeyword())) {
bp.must(QueryBuilders.multiMatchQuery(dto.getKeyword(), FieldUtils.val(Product::getBrandName),
FieldUtils.val(Product::getName)).analyzer("ik_smart"));
}
//分类ID
if (!CollectionUtils.isEmpty(dto.getProductCategoryIds())) {
bp.filter(QueryBuilders.termQuery(FieldUtils.val(Product::getProductCategoryId), dto.getProductCategoryIds()));
}
//品牌ID
if (!CollectionUtils.isEmpty(dto.getBrandIds())) {
bp.filter(QueryBuilders.termsQuery(FieldUtils.val(Product::getBrandId), dto.getBrandIds()));
}
if (StringUtils.isNotBlank(dto.getProductType())) {
bp.filter(QueryBuilders.termQuery(FieldUtils.val(Product::getProductType), dto.getProductType()));
}
//上下架
if (!CollectionUtils.isEmpty(dto.getPublishStatus())) {
bp.filter(QueryBuilders.termsQuery(FieldUtils.val(Product::getPublishStatus), dto.getPublishStatus()));
}
//价格
if (dto.getPriceMax() != null) {
bp.must(QueryBuilders.rangeQuery(FieldUtils.val(Product::getPrice)).lte(dto.getPriceMax()));
}
if (dto.getPriceMin() != null) {
bp.must(QueryBuilders.rangeQuery(FieldUtils.val(Product::getPrice)).gte(dto.getPriceMin()));
}
if (StringUtils.isNotBlank(dto.getProductCategoryPath())) {
bp.must(QueryBuilders.wildcardQuery(FieldUtils.val(Product::getProductCategoryPath), dto.getProductCategoryPath() + "*"));
}
//分组
if (!CollectionUtils.isEmpty(dto.getGroupingIdList())) {
//俺也不想这样写,后面在优化吧。
String groupId = FieldUtils.val(Product::getGroupInfos) + "." + FieldUtils.val(ProductGroup::getGroupId);
String delFlag = FieldUtils.val(Product::getGroupInfos) + "." + FieldUtils.val(ProductGroup::getDelFlag);
//嵌套查询
bp.must(QueryBuilders.nestedQuery(FieldUtils.val(Product::getGroupInfos), new TermsQueryBuilder(groupId, dto.getGroupingIdList()), ScoreMode.None));
bp.must(QueryBuilders.nestedQuery(FieldUtils.val(Product::getGroupInfos), new TermsQueryBuilder(delFlag, false), ScoreMode.None));
}
bp.must(QueryBuilders.termQuery(FieldUtils.val(Product::getDelFlag), false));
bp.must(QueryBuilders.termQuery(FieldUtils.val(Product::getInitStatus), CommonConstants.TWO));
bp.mustNot(QueryBuilders.termQuery(FieldUtils.val(Product::getBrandName), ProductConstant.DEFUTE_BRAND_NAME_ZH));
return bp;
}
这里就是方法的入口,productMapper.selectList(wrapper)和Mybatis-Plus的写法基本上一摸一样,如果不懂可以去看一下EE官方使用方法
Easy-Es文章来源地址https://www.toymoban.com/news/detail-584802.html
public Page<EntProductSearchVO> searchProduct(EntProductSearchDTO dto) {
if (dto == null) {
return new Page<>();
}
//查询条件
LambdaEsQueryWrapper<Product> wrapper = buildSearchCondition(dto, false);
List<EntProductSearchVO> vos = new ArrayList<>();
//是否需要查询数据 默认查询数据
if (dto.getNeedData() == null || dto.getNeedData()) {
List<Product> products = productMapper.selectList(wrapper);
products.forEach(product -> {
EntProductSearchVO vo = BeanConvertUtil.convert(product,EntProductSearchVO.class);
vo.setPromotionPrice(product.getOriginalPrice());
//查询最小的价格的sku
SkuStock skuStock = product.getSkuInfos().stream().min(Comparator.comparing(SkuStock::getPlatformSellPrice)).orElse(new SkuStock());
vo.setChannelPriceCode(skuStock.getChannelCode());
vo.setChannelPriceSkuCode(skuStock.getChannelSkuCode());
List<EntProductSearchVO.EntSkuVO> skuVOList = new ArrayList<>();
product.getSkuInfos().forEach(sku->{
EntProductSearchVO.EntSkuVO skuVO = BeanConvertUtil.convert(sku,EntProductSearchVO.EntSkuVO.class);
skuVO.setPrice(sku.getPlatformSellPrice());
skuVO.setPromotionPrice(sku.getOriginalPrice());
skuVOList.add(skuVO);
});
vo.setSkuList(skuVOList);
vos.add(vo);
});
}
Long total = productMapper.selectCount(wrapper);
return new Page<EntProductSearchVO>().setCurrent(dto.getOffset()).setSize(dto.getSize()).setRecords(vos).setTotal(total);
}
到了这里,关于ES商品搜索实战 Easy-Es搭配原生SearchSourceBuilder的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!