乐优商城笔记三:规格与商品模块

乐优商城规格管理和商品管理模块基本构建完成

规格模块

后端

编写实体类
package com.leyou.pojo;

import lombok.Data;

import javax.persistence.Id;
import javax.persistence.Table;

@Table(name = "tb_specification")
@Data
public class Specification {

    @Id
    private Long categoryId;
    private String specifications;
}
编写Mapper
package com.leyou.item.mapper;

import com.leyou.pojo.Specification;
import tk.mybatis.mapper.common.Mapper;

public interface SpecificationMapper extends Mapper<Specification> {
}
编写Service
package com.leyou.item.service;

import com.leyou.common.enums.LyExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.item.mapper.CategoryMapper;
import com.leyou.item.mapper.SpecificationMapper;
import com.leyou.pojo.Specification;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class SpecificationService {

    @Autowired
    private SpecificationMapper specificationMapper;

    @Autowired
    private CategoryMapper categoryMapper;

    /**
     * 按分类ID查询规格(此表中分类ID其实为该表的ID)
     *
     * @param categoryId 分类ID
     * @return 规格信息
     */
    public Specification queryById(Long categoryId) {
        return specificationMapper.selectByPrimaryKey(categoryId);
    }

    /**
     * 新增规格
     *
     * @param specification 规格信息
     * @return 规格信息
     */
    @Transactional
    public Specification addSpecification(Specification specification) {
        // 先查询并判断是否有该分类
        if (categoryMapper.selectByPrimaryKey(specification.getCategoryId()) == null) {
            throw new LyException(LyExceptionEnum.CATEGORY_NOT_FOUND);
        }
        // 新增
        if (specificationMapper.insert(specification) == 1)
            return specification;

        throw new LyException(LyExceptionEnum.SAVE_FAILURE);
    }

    /**
     * 编辑规格
     * @param specification 规格信息
     * @return 规格信息
     */
    public Specification saveSpecification(Specification specification) {
        // 先查询并判断是否有该分类
        if (categoryMapper.selectByPrimaryKey(specification.getCategoryId()) == null) {
            throw new LyException(LyExceptionEnum.CATEGORY_NOT_FOUND);
        }
        // 编辑
        if (specificationMapper.updateByPrimaryKey(specification) == 1)
            return specification;

        throw new LyException(LyExceptionEnum.SAVE_FAILURE);
    }
}
编写Controller
package com.leyou.item.cotroller;

import com.leyou.common.enums.LyExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.item.service.SpecificationService;
import com.leyou.pojo.Specification;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("spec")
public class SpecificationController {

    @Autowired
    private SpecificationService specificationService;

    @GetMapping("/{categoryId}")
    public ResponseEntity<String> querySpecificationByCategoryId(@PathVariable("categoryId") Long categoryId) {
        if (categoryId == null) {
            throw new LyException(LyExceptionEnum.PARAM_CANNOT_BE_NULL);
        }
        // 查询规格
        Specification specification = specificationService.queryById(categoryId);
        if (specification == null) {
            throw new LyException(LyExceptionEnum.SPU_NOT_FOUND);
        }
        // 返回存储的规格JSON字符串
        return ResponseEntity.ok(specification.getSpecifications());
    }

    @PostMapping
    public ResponseEntity<Specification> addSpecification(@RequestBody Specification specification) {
        if (specification == null || specification.getCategoryId() == null) {
            throw new LyException(LyExceptionEnum.PARAM_INVALID);
        }
        // 新增
        return ResponseEntity.status(HttpStatus.CREATED).body(specificationService.addSpecification(specification));
    }

    @PutMapping
    public ResponseEntity<Specification> saveSpecification(@RequestBody Specification specification) {
        if (specification == null || specification.getCategoryId() == null) {
            throw new LyException(LyExceptionEnum.PARAM_INVALID);
        }
        // 新增
        return ResponseEntity.ok(specificationService.saveSpecification(specification));
    }
}

这里规格的修改和删除为一体

商品模块

后端

实体类
  1. Spu
package com.leyou.pojo;

import lombok.Data;
import tk.mybatis.mapper.annotation.KeySql;

import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;

