javaweb-青橙项目-12-87

第4章 搜索解决方案-2 过滤查询

学习目标

并无新技术,使用es完善增删改查
完成关键字搜索功能
完成商品分类过滤功能
完成品牌过滤功能
完成规格过滤功能
完成价格区间过滤功能
项目序列-12:

1. 关键字搜索

1.1 需求分析

在首页或搜索页输入关键字,点击搜索

在这里插入图片描述

显示列表搜索结果:

在这里插入图片描述

1.2 实现思路

(1)后端使用匹配查询和布尔查询
(2)前端使用thymeleaf模板渲染
(3)前端向后端传递map(因为提交的不仅仅是关键字,还有品牌、规格、分类等信息)
(4)后端向前端返回map(因为返回的不仅仅是列表,还有商品分类、品牌和规格列表等数据)

1.3 代码实现

1.3.1 集成elasticsearch高级客户端

(1)qingcheng_service_goods工程pom.xml新增依赖

<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch‐rest‐high‐level‐client</artifactId>
<version>6.5.3</version>
</dependency>

(2)qingcheng_service_goods工程新增工厂类

public class RestClientFactory {
    public static RestHighLevelClient getRestHighLevelClient(String  hostname,int port){
        HttpHost http=new HttpHost(hostname,port,"http");
        RestClientBuilder builder= RestClient.builder(http);//rest构建器
        return new RestHighLevelClient(builder);//高级客户端对象 (连接)
    }
}

(3)qingcheng_service_goods工程applicationContext-service.xml新增配置

<!‐‐es client‐‐>
<bean id="restHighLevelClient"class="com.qingcheng.service.impl.RestClientFactory" factory‐method="getRestHighLevelClient">
	<constructor‐arg index="0" value="127.0.0.1"></constructor‐arg>
	<constructor‐arg index="1" value="9200"></constructor‐arg>
</bean>

1.3.2 关键字搜索逻辑

(1)qingcheng_interface工程新增接口 SkuSearchService 服务接口新增方法定义

public interface SkuSearchService {
    public Map search(Map<String,String> searchMap);
}

(2)qingcheng_service_goods工程新增服务实现类SkuSearchServiceImpl 实现此方
法,实现关键字查询

@Service
public class SkuSearchServiceImpl implements SkuSearchService {
    @Autowired
    private RestHighLevelClient restHighLevelClient;
    public Map search(Map<String,String> searchMap) {
//1.封装查询请求
        SearchRequest searchRequest=new SearchRequest("sku");
        searchRequest.types("doc"); //设置查询的类型
        SearchSourceBuilder searchSourceBuilder=new  SearchSourceBuilder();
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();// 布尔查询构建器
//1.1 关键字搜索
        MatchQueryBuilder matchQueryBuilder=QueryBuilders.matchQuery("name",searchMap.get("keywords"));
        boolQueryBuilder.must(matchQueryBuilder);
        searchSourceBuilder.query(boolQueryBuilder);
        searchRequest.source(searchSourceBuilder);
//2.封装查询结果
        Map resultMap=new HashMap();
        try {
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            SearchHits searchHits = searchResponse.getHits();
            long totalHits = searchHits.getTotalHits();
            System.out.println("记录数:"+totalHits);
            SearchHit[] hits = searchHits.getHits();
//2.1 商品列表
            List<Map<String,Object>> resultList=new ArrayList<Map<String, Object>>();
        for(SearchHit hit:hits){
	        Map<String, Object> skuMap = hit.getSourceAsMap();
	        resultList.add(skuMap);
        }
        	resultMap.put("rows",resultList);
        } catch (IOException e) {
       		 e.printStackTrace();
        }
        	return resultMap;
        }
        }

(3)qingcheng_web_portal工程新增类

@Controller
public class SearchController {
    @Reference
    private SkuSearchService skuSearchService;
    @GetMapping("/search")
    public String search(Model model, @RequestParam Map<String, String> searchMap) throws Exception {
//字符集处理
        searchMap = WebUtil.convertCharsetToUTF8(searchMap);
//远程调用接口
        Map result = skuSearchService.search(searchMap);
        model.addAttribute("result", result);
        return "search";
    }
}

WebUtil类是工具类,用于字符集转码

1.3.3 模板构建

(1)qingcheng_web_portal工程新增模板,编写search.htm到WEB-INF下,并进行以下修改

遍历查询结果
<ul class="yui3‐g">
<li class="yui3‐u‐1‐5" th:each="sku:${result.rows}">
<div class="list‐wrap">
<div class="p‐img">
<a href="item.html" target="_blank"><img
th:src="${sku.image}" /></a>
</div>
<div class="price">
<strong>
<em>¥</em>
<i
th:text="${#numbers.formatDecimal(sku.price/100.0,0,2)}"></i>
</strong>
</div>
<div class="attr">
<em th:text="${sku.name}"></em>
</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>
</div>
</div>
</li>
</ul>

