Java-springboot生鲜电商项目(四)商品模块

Java-springboot生鲜电商项目(四)商品模块

商品模块

涉及到的接口

  1. 增加商品
  2. 上传图片
  3. 更新商品
  4. 删除商品
  5. 批量上下架商品
  6. 商品列表(后台)
  7. 前台:商品列表
  8. 商品详情

这个模块的难点

  1. 商品搜索
  2. 排序
  3. 目录查询

常见错误

  1. 更新和新增放在同一个接口
  2. 排序字段不用枚举

(一)后台的新增商品

在request创建AddProduct类,目的是为了修改product不会污染pojo下的product类

package com.hyb.mall.model.request;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.util.Date;

public class AddProductReq {

    @NotNull(message = "商品名称不能为空")
    private String name;

    @NotNull(message = "商品图片不能为空")
    private String image;

    private String detail;

    @NotNull(message = "商品分类不能为空")
    private Integer categoryId;

    @NotNull(message = "商品价格不能为空")
    @Min(value = 1, message = "价格不能小于1分钱")
    private Integer price;

    @NotNull(message = "商品库存不能为空")
    @Max(value = 10000, message = "库存不能大于1万")
    private Integer stock;

    private Integer status;


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name == null ? null : name.trim();
    }

    public String getImage() {
        return image;
    }

    public void setImage(String image) {
        this.image = image == null ? null : image.trim();
    }

    public String getDetail() {
        return detail;
    }

    public void setDetail(String detail) {
        this.detail = detail == null ? null : detail.trim();
    }

    public Integer getCategoryId() {
        return categoryId;
    }

    public void setCategoryId(Integer categoryId) {
        this.categoryId = categoryId;
    }

    public Integer getPrice() {
        return price;
    }

    public void setPrice(Integer 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;
    }
}

在productMapper创建查询商品名的接口,用于之后的重名判断

Product selectByName(String name);

在productMapper.xml编写SQL语句

  <select id="selectByName" parameterType="java.lang.String" resultMap="BaseResultMap">
    select
    <include refid="Base_Column_List"/>
    from  imooc_mall_product
    where name=#{name,jdbcType=VARCHAR}
  </select>

在productServiceImpl实现商品插入的业务逻辑

package com.hyb.mall.service.impl;

import com.hyb.mall.exception.MallException;
import com.hyb.mall.exception.MallExceptionEnum;
import com.hyb.mall.model.dao.ProductMapper;
import com.hyb.mall.model.pojo.Product;
import com.hyb.mall.model.request.AddProductReq;
import com.hyb.mall.service.ProductService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class ProductServiceImpl implements ProductService {
    @Autowired
    ProductMapper productMapper;

    @Override
    public void add(AddProductReq addProductReq) {
        //1.创建product实例
        Product product = new Product();
        //2.将新增的product复制到pojo中product类中,对数据进行一次覆盖
        BeanUtils.copyProperties(addProductReq, product);
        //3.通过查询数据库是否有重名的商品,有就添加失败
        Product productOld = productMapper.selectByName(addProductReq.getName());
        if (productOld != null) {
            throw new MallException(MallExceptionEnum.NAME_EXISTED);
        }
        //4.进行插入数据操作并判断是否有效插入
        int count = productMapper.insertSelective(product);
        if (count == 0) {
            throw new MallException(MallExceptionEnum.CREATE_FAILE);
        }
    }
}

写到这里,会出现一个问题,就是图片上传问题,处理步骤如下:

UUID
  1. 使用UUID(通用唯一识别码)在本地上传,风险不大,在服务器上传图片,如果有重名就会将原来的图片覆盖掉,也是防止爬图
  2. UUID的生成规则:日期和时间,Mac地址,hashcode,随机数

在Constant常量类中编写图片文件保存的地址并在类上加入@Component注解

    //因为存在静态的static,用普通的方式进行处理,是注入不进去的 在进行图片上传的时候回报错
    public static  String FILE_UPLOAD_DIR;
    @Value("${file.upload.dir}")
    public void setFileUploadDir(String fileUploadDir){
        FILE_UPLOAD_DIR=fileUploadDir;
    }

