练手项目1笔记 day10

目标

  • 实现品优购搜索结果高亮显示功能
  • 说出品优购搜索的业务规则和实现思路
  • 完成查询分类列表的功能
  • 完成缓存品牌和规格数据的功能
  • 完成显示品牌和规格数据的功能
  • 完成过滤条件构建的功能
  • 完成过滤查询的功能

1. 品优购-高亮显示

1. 需求分析

将用户输入的关键字在标题中以红色的字体显示,就是搜索中常用的高亮显示

2. 后端代码

修改服务层代码ItemSearchServiceImpl.java

 @Override
    public Map search(Map searchMap) {
        Map map = new HashMap();
/*
        Query query = new SimpleQuery("*:*");
        // 添加查询条件
        Criteria criteria = new Criteria("item_keywords").is(searchMap.get("keywords"));
        query.addCriteria(criteria);

        ScoredPage<TbItem> page = solrTemplate.queryForPage(query, TbItem.class);
        map.put("rows",page.getContent());*/

        // 高亮显示
        HighlightQuery query = new SimpleHighlightQuery();
        HighlightOptions highlightOptions = new HighlightOptions().addField("item_title");//设置高亮的域
        highlightOptions.setSimplePrefix("<em style='color:red'>");//高亮前缀
        highlightOptions.setSimplePostfix("</em>");// 高亮后缀
        query.setHighlightOptions(highlightOptions);

        // 按照关键字查询
        Criteria criteria = new Criteria("item_keywords").is(searchMap.get("keywords"));
        query.addCriteria(criteria);
        HighlightPage<TbItem> page = solrTemplate.queryForHighlightPage(query,TbItem.class);
        for (HighlightEntry<TbItem> h : page.getHighlighted()) {//循环高亮的入口集合
            TbItem item = h.getEntity();//获取原实体类

            if(h.getHighlights().size()>0 && h.getHighlights().get(0).getSnipplets().size()>0){
                item.setTitle(h.getHighlights().get(0).getSnipplets().get(0));// 设置高亮的效果
            }
        }

        map.put("rows",page.getContent());
        return map;
    }

考虑到后续代码的可扩展性,代码的可读性,创建私有方法,用于返回查询列表的结果(高亮)

 @Override
    public Map search(Map searchMap) {
        Map map = new HashMap();
        // 查询列表
        map.putAll(searchList(searchMap));
        return map;
    }

    private Map searchList(Map searchMap){
        Map map = new HashMap();
        HighlightQuery query = new SimpleHighlightQuery();
        HighlightOptions highlightOptions = new HighlightOptions().addField("item_title");//设置高亮的域
        highlightOptions.setSimplePrefix("<em style='color:red'>");//高亮前缀
        highlightOptions.setSimplePostfix("</em>");// 高亮后缀
        query.setHighlightOptions(highlightOptions);

        // 按照关键字查询
        Criteria criteria = new Criteria("item_keywords").is(searchMap.get("keywords"));
        query.addCriteria(criteria);
        HighlightPage<TbItem> page = solrTemplate.queryForHighlightPage(query,TbItem.class);
        for (HighlightEntry<TbItem> h : page.getHighlighted()) {//循环高亮的入口集合
            TbItem item = h.getEntity();//获取原实体类

            if(h.getHighlights().size()>0 && h.getHighlights().get(0).getSnipplets().size()>0){
                item.setTitle(h.getHighlights().get(0).getSnipplets().get(0));// 设置高亮的效果
            }
        }

        map.put("rows",page.getContent());
        return map;
    }
}

3. 前端代码

测试后发现高亮显示的HTML代码原样输出,<em>{{item.title}}</em>,这是angularJS为了防止HTML攻击的安全机制。我们如何在页面显示HTML结果呢?用$sce服务的trustAsHtml方法来实现转换。

因为该功能具有一定通用性,采用angularJS的过滤器来简化开发,方便调用

  1. 修改base.js