测试 浏览器输入 http://localhost:9102/search.do?keywords=手机
(2)修改index.html的关键字搜索表单

<form action="/search.do" class="sui‐form form‐inline">
<!‐‐searchAutoComplete‐‐>
<div class="input‐append">
<input type="text" name="keywords" id="autocomplete"
class="input‐error input‐xxlarge" />
<button class="sui‐btn btn‐xlarge btn‐danger" >搜索</button>
</div>
</form>

修改search.html的关键字搜索表单,内容同上

2. 商品分类过滤

2.1 需求分析

以关键字作为查询条件,查询结果中包含的商品分类,在页面中显示出来

在这里插入图片描述

点击商品分类,按商品分类对结果进行过滤查询,并且在查询条件列表中添加已经选择的商品分类标签,隐藏搜索面板中的商品分类一行。
点击条件标签的“×”,取消该过滤条件

2.2 实现思路

(1)商品分类列表的显示使用聚合查询
(2)使用过滤查询

2.3 代码实现

2.3.1 商品分类列表

(1)修改SkuSearchServiceImpl的search方法,在第一段代码(封装查询请求)的末尾处添加以下代码:
将商品分类

//聚合查询(商品分类)
TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms("sku_category").field("categoryName");
searchSourceBuilder.aggregation(termsAggregationBuilder);

在第二段代码中添加以下代码:
将查询得到的结果提取出数据

//2.2 商品分类列表
Aggregations aggregations = searchResponse.getAggregations();
        Map<String, Aggregation> aggregationMap = aggregations.getAsMap();
        Terms terms = (Terms) aggregationMap.get("sku_category");
        List<? extends Terms.Bucket> buckets = terms.getBuckets();
        List<String> categoryList=new ArrayList();
        for( Terms.Bucket bucket:buckets ){
        	categoryList.add(bucket.getKeyAsString());
        }
        resultMap.put("categoryList",categoryList);

(2)修改qingcheng_web_portal的search.html 商品分类列表

<div class="type‐wrap">
<div class="fl key">商品分类</div>
<div class="fl value" th:each="category:${result.categoryList}">
<span>
<a th:text="${category}" ></a>
</span>
</div>
<div class="fl ext"></div>
</div>

2.3.2 分类过滤查询

(1)修改SkuSearchServiceImpl类的search方法,在第一段代码(封装查询请求)中添加如下代码:

//1.2 商品分类过滤
if(searchMap.get("category")!=null){
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("categoryName", searchMap.get("category"));
        boolQueryBuilder.filter(termQueryBuilder);
        }

(2)修改SearchController的search方法,添加以下代码

//url处理
StringBuffer url=new StringBuffer("/search.do?");
        for(String key:searchMap.keySet()){
        url.append( "&"+key+"="+ searchMap.get(key) );
        }
        model.addAttribute("url",url);

(3)修改changgou_web_search的search.html 商品分类列表

2.3.3 取消分类过滤

(1)修改SearchController的search方法,添加以下代码

model.addAttribute("searchMap",searchMap);

(2)修改search.html的条件列表部分

<div class="type‐wrap"
        th:if="!${#maps.containsKey(searchMap,'category')}">
<div class="fl key">商品分类</div>
<div class="fl value" th:each="category:${result.categoryList}">
<span>
<a th:href="${url+'&category='+category}"
        th:text="${category}"></a>
</span>
</div>
<div class="fl ext"></div>
</div>

为商品分类的div添加条件 ,判断当查询条件中不存在category时显示商品分类

<div class="type‐wrap" th:if="!${#maps.containsKey(searchMap,'category')}">

3. 品牌过滤

3.1 需求分析

根据关键字搜索得到的商品分类列表,按照第一个分类查询该分类下的品牌列表

在这里插入图片描述
如果用户选择了商品分类,按照选择的商品分类查询该分类下的品牌,并添加条件标签,隐藏搜索面板中的品牌一行。
点击品牌,按品牌筛选查询结果。
点击条件标签的“×”,取消该过滤条件

3.2 实现思路

(1)数据访问层添加方法,根据商品分类名称得到品牌列表
(2)在搜索商品的方法中,添加按品牌过滤的逻辑。

3.3 代码实现

3.3.1 品牌列表

(1)BrandMapper接口新增方法定义

/**
 * 根据分类名称查询品牌列表
 * @param categoryName
 * @return
 */