在配置文件是配置本地电脑保存图片文件的路径,我在这里踩了一个坑,我在文件夹后面少了一个"/"导致postman测试不到,一直报20000系统错误

file.upload.dir=/Users/hyb/Desktop/Pfile/

在MallExceptionEnum中加入

MKDIR_FAILE(10014,"文件夹创建失败"),

在productController中编写增加商品和图片上传的接口

package com.hyb.mall.controller;

import com.hyb.mall.common.ApiRestResponse;
import com.hyb.mall.common.Constant;
import com.hyb.mall.exception.MallException;
import com.hyb.mall.exception.MallExceptionEnum;
import com.hyb.mall.model.request.AddProductReq;
import com.hyb.mall.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.UUID;

/**
 * 描述:后台管理Controller
 */
@RestController
public class ProductAdminController {
    @Autowired
    ProductService productService;

    @PostMapping("admin/product/add")
    public ApiRestResponse addProduct(@Valid @RequestBody AddProductReq addProductReq) {
        productService.add(addProductReq);
        return ApiRestResponse.success();
    }

    @PostMapping("admin/upload/file")
    public ApiRestResponse upload(HttpServletRequest httpServletRequest,
                                  @RequestParam("file") MultipartFile file) {
        //获取原始名字
        String fileName = file.getOriginalFilename();
        //获取名字后缀
        String suffixName = fileName.substring(fileName.lastIndexOf("."));
        //生成UUID
        UUID uuid = UUID.randomUUID();
        String newFileName = uuid.toString() + suffixName;
        //创建文件
        File fileDirectory = new File(Constant.FILE_UPLOAD_DIR);
        File destFile = new File(Constant.FILE_UPLOAD_DIR + newFileName);
        //如果文件夹不存在则新建文件夹
        if (!fileDirectory.exists()) {
            if (!fileDirectory.mkdir()) {
                throw new MallException(MallExceptionEnum.MKDIR_FAILE);
            }
        }
        try {
            file.transferTo(destFile);
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            return ApiRestResponse.success(getHost(new URI(httpServletRequest.getRequestURI()+""))+"/image/"+newFileName);
        } catch (URISyntaxException e) {
            return ApiRestResponse.error(MallExceptionEnum.UPLOAD_FAILE);
        }
    }

    /**
     * 获取IP和端口号
     */
    private URI getHost(URI uri) {
        URI effectiveURI;
        try {
            effectiveURI = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(),
                    null, null, null);
        } catch (URISyntaxException e) {
            effectiveURI = null;
        }
        return effectiveURI;
    }
}

在postman中进行商品添加和添加图片的操作

在这里插入图片描述
在这里插入图片描述

测试都没毛病,但是,我把图片地址信息放在浏览器上,会出现报错信息,打不开图片:原因是关于自定义静态目录资源映射目录这个点

遇到这个问题需要在config下的MallWebMvcConfig进行配置地址映射,添加上

//图片回传
        registry.addResourceHandler("/images/**").
                addResourceLocations("file:"+ Constant.FILE_UPLOAD_DIR);

(二)后台的更新商品接口

在request新增UpdateProduct

package com.hyb.mall.model.request;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.util.Date;
/**
 * 描述:更新商品
 */
public class UpdateProductReq {
    @NotNull(message = "商品id不能为空")
    private Integer id;

    private String name;

    private String image;

    private String detail;

    private Integer categoryId;

    @Min(value = 1, message = "价格不能小于1分钱")
    private Integer price;

    @Max(value = 10000, message = "库存不能大于1万")
    private Integer stock;

    private Integer status;


    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name == null ? null : name.trim();
    }

    public String getImage() {
        return image;
    }

    public void setImage(String image) {
        this.image = image == null ? null : image.trim();
    }

    public String getDetail() {
        return detail;
    }

    public void setDetail(String detail) {
        this.detail = detail == null ? null : detail.trim();
    }

    public Integer getCategoryId() {
        return categoryId;
    }

    public void setCategoryId(Integer categoryId) {
        this.categoryId = categoryId;
    }

    public Integer getPrice() {
        return price;
    }

    public void setPrice(Integer 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;
    }
}

