欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

电商平台搭建--商品管理功能模块开发(一)

程序员文章站 2022-04-14 21:07:30
...

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;

  写到这里,前台商品模块所有功能就实现完毕了,在本篇博文中,我们用了较长的篇幅来搭建商品模块的开发基础,希望大家能动手写写,体会一下前台商品模块的开发流程。如果有不懂的地方,欢迎关注,欢迎评论留言。我们下篇再见!!