第十九章:过滤条件的筛选和商品详情后台实现

此博客用于个人学习,来源于网上,对知识点进行一个整理。

1. 过滤条件的筛选:

当我们点击页面的过滤项,要做哪些事情?

  • 把过滤条件保存在 search 对象中(watch 监控到 search 变化后就会发送到后台)
  • 在页面顶部展示已选择的过滤项
  • 把商品分类展示到顶部面包屑

1.1 保存过滤项:

1)定义属性:

我们把已选择的过滤项保存在 search 中:

在这里插入图片描述
要注意,在 created 构造函数中会对 search 进行初始化,所以要在构造函数中对 filter 进行初始化:

在这里插入图片描述
search.filter 是一个对象,结构:

{
    "过滤项名":"过滤项值"
}

2) 绑定点击事件:

给所有的过滤项绑定点击事件:

在这里插入图片描述
要注意,点击事件传2个参数:

  • k:过滤项的 key
  • option:当前过滤项对象

在点击事件中,保存过滤项到 selectedFilter :

selectFilter(k, o){
    const obj = {};
    Object.assign(obj, this.search);
    if(k === '分类' || k === '品牌'){
        o = o.id;
    }
    obj.filter[k] = o.name || o;
    this.search = obj;
}

另外,这里 search 对象中嵌套了 filter 对象,请求参数格式化时需要进行特殊处理,修改 common.js 中的一段代码:

在这里插入图片描述

1.2 后台添加过滤条件:

既然请求已经发送到了后台,那接下来我们就在后台去添加这些条件:

1)拓展请求对象:

我们需要在请求类: SearchRequest 中添加属性,接收过滤属性。过滤属性都是键值对格式,但是 key 不确定,所以用一个 map 来接收即可。

public class SearchRequest {
    private String key;// 搜索条件

    private Integer page;// 当前页

    private Map<String,Object> filter;//过滤条件

    private static final Integer DEFAULT_SIZE = 20;// 每页大小,不从页面接收,而是固定大小
    private static final Integer DEFAULT_PAGE = 1;// 默认页

    public String getKey() {
        return key;
    }

    public Map<String, Object> getFilter() {
        return filter;
    }

    public void setFilter(Map<String, Object> filter) {
        this.filter = filter;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public Integer getPage() {
        if(page == null){
            return DEFAULT_PAGE;
        }
        // 获取页码时做一些校验,不能小于1
        return Math.max(DEFAULT_PAGE, page);
    }

    public void setPage(Integer page) {
        this.page = page;
    }

    public Integer getSize() {
        return DEFAULT_SIZE;
    }
}

2)添加过滤条件:

目前,我们的基本查询是这样的:

在这里插入图片描述
现在,我们要把页面传递的过滤条件也加入进去。

因此不能在使用普通的查询,而是要用到 BooleanQuery,基本结构是这样的:

GET /heima/_search
{
    "query":{
        "bool":{
        	"must":{ "match": { "title": "小米手机",operator:"and"}},
        	"filter":{
                "range":{"price":{"gt":2000.00,"lt":3800.00}}
        	}
        }
    }
}

所以,我们对原来的基本查询进行改造,因为比较复杂,我们将其封装到一个方法中:

/**
 * 构建布尔查询
 * @param request
 * @return
 */
private BoolQueryBuilder buildBoolQueryBuilder(SearchRequest request) {
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
    //给布尔值添加基本查询条件
    boolQueryBuilder.must(QueryBuilders.matchQuery("all",request.getKey()).operator(Operator.AND));
    //添加过滤条件
    //获取用户选择的过滤信息
    Map<String,Object> filter = request.getFilter();
    for (Map.Entry<String,Object> entry: filter.entrySet()){
        String key = entry.getKey();
        if (StringUtils.equals("品牌",key)){
            key = "brandId";
        }else if (StringUtils.equals("分类",key)){
            key = "cid3";
        }else {
            key = "specs." + key + ".keyword";
        }
        boolQueryBuilder.filter(QueryBuilders.termQuery(key,entry.getValue()));
    }
    return boolQueryBuilder;
}

2. 商品详情后台实现:

当用户搜索到商品,肯定会点击查看,就会进入商品详情页,接下来我们完成商品详情页的后台部分。

2.1 商品详情页服务:

商品详情浏览量比较大,并发高,我们会独立开启一个微服务,用来展示商品详情。

1)创建 module:商品的详情页服务,命名为: leyou-goods-web。

