广告轮播java_[springboot 开发单体web shop] 6. 商品分类和轮播广告展示

商品分类&轮播广告

因最近又被困在了OSGI技术POC,更新进度有点慢,希望大家不要怪罪哦。

上节 我们实现了登录之后前端的展示,如:

0445a1b8fee3acd78ad17c9dd389acbb.png

a66fc717bd6474420a13977cdb6dae68.png

接着,我们来实现左侧分类栏目的功能。

商品分类|ProductCategory

从上图我们可以看出,商品的分类其实是有层级关系的,而且这种关系一般都是无限层级。在我们的实现中,为了效果的展示,我们仅仅是展示3级分类,在大多数的中小型电商系统中,三级分类完全足够应对SKU的分类。

需求分析

先来分析分类都包含哪些元素,以jd为例:

abcb6f97880d0ed079b652102f59b2b5.png

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

SELECT p.id as id,p.`name` as `name`,p.`type` as `type`,p.father_id as parentId,

c.id as subId,c.`name` as subName,c.`type` as subType,c.parent_id as subParentId

FROM category p

LEFT JOIN category c

ON p.id = c.parent_id

WHERE p.parent_id = ${parentId};

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秒自动切换图片。接下来我们分析一下轮播图中都包含哪些信息:

9ec039a372bec03d3755f7e19098a94e.png

图片(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 String catId;

/**

* 图片跳转URL

*/

private String imageLinkUrl;

/**

* 轮播图类型 用于判断,可以根据商品id或者分类进行页面跳转,1:商品 2:分类 3:链接url

*/

private Integer type;

/**

* 轮播图展示顺序 轮播图展示顺序,从小到大

*/

private Integer sort;

/**

* 是否展示 是否展示,1:展示 0:不展示

*/

private Integer isShow;

/**

* 创建时间 创建时间

*/

private Date createTime;

/**

* 更新时间 更新

*/

private Date updateTime;

}

Controller实现

在com.liferunner.api.controller.IndexController中,新添加一个查询轮播图API,代码如下:

@Autowired

private ISlideAdService slideAdService;

@GetMapping("/slideAds")

@ApiOperation(value = "查询轮播广告", notes = "查询轮播广告接口")

public JsonResponse findAllSlideList() {

log.info("============查询所有轮播广告,isShow={},sortRanking={}=============="

, 1, "desc");

val slideAdsList = this.slideAdService.findAll(1, "desc");

if (CollectionUtils.isEmpty(slideAdsList)) {

log.info("============未查询到任何轮播广告==============");

return JsonResponse.ok(Collections.EMPTY_LIST);

}

log.info("============轮播广告查询result:{}=============="

, slideAdsList);

return JsonResponse.ok(slideAdsList);

}

Test API

{

"status": 200,

"message": "OK",

"data": [

{

"id": "slide-100002",

"imageUrl": "http://www.life-runner.com/2019/11/CpoxxF0ZmH6AeuRrAAEZviPhyQ0768.png",

"backgroundColor": "#55be59",

"productId": "",

"catId": "133",

"type": 2,

"sort": 2,

"isShow": 1,

"createTime": "2019-10-11T21:33:01.000+0000",

"updateTime": "2019-10-11T21:33:02.000+0000"

},

{

"id": "slide-100003",

"imageUrl": "http://www.life-runner.com/2019/11/CpoxxF0ZmHuAPlXvAAFe-H5_-Nw961.png",

"backgroundColor": "#ff9801",

"productId": "y200008",

"catId": "",

"type": 1,

"sort": 1,

"isShow": 1,

"createTime": "2019-10-11T21:33:01.000+0000",

"updateTime": "2019-10-11T21:33:02.000+0000"

}

],

"ok": true

}

福利讲解

在我们的实现代码中,有心的同学可以看到,我使用了3种不同的Bean注入方式:

属性注入

@Autowired

private ISlideAdService slideAdService;

构造函数注入

// 注入mapper

private final SlideAdsMapper slideAdsMapper;

@Autowired

public SlideAdServiceImpl(SlideAdsMapper slideAdsMapper) {

this.slideAdsMapper = slideAdsMapper;

}

Lombok插件注入(本质也是构造器注入,代码会动态生成。)

@RequiredArgsConstructor(onConstructor = @__(@Autowired))

public class ProductServiceImpl implements IProductService {

// RequiredArgsConstructor 构造器注入

private final ProductCustomMapper productCustomMapper;

private final ProductsMapper productsMapper;

...

}

那么,这几种注入都有什么区别呢?首先我们下了解一下Spring的注入是干什么的?

Spring提出了依赖注入的思想,即依赖类不由程序员实例化,而是通过Spring容器帮我们new指定实例并且将实例注入到需要该对象的类中。

依赖注入的另一种说法是"控制反转"。通俗的理解是:平常我们new一个实例,这个实例的控制权是我们程序员, 而控制反转是指new实例工作不由我们程序员来做而是交给Spring容器来做。

在传统的SpringMVC中,大家使用的都是XML注入,比如:

注入之后,使用@Autowired,我们可以很方便的自动从IOC容器中查找属性,并返回。

@Autowired的原理

在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。

注意事项:

在使用@Autowired时,首先在容器中查询对应类型的bean

如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据

如果查询的结果不止一个,那么@Autowired会根据名称来查找。

如果查询的结果为空,那么会抛出异常。解决方法时,使用required=false

上述三种注解方式,其实本质上还是注入的2种:set属性注入 & 构造器注入,使用方式都可以,根据个人喜好来用,本人喜欢使用lombok插件注入是因为,它将代码整合在一起,更加符合我们Spring自动注入的规范。

源码下载

下节预告

下一节我们将继续开发我们电商的核心部分-商品列表和详情展示,在过程中使用到的任何开发组件,我都会通过专门的一节来进行介绍的,兄弟们末慌!

gogogo!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值