[springboot 开发单体web shop] 6. 商品分类和轮播广告展示
目录
商品分类&轮播广告
商品分类|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