当前位置:   article > 正文

Spring Boot电商项目42:商品模块八:前台的【商品列表】接口;(这篇博客比较重要;包括:【构建Query查询对象】、【明确限制排序条件】等;)_springboot 电商前端查询多个销售属性列表方法

springboot 电商前端查询多个销售属性列表方法

说明:

(1)本篇博客比较重要的点:

          ●【参数没有放在body中,而是放在了url中】+【后端使用实体类去接收参数】:对于这种情况,不能使用@RequestBody注解,自然也不能使用@RequestParam注解;(只是,我们在写实体类的时候,实体类的属性要比照着接口参数写,不能写错)

          ● 对于那些条件比较多的接口,专门创建一个Query对象,来组织条件;是一个不错的开发技巧;

          ● 对于排序方式这种查询条件,我们最好根据接口要求,在程序中规定好【究竟有哪些排序条件】;这能很好的防止接口乱调用,程序也更加安全;

          ● 本篇博客也涉及到了【mybatis动态SQL,<if></if>】、【MySQL的模糊查询,like】、【MySQL的in子句】、【mybatis的批处理中的<foreack>子标签】等;

(2)本篇博客一个待进一步研究的地方:这人的查询,我们使用的是MySQL的原生查询方式;但是,如果以后我们的项目和大数据结合的时候,似乎可以了解一下【ElasticSearch】这些技术,来更好的实现数据的检索和查询;

目录

一:前台的【商品列表】接口说明;

1.前台的【商品列表】接口,在界面上的表现; 

2.前台的【商品列表】接口文档;

3. 前台的【商品列表】接口,开发分析;

二:正式开发;

1.在ProductController类中,创建查询前台的商品列表的方法:list()方法;

(1)因为前台的【商品列表】接口的参数很多,所以我们创建了一个实体类ProductListReq类,用这个实体类来接收参数;

(2)请求方式,url,接收参数,要写对; 

(3)因为根据接口文档中对接口返回数据格式的要求,我们需要使用PageHelper的PageInfo来包装分页数据,所以我们在开发Service层的方法时,其返回类型就是PageInfo格式的;

(4)Service层的list方法,在下一部分介绍;

2.1.在ProductServiceImpl类中,创建以分页的方式查询符合条件的商品的方法:list()方法;(重点!!!)

(1.1)【categoryId,keyword】这两个查询条件处理:构建一个专门用于组织查询条件的Query对象:ProductListQuery类:用Query对象来组织查询条件,代码会更加有条理,也方便以后的扩展;(这个开发技巧,比较重要!!!)

(1.2)【categoryId,keyword】这两个查询条件处理:构建Query对象之:判断是否有【搜索关键词】这个条件:如果有,就把这个条件构建到Query对象上去;

(1.3)【categoryId,keyword】这两个查询条件处理:构建Query对象之:判断是否有【商品分类Id】这个条件:如果有,就把这个条件处理一下,然后构建到Query对象上去;

(2)【orderBy】这一个条件处理:在构建PageHelper的时候,应用这个排序条件;需要注意:这儿我们需要【在程序中,规定好有哪些字段可以排序】,这非常重要,有关程序的安全;

(3)以前面构建的Query对象作为条件,去调用我们在Dao层编写查询方法:selectList()方法;;这个方法,在下一部分介绍;

(4)把查询结果,包装成PageInfo对象,返回给调用方;

2.2.在ProductService接口中,反向生成方法的声明;

3.1在ProductMapper中,声明根据条件,查询商品的方法:selectList()方法;

3.2在ProductMapper.xml中,编写对应的实现SQL;

三:测试;

测试1:

测试2:

测试3:

测试4:

测试5:一个在目前的项目中,实际上不存在的情况;


一:前台的【商品列表】接口说明;

1.前台的【商品列表】接口,在界面上的表现; 

(1)我们可以不使用任何条件,来分页显示商品数据;

……………………………………………………

(2)我们可以使用【按价格从低到高或从高到低排序】这个条件,来分页显示商品数据;

 ……………………………………………………

(3)我们可以使用【商品名需要包含某个关键词】这个条件,来分页显示商品数据;与此同时,也可以加入【按价格从低到高或从高到低排序】这个条件;

