UfoSearchQueryHelper.java 12.6 KB
package com.yoho.search.service.helper;

import com.yoho.search.base.constants.ISearchConstants;
import com.yoho.search.base.constants.UfoProductIndexEsField;
import com.yoho.search.base.utils.CharUtils;
import com.yoho.search.base.utils.SearchConvertUtils;
import com.yoho.search.common.*;
import com.yoho.search.common.utils.SearchKeyWordUtils;
import com.yoho.search.core.es.model.SearchField;
import com.yoho.search.core.es.utils.SearchFieldUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.*;

/**
 * @author wangnan
 * @version 2018/9/17
 */
@Service
public class UfoSearchQueryHelper extends BaseService {

    @Autowired
    private SearchCommonHelper searchCommonHelper;
    @Autowired
    private SearchServiceConfiger configurer;
    @Autowired
    private SearchDynamicConfigService searchDynamicConfigService;

    private Set<String> orderValues = new HashSet<>();

    @PostConstruct
    private void init() {
        //默认
        orderValues.add("_score:desc");
        orderValues.add("_score:asc");
        //价格
        orderValues.add("price:asc");
        orderValues.add("price:desc");
        orderValues.add("availableNowPrice:asc");
        orderValues.add("availableNowPrice:desc");
        //商品池
        orderValues.add("pools.id:asc");
        orderValues.add("pools.id:desc");
        orderValues.add("pools.order_by:asc");
        orderValues.add("pools.order_by:desc");
        //发售时间
        orderValues.add("saleTime:asc");
        orderValues.add("saleTime:desc");
        //人气
        orderValues.add("salesNum:asc");
        orderValues.add("salesNum:desc");
    }

    public BoolQueryBuilder constructFilterBuilder(Map<String, String> paramMap) throws Exception {
        BoolQueryBuilder boolFilter = QueryBuilders.boolQuery();
        //参数过滤
        this.addMustIntTermsQuery(boolFilter, paramMap, UfoSearchRequestParams.UFO_PARAM_ID, UfoProductIndexEsField.id);
        this.addMustNotIntTermsQuery(boolFilter, paramMap, UfoSearchRequestParams.UFO_PARAM_NOT_ID, UfoProductIndexEsField.id);
        this.addMustIntTermsQuery(boolFilter, paramMap, UfoSearchRequestParams.UFO_PARAM_MAXSORT, UfoProductIndexEsField.maxSortId);
        this.addMustIntTermsQuery(boolFilter, paramMap, UfoSearchRequestParams.UFO_PARAM_MIDSORT, UfoProductIndexEsField.midSortId);
        this.addMustIntTermsQuery(boolFilter, paramMap, UfoSearchRequestParams.UFO_PARAM_GENDER, UfoProductIndexEsField.gender);
        this.addMustIntTermsQuery(boolFilter, paramMap, UfoSearchRequestParams.UFO_PARAM_BRAND, UfoProductIndexEsField.brandId);
        this.addMustNotIntTermsQuery(boolFilter, paramMap, UfoSearchRequestParams.UFO_PARAM_NOT_BRAND, UfoProductIndexEsField.brandId);
        this.addMustIntTermsQuery(boolFilter, paramMap, UfoSearchRequestParams.UFO_PARAM_SERIES, UfoProductIndexEsField.seriesId);
        this.addMustIntTermsQuery(boolFilter, paramMap, UfoSearchRequestParams.UFO_PARAM_SIZE, UfoProductIndexEsField.sizeIds);
        this.addMustIntTermsQuery(boolFilter, paramMap, UfoSearchRequestParams.UFO_PARAM_COLOR, UfoProductIndexEsField.colorIds);
        this.addMustIntTermsQuery(boolFilter, paramMap, UfoSearchRequestParams.UFO_PARAM_POOL, UfoProductIndexEsField.poolIds);
        this.addMustIntTermsQuery(boolFilter, paramMap, UfoSearchRequestParams.UFO_PARAM_SHOW_CHANNEL, UfoProductIndexEsField.showChannel);

        //是否包含有货
        if (!this.containYoho(paramMap)) {
            boolFilter.mustNot(QueryBuilders.termQuery(UfoProductIndexEsField.isYoho, "Y"));
        }

        //是否要过滤独家商品
        if (searchCommonHelper.filterUfoLimitSale(paramMap)) {
            boolFilter.mustNot(QueryBuilders.termQuery(UfoProductIndexEsField.isLimitSale, "Y"));
        }

        //硬过滤[有货无库存的商品,delStatus会设置为1]
        boolFilter.must(QueryBuilders.termQuery(UfoProductIndexEsField.delStatus, 0));
        boolFilter.must(QueryBuilders.termQuery(UfoProductIndexEsField.shelveStatus, 1));

        return boolFilter;
    }

