乐优商城 Day8 (非教程,商品过滤实现)

乐优商城学习Day08:

注意:此次代码都是在第七天的基础上
第七天的链接如下:
https://blog.csdn.net/zcylxzyh/article/details/100637514

此次笔记内容主要为:
1.给出完整的search.html代码
2.实现过滤分类和品牌和实现过滤规格参数(后台)

下面开始第八天的学习:


1.给出完整的search.html代码

此次页面为search.html,本次所有操作均在此页面进行,但我们主要关注后台就直接给出完整的search.html。
包括了过滤,分类等的页面渲染。
在这里插入图片描述

<!DOCTYPE html>
<html xmlns:v-bind="http://www.w3.org/1999/xhtml">

<head>
    <meta charset="utf-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7"/>
    <title>乐优商城--商品搜索结果页</title>
    <link rel="icon" href="assets/img/favicon.ico">
    <link href='./css/material.css' rel="stylesheet">
    <link href="./css/vuetify.min.css" rel="stylesheet">
    <script src="./js/vue/vue.js"></script>
    <script src="./js/vue/vuetify.js"></script>
    <script src="./js/axios.min.js"></script>
    <script src="./js/common.js"></script>
    <link rel="stylesheet" type="text/css" href="css/webbase.css"/>
    <link rel="stylesheet" type="text/css" href="css/pages-list.css"/>
    <link rel="stylesheet" type="text/css" href="css/widget-cartPanelView.css"/>
    <style type="text/css">
        * {
     
     
            box-sizing: unset;
        }

        .btn-arrow, .btn-arrow:visited, .btn-arrow:link, .btn-arrow:active {
     
     
            width: 46px;
            height: 23px;
            border: 1px solid #DDD;
            background: #FFF;
            line-height: 23px;
            font-family: "\5b8b\4f53";
            text-align: center;
            font-size: 16px;
            color: #AAA;
            text-decoration: none;
            out-line: none
        }

        .btn-arrow:hover {
     
     
            background-color: #1299ec;
            color: whitesmoke;
        }

        .top-pagination {
     
     
            display: block;
            padding: 3px 15px;
            font-size: 11px;
            font-weight: 700;
            line-height: 18px;
            color: #999;
            text-shadow: 0 1px 0 rgba(255, 255, 255, .5);
            text-transform: uppercase;
            float: right;
            margin-top: 6px
        }

        .top-pagination span {
     
     
            margin-right: 10px;
        }

        .logo-list li {
     
     
            padding: 8px;
        }

        .logo-list li:hover {
     
     
            background-color: #f3f3f3;
        }

        .type-list a:hover {
     
     
            color: #1299ec;
        }

        .skus {
     
     
            list-style: none;
        }

        .skus li {
     
     
            list-style: none;
            display: inline-block;
            float: left;
            margin-left: 2px;
            border: 2px solid #f3f3f3;
        }

        .skus li.selected {
     
     
            border: 2px solid #dd1144;
        }

        .skus img {
     
     
            width: 25px;
            height: 25px;
        }
    </style>
    <script type="text/javascript" src="plugins/jquery/jquery.min.js"></script>
</head>

<body>