// 过滤器
app.filter('trustHtml',['$sce',function ($sce) {
    return function (data) {// 传入的参数是被过滤的内容
        return $sce.trustAsHtml(data);
    }
}]);
  1. 使用过滤器

ng-bind-html指令用于显示HTML内容

竖线|用于调用过滤器

<div class="attr" ng-bind-html="item.title | trustHtml">
  <!--<em>{{item.title}}</em>-->
</div>

2. 搜索业务规则分析

1. 需求分析

目标是在关键字搜索的基础上添加面板搜索功能。

面板上有商品分类、品牌、各种规格和价格区间等条件

在这里插入图片描述

业务规则:

  1. 当用户输入关键字搜索后,除了显示列表结果外,还应该显示通过这个关键字搜索到的记录都有哪些商品分类
  2. 根据第一个商品分类查询对应的模板,根据模板查询出品牌列表
  3. 根据第一个商品分类查询对应的模板,根据模板查询出规格列表
  4. 当用户点击搜索面板的商品分类时,显示按照这个关键字查询结果的基础上,筛选此分类的结果
  5. 当用户点击搜索面板的品牌时,显示在以上结果的基础上,筛选此品牌的结果
  6. 当用户点击搜索面板的规格时,显示在以上结果的基础上,筛选此规格的结果
  7. 当用户点击价格区间,显示在以上结果的基础上,按价格进行筛选的结果
  8. 当用户点击搜索面板的响应条件时,隐藏已点击的条件。

2. 实现思路

  1. 搜索面板的商品分类要spring data solr的分组查询来实现
  2. 为了提高查询速度,需要把查询面板的品牌、规格数据提前放到redis
  3. 查询条件的构建、面板的隐藏需要使用angularJS来实现
  4. 后端的分类、品牌、规格、价格区间查询需要使用过滤查询实现

3. 查询分类列表

1. 需求分析

根据搜索关键字查询商品分类名称列表,效果见图1

2. 后端代码

修改SearchItemServiceImpl.java创建方法

// 查询分类列表
private List searchCategoryList(Map searchMap){
  List<String> list = new ArrayList<>();

  Query query = new SimpleQuery("*:*");
  // 按照关键字查询
  Criteria criteria = new Criteria("item_category").is(searchMap.get("keywords"));
  query.addCriteria(criteria);// 相当于SQL的where部分
  // 设置分组选项 相当于SQL的group by
  GroupOptions groupOptions = new GroupOptions().addGroupByField("item_category");
  query.setGroupOptions(groupOptions);
  // 得到分组页
  GroupPage<TbItem> page = solrTemplate.queryForGroupPage(query, TbItem.class);
  // 根据列得到分组结果集
  GroupResult<TbItem> groupResult = page.getGroupResult("item_category");
  // 得到分组结果入口页
  Page<GroupEntry<TbItem>> groupEntries = groupResult.getGroupEntries();
  // 得到分组入口集合
  List<GroupEntry<TbItem>> content = groupEntries.getContent();
  for (GroupEntry<TbItem> entry : content) {
    list.add(entry.getGroupValue());
  }
  return list;
}

search方法调用

public Map search(Map searchMap) {
  Map map = new HashMap();
  // 查询列表
  map.putAll(searchList(searchMap));
  // 根据关键字查询商品分类
  List categoryList = searchCategoryList(searchMap);
  map.put("categoryList",categoryList);
  return map;
}

3. 前端代码

修改search.html

<div class="type-wrap" ng-if="resultMap.categoryList!=null">
  <div class="fl key">商品分类</div>
  <div class="fl value">
    <span ng-repeat="category in resultMap.categoryList">
      <a href="#">{{category}}</a>
    </span>
  </div>
  <div class="fl ext"></div>
</div>

4. 缓存品牌和规格数据

1. 需求分析