    /**
     * 判断UFO搜索是否需要包含YOHO
     */
    private boolean containYoho(Map<String, String> paramMap) {
        // 1、如果总开关关了,则肯定不包含yoho商品
        boolean ufoContainYoho = searchDynamicConfigService.ufoContainYoho();
        if (!ufoContainYoho) {
            return false;
        }
        // 2、如果前端传来的参数中不包含contain_yoho!=Y,则不包含yoho的
        if (!paramMap.containsKey(UfoSearchRequestParams.UFO_PARAM_SEARCH_CONTAIN_YOHO) || !"Y".equals(paramMap.get(UfoSearchRequestParams.UFO_PARAM_SEARCH_CONTAIN_YOHO))) {
            return false;
        }
        return true;
    }

    public BoolQueryBuilder constructFilterBuilderIdFilter(Map<String, String> paramMap) throws Exception {
        BoolQueryBuilder boolFilter = QueryBuilders.boolQuery();
        this.addMustIntTermsQuery(boolFilter, paramMap, UfoSearchRequestParams.UFO_PARAM_ID, UfoProductIndexEsField.id);
        return boolFilter;
    }

    public void addMustIntTermsQuery(BoolQueryBuilder boolFilter, Map<String, String> paramMap, String paramName, String esField) {
        List<Integer> values = SearchConvertUtils.stringToIntList(paramMap.get(paramName), ",");
        if (values == null || values.isEmpty()) {
            return;
        }
        boolFilter.must(QueryBuilders.termsQuery(esField, values));
    }

    public void addMustDoubleRangeQuery(BoolQueryBuilder boolFilter, Map<String, String> paramMap, String paramName, String esField) {
        List<Double> values = SearchConvertUtils.stringToDoubleList(paramMap.get(paramName), ",");
        if (values == null || values.isEmpty() || values.size() != 2) {
            return;
        }
        boolFilter.must(QueryBuilders.rangeQuery(esField).gte(values.get(0)).lte(values.get(1)));
    }

    public void addMustNotIntTermsQuery(BoolQueryBuilder boolFilter, Map<String, String> paramMap, String paramName, String esField) {
        List<Integer> values = SearchConvertUtils.stringToIntList(paramMap.get(paramName), ",");
        if (values == null || values.isEmpty()) {
            return;
        }
        boolFilter.mustNot(QueryBuilders.termsQuery(esField, values));
    }

    public QueryBuilder constructQueryBuilder(Map<String, String> paramMap) {
        // 1、处理查询关键字
        String query = SearchKeyWordUtils.getUfoParamKeyword(paramMap, UfoSearchRequestParams.UFO_PARAM_SEARCH_QUERY);
        if (StringUtils.isBlank(query)) {
            return QueryBuilders.matchAllQuery();
        }
        paramMap.put(UfoSearchRequestParams.UFO_PARAM_SEARCH_QUERY, query);

        // 2、处理查询中包含性别的情况
        searchCommonHelper.dealKeywordOfGender(query, paramMap);

        // 3、构建多字段匹配
        MultiMatchQueryBuilder queryBuilder = QueryBuilders.multiMatchQuery(query);
        MultiMatchQueryBuilder.Type multiMatchQueryBuilderType = searchCommonHelper.getMultiMatchQueryBuilderType();
        if (multiMatchQueryBuilderType != null) {
            queryBuilder.type(multiMatchQueryBuilderType);
        }

        // 4.如果查询的是skn或skuIds
        if (searchCommonHelper.isQueryUfoProductId(query)) {
            queryBuilder.field(UfoProductIndexEsField.idString);
            queryBuilder.field(UfoProductIndexEsField.productCode);
            queryBuilder.operator(Operator.OR);
            queryBuilder.minimumShouldMatch("1");
            return queryBuilder;
        }

        // 5.设置查询字段和比重
        List<SearchField> searchFields = SearchFieldUtils.getUfoFuzzySearchFields();
        if (CollectionUtils.isNotEmpty(searchFields)) {
            for (SearchField searchField : searchFields) {
                queryBuilder.field(searchField.getEsField(), searchField.getBoost());
            }
        }

        // 6.设置
        if ("or".equalsIgnoreCase(configurer.getSearchOperator())) {
            queryBuilder.operator(Operator.OR);
            queryBuilder.minimumShouldMatch(configurer.getUfoSearchMinimumShouldMatch());
        } else {
            queryBuilder.operator(Operator.AND);
        }
        return queryBuilder;
    }