在ProductServiceImpl中创建更新商品的业务逻辑

    @Override
    public void update(Product updateProduct){
        //1.查询是否有相同的商品名
        Product productOld = productMapper.selectByName(updateProduct.getName());
        //2.同名且不同id不能继续修改
        if (productOld != null && productOld.getId().equals(updateProduct.getId())) {
            throw new MallException(MallExceptionEnum.NAME_EXISTED);
        }
        //3.通过校验
        int count = productMapper.updateByPrimaryKeySelective(updateProduct);
        if (count == 0) {
            throw new MallException(MallExceptionEnum.UPDATE_FAILED);
        }
    }

在Productcontroller实现更新操作

	@ApiOperation("后台更新商品")
    @PostMapping("admin/product/update")
    public ApiRestResponse update(@Valid @RequestBody UpdateCategoryReq updateCategoryReq){
        Product product = new Product();
        BeanUtils.copyProperties(updateCategoryReq,product);
        productService.update(product);
        return ApiRestResponse.success();
    }

(三)后台的删除商品接口实现

在productServiceImpl根据传入的ID进行商品的删除

    @Override
    public void delete(Integer id) {
        //1.查询商品的id主键是否存在
        Product productOld = productMapper.selectByPrimaryKey(id);
        if (productOld == null) {
            throw new MallException(MallExceptionEnum.DELETE_FAILE);
        }
        //2.存在商品的id则进行删除操作
        int count = productMapper.deleteByPrimaryKey(id);
        if (count == 0) {
            throw new MallException(MallExceptionEnum.DELETE_FAILE);
        }
    }

在ProductController中实现删除商品

    @ApiOperation("后台删除商品")
    @PostMapping("admin/product/delete")
    public ApiRestResponse delete(@RequestParam Integer id){
        productService.delete(id);
        return ApiRestResponse.success();
    }

使用postman进行接口的测试

在这里插入图片描述
在这里插入图片描述

(四)后台的批量上下架(难点)

dao层接口编写

int batchUpdateSellStatus(@Param("ids") Integer[] ids, @Param("sellStatus") Integer sellStatus);

mapper.xml SQL编写

  <update id="batchUpdateSellStatus" >
    update imooc_mall_product
    set status = #{sellStatus}
    where id in
    <foreach collection="ids" close=")" item="id" open="(" separator=",">
      #{id}
    </foreach>
  </update>

serviceImple层接口编写

    @Override
    public void batchUpdateSellStatus(Integer[] ids, Integer sellStatus){
        productMapper.batchUpdateSellStatus(ids,sellStatus);
    }

controller层接口编写

    @ApiOperation("后台批量上下架")
    @PostMapping("admin/product/batchUpdateSellStatus")
    public ApiRestResponse batchUpdateSellStatus(@RequestParam Integer[] ids,
                                                 @RequestParam Integer sellStatus){
    productService.batchUpdateSellStatus(ids,sellStatus);
        return ApiRestResponse.success();
    }

postman测试接口

在这里插入图片描述

(五)前台商品列表接口开发

dao

List<Product> selectListForAdmin();

mapper

  <select id="selectListForAdmin" resultMap="BaseResultMap">
    select
    <include refid="Base_Column_List"/>
    from imooc_mall_product
    order by update_time desc
  </select>

serviceImpl

    @Override
    public PageInfo listForAdmin(Integer pageNum, Integer pageSize){
        PageHelper.startPage(pageNum,pageSize);
        List<Product> products = productMapper.selectListForAdmin();
        PageInfo pageInfo = new PageInfo(products);
        return pageInfo;
    }

controller

    @ApiOperation("商品列表")
    @PostMapping("admin/product/list")
    public ApiRestResponse list(@RequestParam Integer pageNum,
                                                 @RequestParam Integer pageSize) {
        PageInfo pageInfo = productService.listForAdmin(pageNum, pageSize);
        return ApiRestResponse.success(pageInfo);
    }

