diff --git a/consumer/src/main/java/cn/lili/listener/GoodsMessageListener.java b/consumer/src/main/java/cn/lili/listener/GoodsMessageListener.java index 1ae8826f..8738bf0c 100644 --- a/consumer/src/main/java/cn/lili/listener/GoodsMessageListener.java +++ b/consumer/src/main/java/cn/lili/listener/GoodsMessageListener.java @@ -288,9 +288,11 @@ public class GoodsMessageListener implements RocketMQListener { * @param goodsSkuList 商品sku信息 */ private void generatorGoodsIndex(Goods goods, List goodsSkuList) { + int skuSource = 100; for (GoodsSku goodsSku : goodsSkuList) { EsGoodsIndex esGoodsOld = goodsIndexService.findById(goodsSku.getId()); EsGoodsIndex goodsIndex = this.settingUpGoodsIndexData(goods, goodsSku); + goodsIndex.setSkuSource(skuSource--); //如果商品库存不为0,并且es中有数据 if (goodsSku.getQuantity() > 0 && esGoodsOld == null) { log.info("生成商品索引 {}", goodsIndex); diff --git a/framework/src/main/java/cn/lili/elasticsearch/BaseElasticsearchService.java b/framework/src/main/java/cn/lili/elasticsearch/BaseElasticsearchService.java index f5c62ad1..8ece3359 100644 --- a/framework/src/main/java/cn/lili/elasticsearch/BaseElasticsearchService.java +++ b/framework/src/main/java/cn/lili/elasticsearch/BaseElasticsearchService.java @@ -197,6 +197,9 @@ public abstract class BaseElasticsearchService { " \"commentNum\": {\n" + " \"type\": \"long\"\n" + " },\n" + + " \"skuSource\": {\n" + + " \"type\": \"long\"\n" + + " },\n" + " \"goodsId\": {\n" + " \"type\": \"text\",\n" + " \"fields\": {\n" + diff --git a/framework/src/main/java/cn/lili/modules/goods/entity/dos/Goods.java b/framework/src/main/java/cn/lili/modules/goods/entity/dos/Goods.java index 9cff3528..dc8160e5 100644 --- a/framework/src/main/java/cn/lili/modules/goods/entity/dos/Goods.java +++ b/framework/src/main/java/cn/lili/modules/goods/entity/dos/Goods.java @@ -126,7 +126,7 @@ public class Goods extends BaseEntity { @ApiModelProperty(value = "是否为推荐商品", required = true) - private boolean recommend; + private Boolean recommend; @ApiModelProperty(value = "销售模式", required = true) private String salesModel; diff --git a/framework/src/main/java/cn/lili/modules/goods/entity/dos/GoodsSku.java b/framework/src/main/java/cn/lili/modules/goods/entity/dos/GoodsSku.java index 8bba8288..0b0b0370 100644 --- a/framework/src/main/java/cn/lili/modules/goods/entity/dos/GoodsSku.java +++ b/framework/src/main/java/cn/lili/modules/goods/entity/dos/GoodsSku.java @@ -1,13 +1,14 @@ package cn.lili.modules.goods.entity.dos; -import cn.lili.mybatis.BaseEntity; import cn.lili.modules.goods.entity.enums.GoodsAuthEnum; import cn.lili.modules.goods.entity.enums.GoodsStatusEnum; +import cn.lili.mybatis.BaseEntity; import com.baomidou.mybatisplus.annotation.TableName; import com.fasterxml.jackson.annotation.JsonIgnore; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; +import lombok.EqualsAndHashCode; import org.hibernate.validator.constraints.Length; import javax.validation.constraints.Max; @@ -19,6 +20,7 @@ import java.util.Date; * @author pikachu * @since 2020-02-23 9:14:33 */ +@EqualsAndHashCode(callSuper = true) @Data @TableName("li_goods_sku") @ApiModel(value = "商品sku对象") @@ -146,7 +148,7 @@ public class GoodsSku extends BaseEntity { private String goodsVideo; @ApiModelProperty(value = "是否为推荐商品", required = true) - private boolean recommend; + private Boolean recommend; @ApiModelProperty(value = "销售模式", required = true) private String salesModel; diff --git a/framework/src/main/java/cn/lili/modules/goods/serviceimpl/GoodsSkuServiceImpl.java b/framework/src/main/java/cn/lili/modules/goods/serviceimpl/GoodsSkuServiceImpl.java index e56686c4..b6c8fc5a 100644 --- a/framework/src/main/java/cn/lili/modules/goods/serviceimpl/GoodsSkuServiceImpl.java +++ b/framework/src/main/java/cn/lili/modules/goods/serviceimpl/GoodsSkuServiceImpl.java @@ -587,6 +587,7 @@ public class GoodsSkuServiceImpl extends ServiceImpl i sku.setStoreName(goods.getStoreName()); sku.setStoreCategoryPath(goods.getStoreCategoryPath()); sku.setFreightTemplateId(goods.getTemplateId()); + sku.setRecommend(goods.getRecommend()); } /** diff --git a/framework/src/main/java/cn/lili/modules/goods/serviceimpl/ParametersServiceImpl.java b/framework/src/main/java/cn/lili/modules/goods/serviceimpl/ParametersServiceImpl.java index 6f4476ff..122c6777 100644 --- a/framework/src/main/java/cn/lili/modules/goods/serviceimpl/ParametersServiceImpl.java +++ b/framework/src/main/java/cn/lili/modules/goods/serviceimpl/ParametersServiceImpl.java @@ -63,19 +63,20 @@ public class ParametersServiceImpl extends ServiceImpl> goodsList = this.goodsService.listMaps(queryWrapper); - for (Map goods : goodsList) { - String params = (String) goods.get("params"); - List goodsParamsDTOS = JSONUtil.toList(params, GoodsParamsDTO.class); - List goodsParamsDTOList = goodsParamsDTOS.stream().filter(i -> i.getGroupId() != null && i.getGroupId().equals(parameters.getGroupId())).collect(Collectors.toList()); - this.setGoodsItemDTOList(goodsParamsDTOList, parameters); - this.goodsService.updateGoodsParams(goods.get("id").toString(), JSONUtil.toJsonStr(goodsParamsDTOS)); - goodsIds.add(goods.get("id").toString()); + if (!goodsList.isEmpty()) { + for (Map goods : goodsList) { + String params = (String) goods.get("params"); + List goodsParamsDTOS = JSONUtil.toList(params, GoodsParamsDTO.class); + List goodsParamsDTOList = goodsParamsDTOS.stream().filter(i -> i.getGroupId() != null && i.getGroupId().equals(parameters.getGroupId())).collect(Collectors.toList()); + this.setGoodsItemDTOList(goodsParamsDTOList, parameters); + this.goodsService.updateGoodsParams(goods.get("id").toString(), JSONUtil.toJsonStr(goodsParamsDTOS)); + goodsIds.add(goods.get("id").toString()); + } + + String destination = rocketmqCustomProperties.getGoodsTopic() + ":" + GoodsTagsEnum.UPDATE_GOODS_INDEX.name(); + //发送mq消息 + rocketMQTemplate.asyncSend(destination, JSONUtil.toJsonStr(goodsIds), RocketmqSendCallbackBuilder.commonCallback()); } - - - String destination = rocketmqCustomProperties.getGoodsTopic() + ":" + GoodsTagsEnum.UPDATE_GOODS_INDEX.name(); - //发送mq消息 - rocketMQTemplate.asyncSend(destination, JSONUtil.toJsonStr(goodsIds), RocketmqSendCallbackBuilder.commonCallback()); return this.updateById(parameters); } diff --git a/framework/src/main/java/cn/lili/modules/search/entity/dos/EsGoodsIndex.java b/framework/src/main/java/cn/lili/modules/search/entity/dos/EsGoodsIndex.java index 9d0bd4d0..80e5db4a 100644 --- a/framework/src/main/java/cn/lili/modules/search/entity/dos/EsGoodsIndex.java +++ b/framework/src/main/java/cn/lili/modules/search/entity/dos/EsGoodsIndex.java @@ -259,6 +259,12 @@ public class EsGoodsIndex implements Serializable { @ApiModelProperty(value = "商品类型", required = true) private String goodsType; + /** + * @see cn.lili.modules.goods.entity.enums.GoodsTypeEnum + */ + @ApiModelProperty(value = "商品sku基础分数", required = true) + private Integer skuSource; + /** * 商品属性(参数和规格) */ @@ -289,7 +295,7 @@ public class EsGoodsIndex implements Serializable { this.categoryPath = sku.getCategoryPath(); this.goodsVideo = sku.getGoodsVideo(); this.mobileIntro = sku.getMobileIntro(); - this.buyCount = sku.getBuyCount(); + this.buyCount = sku.getBuyCount() != null ? sku.getBuyCount() : 0; this.commentNum = sku.getCommentNum(); this.small = sku.getSmall(); this.brandId = sku.getBrandId(); @@ -302,6 +308,7 @@ public class EsGoodsIndex implements Serializable { this.isAuth = sku.getIsAuth(); this.intro = sku.getIntro(); this.grade = sku.getGrade(); + this.recommend = sku.getRecommend(); this.releaseTime = new Date(); } } diff --git a/framework/src/main/java/cn/lili/modules/search/serviceimpl/EsGoodsIndexServiceImpl.java b/framework/src/main/java/cn/lili/modules/search/serviceimpl/EsGoodsIndexServiceImpl.java index 4c343c42..7bead3dd 100644 --- a/framework/src/main/java/cn/lili/modules/search/serviceimpl/EsGoodsIndexServiceImpl.java +++ b/framework/src/main/java/cn/lili/modules/search/serviceimpl/EsGoodsIndexServiceImpl.java @@ -126,48 +126,30 @@ public class EsGoodsIndexServiceImpl extends BaseElasticsearchService implements queryWrapper.eq(GoodsSku::getIsAuth, GoodsAuthEnum.PASS.name()); queryWrapper.eq(GoodsSku::getMarketEnable, GoodsStatusEnum.UPPER.name()); - List list = goodsSkuService.list(queryWrapper); List esGoodsIndices = new ArrayList<>(); - //库存锁是在redis做的,所以生成索引,同时更新一下redis中的库存数量 - for (GoodsSku goodsSku : list) { - Goods goods = goodsService.getById(goodsSku.getGoodsId()); - //如果出现极端情况,有sku,没有与之匹配的商品,则跳过 - if (goods == null) { - continue; - } - EsGoodsIndex index = new EsGoodsIndex(goodsSku); - //商品参数索引 - if (goods.getParams() != null && !goods.getParams().isEmpty()) { - List goodsParamDTOS = JSONUtil.toList(goods.getParams(), GoodsParamsDTO.class); - index = new EsGoodsIndex(goodsSku, goodsParamDTOS); + LambdaQueryWrapper goodsQueryWrapper = new LambdaQueryWrapper<>(); + goodsQueryWrapper.eq(Goods::getIsAuth, GoodsAuthEnum.PASS.name()); + goodsQueryWrapper.eq(Goods::getMarketEnable, GoodsStatusEnum.UPPER.name()); + + for (Goods goods : goodsService.list(goodsQueryWrapper)) { + LambdaQueryWrapper skuQueryWrapper = new LambdaQueryWrapper<>(); + skuQueryWrapper.eq(GoodsSku::getGoodsId, goods.getId()); + skuQueryWrapper.eq(GoodsSku::getIsAuth, GoodsAuthEnum.PASS.name()); + skuQueryWrapper.eq(GoodsSku::getMarketEnable, GoodsStatusEnum.UPPER.name()); + + List goodsSkuList = goodsSkuService.list(skuQueryWrapper); + int skuSource = 100; + for (GoodsSku goodsSku : goodsSkuList) { + EsGoodsIndex esGoodsIndex = wrapperEsGoodsIndex(goodsSku, goods); + esGoodsIndex.setSkuSource(skuSource--); + esGoodsIndices.add(esGoodsIndex); + //库存锁是在redis做的,所以生成索引,同时更新一下redis中的库存数量 + cache.put(GoodsSkuService.getStockCacheKey(goodsSku.getId()), goodsSku.getQuantity()); } - //商品分类索引 - if (goods.getCategoryPath() != null) { - List categories = categoryService.listByIdsOrderByLevel(Arrays.asList(goods.getCategoryPath().split(","))); - if (!categories.isEmpty()) { - index.setCategoryNamePath(ArrayUtil.join(categories.stream().map(Category::getName).toArray(), ",")); - } - } - //商品品牌索引 - Brand brand = brandService.getById(goods.getBrandId()); - if (brand != null) { - index.setBrandName(brand.getName()); - index.setBrandUrl(brand.getLogo()); - } - //店铺分类索引 - if (goods.getStoreCategoryPath() != null && CharSequenceUtil.isNotEmpty(goods.getStoreCategoryPath())) { - List storeGoodsLabels = storeGoodsLabelService.listByStoreIds(Arrays.asList(goods.getStoreCategoryPath().split(","))); - if (!storeGoodsLabels.isEmpty()) { - index.setStoreCategoryNamePath(ArrayUtil.join(storeGoodsLabels.stream().map(StoreGoodsLabel::getLabelName).toArray(), ",")); - } - } - //促销索引 - Map goodsCurrentPromotionMap = promotionService.getGoodsCurrentPromotionMap(index); - index.setPromotionMap(goodsCurrentPromotionMap); - esGoodsIndices.add(index); - cache.put(GoodsSkuService.getStockCacheKey(goodsSku.getId()), goodsSku.getQuantity()); + } + //初始化商品索引 this.initIndex(esGoodsIndices); } catch (Exception e) { @@ -663,6 +645,40 @@ public class EsGoodsIndexServiceImpl extends BaseElasticsearchService implements return elasticsearchProperties.getIndexPrefix() + "_" + EsSuffix.GOODS_INDEX_NAME; } + private EsGoodsIndex wrapperEsGoodsIndex(GoodsSku goodsSku, Goods goods) { + EsGoodsIndex index = new EsGoodsIndex(goodsSku); + + //商品参数索引 + if (goods.getParams() != null && !goods.getParams().isEmpty()) { + List goodsParamDTOS = JSONUtil.toList(goods.getParams(), GoodsParamsDTO.class); + index = new EsGoodsIndex(goodsSku, goodsParamDTOS); + } + //商品分类索引 + if (goods.getCategoryPath() != null) { + List categories = categoryService.listByIdsOrderByLevel(Arrays.asList(goods.getCategoryPath().split(","))); + if (!categories.isEmpty()) { + index.setCategoryNamePath(ArrayUtil.join(categories.stream().map(Category::getName).toArray(), ",")); + } + } + //商品品牌索引 + Brand brand = brandService.getById(goods.getBrandId()); + if (brand != null) { + index.setBrandName(brand.getName()); + index.setBrandUrl(brand.getLogo()); + } + //店铺分类索引 + if (goods.getStoreCategoryPath() != null && CharSequenceUtil.isNotEmpty(goods.getStoreCategoryPath())) { + List storeGoodsLabels = storeGoodsLabelService.listByStoreIds(Arrays.asList(goods.getStoreCategoryPath().split(","))); + if (!storeGoodsLabels.isEmpty()) { + index.setStoreCategoryNamePath(ArrayUtil.join(storeGoodsLabels.stream().map(StoreGoodsLabel::getLabelName).toArray(), ",")); + } + } + //促销索引 + Map goodsCurrentPromotionMap = promotionService.getGoodsCurrentPromotionMap(index); + index.setPromotionMap(goodsCurrentPromotionMap); + return index; + } + private ActionListener actionListener() { return new ActionListener() { @Override diff --git a/framework/src/main/java/cn/lili/modules/search/serviceimpl/EsGoodsSearchServiceImpl.java b/framework/src/main/java/cn/lili/modules/search/serviceimpl/EsGoodsSearchServiceImpl.java index efd86c71..8d5bef53 100644 --- a/framework/src/main/java/cn/lili/modules/search/serviceimpl/EsGoodsSearchServiceImpl.java +++ b/framework/src/main/java/cn/lili/modules/search/serviceimpl/EsGoodsSearchServiceImpl.java @@ -17,11 +17,15 @@ import cn.lili.modules.search.entity.dto.SelectorOptions; import cn.lili.modules.search.service.EsGoodsSearchService; import lombok.extern.slf4j.Slf4j; import org.apache.lucene.search.join.ScoreMode; +import org.elasticsearch.common.lucene.search.function.FieldValueFactorFunction; import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery; import org.elasticsearch.index.query.BoolQueryBuilder; +import org.elasticsearch.index.query.MatchQueryBuilder; import org.elasticsearch.index.query.Operator; import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.index.query.functionscore.FieldValueFactorFunctionBuilder; import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder; +import org.elasticsearch.index.query.functionscore.GaussDecayFunctionBuilder; import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders; import org.elasticsearch.search.aggregations.*; import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested; @@ -38,7 +42,7 @@ import org.springframework.data.elasticsearch.core.SearchHits; import org.springframework.data.elasticsearch.core.SearchPage; import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; -import org.springframework.data.redis.core.DefaultTypedTuple; +import org.springframework.data.redis.core.ZSetOperations; import org.springframework.stereotype.Service; import java.util.*; @@ -58,6 +62,8 @@ public class EsGoodsSearchServiceImpl implements EsGoodsSearchService { private static final String ATTR_NAME = "attrList.name"; private static final String ATTR_SORT = "attrList.sort"; private static final String ATTR_BRAND_ID = "brandId"; + private static final String ATTR_BRAND_NAME = "brandNameAgg"; + private static final String ATTR_BRAND_URL = "brandUrlAgg"; private static final String ATTR_NAME_KEY = "nameList"; private static final String ATTR_VALUE_KEY = "valueList"; /** @@ -69,14 +75,14 @@ public class EsGoodsSearchServiceImpl implements EsGoodsSearchService { * 缓存 */ @Autowired - private Cache cache; + private Cache cache; @Override public SearchPage searchGoods(EsGoodsSearchDTO searchDTO, PageVO pageVo) { if (CharSequenceUtil.isNotEmpty(searchDTO.getKeyword())) { cache.incrementScore(CachePrefix.HOT_WORD.getPrefix(), searchDTO.getKeyword()); } - NativeSearchQueryBuilder searchQueryBuilder = createSearchQueryBuilder(searchDTO, pageVo, true); + NativeSearchQueryBuilder searchQueryBuilder = createSearchQueryBuilder(searchDTO, pageVo); NativeSearchQuery searchQuery = searchQueryBuilder.build(); log.info("searchGoods DSL:{}", searchQuery.getQuery()); SearchHits search = restTemplate.search(searchQuery, EsGoodsIndex.class); @@ -91,11 +97,11 @@ public class EsGoodsSearchServiceImpl implements EsGoodsSearchService { List hotWords = new ArrayList<>(); // redis 排序中,下标从0开始,所以这里需要 -1 处理 count = count - 1; - Set> set = cache.reverseRangeWithScores(CachePrefix.HOT_WORD.getPrefix(), count); + Set> set = cache.reverseRangeWithScores(CachePrefix.HOT_WORD.getPrefix(), count); if (set == null || set.isEmpty()) { return new ArrayList<>(); } - for (DefaultTypedTuple defaultTypedTuple : set) { + for (ZSetOperations.TypedTuple defaultTypedTuple : set) { hotWords.add(Objects.requireNonNull(defaultTypedTuple.getValue()).toString()); } return hotWords; @@ -108,15 +114,15 @@ public class EsGoodsSearchServiceImpl implements EsGoodsSearchService { @Override public EsGoodsRelatedInfo getSelector(EsGoodsSearchDTO goodsSearch, PageVO pageVo) { - NativeSearchQueryBuilder builder = createSearchQueryBuilder(goodsSearch, null, true); + NativeSearchQueryBuilder builder = createSearchQueryBuilder(goodsSearch, null); //分类 AggregationBuilder categoryNameBuilder = AggregationBuilders.terms("categoryNameAgg").field("categoryNamePath.keyword"); builder.addAggregation(AggregationBuilders.terms("categoryAgg").field("categoryPath").subAggregation(categoryNameBuilder)); //品牌 - AggregationBuilder brandNameBuilder = AggregationBuilders.terms("brandNameAgg").field("brandName.keyword"); + AggregationBuilder brandNameBuilder = AggregationBuilders.terms(ATTR_BRAND_NAME).field("brandName.keyword"); builder.addAggregation(AggregationBuilders.terms("brandIdNameAgg").field(ATTR_BRAND_ID).size(Integer.MAX_VALUE).subAggregation(brandNameBuilder)); - AggregationBuilder brandUrlBuilder = AggregationBuilders.terms("brandUrlAgg").field("brandUrl.keyword"); + AggregationBuilder brandUrlBuilder = AggregationBuilders.terms(ATTR_BRAND_URL).field("brandUrl.keyword"); builder.addAggregation(AggregationBuilders.terms("brandIdUrlAgg").field(ATTR_BRAND_ID).size(Integer.MAX_VALUE).subAggregation(brandUrlBuilder)); //参数 AggregationBuilder valuesBuilder = AggregationBuilders.terms("valueAgg").field(ATTR_VALUE); @@ -152,29 +158,7 @@ public class EsGoodsSearchServiceImpl implements EsGoodsSearchService { ParsedStringTerms categoryTerms = (ParsedStringTerms) aggregationMap.get("categoryAgg"); List categoryBuckets = categoryTerms.getBuckets(); if (categoryBuckets != null && !categoryBuckets.isEmpty()) { - for (Terms.Bucket categoryBucket : categoryBuckets) { - String categoryPath = categoryBucket.getKey().toString(); - ParsedStringTerms categoryNameAgg = categoryBucket.getAggregations().get("categoryNameAgg"); - List categoryNameBuckets = categoryNameAgg.getBuckets(); - - - String categoryNamePath = categoryPath; - if (!categoryNameBuckets.isEmpty()) { - categoryNamePath = categoryNameBuckets.get(0).getKey().toString(); - } - String[] split = ArrayUtil.distinct(categoryPath.split(",")); - String[] nameSplit = categoryNamePath.split(","); - if (split.length == nameSplit.length) { - for (int i = 0; i < split.length; i++) { - SelectorOptions so = new SelectorOptions(); - so.setName(nameSplit[i]); - so.setValue(split[i]); - if (!categoryOptions.contains(so)) { - categoryOptions.add(so); - } - } - } - } + categoryOptions = this.convertCategoryOptions(categoryBuckets); } esGoodsRelatedInfo.setCategories(categoryOptions); @@ -185,46 +169,7 @@ public class EsGoodsSearchServiceImpl implements EsGoodsSearchService { List brandUrlBuckets = brandUrlTerms.getBuckets(); List brandOptions = new ArrayList<>(); if (brandBuckets != null && !brandBuckets.isEmpty()) { - for (int i = 0; i < brandBuckets.size(); i++) { - String brandId = brandBuckets.get(i).getKey().toString(); - //当商品品牌id为0时,代表商品没有选择品牌,所以过滤掉品牌选择器 - if (brandId.equals("0")) { - continue; - } - if (CharSequenceUtil.isNotEmpty(goodsSearch.getBrandId())) { - List brandList = Arrays.asList(goodsSearch.getBrandId().split("@")); - if (brandList.contains(brandId)) { - continue; - } - } - - String brandName = ""; - if (brandBuckets.get(i).getAggregations() != null && brandBuckets.get(i).getAggregations().get("brandNameAgg") != null) { - ParsedStringTerms brandNameAgg = brandBuckets.get(i).getAggregations().get("brandNameAgg"); - List categoryNameBuckets = brandNameAgg.getBuckets(); - if (categoryNameBuckets != null && !categoryNameBuckets.isEmpty()) { - brandName = categoryNameBuckets.get(0).getKey().toString(); - } - } - - String brandUrl = ""; - if (brandUrlBuckets != null && !brandUrlBuckets.isEmpty() && - brandUrlBuckets.get(i).getAggregations() != null && - brandUrlBuckets.get(i).getAggregations().get("brandUrlAgg") != null) { - - ParsedStringTerms brandUrlAgg = brandUrlBuckets.get(i).getAggregations().get("brandUrlAgg"); - List categoryUrlBuckets = brandUrlAgg.getBuckets(); - if (categoryUrlBuckets != null && !categoryUrlBuckets.isEmpty()) { - brandUrl = categoryUrlBuckets.get(0).getKey().toString(); - } - - } - SelectorOptions so = new SelectorOptions(); - so.setName(brandName); - so.setValue(brandId); - so.setUrl(brandUrl); - brandOptions.add(so); - } + brandOptions = this.convertBrandOptions(goodsSearch, brandBuckets, brandUrlBuckets); } esGoodsRelatedInfo.setBrands(brandOptions); @@ -232,72 +177,167 @@ public class EsGoodsSearchServiceImpl implements EsGoodsSearchService { ParsedNested attrTerms = (ParsedNested) aggregationMap.get("attrAgg"); if (!goodsSearch.getNotShowCol().isEmpty()) { if (goodsSearch.getNotShowCol().containsKey(ATTR_NAME_KEY) && goodsSearch.getNotShowCol().containsKey(ATTR_VALUE_KEY)) { - esGoodsRelatedInfo.setParamOptions(buildGoodsParam(attrTerms, goodsSearch.getNotShowCol().get(ATTR_NAME_KEY), goodsSearch.getNotShowCol().get(ATTR_VALUE_KEY))); + esGoodsRelatedInfo.setParamOptions(buildGoodsParam(attrTerms, goodsSearch.getNotShowCol().get(ATTR_NAME_KEY))); } } else { - esGoodsRelatedInfo.setParamOptions(buildGoodsParam(attrTerms, null, null)); + esGoodsRelatedInfo.setParamOptions(buildGoodsParam(attrTerms, null)); } return esGoodsRelatedInfo; } + /** + * 将品牌聚合结果转换品牌选择项 + * + * @param goodsSearch 查询参数 + * @param brandBuckets 品牌聚合结果桶 + * @param brandUrlBuckets 品牌地址聚合结果桶 + * @return 品牌选择项列表 + */ + private List convertBrandOptions(EsGoodsSearchDTO goodsSearch, List brandBuckets, List brandUrlBuckets) { + List brandOptions = new ArrayList<>(); + for (int i = 0; i < brandBuckets.size(); i++) { + String brandId = brandBuckets.get(i).getKey().toString(); + //当商品品牌id为0时,代表商品没有选择品牌,所以过滤掉品牌选择器 + if (brandId.equals("0") || + (CharSequenceUtil.isNotEmpty(goodsSearch.getBrandId()) + && Arrays.asList(goodsSearch.getBrandId().split("@")).contains(brandId))) { + continue; + } + + String brandName = ""; + if (brandBuckets.get(i).getAggregations() != null && brandBuckets.get(i).getAggregations().get(ATTR_BRAND_NAME) != null) { + brandName = this.getAggregationsBrandOptions(brandBuckets.get(i).getAggregations().get(ATTR_BRAND_NAME)); + } + + String brandUrl = ""; + if (brandUrlBuckets != null && !brandUrlBuckets.isEmpty() && + brandUrlBuckets.get(i).getAggregations() != null && + brandUrlBuckets.get(i).getAggregations().get(ATTR_BRAND_URL) != null) { + brandUrl = this.getAggregationsBrandOptions(brandUrlBuckets.get(i).getAggregations().get(ATTR_BRAND_URL)); + } + SelectorOptions so = new SelectorOptions(); + so.setName(brandName); + so.setValue(brandId); + so.setUrl(brandUrl); + brandOptions.add(so); + } + return brandOptions; + } + + /** + * 获取品牌聚合结果内的参数 + * + * @param brandAgg 品牌聚合结果 + * @return 品牌聚合结果内的参数 + */ + private String getAggregationsBrandOptions(ParsedStringTerms brandAgg) { + List brandAggBuckets = brandAgg.getBuckets(); + if (brandAggBuckets != null && !brandAggBuckets.isEmpty()) { + return brandAggBuckets.get(0).getKey().toString(); + } + return ""; + } + + + /** + * 将分类聚合结果转换分类选择项 + * + * @param categoryBuckets 分类聚合结果 + * @return 分类选择项集合 + */ + private List convertCategoryOptions(List categoryBuckets) { + List categoryOptions = new ArrayList<>(); + for (Terms.Bucket categoryBucket : categoryBuckets) { + String categoryPath = categoryBucket.getKey().toString(); + ParsedStringTerms categoryNameAgg = categoryBucket.getAggregations().get("categoryNameAgg"); + List categoryNameBuckets = categoryNameAgg.getBuckets(); + + + String categoryNamePath = categoryPath; + if (!categoryNameBuckets.isEmpty()) { + categoryNamePath = categoryNameBuckets.get(0).getKey().toString(); + } + String[] split = ArrayUtil.distinct(categoryPath.split(",")); + String[] nameSplit = categoryNamePath.split(","); + if (split.length == nameSplit.length) { + for (int i = 0; i < split.length; i++) { + SelectorOptions so = new SelectorOptions(); + so.setName(nameSplit[i]); + so.setValue(split[i]); + if (!categoryOptions.contains(so)) { + categoryOptions.add(so); + } + } + } + } + return categoryOptions; + } + /** * 构建商品参数信息 * * @param attrTerms 商品参数搜索结果 * @param nameList 查询的规格名 - * @param valueList 查询的规格项 * @return 商品参数信息 */ - private List buildGoodsParam(ParsedNested attrTerms, List nameList, List valueList) { - List paramOptions = new ArrayList<>(); + private List buildGoodsParam(ParsedNested attrTerms, List nameList) { if (attrTerms != null) { Aggregations attrAggregations = attrTerms.getAggregations(); Map attrMap = attrAggregations.getAsMap(); ParsedStringTerms nameAgg = (ParsedStringTerms) attrMap.get("nameAgg"); if (nameAgg != null) { - List nameBuckets = nameAgg.getBuckets(); - - for (Terms.Bucket bucket : nameBuckets) { - String name = bucket.getKey().toString(); - ParamOptions paramOptions1 = new ParamOptions(); - ParsedStringTerms valueAgg = bucket.getAggregations().get("valueAgg"); - List valueBuckets = valueAgg.getBuckets(); - List valueSelectorList = new ArrayList<>(); - - for (Terms.Bucket valueBucket : valueBuckets) { - String value = valueBucket.getKey().toString(); - - if (CharSequenceUtil.isNotEmpty(value)) { - valueSelectorList.add(value); - } - - } - if (nameList == null || !nameList.contains(name)) { - paramOptions1.setKey(name); - paramOptions1.setValues(valueSelectorList); - paramOptions.add(paramOptions1); - } - } - - + return this.buildGoodsParamOptions(nameAgg, nameList); } } + return new ArrayList<>(); + } + + /** + * 构造商品参数属性 + * + * @param nameAgg 商品参数聚合内容 + * @param nameList 查询的规格名 + * @return 商品参数属性集合 + */ + private List buildGoodsParamOptions(ParsedStringTerms nameAgg, List nameList) { + List paramOptions = new ArrayList<>(); + List nameBuckets = nameAgg.getBuckets(); + + for (Terms.Bucket bucket : nameBuckets) { + String name = bucket.getKey().toString(); + ParamOptions paramOptions1 = new ParamOptions(); + ParsedStringTerms valueAgg = bucket.getAggregations().get("valueAgg"); + List valueBuckets = valueAgg.getBuckets(); + List valueSelectorList = new ArrayList<>(); + + for (Terms.Bucket valueBucket : valueBuckets) { + String value = valueBucket.getKey().toString(); + + if (CharSequenceUtil.isNotEmpty(value)) { + valueSelectorList.add(value); + } + + } + if (nameList == null || !nameList.contains(name)) { + paramOptions1.setKey(name); + paramOptions1.setValues(valueSelectorList); + paramOptions.add(paramOptions1); + } + } return paramOptions; } - /** * 创建es搜索builder * - * @param searchDTO 搜索条件 - * @param pageVo 分页参数 - * @param isAggregation 是否是聚合查询 + * @param searchDTO 搜索条件 + * @param pageVo 分页参数 * @return es搜索builder */ - private NativeSearchQueryBuilder createSearchQueryBuilder(EsGoodsSearchDTO searchDTO, PageVO pageVo, boolean isAggregation) { + private NativeSearchQueryBuilder createSearchQueryBuilder(EsGoodsSearchDTO searchDTO, PageVO pageVo) { NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder(); if (pageVo != null) { int pageNumber = pageVo.getPageNumber() - 1; @@ -312,11 +352,9 @@ public class EsGoodsSearchServiceImpl implements EsGoodsSearchService { if (searchDTO != null) { //过滤条件 BoolQueryBuilder filterBuilder = QueryBuilders.boolQuery(); - //查询条件 - BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); //对查询条件进行处理 - this.commonSearch(filterBuilder, queryBuilder, searchDTO, isAggregation); + this.commonSearch(filterBuilder, searchDTO); //未上架的商品不显示 filterBuilder.must(QueryBuilders.matchQuery("marketEnable", GoodsStatusEnum.UPPER.name())); @@ -328,16 +366,12 @@ public class EsGoodsSearchServiceImpl implements EsGoodsSearchService { if (CharSequenceUtil.isEmpty(searchDTO.getKeyword())) { nativeSearchQueryBuilder.withQuery(QueryBuilders.matchAllQuery()); } else { - this.keywordSearch(filterBuilder, queryBuilder, searchDTO.getKeyword(), isAggregation); + this.keywordSearch(filterBuilder, searchDTO.getKeyword()); } //如果是聚合查询 - if (isAggregation) { - nativeSearchQueryBuilder.withQuery(filterBuilder); - } else { - nativeSearchQueryBuilder.withQuery(queryBuilder); - nativeSearchQueryBuilder.withFilter(filterBuilder); - } + + nativeSearchQueryBuilder.withQuery(filterBuilder); if (pageVo != null && CharSequenceUtil.isNotEmpty(pageVo.getOrder()) && CharSequenceUtil.isNotEmpty(pageVo.getSort())) { @@ -354,11 +388,9 @@ public class EsGoodsSearchServiceImpl implements EsGoodsSearchService { * 查询属性处理 * * @param filterBuilder 过滤构造器 - * @param queryBuilder 查询构造器 * @param searchDTO 查询参数 - * @param isAggregation 是否为聚合查询 */ - private void commonSearch(BoolQueryBuilder filterBuilder, BoolQueryBuilder queryBuilder, EsGoodsSearchDTO searchDTO, boolean isAggregation) { + private void commonSearch(BoolQueryBuilder filterBuilder, EsGoodsSearchDTO searchDTO) { //品牌判定 if (CharSequenceUtil.isNotEmpty(searchDTO.getBrandId())) { String[] brands = searchDTO.getBrandId().split("@"); @@ -385,46 +417,7 @@ public class EsGoodsSearchServiceImpl implements EsGoodsSearchService { } //属性判定 if (CharSequenceUtil.isNotEmpty(searchDTO.getProp())) { - String[] props = searchDTO.getProp().split("@"); - List nameList = new ArrayList<>(); - List valueList = new ArrayList<>(); - Map> valueMap = new HashMap<>(16); - for (String prop : props) { - String[] propValues = prop.split("_"); - String name = propValues[0]; - String value = propValues[1]; - if (!nameList.contains(name)) { - nameList.add(name); - } - if (!valueList.contains(value)) { - valueList.add(value); - } - //将同一规格名下的规格值分组 - if (!valueMap.containsKey(name)) { - List values = new ArrayList<>(); - values.add(value); - valueMap.put(name, values); - } else { - valueMap.get(name).add(value); - } - } - BoolQueryBuilder usedQueryBuilder; - if (isAggregation) { - usedQueryBuilder = filterBuilder; - } else { - usedQueryBuilder = queryBuilder; - } - //遍历所有的规格 - for (Map.Entry> entry : valueMap.entrySet()) { - usedQueryBuilder.must(QueryBuilders.nestedQuery(ATTR_PATH, QueryBuilders.wildcardQuery(ATTR_NAME, "*" + entry.getKey() + "*"), ScoreMode.None)); - BoolQueryBuilder shouldBuilder = QueryBuilders.boolQuery(); - for (String s : entry.getValue()) { - shouldBuilder.should(QueryBuilders.nestedQuery(ATTR_PATH, QueryBuilders.wildcardQuery(ATTR_VALUE, "*" + s + "*"), ScoreMode.None)); - } - usedQueryBuilder.must(shouldBuilder); - } - searchDTO.getNotShowCol().put(ATTR_NAME_KEY, nameList); - searchDTO.getNotShowCol().put(ATTR_VALUE_KEY, valueList); + this.propSearch(filterBuilder, searchDTO); } //价格区间判定 if (CharSequenceUtil.isNotEmpty(searchDTO.getPrice())) { @@ -442,47 +435,98 @@ public class EsGoodsSearchServiceImpl implements EsGoodsSearchService { } } + /** + * 商品参数查询处理 + * + * @param filterBuilder 过滤构造器 + * @param searchDTO 查询参数 + */ + private void propSearch(BoolQueryBuilder filterBuilder, EsGoodsSearchDTO searchDTO) { + String[] props = searchDTO.getProp().split("@"); + List nameList = new ArrayList<>(); + List valueList = new ArrayList<>(); + Map> valueMap = new HashMap<>(16); + for (String prop : props) { + String[] propValues = prop.split("_"); + String name = propValues[0]; + String value = propValues[1]; + if (!nameList.contains(name)) { + nameList.add(name); + } + if (!valueList.contains(value)) { + valueList.add(value); + } + //将同一规格名下的规格值分组 + if (!valueMap.containsKey(name)) { + List values = new ArrayList<>(); + values.add(value); + valueMap.put(name, values); + } else { + valueMap.get(name).add(value); + } + } + //遍历所有的规格 + for (Map.Entry> entry : valueMap.entrySet()) { + filterBuilder.must(QueryBuilders.nestedQuery(ATTR_PATH, QueryBuilders.matchQuery(ATTR_NAME, entry.getKey()), ScoreMode.None)); + BoolQueryBuilder shouldBuilder = QueryBuilders.boolQuery(); + for (String s : entry.getValue()) { + shouldBuilder.should(QueryBuilders.nestedQuery(ATTR_PATH, QueryBuilders.matchQuery(ATTR_VALUE, s), ScoreMode.None)); + } + filterBuilder.must(shouldBuilder); + } + searchDTO.getNotShowCol().put(ATTR_NAME_KEY, nameList); + searchDTO.getNotShowCol().put(ATTR_VALUE_KEY, valueList); + } + /** * 关键字查询处理 * * @param filterBuilder 过滤构造器 - * @param queryBuilder 查询构造器 * @param keyword 关键字 - * @param isAggregation 是否为聚合查询 */ - private void keywordSearch(BoolQueryBuilder filterBuilder, BoolQueryBuilder queryBuilder, String keyword, boolean isAggregation) { + private void keywordSearch(BoolQueryBuilder filterBuilder, String keyword) { List filterFunctionBuilders = new ArrayList<>(); if (keyword.contains(" ")) { for (String s : keyword.split(" ")) { - filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("goodsName", s).operator(Operator.AND), - ScoreFunctionBuilders.weightFactorFunction(10))); - //属性匹配 - filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.nestedQuery(ATTR_PATH, QueryBuilders.wildcardQuery(ATTR_VALUE, "*" + s + "*"), ScoreMode.None), - ScoreFunctionBuilders.weightFactorFunction(8))); + filterFunctionBuilders.addAll(this.buildKeywordSearch(s)); } } else { - //分词匹配 - filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("goodsName", keyword).operator(Operator.AND), - ScoreFunctionBuilders.weightFactorFunction(10))); - //属性匹配 - filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.nestedQuery(ATTR_PATH, QueryBuilders.wildcardQuery(ATTR_VALUE, "*" + keyword + "*"), ScoreMode.None), - ScoreFunctionBuilders.weightFactorFunction(8))); + filterFunctionBuilders = this.buildKeywordSearch(keyword); } - FunctionScoreQueryBuilder.FilterFunctionBuilder[] builders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[filterFunctionBuilders.size()]; filterFunctionBuilders.toArray(builders); FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(builders) .scoreMode(FunctionScoreQuery.ScoreMode.SUM) .setMinScore(2); //聚合搜索则将结果放入过滤条件 - if (isAggregation) { - filterBuilder.must(functionScoreQueryBuilder); - } - //否则放入查询条件 - else { - queryBuilder.must(functionScoreQueryBuilder); - } + filterBuilder.must(functionScoreQueryBuilder); + } + + /** + * 构造关键字查询 + * + * @param keyword 关键字 + * @return 构造查询的集合 + */ + private List buildKeywordSearch(String keyword) { + List filterFunctionBuilders = new ArrayList<>(); + MatchQueryBuilder goodsNameQuery = QueryBuilders.matchQuery("goodsName", keyword).operator(Operator.AND); + //分词匹配 + filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(goodsNameQuery, + ScoreFunctionBuilders.weightFactorFunction(10))); + //属性匹配 + filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.nestedQuery(ATTR_PATH, QueryBuilders.wildcardQuery(ATTR_VALUE, "*" + keyword + "*"), ScoreMode.None), + ScoreFunctionBuilders.weightFactorFunction(8))); + + GaussDecayFunctionBuilder skuNoScore = ScoreFunctionBuilders.gaussDecayFunction("skuSource", 100, 10).setWeight(7); + FunctionScoreQueryBuilder.FilterFunctionBuilder skuNoBuilder = new FunctionScoreQueryBuilder.FilterFunctionBuilder(goodsNameQuery, skuNoScore); + filterFunctionBuilders.add(skuNoBuilder); + + FieldValueFactorFunctionBuilder buyCountScore = ScoreFunctionBuilders.fieldValueFactorFunction("buyCount").modifier(FieldValueFactorFunction.Modifier.LOG1P).setWeight(6); + FunctionScoreQueryBuilder.FilterFunctionBuilder buyCountBuilder = new FunctionScoreQueryBuilder.FilterFunctionBuilder(goodsNameQuery, buyCountScore); + filterFunctionBuilders.add(buyCountBuilder); + return filterFunctionBuilders; } }