<div id="searchApp">
    <div id="nav-bottom">
        <ly-top/>
    </div>


    <!--list-content-->
    <div class="main">
        <div class="py-container">

            <div class="bread">
                <!--面包屑-->
                <ul class="fl sui-breadcrumb">
                    <li><span>全部结果:</span></li>
                    <li><a href="#">手机</a></li>
                    <li><span>手机通讯</span></li>
                </ul>
                <!--已选择过滤项-->
                <ul class="tags-choose">
                    <li class="tag" v-for="(v,k) in search.filter" :key="k">
                        {
   
   {k === 'brandId' ? '品牌' : k}}:<span style="color: red" v-text="findValue(k,v)">apple</span>
                        <i class="sui-icon icon-tb-close" @click="deleteFilter(k)"></i>
                    </li>
                </ul>
                <div class="clearfix"></div>
            </div>
            <!--selector-->
            <div class="clearfix selector">
                <div class="type-wrap" v-for="(f,i) in remainFilter" v-show="i <= 5 || showMore" :key="f.k"
                     v-if="f.k !== 'brandId'">
                    <div class="fl key" v-text="f.k === 'cid3' ? '分类' : f.k"></div>
                    <div class="fl value">
                        <ul class="type-list">
                            <li v-for="(o,j) in f.options" :key="j" v-if="o" @click="selectFilter(f.k,o.id || o)">
                                <a v-text="o.name || o"></a>
                            </li>
                        </ul>
                    </div>
                    <div class="fl ext"></div>
                </div>
                <div class="type-wrap logo" v-else>
                    <div class="fl key brand">品牌</div>
                    <div class="value logos">
                        <ul class="logo-list" v-for="(o,j) in f.options" :key="j">
                            <li v-if="o.image"><img :src="o.image" @click="selectFilter(f.k,o.id || o)"/></li>
                            <li v-else><a href="#" v-text="o.name" @click="selectFilter(f.k,o.id || o)"></a></li>
                        </ul>
                    </div>
                    <div class="fl ext">
                        <a href="javascript:void(0);" class="sui-btn">多选</a>
                    </div>
                </div>


                <div class="type-wrap" style="text-align: center">
                    <v-btn small flat v-show="!showMore" @click="showMore=true">
                        更多
                        <v-icon>arrow_drop_down</v-icon>
                    </v-btn>
                    <v-btn small="" flat v-show="showMore" @click="showMore=false">
                        收起
                        <v-icon>arrow_drop_up</v-icon>
                    </v-btn>
                </div>
            </div>
            <!--details-->
            <div class="details">
                <div class="sui-navbar">
                    <div class="navbar-inner filter">
                        <ul class="sui-nav">
                            <li class="active">
                                <a href="#">综合</a>
                            </li>
                            <li>
                                <a href="#">销量</a>
                            </li>
                            <li>
                                <a href="#">新品</a>
                            </li>
                            <li>
                                <a href="#">评价</a>
                            </li>
                            <li>
                                <a href="#">价格</a>
                            </li>
                        </ul>
                        <div class="top-pagination">
                            <span><i style="color: #222;">{
   
   {total}}</i> 商品</span>
                            <span><i style="color: red;">{
   
   {search.page}}</i>/{
   
   {totalPage}}</span>
                            <a class="btn-arrow" href="#" @click.prevent="prePage"
                               style="display: inline-block">&lt;</a>
                            <a class="btn-arrow" href="#" @click.prevent="nextPage"
                               style="display: inline-block">&gt;</a>
                        </div>
                    </div>
                </div>
                <div class="goods-list">
                    <ul class="yui3-g">
                        <li class="yui3-u-1-5" v-for="goods in goodsList" :key="goods.id">
                            <div class="list-wrap">
                                <div class="p-img">
                                    <a :href="'/item/'+goods.id+'.html'" target="_blank"><img :src="goods.selectedSku.image"
                                                                             height="200"/></a>
                                    <ul class="skus">
                                        <li :class="{selected: goods.selectedSku.id === sku.id}"
                                            @mouseenter="goods.selectedSku=sku"
                                            v-for="sku in goods.skus" :key="sku.id">
                                            <img :src="sku.image">
                                        </li>
                                    </ul>
                                </div>
                                <div class="clearfix"></div>
                                <div class="price">
                                    <strong>
                                        <em>¥</em>
                                        <i v-text="ly.formatPrice(goods.selectedSku.price)"></i>
                                    </strong>
                                </div>
                                <div class="attr">
                                    <em v-text="goods.selectedSku.title.substring(0,21) + '...' "></em>
                                </div>
                                <div class="cu">
                                    <em><span></span>{
   
   {goods.subTitle.substring(0,15) + '...'}}</em>
                                </div>
                                <div class="commit">
                                    <i class="command">已有2000人评价</i>
                                </div>
                                <div class="operate">
                                    <a href="success-cart.html" target="_blank" class="sui-btn btn-bordered btn-danger">加入购物车</a>
                                    <a href="javascript:void(0);" class="sui-btn btn-bordered">对比</a>
                                    <a href="javascript:void(0);" class="sui-btn btn-bordered">关注</a>
                                </div>
                            </div>
                        </li>
                    </ul>
                </div>
                <div class="fr">
                    <div class="sui-pagination pagination-large">
                        <ul>
                            <li :class="{prev:true, disabled: search.page === 1}">
                                <a href="#" @click.prevent="prePage">«上一页</a>
                            </li>
                            <li :class="{active: index(i) === search.page}" v-for="i in Math.min(5,totalPage)" :key="i">
                                <a href="#" v-text="index(i)"></a>
                            </li>
                            <li class="dotted" v-show="search.page+2<totalPage && totalPage > 5"><span>...</span></li>
                            <li :class="{next:true, disabled: search.page === totalPage}">
                                <a href="#" @click.prevent="nextPage">下一页»</a>
                            </li>
                        </ul>
                        <div><span>共{
   
   {totalPage}}页&nbsp;</span><span>
      到第
      <input type="text" class="page-num"><button class="page-confirm" onclick="alert(1)">确定</button></span></div>
                    </div>
                </div>
            </div>
            <!--hotsale-->
            <div class="clearfix hot-sale">
                <h4 class="title">热卖商品</h4>
                <div class="hot-list">
                    <ul class="yui3-g">
                        <li class="yui3-u-1-4">
                            <div class="list-wrap">
                                <div class="p-img">
                                    <img src="img/like_01.png"/>
                                </div>
                                <div class="attr">
                                    <em>Apple苹果iPhone 6s (A1699)</em>
                                </div>
                                <div class="price">
                                    <strong>
                                        <em>¥</em>
                                        <i>4088.00</i>
                                    </strong>
                                </div>
                                <div class="commit">
                                    <i class="command">已有700人评价</i>
                                </div>
                            </div>
                        </li>
                        <li class="yui3-u-1-4">
                            <div class="list-wrap">
                                <div class="p-img">
                                    <img src="img/like_03.png"/>
                                </div>
                                <div class="attr">
                                    <em>金属A面,360°翻转,APP下单省300!</em>
                                </div>
                                <div class="price">
                                    <strong>
                                        <em>¥</em>
                                        <i>4088.00</i>
                                    </strong>
                                </div>
                                <div class="commit">
                                    <i class="command">已有700人评价</i>
                                </div>
                            </div>
                        </li>
                        <li class="yui3-u-1-4">
                            <div class="list-wrap">
                                <div class="p-img">
                                    <img src="img/like_04.png"/>
                                </div>
                                <div class="attr">
                                    <em>256SSD商务大咖,完爆职场,APP下单立减200</em>
                                </div>
                                <div class="price">
                                    <strong>
                                        <em>¥</em>
                                        <i>4068.00</i>
                                    </strong>
                                </div>
                                <div class="commit">
                                    <i class="command">已有20人评价</i>
                                </div>
                            </div>
                        </li>
                        <li class="yui3-u-1-4">
                            <div class="list-wrap">
                                <div class="p-img">
                                    <img src="img/like_02.png"/>
                                </div>
                                <div class="attr">
                                    <em>Apple苹果iPhone 6s (A1699)</em>
                                </div>
                                <div class="price">
                                    <strong>
                                        <em>¥</em>
                                        <i>4088.00</i>
                                    </strong>
                                </div>
                                <div class="commit">
                                    <i class="command">已有700人评价</i>
                                </div>
                            </div>
                        </li>
                    </ul>
                </div>
            </div>
        </div>
    </div>

