商品分類&輪播廣告
因最近又被困在了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 <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a>
* @since 2019/11/13
*/
public interface ICategoryService {
/**
* 獲取所有有效的一級分類(根節點)
*
* @return
*/
List<CategoryResponseDTO> getAllRootCategorys();
}
2.編寫實現類com.liferunner.service.ICategoryService.java
@Service
@Slf4j
public class CategorySericeImpl implements ICategoryService {
@Autowired
private CategoryMapper categoryMapper;
@Override
public List<CategoryResponseDTO> getAllRootCategorys() {
Example example = new Example(Category.class);
val conditions = example.createCriteria();
conditions.andEqualTo("type", CategoryTypeEnum.ROOT.type);
val categoryList = this.categoryMapper.selectByExample(example);
//宣告返回物件
List<CategoryResponseDTO> 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 <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a>
* @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<SecondSubCategoryResponseDTO> getSubCategorys(Integer parentId);
}
2.resources
目錄下建立目錄mapper.custom
,以及建立和上面的介面相同名稱的XML
檔案mapper/custom/CategoryCustomMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.liferunner.custom.CategoryCustomMapper">
<resultMap id="subCategoryDTO" type="com.liferunner.dto.SecondSubCategoryResponseDTO">
<id column="id" jdbcType="INTEGER" property="id"/>
<result column="name" jdbcType="VARCHAR" property="name"/>
<result column="type" jdbcType="INTEGER" property="type"/>
<result column="parentId" jdbcType="INTEGER" property="parentId"/>
<collection property="thirdSubCategoryResponseDTOList" ofType="com.liferunner.dto.ThirdSubCategoryResponseDTO">
<id column="subId" jdbcType="INTEGER" property="subId"/>
<result column="subName" jdbcType="VARCHAR" property="subName"/>
<result column="subType" jdbcType="INTEGER" property="subType"/>
<result column="subParentId" jdbcType="INTEGER" property="subParentId"/>
</collection>
</resultMap>
<select id="getSubCategorys" resultMap="subCategoryDTO" parameterType="INTEGER">
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};
</select>
</mapper>
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<ThirdSubCategoryResponseDTO> 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<SecondSubCategoryResponseDTO> 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<SecondSubCategoryResponseDTO> 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秒自動切換圖片。接下來我們分析一下輪播圖中都包含哪些資訊:
- 圖片(img_url)是最基本的
- 圖片跳轉連線(img_link_url),這個是在我們點選這個圖片的時候需要跳轉到的頁面
- 有的可以直接跳轉到商品詳情頁面
- 有的可以直接跳轉到某一分類商品列表頁面
- 輪播圖的播放順序(sort)
- ...
開發梳理
直接查詢出所有的有效的
輪播圖片,並且進行排序
。
編碼實現
Service 實現
和商品分類實現一樣,在mscx-shop-service
中建立com.liferunner.service.ISlideAdService
並實現,程式碼如下:
public interface ISlideAdService {
/**
* 查詢所有可用廣告並排序
* @param isShow
* @return
*/
List<SlideAdResponseDTO> 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<SlideAdResponseDTO> 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<SlideAdResponseDTO> 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注入
,比如:
<!--配置bean,配置後該類由spring管理-->
<bean name="CategorySericeImpl" class="com.liferunner.service.impl.CategorySericeImpl">
<!--注入配置當前類中相應的屬性-->
<property name="categoryMapper" ref="categoryMapper"></property>
</bean>
<bean name="categoryMapper" class="com.liferunner.mapper.CategoryMapper"></bean>
注入之後,使用@Autowired
,我們可以很方便的自動從IOC容器中查詢屬性,並返回。
@Autowired的原理
在啟動spring IoC時,容器自動裝載了一個AutowiredAnnotationBeanPostProcessor
後置處理器,當容器掃描到@Autowied、@Resource或@Inject時,就會在IoC容器自動查詢需要的bean,並裝配給該物件的屬性。
注意事項:
在使用@Autowired時,首先在容器中查詢對應型別的bean如果查詢結果剛好為一個,就將該bean裝配給@Autowired指定的資料 如果查詢的結果不止一個,那麼@Autowired會根據名稱來查詢。 如果查詢的結果為空,那麼會丟擲異常。解決方法時,使用required=false
上述三種註解方式,其實本質上還是注入的2種:set屬性注入
& 構造器注入
,使用方式都可以,根據個人喜好來用,本人喜歡使用lombok外掛注入
是因為,它將程式碼整合在一起,更加符合我們Spring自動注入的規範。
原始碼下載
下節預告
下一節我們將繼續開發我們電商的核心部分-商品列表和詳情展示,在過程中使用到的任何開發元件,我都會通過專門的一節來進行介紹的,兄弟們末慌!
gogogo!