@Select("SELECT name,image FROM tb_brand WHERE id IN (
			SELECT brand_id FROM tb_category_brand WHERE category_id IN (
				SELECT id FROM  tb_category WHERE NAME=#{name}) )order by seq")
public List<Map> findListByCategoryName(@Param("name") String
        categoryName);

(2)修改 SkuSearchServiceImpl ,注入BrandMapper

@Autowired
private BrandMapper brandMapper;

修改search方法,新增代码

//2.3 品牌列表
String categoryName="";//商品分类名称
        if(searchMap.get("category")==null){ // 如果没有分类条件
        	if(categoryList.size()>0){
        		categoryName=categoryList.get(0);//提取分类列表的第一个分类
        	}
        }else{
        	categoryName=searchMap.get("category");//取出参数中的分类
        }
        List<Map> brandList = brandMapper.findListByCategoryName(categoryName);//
        查询品牌列表
        resultMap.put("brandList",brandList);

(3)修改search.html ,展现品牌列表

<div class="type‐wrap logo">
<div class="fl key brand">品牌</div>
<div class="value logos">
<ul class="logo‐list">
<li th:each="brand:${result.brandList}">
<a>
<img th:if="${brand.image}!=''"
        th:src="${brand.image}" />
<span th:if="${brand.image}==''"
        th:text="${brand.name}"></span>
</a>
</li>
</ul>
</div>
<div class="ext">
</div>
</div>

3.3.2 品牌过滤查询

(1)修改SkuSearchServiceImpl类的search方法,添加以下代码

//1.3 品牌过滤
if(searchMap.get("brand")!=null){
        TermQueryBuilder termQueryBuilder =
        QueryBuilders.termQuery("brandName", searchMap.get("brand"));
        boolQueryBuilder.filter(termQueryBuilder);
        }

(2)修改模板

<a th:href="${url+'&brand='+brand.name}">
<img th:if="${brand.image}!=''" th:src="${brand.image}" />
<span th:if="${brand.image}==''" th:text="${brand.name}"></span>
</a>

3.3.3 取消品牌过滤

修改search.html的条件列表部分

<!‐‐品牌‐‐>
<li class="with‐x" th:if="${#maps.containsKey(searchMap, 'brand')}">
<span th:text="|品牌:${searchMap.brand}|"></span>
<i><a th:href="${#strings.replace(url,'&brand='+searchMap.brand
        ,'')}">×</a></i>
</li>

在品牌列表的div上添加条件

<div class="type‐wrap logo" th:if="${!#maps.containsKey(searchMap,
        'brand') }">

代码优化:修改SkuSearchServiceImpl类的search方法 ,当查询条件中包含品牌信息,
则不查询品牌列表

//2.3 品牌列表
if(searchMap.get("brand")==null) {
//...品牌列表查询代码......
        }

4. 规格过滤

4.1 需求分析

根据关键字搜索得到的商品分类列表,按照第一个分类查询该分类下的规格和规格选项
列表

在这里插入图片描述

如果用户选择了商品分类,按照选择的商品分类查询该分类下的规格和规格选项列表
点击某规格下的规格选项,按照该规格和规格选项对结果进行过滤。
点击条件标签的“×”,取消该过滤条件

4.2 实现思路

(1)数据访问层添加方法,根据商品分类名称得到规格列表
(2)在搜索商品的方法中,添加按规格过滤的逻辑。

4.3 代码实现

4.3.1 规格列表

(1)SpecMapper新增方法定义

@Select("SELECT name,options FROM tb_spec WHERE template_id IN (
 			SELECT template_id FROM tb_category WHERE NAME=#{categoryName}) order by seq")
public List<Map> findListByCategoryName(@Param("categoryName") String
        categoryName);

(2)修改SkuSearchServiceImpl ,引入specMapper

@Autowired
private SpecMapper specMapper;

修改search方法,新增代码

//2.4 规格列表
List<Map> specList = specMapper.findListByCategoryName(categoryName);//规格列表
        for(Map spec:specList){
        String[] options = ((String) spec.get("options")).split(",");//规格选项列表
        spec.put("options",options);
        }
        resultMap.put("specList",specList);

(3)修改search.html

<div class="type‐wrap" th:each="spec:${result.specList}">
<div class="fl key" th:text="${spec.name}"></div>
<div class="fl value">
<ul class="type‐list">
<li th:each="option:${spec.options}">
<a th:text="${option}"
        th:href="|${url}&spec.${spec.name}=${option}|"></a>
</li>
</ul>
</div>
<div class="fl ext"></div>
</div>

4.3.2 规格过滤查询

对于对象类型的查询语法:
前后端约定:所有spec.开头的参数都是规格
修改SkuSearchServiceImpl类的search方法,添加以下代码

//1.4 规格过滤
for(String key: searchMap.keySet() ){
        if( key.startsWith("spec.") ){//如果是规格参数
        TermQueryBuilder termQueryBuilder =
        QueryBuilders.termQuery(key+".keyword", searchMap.get(key));
        boolQueryBuilder.filter(termQueryBuilder);
        }
        }

修改模板

<a th:text="${option}" th:href="|${url}&spec.${spec.name}=${option}|">
</a>

4.3.3 取消规格过滤

修改search.html 添加条件标签

<!‐‐规格‐‐>
<li class="with‐x" th:each="item:${searchMap}"
th:if="${#strings.startsWith(item.key,'spec.')}" >
<span th:text="|${#strings.substring(item.key,5)}:${item.value}|">
</span>
<i><a th:href="${#strings.replace(url,'&'+item.key +'='+item.value
,'')}">×</a></i>
</li>