2)pom 依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>leyou</artifactId>
        <groupId>com.leyou.parent</groupId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.leyou.goods</groupId>
    <artifactId>leyou-goods-web</artifactId>
    <version>1.0.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>com.leyou.item</groupId>
            <artifactId>leyou-item-interface</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

3)编写启动类:

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class LeyouGoodsWebApplication {
    public static void main(String[] args) {
        SpringApplication.run(LeyouGoodsWebApplication.class, args);
    }
}

4)application.yml 文件:

server:
  port: 8084
spring:
  application:
    name: goods-web
  thymeleaf:
    cache: false
  rabbitmq:
    host: 192.168.56.101
    virtual-host: /leyou
    username: leyou
    password: leyou
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
    registry-fetch-interval-seconds: 5

5)页面模板:

从 leyou-portal 中复制 item.html 模板到当前项目 resource 目录下的 templates 中。

2.2 页面跳转:

1)修改页面跳转路径:

首先我们需要修改搜索结果页的商品地址,目前所有商品的地址都是:http://www.leyou.com/item.html。

在这里插入图片描述
我们应该跳转到对应的商品的详情页才对。

通过详情页的预览,我们知道商品详情页是多个 SKU 的集合,即 SPU。所以,页面跳转时,我们应该携带 SPU 的 id 信息。

采用了路径占位符的方式来传递 spu 的 id,我们打开 search.html,修改其中的商品路径:

在这里插入图片描述
2)nginx 反向代理:

接下来,我们要把这个地址指向我们刚刚创建的服务:leyou-goods-web,其端口为8084。在 nginx.conf 中添加一段逻辑:

在这里插入图片描述
把以 /item 开头的请求,代理到我们的8084端口。

3)编写跳转 controller:

在 leyou-goods-web 中编写 controller,接收请求,并跳转到商品详情页:

@Controller
@RequestMapping("item")
public class GoodsController {

    /**
     * 跳转到商品详情页
     * @param model
     * @param id
     * @return
     */
    @GetMapping("{id}.html")
    public String toItemPage(Model model, @PathVariable("id")Long id){

        return "item";
    }
}

2.3 封装模型数据:

我们已知的条件是传递来的 spu 的 id,我们需要根据 spu 的 id 查询到下面的数据:

  • spu 信息
  • spu 的详情
  • spu 下的所有 sku
  • 品牌
  • 商品三级分类
  • 商品规格参数、规格参数组

1)商品微服务提供接口:

以上所需数据中,根据 id 查询 spu 的接口目前还没有,我们需要在商品微服务中提供这个接口:

GoodsApi:

/**
 * 根据spu的id查询spu
 * @param id
 * @return
 */
@GetMapping("spu/{id}")
public Spu querySpuById(@PathVariable("id") Long id);

GoodsController:

@GetMapping("spu/{id}")
public ResponseEntity<Spu> querySpuById(@PathVariable("id") Long id){
    Spu spu = this.goodsService.querySpuById(id);
    if(spu == null){
        return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }
    return ResponseEntity.ok(spu);
}

GoodsService:

public Spu querySpuById(Long id) {
    return this.spuMapper.selectByPrimaryKey(id);
}

我们在页面展示规格时,需要按组展示:

在这里插入图片描述
组内有多个参数,为了方便展示。我们在 leyou-item-service 中提供一个接口,查询规格组,同时在规格组内的所有参数。

拓展 SpecGroup 类:我们在 SpecGroup 中添加一个 SpecParam 的集合,保存该组下所有规格参数。

@Table(name = "tb_spec_group")
public class SpecGroup {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private Long cid;

    private String name;

    //忽略该字段
    @Transient
    private List<SpecParam> params;// 该组下的所有规格参数集合

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Long getCid() {
        return cid;
    }

    public void setCid(Long cid) {
        this.cid = cid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<SpecParam> getParams() {
        return params;
    }

    public void setParams(List<SpecParam> params) {
        this.params = params;
    }
}

然后提供查询接口:

SpecificationAPI:

@RequestMapping("spec")
public interface SpecificationApi {

    /**
     * 根据条件查询规格参数
     * @param gid
     * @return
     */
    @GetMapping("params")
    public List<SpecParam> queryParams(
            @RequestParam(value = "gid",required = false)Long gid,
            @RequestParam(value = "cid",required = false)Long cid,
            @RequestParam(value = "generic",required = false)Boolean generic,
            @RequestParam(value = "searching",required = false)Boolean searching
    );