……………………………………………………

(4)我们可以使用【商品需要隶属于“某商品分类及其子分类”】这个条件,来分页显示商品数据;与此同时,也可以加入【按价格从低到高或从高到低排序】这个条件;

PS:上图少标注了一个,id=19的果冻橙这个分类,是id=4的橘子橙子这个分类的子目录;;;所以,id=36的四川果冻橙要需要查询出来;


可以看到,前台的【商品列表】接口,这一个接口需要承担的任务还是挺多的;这就需要我们在开发这个接口时,做好条件判断和处理;

2.前台的【商品列表】接口文档;

……………………………………………………

以这个请求实例为例,来仔细说下前台的【商品列表】接口文档;

接口返回内容:

  1. {
  2. "status": 10000,
  3. "msg": "SUCCESS",
  4. "data": {
  5. "total": 3,
  6. "list": [
  7. {
  8. "id": 24,
  9. "name": "智利帝王蟹礼盒装4.4-4.0斤/只 生鲜活鲜熟冻大螃蟹",
  10. "image": "http://111.231.103.117:8081/images/diwangxie.jpg",
  11. "detail": "商品毛重:3.0kg商品产地:智利大闸蟹售卖方式:公蟹重量:2000-4999g套餐份量:5人份以上国产/进口:进口海水/淡水:海水烹饪建议:火锅,炒菜,烧烤,刺身,加热即食包装:简装原产地:智利保存状态:冷冻公单蟹重:5.5两及以上分类:帝王蟹特产品类:其它售卖方式:单品",
  12. "categoryId": 7,
  13. "price": 222,
  14. "stock": 222,
  15. "status": 1,
  16. "createTime": "2019-12-28T08:06:34.000+0000",
  17. "updateTime": "2020-02-10T16:05:05.000+0000"
  18. },
  19. {
  20. "id": 21,
  21. "name": "智利原味三文鱼排(大西洋鲑)240g/袋 4片装",
  22. "image": "http://111.231.103.117:8081/images/sanwenyu2.jpg",
  23. "detail": "商品毛重:260.00g商品产地:中国大陆保存状态:冷冻国产/进口:进口包装:简装类别:三文鱼海水/淡水:海水烹饪建议:煎炸,蒸菜,烧烤原产地:智利",
  24. "categoryId": 8,
  25. "price": 499,
  26. "stock": 1,
  27. "status": 1,
  28. "createTime": "2019-12-28T07:13:07.000+0000",
  29. "updateTime": "2020-02-10T15:38:46.000+0000"
  30. },
  31. {
  32. "id": 22,
  33. "name": "即食海参大连野生辽刺参 新鲜速食 特级生鲜海产 60~80G",
  34. "image": "http://111.231.103.117:8081/images/haishen.jpg",
  35. "detail": "商品毛重:1.5kg商品产地:中国大陆贮存条件:冷冻重量:50-99g国产/进口:国产适用场景:养生滋补包装:袋装原产地:辽宁年限:9年以上等级:特级食品工艺:冷冻水产热卖时间:9月类别:即食海参固形物含量:70%-90%特产品类:大连海参售卖方式:单品",
  36. "categoryId": 13,
  37. "price": 699,
  38. "stock": 3,
  39. "status": 1,
  40. "createTime": "2019-12-28T07:16:29.000+0000",
  41. "updateTime": "2020-02-10T16:04:29.000+0000"
  42. }
  43. ],
  44. "pageNum": 1,
  45. "pageSize": 10,
  46. "size": 3,
  47. "startRow": 1,
  48. "endRow": 3,
  49. "pages": 1,
  50. "prePage": 0,
  51. "nextPage": 0,
  52. "isFirstPage": true,
  53. "isLastPage": true,
  54. "hasPreviousPage": false,
  55. "hasNextPage": false,
  56. "navigatePages": 8,
  57. "navigatepageNums": [
  58. 1
  59. ],
  60. "navigateFirstPage": 1,
  61. "navigateLastPage": 1
  62. }
  63. }