</div>

<script type="text/javascript">
    var vm = new Vue({
     
     
        el: "#searchApp",
        data: {
     
     
            ly: ly,
            search: {
     
     },
            goodsList: [],
            total: 0,
            totalPage: 0,
            selectedSku: {
     
     },
            filters: [],
            showMore: false //是否显示更多
        },
        created() {
     
     
            //获取请求参数
            const search = ly.parse(location.search.substring(1));
            search.page = parseInt(search.page) || 1;
            search.filter = search.filter ? search.filter : {
     
     };
            this.search = search;
            //发送后台
            this.loadData();

        },
        watch: {
     
     
            search: {
     
     
                deep: true,
                handler(val, old) {
     
     
                    if (!old || !old.key) {
     
     
                        // 如果旧的search值为空,或者search中的key为空,证明是第一次
                        return;
                    }
                    // 把search对象变成请求参数,拼接在url路径
                    window.location.href = "http://www.leyou.com/search.html?" + ly.stringify(val);
                }
            }
        },
        methods: {
     
     
            loadData() {
     
     
                //发送到后台
                ly.http.post("/search/page", this.search).then(resp => {
     
     
                    console.log(resp);
                    //保存分页结果
                    this.total = resp.data.total;
                    this.totalPage = resp.data.totalPage;
                    //保存当前页商品
                    resp.data.items.forEach(goods => {
     
     
                        goods.skus = JSON.parse(goods.skus);
                        goods.selectedSku = goods.skus[0];

                    });
                    this.goodsList = resp.data.items;
                    //获取聚合结果并形成过滤项
                    this.filters.push({
     
     
                        k: "cid3",
                        options: resp.data.categories,
                    });
                    this.filters.push({
     
     
                        k: "brandId",
                        options: resp.data.brands,
                    });
                    console.log(resp);
                    resp.data.specs.forEach(spec => this.filters.push(spec));
                }).catch(error => {
     
     

                })
            },

            index(i) {
     
     
                if (this.search.page <= 3 || this.totalPage < 5) {
     
     
                    return i;
                } else if (this.search.page >= this.totalPage - 2) {
     
     
                    return this.totalPage - 5 + i;
                } else {
     
     
                    return this.search.page - 3 + i;
                }
            },
            prePage() {
     
     
                if (this.search.page > 1) {
     
     
                    this.search.page--;
                }
            },
            nextPage() {
     
     
                if (this.search.page < this.totalPage) {
     
     
                    this.search.page++;
                }
            },
            selectFilter(key, option) {
     
     
                const {
     
     ...obj} = this.search.filter;
                obj[key] = option;
                this.search.filter = obj;
            },
            findValue(k, v) {
     
     
                if (!this.filters)
                    return
                if (k !== 'brandId')
                    return v;
                return this.filters.find(f => f.k === 'brandId').options[0].name;
            },
            deleteFilter(k) {
     
     
                const {
     
     ...obj} = this.search.filter;
                delete obj[k];
                this.search.filter = obj;
            }

        },
        computed: {
     
     
            remainFilter() {
     
     
                const keys = Object.keys(this.search.filter);
                return this.filters.filter(f => !keys.includes(f.k) && f.options.length > 1);
            }
        },
        components: {
     
     
            lyTop: () => import("./js/pages/top.js")
        }
    });