将商品分类数据、品牌数据、规格数据都放入redis存储

  1. 当用户进入运营商后台的商品分类页面时,将商品分类数据放入缓存hash,以分类名称作为key,以模板id作为值
  2. 当用户进入运营商后台的模板管理页面时,分别将品牌数据和规格数据放入缓存hash,以模板id作为key,以品牌列表和规格列表作为值。

2. 缓存商品分类数据

将商品分类表存入缓存 sellergoods-service工程引入pinyougou-common依赖

修改sellergoods-service的ItemCatServiceImpl.java,添加redisTemplate

@Autowired
private RedisTemplate redisTemplate;

// 将商品分类数据放入缓存,分类名称为key,模板id为value
@Override
public List<TbItemCat> findByParentId(Long parentId) {

  TbItemCatExample example = new TbItemCatExample();
  Criteria criteria = example.createCriteria();
  criteria.andParentIdEqualTo(parentId);
  // 每次增删改后都需要执行该方法,在此进行缓存的存储
  List<TbItemCat> list = findAll();
  for (TbItemCat itemCat : list) {
    redisTemplate.boundHashOps("itemCat").put(itemCat.getName(),itemCat.getTypeId());
  }
  System.out.println("redis of the category........");
  return itemCatMapper.selectByExample(example);
}

3. 缓存品牌和规格列表数据

  1. 修改sellergoods-service的TypeTemplateServiceImpl.java
/**
     *  数据放入缓存,品牌和规格列表
     */
private void saveToRedis(){
  // 获取模板数据
  List<TbTypeTemplate> typeTemplateList = findAll();
  for (TbTypeTemplate typeTemplate : typeTemplateList) {
    // 存储品牌列表
    List<Map> brandList = JSON.parseArray(typeTemplate.getBrandIds(), Map.class);
    redisTemplate.boundHashOps("brandList").put(typeTemplate.getId(),brandList);

    // 存储规格列表
    List<Map> specList = findSpecList(typeTemplate.getId());// 根据id查询规格列表
    redisTemplate.boundHashOps("specList").put(typeTemplate.getId(),specList);
  }
}
  1. 在findPage方法调用该方法
		// ...
		saveToRedis();// 存入数据到缓存
        return new PageResult(page.getTotal(), page.getResult());
    }

这样在增删改后会自动调用该方法

4. 加载缓存数据

启动redis,content-service和sellergoods-service以及manager-web

5. 显示品牌和规格数据

1. 需求分析

在搜索面板区域显示第一个分类的品牌和规格列表,效果见图1

2. 后端代码

修改ItemSearchServiceImpl.java,增加方法

@Autowired
private RedisTemplate redisTemplate;

private Map searchBrandAndSpecList(String category){
  Map map = new HashMap();
  // 获取模板id
  Long typeId = (Long) redisTemplate.boundHashOps("itemCat").get(category);
  if (typeId!=null) {
    // 根据模板id查询品牌列表
    List brandList = (List) redisTemplate.boundHashOps("brandList").get(typeId);
    map.put("brandList",brandList);// 返回值添加到品牌列表
    // 根据模板id查询规格列表
    List specList = (List) redisTemplate.boundHashOps("specList").get(typeId);
    map.put("specList",specList);
  }
  return map;
}

search方法调用该方法

 @Override
    public Map search(Map searchMap) {
        Map map = new HashMap();
        // 查询列表
        map.putAll(searchList(searchMap));
        // 根据关键字查询商品分类
        List categoryList = searchCategoryList(searchMap);
        map.put("categoryList",categoryList);
        // 查询品牌和规格列表
        map.putAll(searchBrandAndSpecList((String) categoryList.get(0)));
        return map;
    }

3. 前端代码

1. 获取品牌列表

修改search.html,实现品牌列表

<div class="type-wrap logo" ng-if="resultMap.brandList!=null">
  <div class="fl key brand">品牌</div>
  <div class="value logos">
    <ul class="logo-list">
      <li ng-repeat="brand in resultMap.brandList">
        {{brand.text}}
      </li>
    </ul>
  </div>
  <div class="ext">
    <a href="javascript:void(0);" class="sui-btn">多选</a>
    <a href="javascript:void(0);">更多</a>
  </div>