(六)前台商品详情接口开发

serviceImpl,直接复用之前的业务逻辑,不用重新从底层编写

    @Override
    public Product detail(Integer id){
        Product product = productMapper.selectByPrimaryKey(id);
        return product;
    }

controller

    @ApiOperation("商品详情")
    @PostMapping("product/detail")
    public ApiRestResponse detail(@RequestParam Integer id){
        Product detail = productService.detail(id);
        return ApiRestResponse.success(detail);
    }

(七)前台商品列表接口(非常难)

具体功能实现
  1. 入参判空
  2. 加%通配符
  3. like关键字

前台商品列表的类

package com.hyb.mall.model.request;
public class ProductListReq {

    private String keyword;

    private Integer categoryId;

    private String orderBy;

    private Integer pageNum = 1;

    private Integer pageSize = 10;

    public String getKeyword() {
        return keyword;
    }

    public void setKeyword(String keyword) {
        this.keyword = keyword;
    }

    public Integer getCategoryId() {
        return categoryId;
    }

    public void setCategoryId(Integer categoryId) {
        this.categoryId = categoryId;
    }

    public String getOrderBy() {
        return orderBy;
    }

    public void setOrderBy(String orderBy) {
        this.orderBy = orderBy;
    }

    public Integer getPageNum() {
        return pageNum;
    }

    public void setPageNum(Integer pageNum) {
        this.pageNum = pageNum;
    }

    public Integer getPageSize() {
        return pageSize;
    }

    public void setPageSize(Integer pageSize) {
        this.pageSize = pageSize;
    }
}

因为目录处理:如果查询某个目录下的商品,不仅需要查出来该目录,还需要查询处理子目录的所有商品。所有再新建一个quey包,新建ProductListQuery类,包含商品列表集合

package com.hyb.mall.query;

import java.util.List;

/**
 * 描述:查询前台商品列表的query
 */
public class ProductListQuery {
    //1.关键字
    private String keyword;

    //2.商品列表
    private List<Integer> categoryIds;

    public String getKeyWord() {
        return keyword;
    }

    public void setKeyWord(String keyWord) {
        this.keyWord = keyword;
    }

    public List<Integer> getCategoryIds() {
        return categoryIds;
    }

    public void setCategoryIds(List<Integer> categoryIds) {
        this.categoryIds = categoryIds;
    }
}

因为用户查询的目录不是所有的目录,所以在之前编写的categoryService.listCategoryForCustomer需要进行重构:将parentId变成是用户自己传进来

    @Override
    @Cacheable(value = "listCategoryForCustomer") //是spring所提供的
    public List<CategoryVO> listCategoryForCustomer(Integer parentId){
        ArrayList<CategoryVO> categoryVOList = new ArrayList<>();
        recursivelyFindCategories(categoryVOList,parentId);
        return categoryVOList;
    }

改动完后,还需要对service层和controller层进行同步的改动

 List<CategoryVO> listCategoryForCustomer(Integer parentId);
    @ApiOperation("前台商品分类列表")
    @PostMapping("category/list")
    @ResponseBody
    public ApiRestResponse listCategoryForCustomer(){
    //parentId设置成0,就能查询所有的数据
        List<CategoryVO> categoryVOS = categoryService.listCategoryForCustomer(0);
        return ApiRestResponse.success(categoryVOS);
    }

排序处理

在constant包中创建支持排序的规则

    public interface ProductListOrderBy{
        Set<String> PRICE_ASC_DESC = Sets.newHashSet("price desc","price asc");
    }

dao

List<Product> selectList(@Param("query") ProductListQuery query);

mapper

  <select id="selectList" parameterType="com.hyb.mall.query.ProductListQuery" resultMap="BaseResultMap">
    select
    <include refid="Base_Column_List"/>
    from imooc_mall_product
    <where>
      <if test="query.keyword != null">
        and name like #{query.keyword}
      </if>
      <if test="query.categoryIds != null">
        and category_id in
        <foreach collection="query.categoryIds" close=")" item="item" open="(" separator=",">
          #{item}
        </foreach>
      </if>
      and status = 1
    </where>
    order by update_time desc
  </select>