</script>
<!-- 底部栏位 -->
<!--页面底部,由js动态加载-->
<div class="clearfix footer"></div>
<script type="text/javascript">$(".footer").load("foot.html");</script>
<!--页面底部END-->

</body>
<!--购物车单元格 模板-->
<script type="text/template" id="tbar-cart-item-template">
    <div class="tbar-cart-item">
        <div class="jtc-item-promo">
            <em class="promo-tag promo-mz">满赠<i class="arrow"></i></em>
            <div class="promo-text">已购满600元,您可领赠品</div>
        </div>
        <div class="jtc-item-goods">
            <span class="p-img"><a href="#" target="_blank"><img src="{2}" alt="{1}" height="50" width="50"/></a></span>
            <div class="p-name">
                <a href="#">{
     
     1}</a>
            </div>
            <div class="p-price"><strong>¥{
     
     3}</strong>×{
     
     4}</div>
            <a href="#none" class="p-del J-del">删除</a>
        </div>
    </div>
</script>
<!--侧栏面板结束-->
<script type="text/javascript" src="js/plugins/jquery/jquery.min.js"></script>
<script type="text/javascript">
    $(function () {
     
     
        $("#service").hover(function () {
     
     
            $(".service").show();
        }, function () {
     
     
            $(".service").hide();
        });
        $("#shopcar").hover(function () {
     
     
            $("#shopcarlist").show();
        }, function () {
     
     
            $("#shopcarlist").hide();
        });

    })
</script>
<script type="text/javascript" src="js/model/cartModel.js"></script>
<script type="text/javascript" src="js/czFunction.js"></script>
<script type="text/javascript" src="js/plugins/jquery.easing/jquery.easing.min.js"></script>
<script type="text/javascript" src="js/plugins/sui/sui.min.js"></script>
<script type="text/javascript" src="js/widget/cartPanelView.js"></script>


</html>

此部分结束。


2.实现过滤分类和品牌和实现过滤规格参数(后台)

2.1 过滤分类和品牌分析

先来看分类和品牌。在我们的数据库中已经有所有的分类和品牌信息。在这个位置,是不是把所有的分类和品牌信息都展示出来呢?

显然不是,用户搜索的条件会对商品进行过滤,而在搜索结果中,不一定包含所有的分类和品牌,直接展示出所有商品分类,让用户选择显然是不合适的。