</div>
2. 获取规格列表
<div class="type-wrap" ng-repeat="spec in resultMap.specList">
  <div class="fl key">{{spec.text}}</div>
  <div class="fl value">
    <ul class="type-list">
      <li ng-repeat="pojo in spec.options">
        <a>{{pojo.optionName}}</a>
      </li>

    </ul>
  </div>
  <div class="fl ext"></div>
</div>

6. 过滤条件构建

1. 需求分析

点击搜索面板上的分类、品牌和规格,实现查询条件的构建,查询条件以面包屑的形式显示

当面包屑显示分类、品牌和规格时,要同时隐藏搜索面板对应的区域

用户可以点击面包屑的x,撤销查询条件,撤销后显示搜索面包对应的区域

2. 添加搜索项

1. 添加搜索项方法

修改search-web的searchController.js

$scope.searchMap = {"keywords":"","category":"","brand":"","spec":{}};// 搜索对象
// 添加搜索项,改变searchMap的值
$scope.addSearchItem = function (key, value) {
  if(key==="category" || key==="brand"){
    $scope.searchMap[key] = value;
  }else{
    $scope.searchMap.spec[key] = value;
  }

};
2. 点击搜索项

修改search-web的search.html,为搜索面板添加点击事件

点击商品分类标签

<a href="#" ng-click="addSearchItem('category',category)">{{category}}</a>

点击品牌标签

<a href="#" ng-click="addSearchItem('brand',brand.text)">{{brand.text}}</a>

点击规格标签

<a href="#" ng-click="addSearchItem(spec.text,pojo.optionName)">{{pojo.optionName}}</a>
3. 显示面包屑

修改search-web的search.html,面包屑形式显示搜索条件

<ul class="fl sui-breadcrumb">搜索条件</ul>
<ul class="tags-choose">
<li class="tag" ng-if="searchMap.category!=''" ng-click="removeSearchItem('category')">商品分类:{{searchMap.category}}<i class="sui-icon icon-tb-close"></i></li>
<li class="tag" ng-if="searchMap.brand!=''" ng-click="removeSearchItem('brand')">品牌:{{searchMap.brand}}<i class="sui-icon icon-tb-close"></i></li>
<li class="tag" ng-repeat="(key,value) in searchMap.spec" ng-click="removeSearchItem(key)">{{key}}:{{value}}<i class="sui-icon icon-tb-close"></i></li>

</ul>

3. 撤销搜索项

1. 撤销搜索项的方法

修改searchController.js

// 撤销搜索项
$scope.removeSearchItem = function (key) {
  if(key==="category" || key==="brand"){
    $scope.searchMap[key] = "";
  }else{
    delete $scope.searchMap.spec[key];
  }
  $scope.search();//执行搜索
}
2. 页面调用方法

search.html

见之前代码,搜索部分的ng-click,注意规格部分比较特殊

4. 隐藏查询面板

1. 隐藏分类面板

search.html

<div class="type-wrap" ng-if="resultMap.categoryList!=null && searchMap.category==''">
  <div class="fl key">商品分类</div>
  <div class="fl value">
    <span ng-repeat="category in resultMap.categoryList">
      <a href="#" ng-click="addSearchItem('category',category)">{{category}}</a>
    </span>
  </div>
  <div class="fl ext"></div>
</div>
2. 隐藏品牌面板
<div class="type-wrap logo" ng-if="resultMap.brandList!=null && searchMap.brand==''">
  <div class="fl key brand">品牌</div>