这儿主要说明两点:

          ● 这儿显示的商品都是扁平的,没有嵌套;

          ● 根据接口文档对返回数据的格式要求,我们可以发现,需要使用PageHelper分页组件的PageInfo对象来组织返回的分页数据;

3. 前台的【商品列表】接口,开发分析;

说明:

(1)前台的【商品列表】接口,这儿主要的地方就是根据不同的条件,去搜索的功能;

(2)入参判空;判断参数是否为空;

          ● orderBy、categoryId、keyword这三个参数,可传可不传;如果传的话,我们才会使用这个参数,把这个参数作为条件去查询;

          ● 所以,对于orderBy、categoryId、keyword这三个参数的判空:就不使用@Valid注解的Validation参数校验的方式,来判空了;

(3)如果传了keyword这个参数,比如传了“桃”,就表示我们搜索所有商品名中有“桃”的商品;对于这种模糊搜索的功能,我们使用%通配符和like关键字,去实现模糊查找;

          ● 在查找的时候,我们会使用【%通配符】和【like关键字】,在SQL中进行拼接,实现查找功能;


二:正式开发;

1.在ProductController类中,创建查询前台的商品列表的方法:list()方法;

  1. /**
  2. * 前台的商品列表接口;
  3. *
  4. * @return
  5. */
  6. @ApiOperation("前台商品列表")
  7. @GetMapping("/product/list")
  8. @ResponseBody
  9. public ApiRestResponse list(ProductListReq productListReq) {
  10. PageInfo pageInfoList = productService.list(productListReq);
  11. return ApiRestResponse.success(pageInfoList);
  12. }

说明:

(1)因为前台的【商品列表】接口的参数很多,所以我们创建了一个实体类ProductListReq类,用这个实体类来接收参数;

ProductListReq类:

  1. package com.imooc.mall.model.request;
  2. import io.swagger.models.auth.In;
  3. /**
  4. * 在开发【前台的商品列表】接口时,用该类的对象接收参数
  5. */
  6. public class ProductListReq {
  7. private String orderBy;//排序方式
  8. private Integer categoryId;//商品分类Id
  9. private String keyword;//搜索关键词
  10. private Integer pageNum = 1;//页数
  11. private Integer pageSize = 10;//每页条数
  12. public String getOrderBy() {
  13. return orderBy;
  14. }
  15. public void setOrderBy(String orderBy) {
  16. this.orderBy = orderBy;
  17. }
  18. public Integer getCategoryId() {
  19. return categoryId;
  20. }
  21. public void setCategoryId(Integer categoryId) {
  22. this.categoryId = categoryId;
  23. }
  24. public String getKeyword() {
  25. return keyword;
  26. }
  27. public void setKeyword(String keyword) {
  28. this.keyword = keyword;
  29. }
  30. public Integer getPageNum() {
  31. return pageNum;
  32. }
  33. public void setPageNum(Integer pageNum) {
  34. this.pageNum = pageNum;
  35. }
  36. public Integer getPageSize() {
  37. return pageSize;
  38. }
  39. public void setPageSize(Integer pageSize) {
  40. this.pageSize = pageSize;
  41. }
  42. }

          ●  根据前台的【商品列表】接口的参数,编写ProductListReq类,并且这儿给pageNum和pageSize设了默认值;

          ●  因为,接口是Get请求,即不是【Post请求,且把参数放在Body中】的情况;所以,这儿使用实体类接收参数的时候,是不能使用@RequestBody注解(这个注解是在,POST请求,且参数是放在Body中,而且后端我们使用实体类去接收参数时:才使用的)的;;;;然后,又因为我们这儿是使用实体类去接收参数,所以也不能使用@RequestParam注解;;;;;;;所以,这儿我们使用实体类去接收参数的时候,实体类的属性一定要和接口的参数名写一样,别写错了;

……………………………………………………

(2)请求方式,url,接收参数,要写对; 

……………………………………………………

(3)因为根据接口文档中对接口返回数据格式的要求,我们需要使用PageHelper的PageInfo来包装分页数据,所以我们在开发Service层的方法时,其返回类型就是PageInfo格式的;

…………………………………………………… 

(4)Service层的list方法,在下一部分介绍;