在规格列表的div上添加条件

5. 价格过滤

5.1 需求分析

点击价格区间表现,按价格区间搜索
在这里插入图片描述

点击条件标签的“×”,取消该过滤条件

5.2 实现思路

(1)价格区间可以在模板中写成固定的。
(2)使用范围查询。
需求:查询价格大于等于10000并且小于等于20000的记录
范围查询的语法如下:

#范围查询
        GET sku/_search
        {
        "query": {
        "range": {
        "price": {
        "gte": 10000,
        "lte": 20000
        }
        }
        }
        }

rest高级客户端 :

RangeQueryBuilder rangeQueryBuilder =
        QueryBuilders.rangeQuery("price").gte(10000).lte(20000);

比较神奇的是,我们可以传递字符串

RangeQueryBuilder rangeQueryBuilder =
        QueryBuilders.rangeQuery("price").gte("10000").lte("20000");

也是可以查询到结果,它会自动转换类型。

5.3 代码实现

5.3.1 价格条件列表

<div class="fl key">价格</div>
<div class="fl value">
<ul class="type‐list">
<li>
<a th:href="${url+'&price=0‐500'}">0500</a>
</li>
<li>
<a th:href="${url+'&price=500‐1000'}">5001000</a>
</li>
<li>
<a th:href="${url+'&price=1000‐1500'}">10001500</a>
</li>
<li>
<a th:href="${url+'&price=1500‐2000'}">15002000</a>
</li>
<li>
<a th:href="${url+'&price=2000‐3000'}">20003000</a>
</li>
<li>
<a th:href="${url+'&price=3000‐*'}">3000元以上</a>
</li>
</ul>
</div>

5.3.2 价格过滤

修改SkuSearchServiceImpl类的buildBasicQuery方法,添加代码

//1.5 价格过滤
if(searchMap.get("price")!=null ){
        String[] price = searchMap.get("price").split("‐");
        if(!price[0].equals("0")){ //最低价格不等于0
        RangeQueryBuilder rangeQueryBuilder =
        QueryBuilders.rangeQuery("price").gte(price[0] + "00");
        boolQueryBuilder.filter(rangeQueryBuilder);
        }
        if(!price[1].equals("*")){ //如果价格由上限
        RangeQueryBuilder rangeQueryBuilder =
        QueryBuilders.rangeQuery("price").lte(price[1] + "00");
        boolQueryBuilder.filter(rangeQueryBuilder);
        }
        }

5.3.3 取消价格过滤

修改search.html 添加条件标签

<!‐‐价格条件‐‐>
<li class="with‐x" th:if="${#maps.containsKey(searchMap,'price')}">
<span th:text="${'价格:'+searchMap.price}"></span>
<i>
<a
th:href="${#strings.replace(url,'&price='+searchMap.price,'')}">×</a>
</i>
</li>

在搜索面板的价格一行添加条件

<div class="type‐wrap" th:if="!${#maps.containsKey(searchMap,'price')}">

6. 品牌与规格列表缓存

6.1 需求分析

现在我们每次查询时都需要根据商品分类名称读取品牌和规格列表,这样对于数据库造
成比较大的访问压力,所以我们需要将品牌和规格列表放入缓存。

6.2 实现思路

方式一:定时预热
写一个定时任务,每天执行一次,查询所有的商品分类的品牌和规格列表,放入缓存.
以hash形式存储,以分类名称作为key,以品牌列表和规格列表作为值。
启动时检测缓存中是否存在数据,如果不存在数据则立即执行缓存预热。
方式二:随机过期
获取品牌和规格列表查询缓存中是否存在数据,如果缓存中有数据则返回缓存中的数据,如果没有则查询数据库并放入缓存并设置过期时间,为了避免缓存雪崩,我们将过期时间设置为一定范围内的随机数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值