    @GetMapping("group/param/{cid}")
    public List<SpecGroup> queryGroupsWithParam(@PathVariable("cid")Long cid);
}

SpecificationController:

@GetMapping("group/param/{cid}")
public ResponseEntity<List<SpecGroup>> queryGroupsWithParam(@PathVariable("cid")Long cid){
    List<SpecGroup> groups = this.specificationService.queryGroupsWithParam(cid);
    if (CollectionUtils.isEmpty(groups)){
        return ResponseEntity.notFound().build();
    }
    return ResponseEntity.ok(groups);
}

SpecificationService:

public List<SpecGroup> queryGroupsWithParam(Long cid) {
    List<SpecGroup> groups = queryGroupsByCid(cid);
    groups.forEach(group->{
        List<SpecParam> params = this.queryParamsByGid(group.getId(), null, null, null);
        group.setParams(params);
    });
    return groups;
}

在 service 中,我们调用之前编写过的方法,查询规格组,和规格参数,然后封装返回。

2) 创建 FeignClient:

我们在 leyou-goods-web 服务中,创建 FeignClient:

BrandClient:

@FeignClient("item-service")
public interface BrandClient extends BrandApi {
}

CategoryClient:

@FeignClient("item-service")
public interface CategoryClient extends CategoryApi {
}

GoodsClient:

@FeignClient("item-service")
public interface GoodsClient extends GoodsApi {
}

SpecificationClient:

@FeignClient(value = "item-service")
public interface SpecificationClient extends SpecificationApi{
}

3)封装数据模型:

我们创建一个 GoodsService,在里面来封装数据模型。

这里要查询的数据:

  • SPU

  • SpuDetail

  • SKU 集合

  • 商品分类

    • 这里值需要分类的 id 和 name 就够了,因此我们查询到以后自己需要封装数据
  • 品牌对象

  • 规格组

    • 查询规格组的时候,把规格组下所有的参数也一并查出,上面提供的接口中已经实现该功能,我们直接调用
  • sku 的特有规格参数

    • 在页面渲染时,需要知道参数的名称

我们就需要把 id 和 name一一对应起来,因此需要额外查询 sku 的特有规格参数,然后变成一个 id:name 的键值对格式,也就是一个 Map,方便将来根据 id 查找。

GoodsService:

@Service
public class GoodsService {

    @Autowired
    private BrandClient brandClient;
    @Autowired
    private CategoryClient categoryClient;
    @Autowired
    private GoodsClient goodsClient;
    @Autowired
    private SpecificationClient specificationClient;

    public Map<String,Object> loadData(Long spuId){

        Map<String,Object> model = new HashMap<>();

        //根据spuId查询spu
        Spu spu = this.goodsClient.querySpuById(spuId);

        //查询spuDetail
        SpuDetail spuDetail = this.goodsClient.querySpuDetailBySpuId(spuId);

        //查询分类,Map<String,Object>
        List<Long> cids = Arrays.asList(spu.getCid1(),spu.getCid2(),spu.getCid3());
        List<String> names = this.categoryClient.queryNamesByIds(cids);
        //初始化一个分类的map
        List<Map<String,Object>> categories = new ArrayList<>();
        for (int i = 0;i < cids.size();i++){
            Map<String,Object> map = new HashMap<>();
            map.put("id",cids.get(i));
            map.put("name",names.get(i));
            categories.add(map);
        }

        //查询品牌
        Brand brand = this.brandClient.queryBrandById(spuId);

        //查询skus
        List<Sku> skus = this.goodsClient.querySkusBySpuId(spuId);

        //查询规格参数组
        List<SpecGroup> groups = this.specificationClient.queryGroupsWithParam(spu.getCid3());

        //查询特殊的规格参数
        List<SpecParam> params = this.specificationClient.queryParams(null,spu.getCid3(),null,null);
        //初始化特殊规格参数的map
        Map<Long,String> paramMap = new HashMap<>();
        params.forEach(param->{
            paramMap.put(param.getId(),param.getName());
        });

        model.put("spu",spu);
        model.put("spuDetail",spuDetail);
        model.put("categories",categories);
        model.put("brand",brand);
        model.put("skus",skus);
        model.put("groups",groups);
        model.put("paramMap",paramMap);

        return model;
    }
}

然后在 controller 中把数据放入 model:

@Controller
@RequestMapping("item")
public class GoodsController {

    @Autowired
    private GoodsService goodsService;
    
    /**
     * 跳转到商品详情页
     * @param model
     * @param id
     * @return
     */
    @GetMapping("{id}.html")
    public String toItemPage(Model model, @PathVariable("id")Long id){
        // 加载所需的数据
        Map<String, Object> modelMap = this.goodsService.loadModel(id);
        // 放入模型
        model.addAllAttributes(modelMap);
        return "item";
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值