这次我们要完成一个商品列表页面,首先要完成这个页面,我们需要看看点击浏览器的“商品列表”后,浏览器给我们响应了一个什么样的地址?
可以看到,item就是商品微服务的地址,然后我们需要去截取spu,page然后赋值即可。请求方式为GET
以上说的是如果是在后端进行编写商品逻辑的话该怎么去写
如果我们要查看它是由前端哪个页面写成的,我们还是需要看浏览器网址。
网址后面的地址在上面,后面就有了对应的路由
对应这些
对应这个
这就是我们要去实现的功能,也就是商品查询的功能。我们这里要注意的是,我们要去返回一个结果集,为什么是结果集呢?因为这里涉及到分页和总分页,加上结果就是一个结果集,所以我们注意编写后台业务逻辑就是编写结果集。
表头如下
表的内容绑定给goodsList
goodsList为一个空集合,也就是需要我们后台进行数据绑进去。
查询数据的方法为下图所示,也就是执行getDataFromServer方法。
我们首先去找到他这个方法,你可以发现这个是跟后端有关的方法,为什么这么说呢?因为他有个http,这个方法里面是包括基础路径,还有前缀,也是向后端发送方法的地址,你可以看到它后面已经写好地址,还有附上属性名。顺便说明一下这里是get请求。
这部分应该是怎么去解释呢?首先,赋予属性名。这些属性名可不是商品的,显示在浏览器上的属性名,而是后端用的属性名,也就是便与完成查询操作的属性名,前端先给你定义好这些属性名,然后再把这全部的属性名交付给一个箭头函数,有这个箭头函数进行赋值给结果集,你可以看到data就是结果集,为什么这么说呢?你可以看看箭头函数那里,他指定了这个商品的列表赋值给data的items,又把totalGoods赋值给data的total,所以这个data他不只有商品信息,而且还有商品的每一页,每页大小等等。
下面这张图也是解释了为什么data是商品结果集,因为包含了两个信息,一个是商品信息,一个是总页数信息。
然后我们可以完成后端代码的编写,去添加实体类,要注意的是我们添SPU类实体类,可以看看下图,SPU看起来就是一堆标题,还有基本属性。其实上面我们也解释过SPU是什么,就是你这个商品不可改变的元素,虽然说一级类目,二级类目三级类目,都是不同的商品,但是这个不同的意思是商品的种类根本就不一样,比如说手机跟日用品,两个根本就是没关系的东西,但是为什么这个属性也是SPU,因为只有你去选择了一级类目二级类目到三级类目,才能最终确定你选择的商品是什么,这是一个整体概念的划分,也是一种基本的把不同类型的商品进行区分。但是SKU是同一种类型的商品,因为组件配件不一样而发生区分。所以这就是他们两个的区别。
SPU还需要下面的属性
建立Mapper,日常操作就是建立接口,然后泛型传入Spu实体类,下面细节类也一样
建立Service,我们只需要一个Service一个可以了,来调用两个Mapper
凡是我们编写controller方法返回什么东西的话,第一时间就要想到ResponseEntity,这个是专门返回商品的结果,你传入泛型类型就是他返回类型,因为这里我们需要返回一个结果集,所以我们用到特定的PageResult,这个是专门返回结果集的,也是我们之前早就编写好放在通用工具类里面的,但是如果你要返回结果集,你得返回一个实体类,如果直接返回我们的spu实体类,这样子做行吗?
如果直接放spu实体类的话是没有以下这两个的,那我们该怎么应付这种情况呢?也就是说浏览器页面上有几个属性是我们实体店没有的,但是我们编写后台业务逻辑又需要把他们加进来,并且返回一个结果集出去,这种情况很麻烦,因为我们不可能重新插入另外一个controller去写,也不可能用另外一个方法去写,有个解决方法就是把商品分类和品牌放进实体类里面,但是你想想,你即使放进去了,数据库也没有对应的信息呀。
数据库是这样的。所以你即使把商品分类和品牌放进去实体类,也没有对应的数据库可以支持你。所以你要想另外的办法去应对这种情况。
在商品列表这里,他除了写id跟标题,还写了另外两个参数,你可以观察一下,这两个参数是没有对应的前端逻辑,可以从他们自己的颜色就可以看出来,这两个参数是什么意思呢?也就是说这两个参数留给你编写的空间,也就是说你无论怎么编写这两个参数都可以,他的作用就是预防。除了id跟标题以外,还多出来其他元素,所以留了两个空间给你去写。你可以看看上面我们截图的浏览器页面,他要编写的分类,除了id还有标题,然后就剩下两个位置了,另外那个“操作”我们不需要管他。
还有一个不能随便改spu实体类的原因,就是你可能其他的模块也需要调用这个实体类,如果你改的话会影响到其他模块,所以实体类是不能随便改的,改之前要看看其他模块需不需要。
回到正题上,我们该如何解决刚刚那个问题?也就是说页面上多出两个标题,我们实体类没有封装这两个标题的方法,又不能去更改实体类,那该怎么解决呢?所以我们可以继承它,只要我们去继承了他,我们就可以有他所有的方法,不仅如此,我们还可以顺便加多两个方法,也就是cname和bname,这两个也是前端写好的属性名,我们编写这两个方法的包名是bo,这个是什么意思呢?其实全名就叫business object,就是一种为了完成这个业务而添加的一个类。
然后我们的controller就可以这样去编写,记得如果是结果集的话,一定要给他默认值,如果他没有默认值的话你就写required=false。
首先我们看看这个,这个是决定上架和下架的,我们该如何去判断商品是否上下架?
他对应的是我们页面这里的模块。
当我们点击全部的时候。它并没有传saleable这个参数,这说明了什么?也就是说明了点击全部的时候不执行saleable,在上架的时候执行saleable=true,下架的时候执行saleable=false
因为我们想要用户打开浏览器的时候显示全部商品的内容,包括上架跟下架,如果saleable传入了值,因为他是个Boolean值只有true和false两种选择,无论他是传的哪个值,只要他传了值那就相当于有了参数,有了参数的话,它就不能显示全部商品的内容,所以我们默认值不能有。所以下面就为false。
然后我们的看出了业务逻辑是这样编写的,之前有个问题就是实体类有这么多个对象,我们写了一个实体类的扩展类,去继承真正的实体类,那还是有很多个对象,我们该如何保证返回的时候不会返回其他对象?只返回我们所需要的那四个对象。我们只需要在controller里面传入这四个对象到service里面就可以了,也就是告诉Service只编写这四个对象的业务逻辑,不去碰其他对象,这样就可以解决了这个问题。还有个日常操作就是判断它是否为null,或者他编写的内容是否为空,因为这里我们返回的是一个List列表,所以我们需要一个判断列表是否为空的方法,所以这里用了CollectionUtils方法,这个方法是判断集合内容的方法,Collection就是集合的意思,如果我们单纯是判断文字内容的话,才可以用StringUtils。
然后我们就要去编写对应的Service方法,凡是我们编写查询条件,或者判断商品是否上架,有多少页数,总页数这些方法的时候,我们首先要有一个思路就是,就是怎么样去编写这些方法,在Service里面去编写这些方法是一个难点。首先我们来看看编写通过关键词查找数据的方法怎么编写,也就是key方法,我们要知道通过关键词查找数据的方法就是用户输入一个关键词,然后我们需要根据这个关键词来匹配数据库里面的Id信息或者名字信息,而且这个关键词可能是名字里面的中间字,id的话就是整个id,所以我们要编写好这个业务逻辑,也就是在名字跟Id中加入查询条件。
商品是否上架这个业务逻辑就是,如架显示全部商品,那么我们就没有saleable值,上架和下架对应的分别是true和false。
我们需要知道的一点是,我们这整个service要实现的功能就是查询功能,所以我们编写的一切业务逻辑都是查询后返回的结果集,包括除了key以外的参数都是为了返回这个结果集起到作用的,像saleable这个参数的作用,因为我们要把查询到的商品分为两类,一个是上架跟未上架的,当然还有一个显示全部的,那这个我们该怎么去编写呢?我们可以把saleable作为一个过滤器来进行启动,如果要显示全部商品的话,返回null,如果只显示上架商品的话,就返回上架商品,下架商品同样道理,所以这就涉及到一个判断方法。
可以看到,只要是我们想要返回结果集,执行mapper的查询操作,我们肯定需要Example方法,所以我们需要去new一个example,然后把Spu.class传进来,为什么我们需要example呢?因为selectByExample方法只能传入Example参数,所以我们只能new一个,然后把它传进来。
这里我们只可能根据标题进行查询,为什么我们只能用标题进行查询呢?
因为我们这里要输入的是关键词,关键是只能标题有,其他的像品牌。商品分类,这些都很多,很广泛,没办法去查询。
可能有人会问我这个“title”是从哪里来的呢?难道我们是用前端传进来的吗?但是我们这里也没有涉及到连接前端的东西呀,所以我们这个“title”是从数据库里面获取的,因为数据库有这个“title”属性名,我们在接收用户传入的关键词key之后,就可以到数据库里面查找对应的title。所以上面那个criteria里面的方法我们为什么需要他,就是因为他有个模糊查询的方法。
来编写查询的业务逻辑,我们这里是根据传进来的key值(也就是用户输入进来的关键词)来进行一个判断,为什么加两个%号,因为这个表示关键词在标题里面的意思,也就是从标题里面抽取出来的意思,也是基本写法,此外我们还需要有个判断就是当关键词不为空的情况下,我们可以看到我们这里用的是StringUtils方法进行一个内容上的判断。
我们该去怎么编写这个saleable过滤条件呢?首先我们要知道一个逻辑,就是如果他的值为空的话,我们是要显示全部的商品,如果他只不为空的话,我们就要判断是true还是false,所以我们这里先写不为空的方法。
可以看到数据库判断是true还是false的方法就是通过0和1来进行区分,0为false,1为true
那我们需要去写入他是1还是0吗?其实并不需要,因为只要用户点击了上架或者下架,前端会自动做出判断,并且返回一个true或者false,可能有人会说,我们传入的值是true或者fasle,但是数据库的值是0和1,都不一样的怎么判断,其实数据库会自动根据true或者false转换成0或者1,这是数据库里面的自动匹配方法,所以我们只需要传入true或者fasle就可以了,但是看看下图,我们为什么会写成这样呢?
因为在搜索条件当中,你输入了关键词,并且选择了上架或者下架,他会返回一个含有关键词的上架商品或者含有关键词的下架商品,也或者是含关键词的全部商品,反正就是关键词与这些商品类型的交集。我们该如何获取这个交集?就需要用到criteria的andEqualTo方法,并且传入数据库的属性名saleable,再传入我们前端传来的Boolean,也就是saleable进去,他就会自动做出判断并且返回一个上架了或者下架了的商品。再加上之前传入的关键词搜索,形成了商品集。
然后到我们添加分页这一块,需要传入分页数和总页面
最终我们需要一个返回值,返回的这个只要跟controller那边写的对应的上,这里说明一下为什么下面传入的参数不是ceiteria,有些人可能会说,看上面不是值赋予了ceiteria了吗,为什么下面传入的不是这个参数?因为这个方法传入的只能是Example,而且,你可以到上面去看看,criteria和Example方法是有一定关联的,ceiteria的值是传入Example的。所以最后传入的参数是example。
顺便说一下,这里为什么返回值为Spu的List,而不是直接返回为SpuBo的List,很简单,因为这里是spuMapper方法,SpuBo只是Spu的扩展类,还没有编写他的对应Mapper方法,所以返回的只能是Spu的列表,那有人可能会说我加一个SpuBo的Mapper不就好了?但是这样会造成代码冗余,所以不建议。
因为我们上面返回的是spu列表,所以我们要把旧列表转化为新列表,我们需要用什么方法呢?我们可以用List中的stream().map方法来进行每个属性的转化,把spu转换成每一个spuBo,首先new一个SpuBo,最终返回的也是spuBo,中间我们应该写什么方法呢?当然就是把每个Spu转换为SpuBo的方法啦。只要说明一下,下面的collect以及后面的方法是什么意思呢?他们的意思就是你要转化为什么类型的集合,collect就是转换为集合的意思,后面就是你要转换的集合类型
我们中间的方法可以用这个方法进行转换,这个方法要输入什么参数?第一个参数是源数据,也就是你要被转换的数据,第二个参数是输出的目标数据,也就是你转换之后的数据是哪一个数据。这条方法的意思也就是把所有spu的值转给spuBo。
要注意的是我们在SpuBo实体类里面写的这两个参数,我们是没有传进来的参数,这两个必须要先设为null,为什么要这样做呢?因为我们要去编写如何通过关键词找到对应的品牌,品牌不是外面传进来的,而是我们知道关键词之后,再在数据库里面找到对应的商品,然后再返回品牌,分类名称也是同理。
我们可以注入这个Mapper,因为品牌名不在spu里面,是我们在spuBo里面扩展的,但是品牌id在Brand里面,我们可以通过品牌id去找到品牌,所以需要用到BrandMapper方法。
这里要注意的是,并不是用brandMapper方法在以下这个表查找,而是在下面这张表获取brand_id,然后去另外一个含有品牌的表去查找,另外这个含有品牌的表也是包含了brand_id,所以可以对应起来返回品牌名。
那么我们该如何获取id呢?我们可以直接拿spu的id来传进去,因为我们上面已经拿到了通过关键词搜索的全部商品信息在spus里面,然后我们这里执行的方法是把spus里面的全部元素,一个个转化为spuBo,所以我们也可以用到spu.getBrandId方法,可能有人会问,spu里面哪里会有getBeandId方法,其实你去看看spu的实体类就清楚了。
然后再传入他的名字就可以。
接下来就到我们这个查询分类名称该怎么写?
因为我们这里要查询多个分类名称,有这三个,但是我们该如何去输入它的值,并且在他选择到最后一个分类的时候返回一定的数据信息。我们可以做出一个判断,也就是到一,二级分类的时候,不做出反应,直到三级分类的时候,再做出反应,返回的是商品的类目信息。
我们为什么在这个categoryService类里面去编写呢?因为category实体类包含商品类目信息,所以我们可以用这个Mapper来操作并获取商品类目数据,我们的思路就是通过ID的集合来获取类目的集合。
我们可以在mapper里面继承这个方法,并且传入一个实体类,一个参数类型(注意是参数类型,不是参数)为什么要这样编写呢?
你可以通过他的方法看到,他只有一个方法,就是通过id的集合去找到对应的集合。以后我们如果需要通过一个id的集合来找到对应的商品集合的话,我们只需要继承下面的方法就可以了。
最后我们调用方法并且传入ids的参数,要注意的是,我们可以看看上图中参数的类型是List列表类型,刚好我们下面的ids也是一个列表类型,我们的返回值是把每一个商品的id与分类名字进行配对
这里要注意的是我们用的匹配方法跟上面的赋值方法是不一样的,你可以看到我们上面还有一个品牌名称的赋值,里面是调用了其他方法比如
像这种转换方法,但是你看看我们分类方法这里,他没有用其他方法,而是直接配对。
然后我们记得要去注入这个方法。
然后我们就获取cid1,2,3的分类信息并且返回一个列表,再输入到spuBo的Cname里面去,用StringUtils的方法,加入names这个list列表和分隔符(为什么加入的是names列表,他为什么能知道names是列表,因为第一个参数可以去输入collection值)
最终return的结果是spuBo,也就是对应上面的(如下图红线划出来的地方)
可能有人会问,为什么你刚刚下面这里不用去return一个对象,这里要说明一下,就是你如果代码只有一行的情况下,程序会自动给你添加return,不用你写出来,它默认会有,但是如果你像上面那样有多行代码,那就需要写个return了。
最后你在方法后面加上.var,它就会自动给你返回一个spuBo列表
最后一步,我们需要去返回结果集,我们编写的这个结果集方法里面需要传入两个参数,一个是结果页数,还有一个就是商品列表。商品列表我们已经有了并且传入了,结果页数怎么传入呢?
我们需要用到pageInfo方法把商品列表spus传进去,这里要说明一下spus和spuBo有什么区别,spus传进去PageInfo里面的目的是为了获取整个结果有多少页,pageInfo是根据你的返回结果的商品总数信息去判断你要返回多少页面,并且计算出总页数,和当前页面。所以我们最后拿到pageinfo
最后我们就可以利用pageinfo里面的获取总条数方法getTotal来获取总条数,然后传入到结果集里面去。
最终结果展示