目录 商品分类&轮播广告 商品分类|ProductCategory 需求分析 开发梳理 编码实现 轮播广告|SlideAD 需求分析 开发梳理 编码实现 福利讲解 源码下载 下节预告 商品分类&轮播广告 因最近又被困在了OSGI技术POC,更新进度有点慢,希望大家不要怪罪哦。 上节 我们实现了登录之后前端的展示,如: 登录展示效果 子分类 接着,我们来实现左侧分类栏目的功能。 商品分类|ProductCategory 从上图我们可以看出,商品的分类其实是有层级关系的,而且这种关系一般都是无限层级。在我们的实现中,为了效果的展示,我们仅仅是展示3级分类,在大多数的中小型电商系统中,三级分类完全足够应对SKU的分类。 需求分析 先来分析分类都包含哪些元素,以jd为例: 京东分类 logo(logo) 有的分类文字前面会有小标 分类展示主图(img_url) 主标题(title) 副标题/Slogan 图片跳转地址(img_link_url)-- 大多数时候我们点击分类都会分类Id跳转到固定的分类商品列表展示页面,但是在一些特殊的场景,比如我们要做一个活动,希望可以点击某一个分类的主图直接定位到活动页面,这个url就可以使用了。 上级分类(parent_id) 背景色(bg_color) 顺序(sort) 当前分类级别(type) 开发梳理 在上一小节,我们简单分析了一下要实现商品分类的一些points,那么我们最好在每次拿到需求【开发之前】,对需求进行拆解,然后分解开发流程,这样可以保证我们更好的理解需求,以及在开发之前发现一部分不合理的需求,并且如果需求设计不合理的话,开发人员完全有权,也有责任告知PM。大家的终极目的都是为了我们做的产品更加合理,好用,受欢迎! 首次展示,仅仅读取一级分类(Root) 根据一级分类查询二三级子分类 编码实现 查询一级分类 Service实现 1.在com.liferunner.service中创建service 接口ICategoryService.java, 编写查询所有一级分类的方法getAllRootCategorys,如下: package com.liferunner.service; import com.liferunner.dto.CategoryResponseDTO; import com.liferunner.dto.SecondSubCategoryResponseDTO; import java.util.List; /** * ICategoryService for : 分类service * * @author Isaac.Zhang | 若初 * @since 2019/11/13 */ public interface ICategoryService { /** * 获取所有有效的一级分类(根节点) * * @return */ List getAllRootCategorys(); } 2.编写实现类com.liferunner.service.ICategoryService.java @Service @Slf4j public class CategorySericeImpl implements ICategoryService { @Autowired private CategoryMapper categoryMapper; @Override public List getAllRootCategorys() { Example example = new Example(Category.class); val conditions = example.createCriteria(); conditions.andEqualTo("type", CategoryTypeEnum.ROOT.type); val categoryList = this.categoryMapper.selectByExample(example); //声明返回对象 List categoryResponseDTOS = new ArrayList<>(); if (!CollectionUtils.isEmpty(categoryList)) { //赋值 CategoryResponseDTO dto; for (Category category : categoryList) { dto = new CategoryResponseDTO(); BeanUtils.copyProperties(category, dto); categoryResponseDTOS.add(dto); } } return categoryResponseDTOS; } } 上述代码很好理解,创建tk.mybatis.mapper.entity.Example,将条件传入,然后使用通用Mapper查询到type=1的一级分类,接着将查到的对象列表转换为DTO对象列表。 Controller实现 一般情况下,此类查询都会出现在网站的首页,因此我们来创建一个com.liferunner.api.controller.IndexController,并对外暴露一个查询一级分类的接口: package com.liferunner.api.controller; import com.liferunner.service.ICategoryService; import com.liferunner.service.IProductService; import com.liferunner.service.ISlideAdService; import com.liferunner.utils.JsonResponse; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import java.util.Collections; import lombok.extern.slf4j.Slf4j; import lombok.val; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; /** * IndexController for : 首页controller * * @author Isaac.Zhang | 若初 * @since 2019/11/12 */ @RestController @RequestMapping("/index") @Api(value = "首页信息controller", tags = "首页信息接口API") @Slf4j public class IndexController { @Autowired private ICategoryService categoryService; @GetMapping("/rootCategorys") @ApiOperation(value = "查询一级分类", notes = "查询一级分类") public JsonResponse findAllRootCategorys() { log.info("============查询一级分类=============="); val categoryResponseDTOS = this.categoryService.getAllRootCategorys(); if (CollectionUtils.isEmpty(categoryResponseDTOS)) { log.info("============未查询到任何分类=============="); return JsonResponse.ok(Collections.EMPTY_LIST); } log.info("============一级分类查询result:{}==============", categoryResponseDTOS); return JsonResponse.ok(categoryResponseDTOS); } } Test API 编写完成之后,我们需要对我们的代码进行测试验证,还是通过使用RestService插件来实现,当然,大家也可以通过Postman来测试。 { "status": 200, "message": "OK", "data": [ { "id": 1, "name": "烟酒", "type": 1, "parentId": 0, "logo": "img/cake.png", "slogan": "吸烟受害健康", "catImage": "http://www.life-runner.com/shop/category/cake.png", "bgColor": "#fe7a65" }, { "id": 2, "name": "服装", "type": 1, "parentId": 0, "logo": "img/cookies.png", "slogan": "我选择我喜欢", "catImage": "http://www.life-runner.com/shop/category/cookies.png", "bgColor": "#f59cec" }, { "id": 3, "name": "鞋帽", "type": 1, "parentId": 0, "logo": "img/meat.png", "slogan": "飞一般的感觉", "catImage": "http://www.life-runner.com/shop/category/meat.png", "bgColor": "#b474fe" } ], "ok": true } 根据一级分类查询子分类 因为根据一级id查询子分类的时候,我们是在同一张表中做自连接查询,因此,通用mapper已经不适合我们的使用,因此我们需要自定义mapper来实现我们的需求。 自定义Mybatis Mapper实现 在之前的编码中,我们都是使用的插件帮我们实现的通用Mapper,但是这种查询只能处理简单的单表CRUD,一旦我们需要SQL 包含一部分逻辑处理的时候,那就必须得自己来编写了,let's code. 1.在项目mscx-shop-mapper中,创建一个新的custom package,在该目录下创建自定义mappercom.liferunner.custom.CategoryCustomMapper。 public interface CategoryCustomMapper { List getSubCategorys(Integer parentId); } 2.resources目录下创建目录mapper.custom,以及创建和上面的接口相同名称的XML文件mapper/custom/CategoryCustomMapper.xml TIPS 上述创建的package,一定要在项目的启动类com.liferunner.api.ApiApplication中修改@MapperScan(basePackages = { "com.liferunner.mapper", "com.liferunner.custom"}),如果不把我们的custom package加上,会造成扫描不到而报错。 在上面的xml中,我们定义了两个DTO对象,分别用来处理二级和三级分类的DTO,实现如下: @Data @ToString public class SecondSubCategoryResponseDTO { /** * 主键 */ private Integer id; /** * 分类名称 */ private String name; /** * 分类类型 1:一级大分类 2:二级分类 3:三级小分类 */ private Integer type; /** * 父id */ private Integer parentId; List thirdSubCategoryResponseDTOList; } --- @Data @ToString public class ThirdSubCategoryResponseDTO { /** * 主键 */ private Integer subId; /** * 分类名称 */ private String subName; /** * 分类类型 1:一级大分类 2:二级分类 3:三级小分类 */ private Integer subType; /** * 父id */ private Integer subParentId; } Service实现 编写完自定义mapper之后,我们就可以继续编写service了,在com.liferunner.service.ICategoryService中新增一个方法:getAllSubCategorys(parentId).如下: public interface ICategoryService { ... /** * 根据一级分类获取子分类 * * @param parentId 一级分类id * @return 子分类list */ List getAllSubCategorys(Integer parentId); } 在com.liferunner.service.impl.CategorySericeImpl实现上述方法: @Service @Slf4j public class CategorySericeImpl implements ICategoryService { @Autowired private CategoryMapper categoryMapper; @Autowired private CategoryCustomMapper categoryCustomMapper; ... @Override @Transactional(propagation = Propagation.SUPPORTS) public List getAllSubCategorys(Integer parentId) { return this.categoryCustomMapper.getSubCategorys(parentId); } } Controller实现 @RestController @RequestMapping("/index") @Api(value = "首页信息controller", tags = "首页信息接口API") @Slf4j public class IndexController { @Autowired private ICategoryService categoryService; ... @GetMapping("/subCategorys/{parentId}") @ApiOperation(value = "查询子分类", notes = "根据一级分类id查询子分类") public JsonResponse findAllSubCategorys( @ApiParam(name = "parentId", value = "一级分类id", required = true) @PathVariable Integer parentId) { log.info("============查询id = {}的子分类==============", parentId); val categoryResponseDTOS = this.categoryService.getAllSubCategorys(parentId); if (CollectionUtils.isEmpty(categoryResponseDTOS)) { log.info("============未查询到任何分类=============="); return JsonResponse.ok(Collections.EMPTY_LIST); } log.info("============子分类查询result:{}==============", categoryResponseDTOS); return JsonResponse.ok(categoryResponseDTOS); } } Test API { "status": 200, "message": "OK", "data": [ { "id": 11, "name": "国产", "type": 2, "parentId": 1, "thirdSubCategoryResponseDTOList": [ { "subId": 37, "subName": "中华", "subType": 3, "subParentId": 11 }, { "subId": 38, "subName": "冬虫夏草", "subType": 3, "subParentId": 11 }, { "subId": 39, "subName": "南京", "subType": 3, "subParentId": 11 }, { "subId": 40, "subName": "云烟", "subType": 3, "subParentId": 11 } ] }, { "id": 12, "name": "外烟", "type": 2, "parentId": 1, "thirdSubCategoryResponseDTOList": [ { "subId": 44, "subName": "XXXXX", "subType": 3, "subParentId": 12 }, { "subId": 45, "subName": "RRRRR", "subType": 3, "subParentId": 12 } ] } ], "ok": true } 以上我们就已经实现了和jd类似的商品分类的功能实现。 轮播广告|SlideAD 需求分析 这个就是jd或者tb首先的最顶部的广告图片是一样的,每隔1秒自动切换图片。接下来我们分析一下轮播图中都包含哪些信息: Slide Images 图片(img_url)是最基本的 图片跳转连接(img_link_url),这个是在我们点击这个图片的时候需要跳转到的页面 有的可以直接跳转到商品详情页面 有的可以直接跳转到某一分类商品列表页面 轮播图的播放顺序(sort) ... 开发梳理 直接查询出所有的有效的轮播图片,并且进行排序。 编码实现 Service 实现 和商品分类实现一样,在mscx-shop-service中创建com.liferunner.service.ISlideAdService并实现,代码如下: public interface ISlideAdService { /** * 查询所有可用广告并排序 * @param isShow * @return */ List findAll(Integer isShow, String sortRanking); } @Service @Slf4j public class SlideAdServiceImpl implements ISlideAdService { // 注入mapper private final SlideAdsMapper slideAdsMapper; @Autowired public SlideAdServiceImpl(SlideAdsMapper slideAdsMapper) { this.slideAdsMapper = slideAdsMapper; } @Override public List findAll(Integer isShow, String sortRanking) { Example example = new Example(SlideAds.class); //设置排序 if (StringUtils.isBlank(sortRanking)) { example.orderBy("sort").asc(); } else { example.orderBy("sort").desc(); } val conditions = example.createCriteria(); conditions.andEqualTo("isShow", isShow); val slideAdsList = this.slideAdsMapper.selectByExample(example); //声明返回对象 List slideAdResponseDTOList = new ArrayList<>(); if (!CollectionUtils.isEmpty(slideAdsList)) { //赋值 SlideAdResponseDTO dto; for (SlideAds slideAds : slideAdsList) { dto = new SlideAdResponseDTO(); BeanUtils.copyProperties(slideAds, dto); slideAdResponseDTOList.add(dto); } } return slideAdResponseDTOList; } } 从上述可以看到,这里我使用的是构造函数注入SlideAdsMapper,其余代码单表查询没什么特别的,根据条件查询轮播图,并返回结果,返回的对象是com.liferunner.dto.SlideAdResponseDTO列表,代码如下: @Data @AllArgsConstructor @NoArgsConstructor @Builder @ApiModel(value = "轮播广告返回DTO", description = "轮播广告返回DTO") public class SlideAdResponseDTO{ /** * 主键 */ private String id; /** * 图片地址 */ private String imageUrl; /** * 背景颜色 */ private String backgroundColor; /** * 商品id */ private String productId; /** * 商品分类id */ private