    public List<SortBuilder<?>> buildSortList(Map<String, String> paramMap) {
        List<SortBuilder<?>> sortBuilders = new ArrayList<SortBuilder<?>>();

        String order = this.getLegalOrder(paramMap);

        // 1、默认排序:库存+新品
        if (StringUtils.isBlank(order)) {
            sortBuilders.add(SortBuilders.fieldSort(UfoProductIndexEsField.storage).order(SortOrder.DESC));
            sortBuilders.add(SortBuilders.fieldSort(UfoProductIndexEsField.saleTime).order(SortOrder.DESC));
            this.addDefaultSortBuilders(sortBuilders);
            return sortBuilders;
        }

        // 2、处理人气排序
        if (order.equalsIgnoreCase("salesNum:desc")) {
            sortBuilders.add(SortBuilders.fieldSort(UfoProductIndexEsField.storage).order(SortOrder.DESC));
            sortBuilders.add(SortBuilders.fieldSort(UfoProductIndexEsField.isYoho).order(SortOrder.ASC));//有货的放后面
        }

        // 3、处理价格排序
        if (order.equalsIgnoreCase("price:asc") || order.equalsIgnoreCase("price:desc") || order.equalsIgnoreCase("availableNowPrice:desc") || order.equalsIgnoreCase("availableNowPrice:asc")) {
            sortBuilders.add(SortBuilders.fieldSort(UfoProductIndexEsField.storage).order(SortOrder.DESC));
        }

        // 4、处理参数
        String[] sortTypes = order.split(",");
        for (String sortType : sortTypes) {
            String[] sortParts = sortType.split(ISearchConstants.SPLIT_CHAR_COLON);
            String fieldName = sortParts[0];
            SortOrder sortOrder = SortOrder.ASC.toString().equals(sortParts[1]) ? SortOrder.ASC : SortOrder.DESC;
            this.addSortBuildSorts(sortBuilders, paramMap, fieldName, sortOrder);
        }

        //5、添加默认参数
        this.addDefaultSortBuilders(sortBuilders);

        return sortBuilders;
    }

    private void addDefaultSortBuilders(List<SortBuilder<?>> sortBuilders) {
        sortBuilders.add(SortBuilders.fieldSort(UfoProductIndexEsField.orderBy).order(SortOrder.DESC));
        sortBuilders.add(SortBuilders.fieldSort(UfoProductIndexEsField.id).order(SortOrder.DESC));
    }

    private String getLegalOrder(Map<String, String> paramMap) {
        String sortFields = paramMap.get("order");
        if (StringUtils.isBlank(sortFields)) {
            return null;
        }
        String[] sortFieldArray = sortFields.split(",");
        StringBuilder realOrder = new StringBuilder();
        for (String sortField : sortFieldArray) {
            if (!orderValues.contains(sortField)) {
                continue;
            }
            realOrder.append(',').append(this.dealSortField(sortField));
        }
        return realOrder.toString().replaceFirst(",", "");
    }

    private String dealSortField(String sortField) {
        String[] sortParts = sortField.split(":");
        String realSortField = sortParts[0];
        realSortField = CharUtils.underlineToCamelhump(realSortField);
        return realSortField + ":" + sortParts[1];
    }

    private void addSortBuildSorts(List<SortBuilder<?>> sortBuilders, Map<String, String> paramMap, String fieldName, SortOrder sortOrder) {
        if (fieldName.contains("pools")) {
            BoolQueryBuilder poolsTermsBuilder = this.getPoolIdTermsBuilder(paramMap);
            String flag = sortOrder.toString().equals("desc") ? "_last" : "_first";
            FieldSortBuilder poolsFieldSortBuilder = SortBuilders.fieldSort(fieldName).order(sortOrder).setNestedPath("pools").missing(flag);
            if (poolsFieldSortBuilder != null) {
                poolsFieldSortBuilder.setNestedFilter(poolsTermsBuilder);
            }
            sortBuilders.add(poolsFieldSortBuilder);
        } else {
            sortBuilders.add(SortBuilders.fieldSort(fieldName).order(sortOrder));
        }
    }

    private BoolQueryBuilder getPoolIdTermsBuilder(Map<String, String> paramMap) {
        if (paramMap.containsKey("pool") && StringUtils.isNotBlank("pool")) {
            BoolQueryBuilder nestedBoolFilter = QueryBuilders.boolQuery();
            nestedBoolFilter.must(QueryBuilders.termQuery("pools.poolId", paramMap.get("pool")));
            return nestedBoolFilter;
        }
        return null;
    }

}