2.1.在ProductServiceImpl类中,创建以分页的方式查询符合条件的商品的方法:list()方法;(重点!!!)

在ProductServiceImpl类中,创建查询前台的商品列表的方法:list()方法;

  1. /**
  2. * 根据条件,以分页的方式,查询商品数据;
  3. * @param productListReq
  4. * @return
  5. */
  6. @Override
  7. public PageInfo list(ProductListReq productListReq) {
  8. //先构建一个专门助力于查询的Query对象
  9. ProductListQuery productListQuery = new ProductListQuery();
  10. //搜索条件处理
  11. //如果前端传了keyword这个参数;(其实,也就是前端搜索关键词了):就把这个条件赋值到productListQuery查询对象上去;
  12. if (!StringUtils.isEmpty(productListReq.getKeyword())) {
  13. //根据keyword拼凑"%keyword%",以好在数据库中进行模糊查询;
  14. String keyword = new StringBuilder().append("%").append(productListReq.getKeyword()).append("%").toString();
  15. //然后,把这个在查询数据库时,可以直接使用的"%keyword%"查询条件,赋值到productListQuery查询对象上去;
  16. productListQuery.setKeyword(keyword);
  17. }
  18. //如果前端传了categoryId这个参数;(其实,也就是前端选择某个商品目录):就把这个条件赋值到productListQuery查询对象上去;
  19. if (productListReq.getCategoryId() != null) {
  20. //目录处理:如果查询某个目录下的商品,不仅要查询隶属于该目录下的商品,也要查询隶属于该目录的子目录下的商品;
  21. //所以,先调用以前编写的一个【递归查询所有子目录】的方法;获取当前目录和当前目录的所有子目录的递归查询结果;
  22. List<CategoryVO> categoryVOList = categoryService.listCategoryForCustomer(productListReq.getCategoryId());
  23. //但是,上面的categoryVOList是一个嵌套的结果,我们要想获取当前目录和其子目录的CategoryId的集合,还需要做以下处理;
  24. //首先,创建一个List用来存放所有的CategoryId
  25. List<Integer> categoryIds = new ArrayList<>();
  26. //很自然,当前传参的这个CategoryId肯定在需要添加到集合中;
  27. categoryIds.add(productListReq.getCategoryId());
  28. //编写一个【遍历categoryVOList这种递归结构的数据,以获取所有categoryId的,工具方法】
  29. getCategoryIds(categoryVOList, categoryIds);
  30. //然后,把这个在查询数据库时,【当前目录和当前目录所有子目录的categoryIds】的查询条件,赋值到productListQuery查询对象上去;
  31. productListQuery.setCategoryIds(categoryIds);
  32. }
  33. //排序条件的处理
  34. //首先,尝试从productListReq这个传递参数中,获取排序的参数
  35. String orderBy = productListReq.getOrderBy();
  36. //如果我们从前端请求中的参数中,有orderBy这个有关排序的参数;;如果这个有关排序的参数,在我们预设的排序条件中的话;
  37. if (Constant.ProductListOrderBy.PRICE_ASC_DESC.contains(orderBy)) {
  38. PageHelper.startPage(productListReq.getPageNum(), productListReq.getPageSize(), orderBy);
  39. } else {
  40. //如果前端没有传orderBy参数,或者,传递orderBy参数的值不符合我们在【Constant.ProductListOrderBy.PRICE_ASC_DESC】中定义的格式
  41. PageHelper.startPage(productListReq.getPageNum(), productListReq.getPageSize());
  42. }
  43. //调用Dao层编写的(可能有条件的)查询语句
  44. List<Product> productList = productMapper.selectList(productListQuery);
  45. PageInfo pageInfo = new PageInfo(productList);
  46. return pageInfo;
  47. }
  48. /**
  49. * 工具方法,遍历【List<CategoryVO> categoryVOList】这种递归嵌套的数据结构,获取其中所有的categoryId;
  50. * @param categoryVOList
  51. * @param categoryIds
  52. */
  53. private void getCategoryIds(List<CategoryVO> categoryVOList, List<Integer> categoryIds) {
  54. //遍历传过来的这个【递归嵌套接口的,CategoryVOList】
  55. for (int i = 0; i < categoryVOList.size(); i++) {
  56. CategoryVO categoryVO = categoryVOList.get(i);
  57. if (categoryVO != null) {
  58. categoryIds.add(categoryVO.getId());
  59. //递归调用
  60. getCategoryIds(categoryVO.getChildCategory(), categoryIds);
  61. }
  62. }
  63. }

