電商平臺搭建--商品管理功能模組開發(一)
Hi,大家好,我們又見面了。相信通過前面幾篇博文的學習,大家已經對如何搭建一款屬於自己的電商平臺有了初步的瞭解,也大致懂了SSM框架的主要開發流程,那麼在接下來的幾篇博文中,我將帶領大家完成商品管理功能模組的開發,還在等什麼,直接進入正題吧!
一、商品管理功能模組-概要
先來看商品模組都需要實現哪些功能點
電商平臺的商品管理模組,一般都分為前臺和後臺,所以在後端要寫前臺的商品管理,也要寫後臺的商品管理。按照正常順序,先來完成前臺商品管理的功能。
相對於後臺來講,前臺的商品管理比較簡單,主要功能一共就兩個,獲取商品詳情、前臺商品搜尋。在這兩個功能裡面,獲取商品詳情比較簡單,我們會在前臺商品搜尋中把分頁邏輯寫好,方便前臺呼叫。
二、商品管理功能模組-前臺-獲取商品詳情功能的實現
Service層
//前臺-獲取商品詳細資訊 public ServerResponse<ProductDetailVo> getProductDetail(Integer productId){ if(productId == null){ return ServerResponse.createByErrorCodeMessage(ResponseCode.ILLEGALARGUMENT.getCode(), ResponseCode.ILLEGALARGUMENT.getDesc()); } Product product = productMapper.selectByPrimaryKey(productId); if(product == null){ return ServerResponse.createByErrorMessage("產品已下架或已刪除"); } if(product.getStatus() != Const.ProductStatusEnum.ON_SALE.getCode()){ return ServerResponse.createByErrorMessage("產品已下架或已刪除"); } ProductDetailVo productDetailVo = assembleProductDetailVo(product); return ServerResponse.createBySuccess(productDetailVo); }
首先來看,在getProductDetail方法中,ServerResponse的泛型被指定為ProductDetailVo型別。這裡的Vo是value-object也就是值物件的簡稱。什麼是值物件呢?它的本質也是一個JavaBean,只不過是為了專門解決一種或多種需求獨立出來的JavaBean。有了VO以後,對資料的處理就會更加靈活,因為我們可以單獨的封裝它們,這樣即保證了資料的獨立性,也不會對專案的整體資料造成影響,當我們的VO處理完畢以後,併入到專案中,也降低了各個功能之間的依賴性。就前臺-獲取商品詳細資訊來看,我封裝了一個ProductDetailVo這麼一個值物件,它裡面存放了和商品所有有關的資訊欄位以及getter和setter方法。
先回到該方法,傳遞一個productId來在資料庫中查詢相關的商品資訊。如果productId為空,則提示引數錯誤,否則就向資料庫中查詢,將查詢結果返回給product,如果其為空表示產品已下架或已刪除,還有一種情況是根據商品的狀態,根據返回值來判斷商品的狀態,這個ProductStatusEnum會在下面補充。當商品的校驗狀態通過以後,就可以直接呼叫封裝好的assembleProductDetailVo方法來處理商品的詳細資訊,最後直接返回productDetailVo即可,這個欄位裡面就包括了商品的詳細資訊。
public class ProductDetailVo {
private Integer id;
private Integer categoryId;
private String name;
private String subtitle;
private String mainImage;
private String subImages;
private String detail;
private BigDecimal price;
private Integer stock;
private Integer status;
private String createTime;
private String updateTime;
private String imageHost;
private Integer parentCategoryId;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getCategoryId() {
return categoryId;
}
public void setCategoryId(Integer categoryId) {
this.categoryId = categoryId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSubtitle() {
return subtitle;
}
public void setSubtitle(String subtitle) {
this.subtitle = subtitle;
}
public String getMainImage() {
return mainImage;
}
public void setMainImage(String mainImage) {
this.mainImage = mainImage;
}
public String getSubImages() {
return subImages;
}
public void setSubImages(String subImages) {
this.subImages = subImages;
}
public String getDetail() {
return detail;
}
public void setDetail(String detail) {
this.detail = detail;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public Integer getStock() {
return stock;
}
public void setStock(Integer stock) {
this.stock = stock;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getCreateTime() {
return createTime;
}
public void setCreateTime(String createTime) {
this.createTime = createTime;
}
public String getUpdateTime() {
return updateTime;
}
public void setUpdateTime(String updateTime) {
this.updateTime = updateTime;
}
public String getImageHost() {
return imageHost;
}
public void setImageHost(String imageHost) {
this.imageHost = imageHost;
}
public Integer getParentCategoryId() {
return parentCategoryId;
}
public void setParentCategoryId(Integer parentCategoryId) {
this.parentCategoryId = parentCategoryId;
}
}
為了使用這個VO,我們不能在其內部去寫方法,然後在外部呼叫,這樣是不符合情理的。對於這種情況,可以在對應的Service中封裝一個處理VO的方法,這樣一來,就可以實現“點對點”的功能服務。在該方法中,我寫了一個處理商品詳情的VO方法。
private ProductDetailVo assembleProductDetailVo(Product product){
ProductDetailVo productDetailVo = new ProductDetailVo();
productDetailVo.setId(product.getId());
productDetailVo.setSubtitle(product.getSubtitle());
productDetailVo.setPrice(product.getPrice());
productDetailVo.setName(product.getName());
productDetailVo.setSubImages(product.getSubImages());
productDetailVo.setMainImage(product.getMainImage());
productDetailVo.setDetail(product.getDetail());
productDetailVo.setStatus(product.getStatus());
productDetailVo.setStock(product.getStock());
productDetailVo.setCategoryId(product.getCategoryId());
productDetailVo.setImageHost(PropertiesUtil.getProperty("fet.server.http.prefix", "your-ftp-address"));
Category category = categoryMapper.selectByPrimaryKey(product.getCategoryId());
if(category == null){
productDetailVo.setParentCategoryId(0);
}else{
productDetailVo.setParentCategoryId(category.getParentId());
}
productDetailVo.setCreateTime(DateTimeUtil.dateToStr(product.getCreateTime()));
productDetailVo.setUpdateTime(DateTimeUtil.dateToStr(product.getUpdateTime()));
return productDetailVo;
}
像處理JavaBean一樣,處理VO的方法型別一定要是當前VO型別的並傳遞給VO需要的Product資料,否則資料無法處理。首先在該方法中new出一個當前ProductDetailVo的例項,然後把需要處理的資料用setter方法設定好,然後有一個category判斷。如果getCategoryId為空,就把它的父節點id也設定為空,否則就把當前的id值更新到父節點id上。最後再設定一下更新商品或者建立商品的建立時間和更新時間,返回productDetailVo,就處理完了ProductDetailVo值物件。
這裡提一下,ImageHost欄位為自己的圖片伺服器地址,因為所有的圖片都是儲存在圖片伺服器上面的,所以修改圖片要通過圖片伺服器的方式進行修改,切記。
Service層就寫好了,再來看Controller層
@RequestMapping(value = "detail.do")
@ResponseBody
public ServerResponse<ProductDetailVo> detail(Integer productId){
return iProductService.getProductDetail(productId);
}
因為和功能有關的邏輯在Service層中已經處理完善了,所以在Controller裡直接返回處理結果即可,別忘了這裡的泛型一定是ProductDetailVo型別。
三、商品管理功能模組-前臺-商品搜尋功能的實現
Service層
//前臺商品搜尋
public ServerResponse<PageInfo> getProductByKeywordCategory(String keyword, Integer categoryId, int pageNum, int pageSize, String orderBy){
if(StringUtils.isBlank(keyword) && categoryId == null){
return ServerResponse.createByErrorCodeMessage(ResponseCode.ILLEGALARGUMENT.getCode(), ResponseCode.ILLEGALARGUMENT.getDesc());
}
List<Integer> categoryIdList = new ArrayList<>();
if(categoryId != null){
Category category = categoryMapper.selectByPrimaryKey(categoryId);
if(category == null && StringUtils.isBlank(keyword)){
PageHelper.startPage(pageNum, pageSize);
List<ProductListVo> productListVoList = Lists.newArrayList();
PageInfo pageInfo = new PageInfo(productListVoList);
return ServerResponse.createBySuccess(pageInfo);
}
categoryIdList = iCategoryService.selectCategoryAndChildrenById(category.getId()).getData();
}
if(StringUtils.isNotBlank(keyword)){
keyword = new StringBuilder().append("%").append(keyword).append("%").toString();
}
// 排序處理
PageHelper.startPage(pageNum, pageSize);
if(StringUtils.isNotBlank(orderBy)){
if(Const.ProductListOrderBy.PRICE_ASC_DESC.contains(orderBy)){
String[] orderByArray = orderBy.split("_");
PageHelper.orderBy(orderByArray[0]+" "+orderByArray[1]);
}
}
List<Product> productList = productMapper.selectByNameAndCategoryIds(StringUtils.isBlank(keyword)?null:keyword,categoryIdList.size()==0?null:categoryIdList);
List<ProductListVo> productListVoList = Lists.newArrayList();
for(Product product : productList){
ProductListVo productListVo = assembleProductListVo(product);
productListVoList.add(productListVo);
}
PageInfo pageInfo = new PageInfo(productList);
pageInfo.setList(productListVoList);
return ServerResponse.createBySuccess(pageInfo);
}
在前臺商品搜尋中,我們需要處理很多事情。首先是搜尋方式,可以通過關鍵詞進行搜尋,也可以通過categoryId進行搜尋(這個categoryId是後端為每一個商品單獨新增的id,會直接存放在資料庫中對應商品)。其次就是將搜尋結果進行一個分頁處理,如果沒有分頁的話,商品列表頁會看著非常亂,會嚴重影響使用者體驗,這在企業中也是不允許的。
跟欄位驗證的方法相似,首先驗證keyword和categoryId是否為空,如果為空,則提示引數錯誤,否則進行下一步操作。像上述方法一樣,對於複雜資料的處理,應該封裝一個VO,這裡的是ProductListVo(下面有介紹)。回到方法中,分頁的實現是之前提到的Mybatis三劍客的PageHelper,這是一個開源的GitHub專案(https://github.com/pagehelper/Mybatis-PageHelper)。使用PageHelper進行分頁處理,需要三步實現。第一步,呼叫PageHelper的startPage方法,傳入分頁數量和每頁顯示的數量;第二步,填充分頁資料;第三步,開始分頁。注意,全部用List集合進行處理。對排序邏輯的處理,分為預設排序和價格升降序排序。其實預設排序就是把直接進行搜尋的結果顯示出來就行,不需要再處理,所以這裡的程式碼是不用寫的,只需處理價格的高低即可。因為每排一次序就是重新顯示一個介面,所以在處理價格排序時要重新進行分頁,邏輯和上述相同不再贅述。因為涉及到排序,所以getProductByKeywordCategory方法的泛型需要為PageInfo,否則無法進行排序。最後將排序處理後的結果返回,就完成了該方法的編寫。
PageInfo pageInfo = new PageInfo(productListVoList);//第三步
List<ProductListVo> productListVoList = Lists.newArrayList();//第二步
public class ProductListVo {
private Integer id;
private Integer categoryId;
private String name;
private String subtitle;
private String mainImage;
private BigDecimal price;
private Integer status;
private String imageHost;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getCategoryId() {
return categoryId;
}
public void setCategoryId(Integer categoryId) {
this.categoryId = categoryId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSubtitle() {
return subtitle;
}
public void setSubtitle(String subtitle) {
this.subtitle = subtitle;
}
public String getMainImage() {
return mainImage;
}
public void setMainImage(String mainImage) {
this.mainImage = mainImage;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getImageHost() {
return imageHost;
}
public void setImageHost(String imageHost) {
this.imageHost = imageHost;
}
}
像之前處理VO方式一樣,在對應的Service層中封裝一個assemble方法來處裡ProductListVo
private ProductListVo assembleProductListVo(Product product){
ProductListVo productListVo = new ProductListVo();
productListVo.setId(product.getId());
productListVo.setName(product.getName());
productListVo.setCategoryId(product.getCategoryId());
productListVo.setImageHost(PropertiesUtil.getProperty("ftp.server.http.prefix","your-ftp-address"));
productListVo.setMainImage(product.getMainImage());
productListVo.setPrice(product.getPrice());
productListVo.setSubtitle(product.getSubtitle());
productListVo.setStatus(product.getStatus());
return productListVo;
}
因為只是簡單的獲取到商品的列表資訊,所以不用再封裝處理資料的方法,只是對欄位的一個處理。
Controller層
@RequestMapping(value = "list.do")
@ResponseBody
public ServerResponse<PageInfo> list(@RequestParam(value = "keyword", required = false) String keyword,
@RequestParam(value = "categoryId", required = false)Integer categoryId,
@RequestParam(value = "pageNum", defaultValue = "1") int pageNum,
@RequestParam(value = "pageSize", defaultValue = "10") int pageSize,
@RequestParam(value = "orderBy", defaultValue = "") String orderBy){
return iProductService.getProductByKeywordCategory(keyword, categoryId, pageNum, pageSize, orderBy);
}
因為和功能有關的邏輯在Service層中已經處理完善了,所以在Controller裡直接返回處理結果即可。 這裡又用到了RequestParam註解來為每個欄位設定預設值。keyword和categoryId的預設值為false,表示如果前臺不傳遞這兩個欄位給後臺也是可以進行排序的,即這兩個欄位不是非必須的。pageNum預設值為1,表示預設只有一頁,pageSize預設值為10,表示每頁顯示10條記錄。orderBy預設值為空,表示預設使用預設排序,若使用價格排序,需要前臺傳入orderBy的值。
四、關於商品模組的一些補充
(1)、因為是商品模組,安全性較低,所以所有的介面請求都採用預設的GET請求;
(2)、整個專案的Mybatis層的所有Sql語句,會在後期進行更新;
(3)、筆者預設認為你已經有一定的JavaBean基礎,所以在商品模組沒有對JavaBean進行詳細的解釋;
(4)、值物件VO是依賴POJO的,所以在處理值物件VO的方法中傳入的是對應的POJO型別,例如這裡的Product;
寫到這裡,前臺商品模組所有功能就實現完畢了,在本篇博文中,我們用了較長的篇幅來搭建商品模組的開發基礎,希望大家能動手寫寫,體會一下前臺商品模組的開發流程。如果有不懂的地方,歡迎關注,歡迎評論留言。我們下篇再見!!