无论是分类信息,还是品牌信息,都应该从搜索的结果商品中进行聚合得到。

2.2 扩展返回的结果

原来,我们返回的结果是PageResult对象,里面只有total、totalPage、items3个属性。但是现在要对商品分类和品牌进行聚合,数据显然不够用,我们需要对返回的结果进行扩展,添加分类和品牌的数据。

那么问题来了:以什么格式返回呢?

看页面:
在这里插入图片描述

分类:页面显示了分类名称,但背后肯定要保存id信息。所以至少要有id和name

品牌:页面展示的有logo,有文字,当然肯定有id,基本上是品牌的完整数据

我们新建一个类,继承PageResult,然后扩展两个新的属性:分类集合和品牌集合:
在这里插入图片描述
在这里插入图片描述

package com.leyou.search.pojo;

import com.leyou.common.vo.PageResult;
import com.leyou.item.pojo.Brand;
import com.leyou.item.pojo.Category;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;
import java.util.Map;

@Data
public class SearchResult extends PageResult<Goods> {
    
    

    private List<Category> categories;// 分类待选项

    private List<Brand> brands;// 品牌待选项

    private List<Map<String,Object>> specs; //规格参数 key 及 待选项

    public SearchResult(){
    
    
    }

    public SearchResult(Long total, Integer totalPage, List<Goods> items, List<Category> categories, List<Brand> brands, List<Map<String, Object>> specs) {
    
    
        super(total, totalPage, items);
        this.categories = categories;
        this.brands = brands;
        this.specs = specs;
    }

}

2.3 过滤规格参数分析

有四个问题需要先思考清楚:

  • 什么时候显示规格参数过滤?
  • 如何知道哪些规格需要过滤?
  • 要过滤的参数,其可选值是如何获取的?
  • 规格过滤的可选值,其数据格式怎样的?

什么情况下显示有关规格参数的过滤?

如果用户尚未选择商品分类,或者聚合得到的分类数大于1,那么就没必要进行规格参数的聚合。因为不同分类的商品,其规格是不同的。

因此,我们在后台需要对聚合得到的商品分类数量进行判断,如果等于1,我们才继续进行规格参数的聚合。

如何知道哪些规格需要过滤?

我们不能把数据库中的所有规格参数都拿来过滤。因为并不是所有的规格参数都可以用来过滤,参数的值是不确定的。

值的庆幸的是,我们在设计规格参数时,已经标记了某些规格可搜索,某些不可搜索。

因此,一旦商品分类确定,我们就可以根据商品分类查询到其对应的规格,从而知道哪些规格要进行搜索。

要过滤的参数,其可选值是如何获取的?

虽然数据库中有所有的规格参数,但是不能把一切数据都用来供用户选择。

与商品分类和品牌一样,应该是从用户搜索得到的结果中聚合,得到与结果品牌的规格参数可选值。

规格过滤的可选值,其数据格式怎样的?

我们直接看页面效果:
在这里插入图片描述
我们之前存储时已经将数据分段,恰好符合这里的需求

接下来,我们就用代码实现刚才的思路。

总结一下,应该是以下几步:

  • 1)用户搜索得到商品,并聚合出商品分类
  • 2)判断分类数量是否等于1,如果是则进行规格参数聚合
  • 3)先根据分类,查找可以用来搜索的规格
  • 4)对规格参数进行聚合
  • 5)将规格参数聚合结果整理后返回

2.4 修改SearchService:

在这里插入图片描述