说明:

(1.1)【categoryId,keyword】这两个查询条件处理:构建一个专门用于组织查询条件的Query对象:ProductListQuery类:用Query对象来组织查询条件,代码会更加有条理,也方便以后的扩展;(这个开发技巧,比较重要!!!)

          ● 因为,根据接口文档和项目的实际情况,可以看到前台的【商品列表】接口在查询商品时候,其查询条件可能存在以下几种情况:(PS:这儿的条件种类,其实还不算太多;;;;以后可能遇到条件种类很多的情况,到那时,专门构建一个Query对象的好处,会更加明显)

          ● 创建ProductListQuery类;

  1. package com.imooc.mall.model.query;
  2. import java.util.List;
  3. /**
  4. * 描述:查询商品列表的Query
  5. */
  6. public class ProductListQuery {
  7. private String keyword;//搜索关键词,这个条件
  8. private List<Integer> categoryIds;//商品分类,这个条件
  9. public String getKeyword() {
  10. return keyword;
  11. }
  12. public void setKeyword(String keyword) {
  13. this.keyword = keyword;
  14. }
  15. public List<Integer> getCategoryIds() {
  16. return categoryIds;
  17. }
  18. public void setCategoryIds(List<Integer> categoryIds) {
  19. this.categoryIds = categoryIds;
  20. }
  21. }

说明:

(1)该Query类的第一个属性,就是keyword,其对应条件中的搜索关键词;

(2)该Query类的第二个属性,是一个List集合,这里面是:当接口传了categoryId这个商品分类的条件后:所有符合条件的商品的category_id;

也就是说:【如果在界面上,我们点击了“新鲜水果”后】→【就相当于我们传了categoryId=3,这个参数】→【就表示,我们此时加入了categoryId=3,这个搜索条件】→【这个条件经过处理,等到具体查product表的时候,就变成了:category_id在{3,4,11,12,14,19,28}的商品了】;   其实,这样一看,这个查询条件还是比较复杂的,由这儿也能感受到【专门构建查询条件的Query对象】的好处;

          ● 即,我们根据前端传过来的条件,把这些条件进行处理、分析,然后构建一个Query对象,然后再以这个Query对象作为条件,去数据库查询我们最终需要的结果;;;;这是一个十分不错的、条理清晰的策略;

……………………………………………………

(1.2)【categoryId,keyword】这两个查询条件处理:构建Query对象之:判断是否有【搜索关键词】这个条件:如果有,就把这个条件构建到Query对象上去;

          ● 这儿使用了Spring提供的一个判空工具:StringUtils.isEmpty();

          ● 因为,搜索关键词时:比如我们搜索“冰”,是要查询商品名中包含“冰”是商品;所以,在查数据库的时候,需要是模糊查找;所以,这儿,我们要先拼凑一个模糊查找要用的字符串;(PS:有关MySQL模糊查询,可以参考【数据库的基本查询三:【WHERE子句】条件查询;】);

……………………………………………………

(1.3)【categoryId,keyword】这两个查询条件处理:构建Query对象之:判断是否有【商品分类Id】这个条件:如果有,就把这个条件处理一下,然后构建到Query对象上去;

          ● 开发前台的【分类列表(递归)】接口时,创建的:递归查询分类目录的方法listCategoryForCustomer()方法回顾;如果忘记了,可以回去看下【Spring Boot电商项目30:商品分类模块九:前台的【分类列表(递归)】接口;】;

          ● listCategoryForCustomer()方法在此处使用的说明;

          ●【商品分类Id】这个条件的:处理流程分析;

由此,也可以发现,当接口传入了一个条件后,我们有的时候是无法直接用这个条件去查数据库;;;而是,要先处理下这个条件,把其处理成合适的东西,然后再那这个处理后的东西作为条件去查数据库; 具体来说,就是构建查询对象;;;这会使得程序结构更加条理清晰、有助于后期的功能扩展;

          ●工具方法:getCategoryIds()分析;这个方法很简单,没什么好说的;