service

    @Override
    public PageInfo list(ProductListReq productListReq) {
        //构建query对象
        ProductListQuery productListQuery = new ProductListQuery();
        //搜索处理
        if (!StringUtils.isEmpty(productListReq.getKeyword())) {
            //合成字符串,就能利用数据库的模糊查找功能
            String keyword = new StringBuilder()
                    .append("%")
                    .append(productListReq.getKeyword())
                    .append("%")
                    .toString();
            productListQuery.setKeyWord(keyword);
        }
        //3.目录处理,如果查某个目录下的商品,不仅需要查出该目录下的,还需要把所有子目录的所有商品查出来,所以要拿到一个目录id的List
        if (productListReq.getCategoryId() != null) {
            //这里需要对listCategoryForCustomer进行重构,
            List<CategoryVO> categoryVOSList = categoryService.listCategoryForCustomer(productListReq.getCategoryId());
            //得到的categoryVOSList是一个树状结构,需要进行平铺展开,将子节点的ID都拿过来
            ArrayList<Integer> categoryIds = new ArrayList<>();
            categoryIds.add(productListReq.getCategoryId());
            getCategoryIds(categoryVOSList, categoryIds);
            productListQuery.setCategoryIds(categoryIds);
        }
        //排序处理
        //从前端请求拿到orderby
        String orderBy = productListReq.getOrderBy();
        if (Constant.ProductListOrderBy.PRICE_ASC_DESC.contains(orderBy)) {
            PageHelper.startPage(productListReq.getPageNum(), productListReq.getPageSize(), orderBy);
        } else {
            PageHelper.startPage(productListReq.getPageNum(), productListReq.getPageSize());
        }
        List<Product> productList = productMapper.selectList(productListQuery);
        PageInfo pageInfo = new PageInfo(productList);
        return pageInfo;
    }

    private void getCategoryIds(List<CategoryVO> categoryVOList, ArrayList<Integer> categoryIds) {
        for (int i = 0; i < categoryVOList.size(); i++) {
            CategoryVO categoryVO = categoryVOList.get(i);
            if (categoryVO != null) {
                categoryIds.add(categoryVO.getId());
                getCategoryIds(categoryVO.getChildCategory(), categoryIds);
            }
        }
    }

controller

    @ApiOperation("前台商品列表")
    @PostMapping("product/list")
    public ApiRestResponse list(@RequestParam ProductListReq productListReq){
        PageInfo list = productService.list(productListReq);
        return ApiRestResponse.success(list);
    }

postman

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
这份资源是一个基于SpringBoot+Vue的生鲜超市管理系统的完整开发源码,包括前端、后端、数据库等部分。该系统主要用于生鲜超市的管理,包括商品管理、库存管理、销售管理等功能。该系统支持管理员、销售员、仓库管理员等多个角色,并可以实现数据报表、数据分析、销售预测等功能。 为了更好地使用本资源,我们提供了详细的部署说明和系统介绍。在部署说明中,我们详细介绍了如何将本资源部署到本地或远程服务器上,并配置相关环境参数。在系统介绍中,我们对生鲜超市管理系统的各项功能、前后端框架和技术栈进行了详细介绍和解释,以帮助开发者更好地理解系统的设计思路和功能实现。 对于想要深入学习和了解源码的开发者,我们还提供了源码解释。通过逐行分析源码,我们对系统的技术实现、API设计、业务逻辑等进行深入解读和分析,帮助开发者更好地理解源码和在其基础上进行二次开发,并提供更多开发思路和技巧。 总之,本资源适合对SpringBoot、Vue、生鲜超市管理系统开发有一定基础的开发者学习和参考。生鲜超市管理系统的设计思路、技术实现和业务逻辑等方面都具有高参考价值,为开发者提供了实践和实现超市管理的宝贵经验和思路。该系统可用于优化超市管理流程、提高管理效率,也可拓展至其他类似的零售行业中。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值