3. 隐藏规格面板
<div class="type-wrap" ng-repeat="spec in resultMap.specList" ng-if="searchMap.spec[spec.text]==null">
  <div class="fl key">{{spec.text}}</div>
  <div class="fl value">
    <ul class="type-list">
      <li ng-repeat="pojo in spec.options">
        <a href="#" ng-click="addSearchItem(spec.text,pojo.optionName)">{{pojo.optionName}}</a>
      </li>

    </ul>
  </div>
  <div class="fl ext"></div>
</div>

5. 提交查询

修改searchController.js,在添加和删除筛选条件时自动调用搜索方法

代码部分见之前的

7. 过滤查询

1. 需求分析

根据上一步构建的查询条件,实现分类、品牌和规格的过滤条件

2. 代码实现

1. 分类过滤

修改search-service的SearchItemServiceImpl.java

private Map searchList(Map searchMap){
  Map map = new HashMap();
  // 高亮选项初始化
  HighlightQuery query = new SimpleHighlightQuery();
  HighlightOptions highlightOptions = new HighlightOptions().addField("item_title");//设置高亮的域
  highlightOptions.setSimplePrefix("<em style='color:red'>");//高亮前缀
  highlightOptions.setSimplePostfix("</em>");// 高亮后缀
  query.setHighlightOptions(highlightOptions);

  // 1.1 按照关键字查询
  Criteria criteria = new Criteria("item_keywords").is(searchMap.get("keywords"));
  query.addCriteria(criteria);
  // 1.2 按照商品分类过滤

  if (!"".equals(searchMap.get("category"))) {
    FilterQuery filterQuery = new SimpleFilterQuery();
    Criteria filterCriteria = new Criteria("item_category").is(searchMap.get("category"));
    filterQuery.addCriteria(filterCriteria);
    query.addFilterQuery(filterQuery);
  }

  // 1.3 按照品牌过滤
  if (!"".equals(searchMap.get("brand"))) {
    FilterQuery filterQuery = new SimpleFilterQuery();
    Criteria filterCriteria = new Criteria("item_brand").is(searchMap.get("brand"));
    filterQuery.addCriteria(filterCriteria);
    query.addFilterQuery(filterQuery);
  }

  // 1.4 规格过滤
  if (searchMap.get("spec")!=null) {
    Map<String,String> specMap = (Map) searchMap.get("spec");
    for (String key : specMap.keySet()) {
      FilterQuery filterQuery = new SimpleFilterQuery();
      Criteria filterCriteria = new Criteria("item_spec_"+key).is(searchMap.get(key));
      filterQuery.addCriteria(filterCriteria);
      query.addFilterQuery(filterQuery);
    }
  }


  // ************** 获取高亮结果集 ***************
  // 高亮页对象
  HighlightPage<TbItem> page = solrTemplate.queryForHighlightPage(query,TbItem.class);
  for (HighlightEntry<TbItem> h : page.getHighlighted()) {//循环高亮的入口集合
    TbItem item = h.getEntity();//获取原实体类

    if(h.getHighlights().size()>0 && h.getHighlights().get(0).getSnipplets().size()>0){
      item.setTitle(h.getHighlights().get(0).getSnipplets().get(0));// 设置高亮的效果
    }
  }

  map.put("rows",page.getContent());
  return map;
}
2. 品牌过滤

代码见上

3. 规格过滤

规格有多项,需要循环过滤,循环规格查询条件,根据key得到域名称,根据value设置过滤条件

代码见上

4. 根据分类查询品牌规格列表
public Map search(Map searchMap) {
  Map map = new HashMap();
  // 查询列表
  map.putAll(searchList(searchMap));
  // 根据关键字查询商品分类
  List categoryList = searchCategoryList(searchMap);
  map.put("categoryList",categoryList);
  // 查询品牌和规格列表
  String categoryName = (String) searchMap.get("category");

  // 如果有分类名称
  if (!"".equals(categoryName)) {
    map.putAll(searchBrandAndSpecList(categoryName));
  }else{ // 没有分类名称,按照第一个查询
    map.putAll(searchBrandAndSpecList((String) categoryList.get(0)));
  }
  return map;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值