@Table(name = "tb_spu")
@Data
public class Spu {
    @Id
    @KeySql(useGeneratedKeys = true)
    private Long id;
    private Long brandId;
    private Long cid1;// 1级类目
    private Long cid2;// 2级类目
    private Long cid3;// 3级类目
    private String title;// 标题
    private String subTitle;// 子标题
    private Boolean saleable;// 是否上架
    private Boolean valid;// 是否有效,逻辑删除用
    private Date createTime;// 创建时间
    private Date lastUpdateTime;// 最后修改时间
}
  1. SpuDetail

    package com.leyou.pojo;
    
    import lombok.Data;
    
    import javax.persistence.Id;
    import javax.persistence.Table;
    
    @Table(name="tb_spu_detail")
    @Data
    public class SpuDetail {
        @Id
        private Long spuId;// 对应的SPU的id
        private String description;// 商品描述
        private String specTemplate;// 商品特殊规格的名称及可选值模板
        private String specifications;// 商品的全局规格属性
        private String packingList;// 包装清单
        private String afterService;// 售后服务
    }
    
编写Mapper
package com.leyou.item.mapper;

import com.leyou.pojo.Spu;
import tk.mybatis.mapper.common.Mapper;

public interface GoodsMapper extends Mapper<Spu> {
}
编写Service
package com.leyou.item.service;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.leyou.BO.SpuBO;
import com.leyou.common.enums.LyExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.util.LeyouConstants;
import com.leyou.common.vo.PageResult;
import com.leyou.item.mapper.*;
import com.leyou.pojo.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;