package com.leyou.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.utils.JsonUtils;
import com.leyou.common.utils.NumberUtils;
import com.leyou.common.vo.PageResult;
import com.leyou.item.pojo.*;
import com.leyou.search.client.BrandClient;
import com.leyou.search.client.CategoryClient;
import com.leyou.search.client.GoodsClient;
import com.leyou.search.client.SpecificationClient;
import com.leyou.search.pojo.Goods;
import com.leyou.search.pojo.SearchRequest;
import com.leyou.search.pojo.SearchResult;
import com.leyou.search.repository.GoodsRepository;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchService {
    
    

    @Autowired
    private CategoryClient categoryClient;

    @Autowired
    private BrandClient brandClient;

    @Autowired
    private GoodsClient goodsClient;

    @Autowired
    private SpecificationClient specClient;

    @Autowired
    private GoodsRepository repository;

    @Autowired
    private ElasticsearchTemplate template;

    public Goods buildGoods(Spu spu) {
    
    
        Long spuId = spu.getId();
        //查询分类
        List<Category> categories = categoryClient.queryCategoryByIds(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()));
        if (CollectionUtils.isEmpty(categories)) {
    
    
            throw new LyException(ExceptionEnum.CATEGORY_NOT_FOND);
        }
        //从List集合获取到名字
        List<String> names = categories.stream().map(Category::getName).collect(Collectors.toList());
        //查询品牌
        Brand brand = brandClient.queryBrandById(spu.getBrandId());
        if (brand == null) {
    
    
            throw new LyException(ExceptionEnum.BRAND_NOT_FOUND);
        }

        // 搜索字段
        String all = spu.getTitle() + StringUtils.join(names, " ") + brand.getName();

        //查询sku
        List<Sku> skuList = goodsClient.querySkuBySpuId(spuId);
        if (CollectionUtils.isEmpty(skuList)) {
    
    
            throw new LyException(ExceptionEnum.GOODS_SKU_NOT_FOUND);
        }
        //对sku进行处理
        List<Map<String, Object>> skus = new ArrayList<>();
        //价格集合
        Set<Long> priceList = new HashSet<>();

        for (Sku sku : skuList) {
    
    
            Map<String, Object> map = new HashMap<>();
            map.put("id", sku.getId());
            map.put("title", sku.getTitle());
            map.put("price", sku.getPrice());
            map.put("image", StringUtils.substringBefore(sku.getImages(), ","));
            skus.add(map);
            //处理价格
            priceList.add(sku.getPrice());
        }
        //查询规格参数
        List<SpecParam> params = specClient.queryParamList(null, spu.getCid3(), true);
        if (CollectionUtils.isEmpty(params)) {
    
    
            throw new LyException(ExceptionEnum.SPEC_PARAM_NOT_FOUND);
        }
        //查询商品详情
        SpuDetail spuDetail = goodsClient.queryDetailById(spuId);

        //获取通用规格参数
        Map<Long, String> generSpec = JsonUtils.toMap(spuDetail.getGenericSpec(), Long.class, String.class);
        //获取特有规格参数
        Map<Long, List<String>> specialSpec = JsonUtils.nativeRead(spuDetail.getSpecialSpec(), new TypeReference<Map<Long, List<String>>>() {
    
    
        });
        //规格参数
        Map<String, Object> specs = new HashMap<>();
        for (SpecParam param : params) {
    
    
            //规格名称
            String key = param.getName();
            Object value = "";
            //判断是否是通用规格
            if(param.getGeneric()){
    
    
                value = generSpec.get(param.getId());
                //判断是否是数值类型
                if(param.getNumeric()){
    
    
                    //处理成段
                   value =  chooseSegment(value.toString(),param);
                }
            }else {
    
    
                value = specialSpec.get(param.getId());
            }
            //存入map
            specs.put(key,value);
        }

        //构建goods对象
        Goods goods = new Goods();

        goods.setBrandId(spu.getBrandId());
        goods.setCid1(spu.getCid1());
        goods.setCid2(spu.getCid2());
        goods.setCid3(spu.getCid3());
        goods.setCreateTime(spu.getCreateTime());
        goods.setId(spuId);
        goods.setAll(all);// 搜索字段,包含标题,分类,品牌,规格等
        goods.setPrice(priceList);//所有sku的价格集合
        goods.setSkus(JsonUtils.toString(skus));//所有sku的集合的json格式
        goods.setSpecs(specs);//所有的可搜索的规格参数
        goods.setSubTitle(spu.getSubTitle());


        return goods;
    }
    //处理成段
    private String chooseSegment(String value, SpecParam p) {
    
    
        double val = NumberUtils.toDouble(value);
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
    
    
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = NumberUtils.toDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if(segs.length == 2){
    
    
                end = NumberUtils.toDouble(segs[1]);
            }
            // 判断是否在范围内
            if(val >= begin && val < end){
    
    
                if(segs.length == 1){
    
    
                    result = segs[0] + p.getUnit() + "以上";
                }else if(begin == 0){
    
    
                    result = segs[1] + p.getUnit() + "以下";
                }else{
    
    
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    //搜索
    public PageResult<Goods> search(SearchRequest request) {
    
    
        int page = request.getPage() - 1;
        int size = request.getSize();

        //创建查询构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //0 结果过滤
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{
    
    "id","subTitle","skus"},null));
        //1 分页
        queryBuilder.withPageable(PageRequest.of(page,size));
        //2 搜索条件
        QueryBuilder basicQuery = buildBasicQuery(request);
        queryBuilder.withQuery(basicQuery);

        // 3 聚合分类和品牌
        // 3.1 聚合分类
        String categoryAggName = "category_agg";
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("cid3"));
        // 3.2 聚合品牌
        String brandAggName = "brand_agg";
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));

        //4 查询
        AggregatedPage<Goods> result = template.queryForPage(queryBuilder.build(), Goods.class);

        //5 解析结果
        // 5.1 解析分页结果
        long total = result.getTotalElements();
        int totalPage = result.getTotalPages();
        List<Goods> goodsList = result.getContent();
        // 5.2 解析聚合结果
        Aggregations aggs = result.getAggregations();
        List<Category> categories = parseCategoryAgg(aggs.get(categoryAggName));
        List<Brand> brands = parseBrandAgg(aggs.get(brandAggName));

        //6 完成规格参数聚合
        List<Map<String,Object>> specs = null;
        if(categories != null && categories.size() == 1){
    
    
            // 商品分类存在,并且数量为1,可以聚合规格参数
            specs = buildSpecificationAgg(categories.get(0).getId(), basicQuery);
        }

        return new SearchResult(total,totalPage,goodsList,categories,brands,specs);

    }

    //基础查询
    private QueryBuilder buildBasicQuery(SearchRequest request) {
    
    
        //创建布尔查询
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        //查询条件
        queryBuilder.must(QueryBuilders.matchQuery("all",request.getKey()));
        //过滤条件
        Map<String, String> map = request.getFilter();
        for (Map.Entry<String, String> entry : map.entrySet()) {
    
    
            String key = entry.getKey();
            //处理key
            if(!"cid3".equals(key) && !"brandId".equals(key)){
    
    
                key = "specs."+ key + ".keyword";
            }

            queryBuilder.filter(QueryBuilders.termQuery(key,entry.getValue()));
        }
        return queryBuilder;
    }

    //规格参数聚合
    private List<Map<String, Object>> buildSpecificationAgg(Long cid, QueryBuilder basicQuery) {
    
    
        List<Map<String, Object>> specs = new ArrayList<>();
        //1 查询需要聚合的规格参数
        List<SpecParam> params = specClient.queryParamList(null, cid, true);
        //2 聚合
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //2.1 带上查询条件
        queryBuilder.withQuery(basicQuery);
        // 2.2 聚合
        for (SpecParam param : params) {
    
    
            String name = param.getName();
            queryBuilder.addAggregation(AggregationBuilders.terms(name).field("specs."+name+".keyword"));
        }
        // 3 获取结果
        AggregatedPage<Goods> result = template.queryForPage(queryBuilder.build(), Goods.class);
        // 4 解析结果
        Aggregations aggs = result.getAggregations();
        for (SpecParam param : params) {
    
    
            //规格参数名
            String name = param.getName();
            StringTerms terms = aggs.get(name);

            //准备map
            Map<String, Object> map = new HashMap<>();
            map.put("k",name);
            map.put("options",terms.getBuckets().stream().map(b -> b.getKeyAsString()).collect(Collectors.toList()));
            specs.add(map);
        }
        return specs;
    }

    //品牌聚合
    private List<Brand> parseBrandAgg(LongTerms terms) {
    
    
        try{
    
    
            List<Long> ids = terms.getBuckets().stream().map(b -> b.getKeyAsNumber().longValue()).collect(Collectors.toList());
            List<Brand> brands = brandClient.queryBrandByIds(ids);
            return brands;
        }catch (Exception e) {
    
    
            return null;
        }
    }

    //分类聚合
    private List<Category> parseCategoryAgg(LongTerms terms) {
    
    
        try {
    
    
            List<Long> ids = terms.getBuckets().stream().map(b -> b.getKeyAsNumber().longValue()).collect(Collectors.toList());
            List<Category> categories = categoryClient.queryCategoryByIds(ids);
            return categories;
        }catch (Exception e){
    
    
            return null;
        }

    }
}

至此今天学习结束。

猜你喜欢

转载自blog.csdn.net/zcylxzyh/article/details/100859210