工作中有时候需要根据条件配置的方式实现动态集合呢?本案例讲解一种通用的方案,如下:
(以下仅部分主要逻辑)
一、需要定义条件中需要的字段
定义字段及可以被选择的关系列表
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
@Getter
public enum FilterColumn implements EnumerationType {
PRODUCT_TITLE("product title", false, "title", IS_EQUAL_TO, IS_NOT_EQUAL_TO, CONTAINS),
PRODUCT_TAG("product tag", true, "tag_id", IS_EQUAL_TO, IS_NOT_EQUAL_TO),
UPDATE_TIME("update time", true, "updated_at", IS_EQUAL_TO),
CREATE_TIME("create time", true, "created_at", IS_EQUAL_TO),
COST_PRICE("cost price", true, "cost_price_cent", IS_EQUAL_TO, IS_NOT_EQUAL_TO, IS_GREATER_THAN, IS_LESS_THAN),
SELLING_PRICE("selling price", true, "selling_price_cent", IS_EQUAL_TO, IS_NOT_EQUAL_TO, IS_GREATER_THAN,IS_LESS_THAN),
INVENTORY("inventory", true,"inventory", IS_EQUAL_TO, IS_NOT_EQUAL_TO, IS_GREATER_THAN, IS_LESS_THAN),
CATEGORY("category", true, "product_category_id", IS_EQUAL_TO, IS_NOT_EQUAL_TO),
BRAND_NAME("brand name", false, "id", IS_EQUAL_TO), // id
COLLECTION("collection", true, "collection", IS_EQUAL_TO),
;
private final String type;
private final boolean isNumber;
private final String columnName;
private final FilterRelation[] supportedRelations;
FilterColumn(String type, boolean isNumber, String columnName, FilterRelation... supportedRelations) {
this.type = type;
this.columnName = columnName;
this.isNumber = isNumber;
this.supportedRelations = supportedRelations;
}
@JsonCreator
public static FilterColumn create (String name) {
if(name == null) {
throw new IllegalArgumentException();
}
return Enum.valueOf(FilterColumn.class, name.trim().replaceAll(" ", "_").toUpperCase());
}
@Override
@JsonValue
public String getType() {
return type;
}
@Override
public String getFriendlyType() {
return null;
}
public boolean verifyCondition(String condition) {
if (this.equals(UPDATE_TIME)) {
return TimeCondition.findByType(condition) != null;
} else if(this.equals(CREATE_TIME)){
return TimeCondition.findByType(condition) != null;
} else if (isNumber) {
return NumberUtils.isDigits(condition);
}
return StringUtils.isNotBlank(condition);
}
}
二、定义条件的执行方式
如 等于、不等于、大于、小于等关系
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.Getter;
@Getter
public enum FilterRelation implements EnumerationType {
/**
* collection 筛选条件支持方式
*/
IS_EQUAL_TO("is equal to", "="),
IS_NOT_EQUAL_TO("is not equal to", "!="),
CONTAINS("contains", "like"),
IS_GREATER_THAN("is greater than", ">"),
IS_LESS_THAN("is less than", "<");
private final String type;
private final String operator;
FilterRelation(String type, String operator) {
this.type = type;
this.operator = operator;
}
@JsonCreator
public static FilterColumn create (String name) {
if(name == null) {
throw new IllegalArgumentException();
}
return Enum.valueOf(FilterColumn.class, name.trim().replaceAll(" ", "_").toUpperCase());
}
@Override
@JsonValue
public String getType() {
return type;
}
@Override
public String getFriendlyType() {
return null;
}
}
三、创建集合及条件对象
/**
* 集合对象
*/
@Data
public class Collection {
private Long id;
// 集合名
private String title;
// 是否可用
private Boolean available;
// 是否匹配任意一个条件
private Boolean anyMatch;
// 集合条件
private List<CollectionFilter> filters;
}
/**
* 单个集合条件
*/
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class CollectionFilter implements Entity<Long> {
private Long id;
private FilterColumn column;
private FilterRelation relation;
private String condition;
private Long collectionId;
public static SmartCollectionFilter createFromDTO(
SmartCollectionFilterDTO collectionFilterDTO, Long collectionId) {
if (ObjectUtils.isEmpty(collectionFilterDTO)) {
return null;
}
SmartCollectionFilter collectionFilter = new SmartCollectionFilter();
collectionFilter.setId(IdGenerator.getUID());
collectionFilter.setColumn(collectionFilterDTO.getColumn());
collectionFilter.setRelation(collectionFilterDTO.getRelation());
collectionFilter.setCondition(collectionFilterDTO.getCondition());
collectionFilter.setCollectionId(collectionId);
return collectionFilter;
}
public boolean updateFromDTO(SmartCollectionFilterDTO dto) {
boolean isChanged = false;
if(!ObjectUtils.isEmpty(dto.getColumn()) && !dto.getColumn().equals(getColumn())) {
setColumn(dto.getColumn());
isChanged = true;
}
if(!ObjectUtils.isEmpty(dto.getRelation()) && !dto.getRelation().equals(getRelation())) {
setRelation(dto.getRelation());
isChanged = true;
}
if(!StringUtils.isEmpty(dto.getCondition()) && !dto.getCondition().equals(getCondition())) {
setCondition(dto.getCondition());
isChanged = true;
}
return isChanged;
}
}
四、查询实现方式
/**
* 集合条件列表转 es 条件列表
* @param collectionId
*/
public void buildFilterList(Long collectionId) {
List<CollectionFilter> filters = collectionService.findByCollectionId(collectionId);
if (CollectionUtils.isEmpty(filters)) {
return Page.empty(pageable);
}
BoolQueryBuilder builder = new BoolQueryBuilder();
queryBuilder.filter(builder);
builder.must(QueryBuilders.termQuery("available", true));
if (CollectionUtils.isNotEmpty(excludeIds)){
builder.mustNot(QueryBuilders.termsQuery("_id",excludeIds));
}
for (SmartCollectionFilter filter : filters) {
applyFilter(filter,builder,baseCollection.getAnyMatch());
}
}
/**
* 单个条件转 es 条件
* @param collectionId
*/
private void applyFilter(CollectionFilter filter, BoolQueryBuilder queryBuilder, Boolean anyMatch) {
switch (filter.getColumn()) {
case PRODUCT_TITLE : {
switch (filter.getRelation()) {
case IS_EQUAL_TO : {
if (anyMatch)
queryBuilder.should(QueryBuilders.termQuery("title.keyword", filter.getCondition()));
else
queryBuilder.must(QueryBuilders.termQuery("title.keyword", filter.getCondition()));
break;
}
case IS_NOT_EQUAL_TO : {
if (anyMatch)
queryBuilder.should(new BoolQueryBuilder().mustNot(QueryBuilders.termQuery("title.keyword", filter.getCondition())));
else
queryBuilder.mustNot(QueryBuilders.termQuery("title.keyword", filter.getCondition()));
break;
}
case CONTAINS : {
if (anyMatch)
queryBuilder.should(QueryBuilders.wildcardQuery("title.keyword","*" + filter.getCondition() + "*"));
else
queryBuilder.must(QueryBuilders.wildcardQuery("title.keyword", "*" + filter.getCondition()+ "*"));
break;
}
}
break;
}
case PRODUCT_TAG :
{
switch (filter.getRelation()) {
case IS_EQUAL_TO : {
if (anyMatch)
queryBuilder.should(QueryBuilders.termQuery("tagIds", filter.getCondition()));
else
queryBuilder.must(QueryBuilders.termQuery("tagIds", filter.getCondition()));
break;
}
case IS_NOT_EQUAL_TO : {
if (anyMatch)
queryBuilder.should(new BoolQueryBuilder().mustNot(QueryBuilders.termQuery("tagIds", filter.getCondition())));
else
queryBuilder.mustNot(QueryBuilders.termQuery("tagIds", filter.getCondition()));
break;
}
}
break;
}
case UPDATE_TIME :
{
if (filter.getRelation() == FilterRelation.IS_EQUAL_TO) {
if (filter.getCondition().trim().equals("latest 30 days")) {
if (anyMatch)
queryBuilder.should(QueryBuilders.rangeQuery("updatedAt").gte(System.currentTimeMillis() - 30*24*60*60*1000l));
else
queryBuilder.must(QueryBuilders.rangeQuery("updatedAt").gte(System.currentTimeMillis() - 30*24*60*60*1000l));
} else if (filter.getCondition().trim().equals("latest 15 days")) {
if (anyMatch)
queryBuilder.should(QueryBuilders.rangeQuery("updatedAt").gte(System.currentTimeMillis() - 15*24*60*60*1000l));
else
queryBuilder.must(QueryBuilders.rangeQuery("updatedAt").gte(System.currentTimeMillis() - 15*24*60*60*1000l));
} else if (filter.getCondition().trim().equals("latest 7 days")) {
if (anyMatch)
queryBuilder.should(QueryBuilders.rangeQuery("updatedAt").gte(System.currentTimeMillis() - 15*24*60*60*1000l));
else
queryBuilder.must(QueryBuilders.rangeQuery("updatedAt").gte(System.currentTimeMillis() - 15*24*60*60*1000l));
}
} else {
queryBuilder.must(QueryBuilders.rangeQuery("updatedAt").lt(-100));
}
break;
}
case CREATE_TIME :
{
if (filter.getRelation() == FilterRelation.IS_EQUAL_TO) {
if (filter.getCondition().trim().equals("latest 30 days")) {
if (anyMatch)
queryBuilder.should(QueryBuilders.rangeQuery("createdAt").gte(System.currentTimeMillis() - 30*24*60*60*1000l));
else
queryBuilder.must(QueryBuilders.rangeQuery("createdAt").gte(System.currentTimeMillis() - 30*24*60*60*1000l));
} else if (filter.getCondition().trim().equals("latest 15 days")) {
if (anyMatch)
queryBuilder.should(QueryBuilders.rangeQuery("createdAt").gte(System.currentTimeMillis() - 15*24*60*60*1000l));
else
queryBuilder.must(QueryBuilders.rangeQuery("createdAt").gte(System.currentTimeMillis() - 15*24*60*60*1000l));
} else if (filter.getCondition().trim().equals("latest 7 days")) {
if (anyMatch)
queryBuilder.should(QueryBuilders.rangeQuery("createdAt").gte(System.currentTimeMillis() - 7*24*60*60*1000l));
else
queryBuilder.must(QueryBuilders.rangeQuery("createdAt").gte(System.currentTimeMillis() - 7*24*60*60*1000l));
}
}
break;
}
case SELLING_PRICE :
{
switch (filter.getRelation()) {
case IS_EQUAL_TO : {
if (anyMatch)
queryBuilder.should(QueryBuilders.termQuery("priceMin", filter.getCondition()));
else
queryBuilder.must(QueryBuilders.termQuery("priceMin", filter.getCondition()));
break;
}
case IS_NOT_EQUAL_TO : {
if (anyMatch)
queryBuilder.should(new BoolQueryBuilder().mustNot(QueryBuilders.termQuery("priceMin", filter.getCondition())));
else
queryBuilder.mustNot(QueryBuilders.termQuery("priceMin", filter.getCondition()));
break;
}
case IS_GREATER_THAN : {
if (anyMatch)
queryBuilder.should(QueryBuilders.rangeQuery("priceMin").gte(filter.getCondition()));
else
queryBuilder.must(QueryBuilders.rangeQuery("priceMin").gte(filter.getCondition()));
break;
}
case IS_LESS_THAN : {
if (anyMatch)
queryBuilder.should(QueryBuilders.rangeQuery("priceMin").lte(filter.getCondition()));
else
queryBuilder.must(QueryBuilders.rangeQuery("priceMin").lte(filter.getCondition()));
break;
}
}
break;
}
case INVENTORY :
{
break;
}
case CATEGORY :
{
switch (filter.getRelation()) {
case IS_EQUAL_TO : {
if (anyMatch)
queryBuilder.should(QueryBuilders.termQuery("categoryIds", filter.getCondition()));
else
queryBuilder.must(QueryBuilders.termQuery("categoryIds", filter.getCondition()));
break;
}
case IS_NOT_EQUAL_TO : {
if (anyMatch)
queryBuilder.should(new BoolQueryBuilder().mustNot(QueryBuilders.termQuery("categoryIds", filter.getCondition())));
else
queryBuilder.mustNot(QueryBuilders.termQuery("categoryIds", filter.getCondition()));
break;
}
}
break;
}
case COLLECTION: {
BaseCollection collection = collectionService.findOne(Long.valueOf(filter.getCondition()));
if (!collection.getAvailable() && collection.getType().equals(CollectionType.CUSTOM)) {
break;
}
List<SmartCollectionFilter> filters = collectionService.findByCollectionId(collection.getId());
if (CollectionUtils.isEmpty(filters)) {
break;
}
BoolQueryBuilder subBuilder = new BoolQueryBuilder();
for (SmartCollectionFilter subFilter : filters) {
applyFilter(subFilter, subBuilder, collection.getAnyMatch());
}
if (anyMatch) {
queryBuilder.should(subBuilder);
} else {
queryBuilder.must(subBuilder);
}
break;
}
case BRAND_NAME :
{
switch (filter.getRelation()) {
case IS_EQUAL_TO : {
if (anyMatch)
queryBuilder.should(QueryBuilders.termQuery("brandIds", filter.getCondition()));
else
queryBuilder.must(QueryBuilders.termQuery("brandIds", filter.getCondition()));
break;
}
}
break;
}
case DAY_ORDER: // @Author masl - 2022/4/29 11:47 上午
{
List<String> spus = new ArrayList<>();
if (filter.getRelation() == FilterRelation.IS_GREATER_THAN) {
spus = productServiceFacade.findSpuByOrder(1, Integer.valueOf(filter.getCondition()));
}
if (filter.getRelation() == FilterRelation.IS_LESS_THAN) {
spus = productServiceFacade.findSpuByOrder(2, Integer.valueOf(filter.getCondition()));
}
if (CollectionUtils.isNotEmpty(spus)) {
if (anyMatch)
queryBuilder.should(QueryBuilders.termsQuery("spu.keyword", spus));
else
queryBuilder.must(QueryBuilders.termsQuery("spu.keyword", spus));
}
break;
}
}
}