import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class GoodsService {

    @Autowired
    private GoodsMapper goodsMapper;

    @Autowired
    private CategoryMapper categoryMapper;

    @Autowired
    private BrandMapper brandMapper;

    @Autowired
    private SkuMapper skuMapper;

    @Autowired
    private SpuDetailMapper spuDetailMapper;

    @Autowired
    private StockMapper stockMapper;

    @Autowired
    private AmqpTemplate amqpTemplate;

    /**
     * 分页查询商品列表
     *
     * @param page     当前页
     * @param rows     每页记录数
     * @param sortBy   排序字段
     * @param desc     是否降序
     * @param key      查询关键字
     * @param saleable 是否过滤上下架
     * @return 商品列表
     */
    public PageResult<SpuBO> querySpuByPage(int page, int rows, String sortBy, Boolean desc, String key, Boolean saleable) {
        // 分页
        PageHelper.startPage(page, rows);
        // 封装查询关键字
        Example example = new Example(Spu.class);
        Example.Criteria criteria = example.createCriteria();
        if (StringUtils.isNotBlank(key)) {
            criteria.andLike("title", "%" + key + "%");
        }
        if (saleable != null) {
            criteria.andEqualTo("saleable", saleable);
        }
        // 排序
        if (StringUtils.isNotBlank(sortBy)) {
            example.setOrderByClause(sortBy + (desc ? " DESC" : " ASC"));
        }
        // 执行查询
        List<Spu> spuList = goodsMapper.selectByExample(example);
        if (spuList.size() < 1) {
            throw new LyException(LyExceptionEnum.SPU_LIST_NOT_FOUND);
        }
        PageInfo<Spu> pageInfo = new PageInfo<>(spuList);

        // 使用spu集合 构造spuBO集合
        List<SpuBO> spuBOList = pageInfo.getList().stream().map(spu -> {
            SpuBO spuBO = new SpuBO();
            BeanUtils.copyProperties(spu, spuBO);
            // 查询分类名称列表并处理成字符串
            Optional<String> categoryNameString = categoryMapper.selectByIdList(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()))
                    .stream()
                    .map(Category::getName)
                    .reduce((name1, name2) -> name1 + "/" + name2);
            // 查询品牌名称
            Brand brand = brandMapper.selectByPrimaryKey(spu.getBrandId());
            if (brand == null) {
                throw new LyException(LyExceptionEnum.BRAND_NOT_FOUND);
            }
            // 设置分类以及品牌名称
            spuBO.setBname(brand.getName());
            spuBO.setCname(categoryNameString.get());
            return spuBO;
        }).collect(Collectors.toList());

        return new PageResult<>(pageInfo.getTotal(), spuBOList);
    }

    /**
     * 新增商品
     *
     * @param goods 商品信息
     * @return SPU
     */
    @Transactional
    public Spu addGoods(SpuBO goods) {
        try {
            // 保存SPU
            goods.setSaleable(true); // 上架
            goods.setCreateTime(new Date()); // 新增时间
            goods.setLastUpdateTime(goods.getCreateTime()); // 最后更新时间
            goodsMapper.insert(goods);

            // 保存SPU描述
            SpuDetail spuDetail = goods.getSpuDetail();
            spuDetail.setSpuId(goods.getId()); // 设置ID
            spuDetailMapper.insert(spuDetail);

            // 保存SKU列表
            saveSkuList(goods);

        } catch (Exception e) {
            throw new LyException(LyExceptionEnum.SAVE_FAILURE);
        }
        return goods;
    }

    // 保存SKU列表
    private void saveSkuList(SpuBO goods) {
        goods.getSkuList().forEach(sku -> {
            if (sku.getEnable()) {
                // 保存sku信息
                sku.setCreateTime(goods.getCreateTime());
                sku.setSpuId(goods.getId());
                sku.setLastUpdateTime(goods.getCreateTime());
                skuMapper.insert(sku);
                // 保存库存信息
                Stock stock = new Stock();
                stock.setSkuId(sku.getId());
                stock.setStock(sku.getStock());
                stockMapper.insert(stock);
            }
        });
    }

    /**
     * 编辑商品
     *
     * @param goods 商品信息
     * @return SPU
     */
    @Transactional
    public Spu saveGoods(SpuBO goods) {
        try {
            // 保存SPU
            goods.setSaleable(true); // 上架
            goods.setLastUpdateTime(new Date()); // 最后更新时间
            goodsMapper.updateByPrimaryKey(goods);

            // 保存SPU描述
            spuDetailMapper.updateByPrimaryKeySelective(goods.getSpuDetail());
            // 保存SKU列表,需要先删除原先的SKU列表
            Sku sku = new Sku();
            sku.setSpuId(goods.getId());
            skuMapper.delete(sku);
            // 删除库存信息
            List<Sku> skuList = skuMapper.select(sku);
            List<Long> ids = skuList.stream().map(Sku::getId).collect(Collectors.toList());
            stockMapper.deleteByIdList(ids);
            // 保存更新后的数据
            saveSkuList(goods);

        } catch (Exception e) {
            throw new LyException(LyExceptionEnum.SAVE_FAILURE);
        }
        return goods;
    }

    /**
     * 按ID查询商品信息
     *
     * @param spuId spuid
     * @return 商品信息
     */
    public SpuBO queryGoodsById(Long spuId) {
        SpuBO spuBO = new SpuBO();
        // 查询spu基本信息
        Spu spu = goodsMapper.selectByPrimaryKey(spuId);
        if (spu == null || spu.getId() == null) {
            throw new LyException(LyExceptionEnum.GOODS_NOT_FOUND);
        }
        BeanUtils.copyProperties(spu, spuBO);
        // 查询商品描述信息
        spuBO.setSpuDetail(spuDetailMapper.selectByPrimaryKey(spuId));
        // 查询商品SKU列表
        Sku sku = new Sku();
        sku.setSpuId(spuId);
        List<Sku> skuList = skuMapper.select(sku);
        spuBO.setSkuList(skuList);

        return spuBO;
    }

    /**
     * 按spuid查询商品描述
     *
     * @param spuId id
     * @return 描述信息
     */
    public SpuDetail querySpuDetailById(Long spuId) {
        SpuDetail spuDetail = spuDetailMapper.selectByPrimaryKey(spuId);
        if (spuDetail == null) {
            throw new LyException(LyExceptionEnum.GOODS_NOT_FOUND);
        }
        return spuDetail;
    }

    /**
     * 按spuid查询sku列表
     *
     * @param spuId id
     * @return sku列表
     */
    public List<Sku> querySkuListById(Long spuId) {
        Sku sku = new Sku();
        sku.setSpuId(spuId);
        List<Sku> skuList = skuMapper.select(sku);
        if (skuList.isEmpty()) {
            throw new LyException(LyExceptionEnum.SKU_LIST_NOT_FOUND);
        }
        // 查询每个sku的库存信息
        List<Stock> stockList = stockMapper.selectByIdList(skuList.stream().map(Sku::getId).collect(Collectors.toList()));
        if (stockList.isEmpty()) {
            throw new LyException(LyExceptionEnum.GOODS_STOCK_NOT_FOUND);
        }
        // 将库存数量与skuId生成map
        Map<Long, Long> stockMap = stockList.stream().collect(Collectors.toMap(Stock::getSkuId, Stock::getStock));
        // 设置库存数量
        skuList.forEach(s -> s.setStock(stockMap.get(s.getId())));

        return skuList;
    }


    /**
     * 更新商品状态(上下架)
     *
     * @param skuId  skuId
     * @param status 商品状态
     */
    public Sku updateGoodsStatus(Long skuId, Boolean status) {
        // 查询sku
        Sku sku = skuMapper.selectByPrimaryKey(skuId);
        if (sku == null) {
            throw new LyException(LyExceptionEnum.GOODS_NOT_FOUND);
        }
        // 修改商品状态
        sku.setEnable(status);
        // 保存
        skuMapper.updateByPrimaryKey(sku);
        return sku;
    }

    /**
     * 删除商品
     *
     * @param spuId 商品ID
     * @return 被删除的商品
     */
    @Transactional
    public Spu deleteGoods(Long spuId) {
        // 查询spu
        Spu spu = goodsMapper.selectByPrimaryKey(spuId);
        if (spu == null) {
            throw new LyException(LyExceptionEnum.GOODS_NOT_FOUND);
        }
        // 删除spu和spuDetail
        goodsMapper.deleteByPrimaryKey(spuId);
        spuDetailMapper.deleteByPrimaryKey(spuId);

        // 删除sku列表
        Sku sku = new Sku();
        sku.setSpuId(spuId);
        List<Sku> skuList = skuMapper.select(sku);
        List<Long> ids = skuList.stream().map(Sku::getId).collect(Collectors.toList());
        skuMapper.deleteByIdList(ids);
        // 删除库存信息
        stockMapper.deleteByIdList(ids);

        return spu;
    }
}

