1. 程式人生 > >電商平臺搭建--商品管理功能模組開發(一)

電商平臺搭建--商品管理功能模組開發(一)

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;

  寫到這裡,前臺商品模組所有功能就實現完畢了,在本篇博文中,我們用了較長的篇幅來搭建商品模組的開發基礎,希望大家能動手寫寫,體會一下前臺商品模組的開發流程。如果有不懂的地方,歡迎關注,歡迎評論留言。我們下篇再見!!