……………………………………………………

至此,接口可能的条件:keyword和categoryId都已经构建到ProductListQuery这个Query对象上去了;

疑问:我们接口还有一个可能的条件就是排序方式,为什么这个条件不也构建到ProductListQuery这个Query对象上去呐?

这是因为,orderBy是有关排序的一个条件;我们这儿是使用PageHelper这个分页插件来实现分页查询;而,PageHelper在构建的时候,就可以设置排序方式,即我们可以通过PageHelper来应用这个条件;所以,就没有把其构建到ProductListQuery这个Query对象上去;

……………………………………………………

(2)【orderBy】这一个条件处理:在构建PageHelper的时候,应用这个排序条件;需要注意:这儿我们需要【在程序中,规定好有哪些字段可以排序】,这非常重要,有关程序的安全;

一种错误写法:前端传什么条件,我们就原样不动的把其传到MySQL上去进行排序;;;其实,有哪些字段可以排序,我们必须要提前设计好;

……………………………………………………

(3)以前面构建的Query对象作为条件,去调用我们在Dao层编写查询方法:selectList()方法;;这个方法,在下一部分介绍;

……………………………………………………

(4)把查询结果,包装成PageInfo对象,返回给调用方;

2.2.在ProductService接口中,反向生成方法的声明;

3.1在ProductMapper中,声明根据条件,查询商品的方法:selectList()方法;

说明:

(1)可以看到,虽然这儿我们传给Dao层的参数是一个实体类,但我们这儿依旧使用了@Param注解;

3.2在ProductMapper.xml中,编写对应的实现SQL;

  1. <select id="selectList" parameterType="com.imooc.mall.model.query.ProductListQuery" resultMap="BaseResultMap">
  2. select
  3. <include refid="Base_Column_List"/>
  4. from imooc_mall_product
  5. <where>
  6. <if test="query.keyword != null">
  7. and name like #{query.keyword}
  8. </if>
  9. <if test="query.categoryIds != null">
  10. and category_id in
  11. <foreach collection="query.categoryIds" close=")" item="item" open="(" separator=",">
  12. #{item}
  13. </foreach>
  14. </if>
  15. and status = 1
  16. </where>
  17. order by update_time desc
  18. </select>

说明:

(1)入参需要是我们的Query查询类:parameterType="com.imooc.mall.model.query.ProductListQuery";

(2)因为,这儿ProductListQuery中的两个条件是可有可无的;即,如果前端传了条件,这儿就有,如果前端没传条件,这儿就没有;所以,这儿查询SQL的时候,需要判断一下;于是就涉及到了Mybatis的动态SQL了;

          ● 有关mybatis动态SQL,如有需要可以参考:【MyBatis进阶二:MyBatis动态SQL;(仅仅涉及【if语句】)】;

(3)SQL语句,整体分析;

(4)这儿使用到了MySQL的like模糊查询;

           ● 有关MySQL的like模糊查询,如有需要可以参考:【数据库的基本查询三:【WHERE子句】条件查询;】;

(5)这儿使用到了MySQL的in子句,用来进行范围判断;

            ● 有关MySQL的in子句,如有需要可以参考:【数据库的基本查询三:【WHERE子句】条件查询;】;

(6)这儿使用到了Mybatis的<foreach>子标签;

            ● 上一次我们使用到Mybatis的<foreach>子标签是在【Spring Boot电商项目39:商品模块五:【批量上下架商品】接口;

            ● Mybatis<foreach>子标签:最初,是在【MyBatis进阶八:Mybatis批处理;(批量插入,批量删除,批量更新待写…)】中进行介绍的;如有需要,可以去参考;

至此,前台的【商品列表】接口就开发完成了;


三:测试;

启动项目;

测试1:

……………………………………………………

测试2:

……………………………………………………

测试3:

……………………………………………………

测试4:

……………………………………………………

测试5:一个在目前的项目中,实际上不存在的情况;

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/355609
推荐阅读
相关标签
  

闽ICP备14008679号