编写Controller
package com.leyou.item.cotroller;

import com.leyou.BO.SpuBO;
import com.leyou.common.enums.LyExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.vo.PageResult;
import com.leyou.item.service.GoodsService;
import com.leyou.pojo.Sku;
import com.leyou.pojo.Spu;
import com.leyou.pojo.SpuDetail;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/goods")
public class GoodsController {

    @Autowired
    private GoodsService goodsService;

    /**
     * 分页查询商品列表
     *
     * @param page     当前页码
     * @param rows     每页记录数
     * @param sortBy   排序字段
     * @param desc     是否降序
     * @param key      查询关键字
     * @param saleable 是否上架
     * @return List<SpuBO>
     */
    @GetMapping("/spu/page")
    public ResponseEntity<PageResult<SpuBO>> querySpuByPage(@RequestParam(value = "page", defaultValue = "1") int page,
                                                            @RequestParam(value = "rows", defaultValue = "5") int rows,
                                                            @RequestParam(value = "sortBy", required = false) String sortBy,
                                                            @RequestParam(value = "desc", required = false, defaultValue = "false") Boolean desc,
                                                            @RequestParam(value = "key", required = false) String key,
                                                            @RequestParam(value = "saleable", required = false) Boolean saleable) {
        return ResponseEntity.ok(goodsService.querySpuByPage(page, rows, sortBy, desc, key, saleable));
    }

    /**
     * 新增商品
     *
     * @param goods 商品
     * @return Spu
     */
    @PostMapping
    public ResponseEntity<Spu> addGoods(@RequestBody SpuBO goods) {
        if (goods == null)
            throw new LyException(LyExceptionEnum.PARAM_CANNOT_BE_NULL);

        // 保存
        return ResponseEntity.status(HttpStatus.CREATED).body(goodsService.addGoods(goods));
    }

    /**
     * 编辑商品
     *
     * @param goods 商品
     * @return Spu
     */
    @PutMapping
    public ResponseEntity<Spu> saveGoods(@RequestBody SpuBO goods) {
        if (goods == null)
            throw new LyException(LyExceptionEnum.PARAM_CANNOT_BE_NULL);

        // 保存
        return ResponseEntity.ok(goodsService.saveGoods(goods));
    }

    /**
     * 按商品ID查询商品描述
     *
     * @param spuId 商品ID
     * @return SpuDetail
     */
    @GetMapping("/spu/detail/{spuId}")
    public ResponseEntity<SpuDetail> querySpuDetailById(@PathVariable("spuId") Long spuId) {
        if (spuId == null) {
            throw new LyException(LyExceptionEnum.PARAM_CANNOT_BE_NULL);
        }
        return ResponseEntity.ok(goodsService.querySpuDetailById(spuId));
    }

    /**
     * 按商品ID查询该商品所有规格的商品列表
     *
     * @param spuId 商品ID
     * @return List<Sku>
     */
    @GetMapping("/sku/list/{spuId}")
    public ResponseEntity<List<Sku>> querySkuListById(@PathVariable("spuId") Long spuId) {
        if (spuId == null) {
            throw new LyException(LyExceptionEnum.PARAM_CANNOT_BE_NULL);
        }
        return ResponseEntity.ok(goodsService.querySkuListById(spuId));
    }

    /**
     * 上架下架
     *
     * @param skuId  skuID
     * @param status 修改后的商品状态
     * @return Sku
     */
    @GetMapping("/sku/status/{skuId}")
    public ResponseEntity<Sku> updateSkuStatus(@PathVariable("skuId") Long skuId,
                                               @RequestParam("status") Boolean status) {
        return ResponseEntity.ok(goodsService.updateGoodsStatus(skuId, status));
    }

    /**
     * 删除指定spuId的商品
     *
     * @param spuId 商品ID
     * @return Spu 被删除的spu
     */
    @DeleteMapping("/{spuId}")
    public ResponseEntity<Spu> deleteGoods(@PathVariable("spuId") Long spuId) {
        return ResponseEntity.ok(goodsService.deleteGoods(spuId));
    }

    /**
     * 查询spu信息
     *
     * @param spuId 商品ID
     * @return Spu
     */
    @GetMapping("/{spuId}")
    public ResponseEntity<SpuBO> queryGoodsById(@PathVariable("spuId") Long spuId) {
        return ResponseEntity.ok(goodsService.queryGoodsById(spuId));
    }
}

其他后端改动

分类Mapper新增继承
package com.leyou.item.mapper;

import com.leyou.pojo.Category;
import tk.mybatis.mapper.additional.idlist.SelectByIdListMapper;
import tk.mybatis.mapper.common.Mapper;

public interface CategoryMapper extends Mapper<Category>, SelectByIdListMapper<Category, Long> {
}

继承SelectByIdListMapper<T, PK>,泛型第一参数为实体类,第二个参数为ID类型

品牌新增接口
  1. BrandController

        @GetMapping("/cid/{categoryId}")
        public ResponseEntity<List<Brand>> queryBrandByCategoryId(@PathVariable("categoryId") Long categoryId) {
            if (categoryId == null) {
                throw new LyException(LyExceptionEnum.PARAM_CANNOT_BE_NULL);
            }
            // 查询
            return ResponseEntity.ok(brandService.queryBrandByCategoryId(categoryId));
        }
    
  2. BrandService

        /**
         * 按分类ID查询品牌列表
         *
         * @param categoryId 分类ID
         * @return 品牌列表
         */
        public List<Brand> queryBrandByCategoryId(Long categoryId) {
            List<Brand> brands = brandMapper.queryBrandByCategoryId(categoryId);
            if (brands.isEmpty()) {
                throw new LyException(LyExceptionEnum.BRAND_LIST_NOT_FOUND);
            }
            return brands;
        }
    
  3. BrandMapper

        @Select("SELECT b.* FROM tb_category_brand cb, tb_brand b WHERE cb.category_id = #{categoryId} AND cb.brand_id = b.id")
        List<Brand> queryBrandByCategoryId(Long categoryId);
    
新增商品BO
package com.leyou.BO;

import com.leyou.pojo.Sku;
import com.leyou.pojo.Spu;
import com.leyou.pojo.SpuDetail;
import lombok.Data;

import javax.persistence.Transient;
import java.util.List;

@Data
public class SpuBO extends Spu {
    // 分类名称
    @Transient
    private String cname;

    // 品牌名称
    @Transient
    private String bname;

    // 商品描述
    @Transient
    private SpuDetail spuDetail;

    // 商品列表
    @Transient
    private List<Sku> skuList;
}
新增异常枚举
    SPU_LIST_NOT_FOUND(404, "未查询到商品列表"),
    SPU_NOT_FOUND(404, "未查询到商品列表"),

前端

Goods.vue
getDataFromApi() {
        this.loading = true;
        this.$http.get("/item/goods/spu/page", {
          params: {
            page: this.pagination.page, // 当前页
            rows: this.pagination.rowsPerPage, // 每页条数
            sortBy: this.pagination.sortBy, // 排序字段
            desc: this.pagination.descending, // 是否降序
            key: this.search.key, // 查询字段
            saleable: this.search.saleable // 过滤上下架
          }
        }).then(response => { // 获取响应结果对象
          this.totalItems = response.data.total; // 总条数
          this.items = response.data.items; // 品牌数据
          this.loading = false; // 加载完成
        }).catch(response => {
          // http返回的error结构很复杂,并不是直接返回你定义好的对象格式。
          // 如果你也想使用 response.data.message 获取到错误消息
          // 需要在http.js中自定义个一个响应拦截器
          // 或者你也可以使用 response.response.data.message 直接获取到错误消息
          this.$message.error(response.data.message);
        });
      }

goods.vue 其他地方均无太大改动。如有报错,注意查看浏览器中提示的错误信息。

http.js新增响应拦截器
axios.interceptors.response.use(
	response => {
		const res = response.status
		if (res !== 200) {
			return Promise.reject('error')
		} else {
			return response
		}
	},
	error => {
		console.log('err' + error.response) // for debug
		console.log('err' + error.message)
		return Promise.reject(error.response)
	}
)

由于我的那部分乐优资料中的SQL语句和视频中不一致,这一块有很多的实现都和视频中的不同,我写这一块的时候也有许多的BUG,但是并不要紧,这不是关键流程,写不写得出来问题都不大。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值