学习目标

  • SPU与SKU概念理解

    1. SPU:某一款商品的公共属性
    2. SKU:某款商品的不同参数对应的商品信息[某个商品]
  • 新增商品、修改商品

    1. 增加:增加SPUSKU
    2. 修改:修改SPUSKU
  • 商品审核、上架、下架

    1. 审核:修改审核状态
    2. 上架下架:修改上架下架状态
  • 删除商品

    1. 逻辑删除:修改了删除状态
    2. 物理删除:真实删除了数据
  • 找回商品

    1. 找回商品:一定是属于逻辑删除的商品

    1 SPU与SKU

    1.1 SPU与SKU概念

    SPU = Standard Product Unit (标准产品单位)

  • 概念 : SPU 是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。

  • 通俗点讲,属性值、特性相同的货品就可以称为一个 SPU
    同款商品的公共属性抽取
    例如:华为P30 就是一个 SPU

SKU=stock keeping unit( 库存量单位)

  • SKU 即库存进出计量的单位, 可以是以件、盒、托盘等为单位。
  • SKU 是物理上不可分割的最小存货单元。在使用时要根据不同业态,不同管理模式来处理。
  • 在服装、鞋类商品中使用最多最普遍。
    例如:华为P30 红色 64G 就是一个 SKU
    某个库存单位的商品独有属性(某个商品的独有属性)

    1.2 表结构分析

    tb_spu 表 (SPU表)
字段名称 字段含义 字段类型 字段长度 备注
id 主键 BIGINT
sn 货号 VARCHAR
name SPU名 VARCHAR
caption 副标题 VARCHAR
brand_id 品牌ID INT
category1_id 一级分类 INT
category2_id 二级分类 INT
category3_id 三级分类 INT
template_id 模板ID INT
freight_id 运费模板id INT
image 图片 VARCHAR
images 图片列表 VARCHAR
sale_service 售后服务 VARCHAR
introduction 介绍 TEXT
spec_items 规格列表 VARCHAR
para_items 参数列表 VARCHAR
sale_num 销量 INT
comment_num 评论数 INT
is_marketable 是否上架 CHAR
is_enable_spec 是否启用规格 CHAR
is_delete 是否删除 CHAR
status 审核状态 CHAR

tb_sku 表(SKU商品表)

字段名称 字段含义 字段类型 字段长度 备注
id 商品id BIGINT
sn 商品条码 VARCHAR
name SKU名称 VARCHAR
price 价格(分) INT
num 库存数量 INT
alert_num 库存预警数量 INT
image 商品图片 VARCHAR
images 商品图片列表 VARCHAR
weight 重量(克) INT
create_time 创建时间 DATETIME
update_time 更新时间 DATETIME
spu_id SPUID BIGINT
category_id 类目ID INT
category_name 类目名称 VARCHAR
brand_name 品牌名称 VARCHAR
spec 规格 VARCHAR
sale_num 销量 INT
comment_num 评论数 INT
status 商品状态 1-正常,2-下架,3-删除 CHAR

2 新增和修改商品

2.1 需求分析

实现商品的新增与修改功能。
(1)第1个步骤,先选择添加的商品所属分类
第3章 商品发布 - 图1
这块在第2天的代码中已经有一个根据父节点ID查询分类信息的方法,参考第2天的4.3.4的findByPrantId方法,首先查询顶级分类,也就是pid=0,然后根据用户选择的分类,将选择的分类作为pid查询子分类。
(2)第2个步骤,填写SPU的信息
第3章 商品发布 - 图2
(3)第3个步骤,填写SKU信息
第3章 商品发布 - 图3
先进入选择商品分类 再填写商品的信息 填写商品的属性添加商品。

2.2 实现思路

前端传递给后端的数据格式 是一个spu对象和sku列表组成的对象,如下图:
第3章 商品发布 - 图4
上图JSON数据如下:

  1. {
  2. "spu": {
  3. "name": "这个是商品名称",
  4. "caption": "这个是副标题",
  5. "brandId": 12,
  6. "category1Id": 558,
  7. "category2Id": 559,
  8. "category3Id": 560,
  9. "freightId": 10,
  10. "image": "http://www.qingcheng.com/image/1.jpg",
  11. "images": "http://www.qingcheng.com/image/1.jpg,http://www.qingcheng.com/image/2.jpg",
  12. "introduction": "这个是商品详情,html代码",
  13. "paraItems": {
  14. "出厂年份": "2019",
  15. "赠品": "充电器"
  16. },
  17. "saleService": "七天包退,闪电退货",
  18. "sn": "020102331",
  19. "specItems": {
  20. "颜色": [
  21. "红",
  22. "绿"
  23. ],
  24. "机身内存": [
  25. "64G",
  26. "8G"
  27. ]
  28. },
  29. "templateId": 42
  30. },
  31. "skuList": [
  32. {
  33. "sn": "10192010292",
  34. "num": 100,
  35. "alertNum": 20,
  36. "price": 900000,
  37. "spec": {
  38. "颜色": "红",
  39. "机身内存": "64G"
  40. },
  41. "image": "http://www.qingcheng.com/image/1.jpg",
  42. "images": "http://www.qingcheng.com/image/1.jpg,http://www.qingcheng.com/image/2.jpg",
  43. "status": "1",
  44. "weight": 130
  45. },
  46. {
  47. "sn": "10192010293",
  48. "num": 100,
  49. "alertNum": 20,
  50. "price": 600000,
  51. "spec": {
  52. "颜色": "绿",
  53. "机身内存": "8G"
  54. },
  55. "image": "http://www.qingcheng.com/image/1.jpg",
  56. "images": "http://www.qingcheng.com/image/1.jpg,http://www.qingcheng.com/image/2.jpg",
  57. "status": "1",
  58. "weight": 130
  59. }
  60. ]
  61. }

2.3 代码生成

准备工作:为了更快的实现代码编写,我们可以采用《黑马代码生成器》来批量生成代码,这些代码就已经实现了我们之前的增删改查功能。
《黑马代码生成器》一款由传智播客教育集团JavaEE教研团队开发的基于Freemarker模板引擎的“代码生成神器”。即便是一个工程几百个表,也可以瞬间完成基础代码的构建!用户只需建立数据库表结构,运行main方法就可快速生成可以运行的一整套代码,可以极大地缩短开发周期,降低人力成本。《黑马代码生成器》的诞生主要用于迅速构建生成微服务工程的Pojo、Dao、Service、Controller各层、并且可以生成swagger API模板等。 用户通过自己开发模板也可以实现生成php、python、C# 、c++、数据库存储过程等其它编程语言的代码。
《黑马代码生成器》目前已经开源 地址:https://github.com/shenkunlin/code-template.git
如下图资料,将其导入到idea中 并执行即可:
第3章 商品发布 - 图5
使用说明,简单来说如下图所示:
第3章 商品发布 - 图6
带有core的最新代码生成器目录如下:
第3章 商品发布 - 图7

2.4 代码实现

一会儿会用到ID生成,我们可以使用IdWorker,在启动类GoodsApplication中添加如下代码,用于创建IdWorker,并将IdWorker交给Spring容器,代码如下:

  1. /***
  2. * IdWorker
  3. * @return
  4. */
  5. @Bean
  6. public IdWorker idWorker(){
  7. return new IdWorker(0,0);
  8. }

2.4.1 查询分类

2.4.1.1 分析

第3章 商品发布 - 图8
在实现商品增加之前,需要先选择对应的分类,选择分类的时候,首选选择一级分类,然后根据选中的分类,将选中的分类作为查询的父ID,再查询对应的子分类集合,因此我们可以在后台编写一个方法,根据父类ID查询对应的分类集合即可。

2.4.1.2 代码实现

(1)Service层
修改com.changgou.goods.service.CategoryService添加根据父类ID查询所有子节点,代码如下:

  1. /***
  2. * 根据分类的父ID查询子分类节点集合
  3. */
  4. List<Category> findByParentId(Integer pid);

修改com.changgou.goods.service.impl.CategoryServiceImpl添加上面的实现,代码如下:

  1. /***
  2. * 根据分类的父节点ID查询所有子节点
  3. * @param pid
  4. * @return
  5. */
  6. @Override
  7. public List<Category> findByParentId(Integer pid) {
  8. //SELECT * FROM tb_category WHERE parent_id=?
  9. Category category = new Category();
  10. category.setParentId(pid);
  11. return categoryMapper.select(category);
  12. }

(2)Controller层
修改com.changgou.goods.controller.CategoryController添加根据父ID查询所有子类集合,代码如下:

  1. /****
  2. * 根据节点ID查询所有子节点分类集合
  3. */
  4. @GetMapping(value = "/list/{pid}")
  5. public Result<List<Category>> findByParentId(@PathVariable(value = "pid")Integer pid){
  6. //调用Service实现查询
  7. List<Category> categories = categoryService.findByParentId(pid);
  8. return new Result<List<Category>>(true,StatusCode.OK,"查询成功!",categories);
  9. }

2.4.2 模板查询(规格参数组)

同学作业

2.4.2.1 分析

第3章 商品发布 - 图9
如上图,当用户选中了分类后,需要根据分类的ID查询出对应的模板数据,并将模板的名字显示在这里,模板表结构如下:

  1. CREATE TABLE `tb_template` (
  2. `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  3. `name` varchar(50) DEFAULT NULL COMMENT '模板名称',
  4. `spec_num` int(11) DEFAULT '0' COMMENT '规格数量',
  5. `para_num` int(11) DEFAULT '0' COMMENT '参数数量',
  6. PRIMARY KEY (`id`)
  7. ) ENGINE=InnoDB AUTO_INCREMENT=44 DEFAULT CHARSET=utf8;

2.4.2.2 代码实现

(1)Service层
修改com.changgou.goods.service.TemplateController接口,添加如下方法根据分类ID查询模板:

  1. /**
  2. * 根据分类ID查询模板信息
  3. * @param id
  4. * @return
  5. */
  6. Template findByCategoryId(Integer id);

修改com.changgou.goods.service.impl.TemplateServiceImpl添加上面方法的实现:

  1. @Autowired
  2. private CategoryMapper categoryMapper;
  3. /***
  4. * 根据分类ID查询模板信息
  5. * @param id
  6. * @return
  7. */
  8. @Override
  9. public Template findByCategoryId(Integer id) {
  10. //查询分类信息
  11. Category category = categoryMapper.selectByPrimaryKey(id);
  12. //根据模板Id查询模板信息
  13. return templateMapper.selectByPrimaryKey(category.getTemplateId());
  14. }

(2)Controller层
修改com.changgou.goods.controller.TemplateController,添加根据分类ID查询模板数据:

  1. /***
  2. * 根据分类查询模板数据
  3. * @param id:分类ID
  4. */
  5. @GetMapping(value = "/category/{id}")
  6. public Result<Template> findByCategoryId(@PathVariable(value = "id")Integer id){
  7. //调用Service查询
  8. Template template = templateService.findByCategoryId(id);
  9. return new Result<Template>(true, StatusCode.OK,"查询成功",template);
  10. }

2.4.3 查询分类品牌数据

2.4.3.1 分析

第3章 商品发布 - 图10
用户每次选择了分类之后,可以根据用户选择的分类到tb_category_brand表中查询指定的品牌集合ID,然后根据品牌集合ID查询对应的品牌集合数据,再将品牌集合数据拿到这里来展示即可实现上述功能。

2.4.3.2 代码实现

(1)Dao实现
修改com.changgou.goods.dao.BrandMapper添加根据分类ID查询对应的品牌数据,代码如下:

  1. public interface BrandMapper extends Mapper<Brand> {
  2. /***
  3. * 查询分类对应的品牌集合
  4. */
  5. @Select("SELECT tb.* FROM tb_category_brand tcb,tb_brand tb WHERE tcb.category_id=#{categoryid} AND tb.id=tcb.brand_id")
  6. List<Brand> findByCategory(Integer categoryid);
  7. }

(2)Service层
修改com.changgou.goods.service.BrandService,添加根据分类ID查询指定的品牌集合方法,代码如下:

  1. /***
  2. * 根据分类ID查询品牌集合
  3. * @param categoryid:分类ID
  4. */
  5. List<Brand> findByCategory(Integer categoryid);

修改com.changgou.goods.service.impl.BrandServiceImpl添加上面方法的实现,代码如下:

  1. /***
  2. * 根据分类ID查询品牌集合
  3. * @param categoryid:分类ID
  4. * @return
  5. */
  6. @Override
  7. public List<Brand> findByCategory(Integer categoryid) {
  8. //1.查询当前分类所对应的所有品牌信息
  9. //2.根据品牌ID查询对应的品牌集合
  10. //自己创建DAO实现查询
  11. return brandMapper.findByCategory(categoryid);
  12. }

(3)Controller层
修改,添加根据分类ID查询对应的品牌数据代码如下:

  1. /***
  2. * 根据分类实现品牌列表查询
  3. * /brand/category/{id} 分类ID
  4. */
  5. @GetMapping(value = "/category/{id}")
  6. public Result<List<Brand>> findBrandByCategory(@PathVariable(value = "id")Integer categoryId){
  7. //调用Service查询品牌数据
  8. List<Brand> categoryList = brandService.findByCategory(categoryId);
  9. return new Result<List<Brand>>(true,StatusCode.OK,"查询成功!",categoryList);
  10. }

2.4.4 规格查询

2.4.4.1 分析

第3章 商品发布 - 图11
用户选择分类后,需要根据所选分类对应的模板ID查询对应的规格,规格表结构如下:

  1. CREATE TABLE `tb_spec` (
  2. `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  3. `name` varchar(50) DEFAULT NULL COMMENT '名称',
  4. `options` varchar(2000) DEFAULT NULL COMMENT '规格选项',
  5. `seq` int(11) DEFAULT NULL COMMENT '排序',
  6. `template_id` int(11) DEFAULT NULL COMMENT '模板ID',
  7. PRIMARY KEY (`id`)
  8. ) ENGINE=InnoDB AUTO_INCREMENT=40 DEFAULT CHARSET=utf8;

2.4.4.2 代码实现

(1)Service层
修改com.changgou.goods.service.SpecService添加根据分类ID查询规格列表,代码如下:

  1. /***
  2. * 根据分类ID查询规格列表
  3. * @param categoryid
  4. * @return
  5. */
  6. List<Spec> findByCategoryId(Integer categoryid);

修改com.changgou.goods.service.impl.SpecServiceImpl添加上面方法的实现,代码如下:

  1. @Autowired
  2. private CategoryMapper categoryMapper;
  3. /***
  4. * 根据分类ID查询规格列表
  5. * @param categoryid
  6. * @return
  7. */
  8. @Override
  9. public List<Spec> findByCategoryId(Integer categoryid) {
  10. //查询分类
  11. Category category = categoryMapper.selectByPrimaryKey(categoryid);
  12. //根据分类的模板ID查询规格
  13. Spec spec = new Spec();
  14. spec.setTemplateId(category.getTemplateId());
  15. return specMapper.select(spec);
  16. }

(2)Controller层
修改com.changgou.goods.controller.SpecController添加根据分类ID查询规格数据,代码如下:

  1. /***
  2. * 根据分类ID查询对应的规格列表
  3. */
  4. @GetMapping(value = "/category/{id}")
  5. public Result<List<Spec>> findByCategoryId(@PathVariable(value = "id")Integer categoryid){
  6. //调用Service查询
  7. List<Spec> specs = specService.findByCategoryId(categoryid);
  8. return new Result<List<Spec>>(true, StatusCode.OK,"查询成功",specs);
  9. }

2.4.5 参数列表查询

2.4.5.1 分析

第3章 商品发布 - 图12
当用户选中分类后,需要根据分类的模板ID查询对应的参数列表,参数表结构如下:

  1. CREATE TABLE `tb_para` (
  2. `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
  3. `name` varchar(50) DEFAULT NULL COMMENT '名称',
  4. `options` varchar(2000) DEFAULT NULL COMMENT '选项',
  5. `seq` int(11) DEFAULT NULL COMMENT '排序',
  6. `template_id` int(11) DEFAULT NULL COMMENT '模板ID',
  7. PRIMARY KEY (`id`)
  8. ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

2.4.5.2 代码实现

(1)Service层
修改com.changgou.goods.service.ParaService添加根据分类ID查询参数列表,代码如下:

  1. /***
  2. * 根据分类ID查询参数列表
  3. * @param id
  4. * @return
  5. */
  6. List<Para> findByCategoryId(Integer id);

修改com.changgou.goods.service.impl.ParaServiceImpl添加上面方法的实现,代码如下:

  1. @Autowired
  2. private CategoryMapper categoryMapper;
  3. /***
  4. * 根据分类ID查询参数列表
  5. * @param id
  6. * @return
  7. */
  8. @Override
  9. public List<Para> findByCategoryId(Integer id) {
  10. //查询分类信息
  11. Category category = categoryMapper.selectByPrimaryKey(id);
  12. //根据分类的模板ID查询参数列表
  13. Para para = new Para();
  14. para.setTemplateId(category.getTemplateId());
  15. return paraMapper.select(para);
  16. }

(2)Controller层
修改com.changgou.goods.controller.ParaController,添加根据分类ID查询参数列表,代码如下:

  1. /**
  2. * 根据分类ID查询参数列表
  3. * @param id
  4. * @return
  5. */
  6. @GetMapping(value = "/category/{id}")
  7. public Result<List<Para>> getByCategoryId(@PathVariable(value = "id")Integer id){
  8. //根据分类ID查询对应的参数信息
  9. List<Para> paras = paraService.findByCategoryId(id);
  10. Result<List<Para>> result = new Result<List<Para>>(true,StatusCode.OK,"查询分类对应的品牌成功!",paras);
  11. return result;
  12. }

2.4.6 SPU+SKU保存

2.4.6.1 分析

保存商品数据的时候,需要保存Spu和Sku,一个Spu对应多个Sku,我们可以先构建一个Goods对象,将SpuList<Sku>组合到一起,前端将2者数据提交过来,再实现添加操作。

2.4.62 代码实现

(1)Pojo改造
修改changgou-service-goods-api工程创建组合实体类,创建com.changgou.goods.pojo.Goods,代码如下:

  1. public class Goods implements Serializable {
  2. //SPU
  3. private Spu spu;
  4. //SKU集合
  5. private List<Sku> skuList;
  6. //..get..set..toString
  7. }

(2) 业务层
修改com.changgou.goods.service.SpuService接口,添加保存Goods方法,代码如下:

  1. /**
  2. * 保存商品
  3. * @param goods
  4. */
  5. void saveGoods(Goods goods);

修改com.changgou.goods.service.impl.SpuServiceImpl类,添加保存Goods的方法实现,代码如下:

  1. @Autowired
  2. private IdWorker idWorker;
  3. @Autowired
  4. private CategoryMapper categoryMapper;
  5. @Autowired
  6. private BrandMapper brandMapper;
  7. @Autowired
  8. private SkuMapper skuMapper;
  9. /***
  10. * 保存Goods
  11. * @param goods
  12. */
  13. @Override
  14. public void saveGoods(Goods goods) {
  15. //增加Spu
  16. Spu spu = goods.getSpu();
  17. spu.setId(idWorker.nextId());
  18. spuMapper.insertSelective(spu);
  19. //增加Sku
  20. Date date = new Date();
  21. Category category = categoryMapper.selectByPrimaryKey(spu.getCategory3Id());
  22. Brand brand = brandMapper.selectByPrimaryKey(spu.getBrandId());
  23. //获取Sku集合
  24. List<Sku> skus = goods.getSkus();
  25. //循环将数据加入到数据库
  26. for (Sku sku : skus) {
  27. //构建SKU名称,采用SPU+规格值组装
  28. if(StringUtils.isEmpty(sku.getSpec())){
  29. sku.setSpec("{}");
  30. }
  31. //获取Spu的名字
  32. String name = spu.getName();
  33. //将规格转换成Map
  34. Map<String,String> specMap = JSON.parseObject(sku.getSpec(), Map.class);
  35. //循环组装Sku的名字
  36. for (Map.Entry<String, String> entry : specMap.entrySet()) {
  37. name+=" "+entry.getValue();
  38. }
  39. sku.setName(name);
  40. //ID
  41. sku.setId(idWorker.nextId());
  42. //SpuId
  43. sku.setSpuId(spu.getId());
  44. //创建日期
  45. sku.setCreateTime(date);
  46. //修改日期
  47. sku.setUpdateTime(date);
  48. //商品分类ID
  49. sku.setCategoryId(spu.getCategory3Id());
  50. //分类名字
  51. sku.setCategoryName(category.getName());
  52. //品牌名字
  53. sku.setBrandName(brand.getName());
  54. //增加
  55. skuMapper.insertSelective(sku);
  56. }
  57. }

(3)控制层
修改com.changgou.goods.controller.SpuController,增加保存Goods方法,代码如下:

  1. /***
  2. * 添加Goods
  3. * @param goods
  4. * @return
  5. */
  6. @PostMapping("/save")
  7. public Result save(@RequestBody Goods goods){
  8. spuService.saveGoods(goods);
  9. return new Result(true,StatusCode.OK,"保存成功");
  10. }

测试数据

  1. {
  2. "skuList": [
  3. {
  4. "alertNum": 10,
  5. "brandName": "华为",
  6. "categoryId": 64,
  7. "commentNum": 0,
  8. "image": "http://www.baidu.com",
  9. "images": "",
  10. "name": "华为P30手机",
  11. "num": 5,
  12. "price": 1000,
  13. "saleNum": 0,
  14. "sn": "No1001",
  15. "spec": "{\"颜色\":\"红\",\"机身内存\":\"64G\"}",
  16. "weight": 0
  17. },
  18. {
  19. "alertNum": 10,
  20. "brandName": "华为",
  21. "categoryId": 64,
  22. "commentNum": 0,
  23. "image": "http://www.baidu.com",
  24. "images": "",
  25. "name": "华为P30手机",
  26. "num": 5,
  27. "price": 1000,
  28. "saleNum": 0,
  29. "sn": "No1001",
  30. "spec": "{\"颜色\":\"绿\",\"机身内存\":\"64G\"}",
  31. "weight": 0
  32. },
  33. {
  34. "alertNum": 10,
  35. "brandName": "华为",
  36. "categoryId": 64,
  37. "commentNum": 0,
  38. "image": "http://www.baidu.com",
  39. "images": "",
  40. "name": "华为P30手机",
  41. "num": 5,
  42. "price": 1000,
  43. "saleNum": 0,
  44. "sn": "No1001",
  45. "spec": "{\"颜色\":\"绿\",\"机身内存\":\"8G\"}",
  46. "weight": 0
  47. },
  48. {
  49. "alertNum": 10,
  50. "brandName": "华为",
  51. "categoryId": 64,
  52. "commentNum": 0,
  53. "image": "http://www.baidu.com",
  54. "images": "",
  55. "name": "华为P30手机",
  56. "num": 5,
  57. "price": 1000,
  58. "saleNum": 0,
  59. "sn": "No1001",
  60. "spec": "{\"颜色\":\"红\",\"机身内存\":\"8G\"}",
  61. "weight": 0
  62. }
  63. ],
  64. "spu": {
  65. "brandId": 8557,
  66. "caption": "104期手机大促销",
  67. "category1Id": 1,
  68. "category2Id": 59,
  69. "category3Id": 64,
  70. "commentNum": 0,
  71. "freightId": 0,
  72. "images": "http://www.qingcheng.com/image/1.jpg,http://www.qingcheng.com/image/2.jpg",
  73. "introduction": "华为产品世界最强",
  74. "isEnableSpec": "1",
  75. "isMarketable": "1",
  76. "name": "104期特牛逼的手机",
  77. "specItems": "{\"颜色\":[\"红\",\"绿\"],\"机身内存\":[\"64G\",\"8G\"]}",
  78. "paraItems": "{\"赠品\":\"充电器\",\"出厂年份\":\"2019\"}",
  79. "saleNum": 0,
  80. "saleService": "一年包换",
  81. "sn": "No10001",
  82. "status": "1",
  83. "templateId": 42
  84. }
  85. }

2.4.7 根据ID查询商品

2.4.7.1 需求分析

需求:根据id 查询SPU和SKU列表 ,显示效果如下:

  1. {
  2. "spu": {
  3. "brandId": 0,
  4. "caption": "111",
  5. "category1Id": 558,
  6. "category2Id": 559,
  7. "category3Id": 560,
  8. "commentNum": null,
  9. "freightId": null,
  10. "id": 149187842867993,
  11. "image": null,
  12. "images": null,
  13. "introduction": null,
  14. "isDelete": null,
  15. "isEnableSpec": "0",
  16. "isMarketable": "1",
  17. "name": "黑马智能手机",
  18. "paraItems": null,
  19. "saleNum": null,
  20. "saleService": null,
  21. "sn": null,
  22. "specItems": null,
  23. "status": null,
  24. "templateId": 42
  25. },
  26. "skuList": [{
  27. "alertNum": null,
  28. "brandName": "金立(Gionee)",
  29. "categoryId": 560,
  30. "categoryName": "手机",
  31. "commentNum": null,
  32. "createTime": "2018-11-06 10:17:08",
  33. "id": 1369324,
  34. "image": null,
  35. "images": "blob:http://localhost:8080/ec04d1a5-d865-4e7f-a313-2e9a76cfb3f8",
  36. "name": "黑马智能手机",
  37. "num": 100,
  38. "price": 900000,
  39. "saleNum": null,
  40. "sn": "",
  41. "spec": null,
  42. "spuId": 149187842867993,
  43. "status": "1",
  44. "updateTime": "2018-11-06 10:17:08",
  45. "weight": null
  46. },{
  47. "alertNum": null,
  48. "brandName": "金立(Gionee)",
  49. "categoryId": 560,
  50. "categoryName": "手机",
  51. "commentNum": null,
  52. "createTime": "2018-11-06 10:17:08",
  53. "id": 1369325,
  54. "image": null,
  55. "images": "blob:http://localhost:8080/ec04d1a5-d865-4e7f-a313-2e9a76cfb3f8",
  56. "name": "黑马智能手机",
  57. "num": 100,
  58. "price": 900000,
  59. "saleNum": null,
  60. "sn": "",
  61. "spec": null,
  62. "spuId": 149187842867993,
  63. "status": "1",
  64. "updateTime": "2018-11-06 10:17:08",
  65. "weight": null
  66. }
  67. ]
  68. }

2.4.7.2 代码实现

(1)业务层
修改changgou-service-goods工程,修改com.changgou.goods.service.SpuService接口,添加根据ID查找方法findGoodsById代码如下:

  1. /***
  2. * 根据SPU的ID查找SPU以及对应的SKU集合
  3. * @param spuId
  4. */
  5. Goods findGoodsById(Long spuId);

修改qingcheng-service-goods工程,修改com.changgou.goods.service.impl.SpuServiceImpl类,添加根据ID查找findGoodsById方法,代码如下:

  1. /***
  2. * 根据SpuID查询goods信息
  3. * @param spuId
  4. * @return
  5. */
  6. @Override
  7. public Goods findGoodsById(Long spuId) {
  8. //查询Spu
  9. Spu spu = spuMapper.selectByPrimaryKey(spuId);
  10. //查询List<Sku>
  11. Sku sku = new Sku();
  12. sku.setSpuId(spuId);
  13. List<Sku> skus = skuMapper.select(sku);
  14. //封装Goods
  15. Goods goods = new Goods();
  16. goods.setSkus(skus);
  17. goods.setSpu(spu);
  18. return goods;
  19. }

(2)控制层
修改com.changgou.goods.controller.SpuController,修改findById方法,代码如下:

  1. /***
  2. * 根据ID查询Goods
  3. * @param id
  4. * @return
  5. */
  6. @GetMapping("/goods/{id}")
  7. public Result<Goods> findGoodsById(@PathVariable Long id){
  8. //根据ID查询Goods(SPU+SKU)信息
  9. Goods goods = spuService.findGoodsById(id);
  10. return new Result<Goods>(true,StatusCode.OK,"查询成功",goods);
  11. }

测试:http://localhost:18081/spu/goods/1088256029394866176

2.4.8 保存修改

修改changgou-service-goods的SpuServiceImpl的saveGoods方法,修改添加SPU部分代码:
第3章 商品发布 - 图13
上图代码如下:

  1. if(spu.getId()==null){
  2. //增加
  3. spu.setId(idWorker.nextId());
  4. spuMapper.insertSelective(spu);
  5. }else{
  6. //修改数据
  7. spuMapper.updateByPrimaryKeySelective(spu);
  8. //删除该Spu的Sku
  9. Sku sku = new Sku();
  10. sku.setSpuId(spu.getId());
  11. skuMapper.delete(sku);
  12. }

2.4.9 修改SKU库存

(学员实现)

3 商品审核与上下架

3.1 需求分析

商品新增后,审核状态为0(未审核),默认为下架状态。
审核商品,需要校验是否是被删除的商品,如果未删除则修改审核状态为1
下架商品,需要校验是否是被删除的商品,如果未删除则修改上架状态为0
上架商品,需要校验是否被删除的商品,如果未被删除,则需要审核通过的商品,才能上架.

3.2 实现思路

(1)按照ID查询SPU信息
(2)判断修改审核、上架和下架状态
(3)保存SPU

3.3 代码实现

3.3.1 商品审核

实现审核通过,自动上架。
(1)业务层
修改修改changgou-service-goods工程的com.changgou.goods.service.SpuService接口,添加审核方法,代码如下:

  1. /***
  2. * 商品审核
  3. * @param spuId
  4. */
  5. void audit(Long spuId);

修改changgou-service-goods工程的com.changgou.goods.service.impl.SpuServiceImpl类,添加audit方法,代码如下:

  1. /***
  2. * 商品审核
  3. * @param spuId
  4. */
  5. @Override
  6. public void audit(Long spuId) {
  7. //查询商品
  8. Spu spu = spuMapper.selectByPrimaryKey(spuId);
  9. //判断商品是否已经删除
  10. if(spu.getIsDelete().equalsIgnoreCase("1")){
  11. throw new RuntimeException("该商品已经删除!");
  12. }
  13. //实现审核
  14. spu.setStatus("1"); //审核通过
  15. spuMapper.updateByPrimaryKeySelective(spu);
  16. }

(2)控制层
修改com.changgou.goods.controller.SpuController,新增audit方法,代码如下:

  1. /**
  2. * 审核
  3. * @param id
  4. * @return
  5. */
  6. @PutMapping("/audit/{id}")
  7. public Result audit(@PathVariable Long id){
  8. spuService.audit(id);
  9. return new Result(true,StatusCode.OK,"审核成功");
  10. }

3.3.2 下架商品

(1)业务层
修改com.changgou.goods.service.SpuService接口,添加pull方法,用于商品下架,代码如下:

  1. /***
  2. * 商品下架
  3. * @param spuId
  4. */
  5. void pull(Long spuId);

修改com.changgou.goods.service.impl.SpuServiceImpl,添加如下方法:

  1. /**
  2. * 商品下架
  3. * @param spuId
  4. */
  5. @Override
  6. public void pull(Long spuId) {
  7. Spu spu = spuMapper.selectByPrimaryKey(spuId);
  8. if(spu.getIsDelete().equals("1")){
  9. throw new RuntimeException("此商品已删除!");
  10. }
  11. spu.setIsMarketable("0");//下架状态
  12. spuMapper.updateByPrimaryKeySelective(spu);
  13. }

(2)控制层
修改com.changgou.goods.controller.SpuController,添加pull方法,代码如下:

  1. /**
  2. * 下架
  3. * @param id
  4. * @return
  5. */
  6. @PutMapping("/pull/{id}")
  7. public Result pull(@PathVariable Long id){
  8. spuService.pull(id);
  9. return new Result(true,StatusCode.OK,"下架成功");
  10. }

3.3.3 上架商品

(1)业务层
修改com.changgou.goods.service.SpuService,添加put方法,代码如下:

  1. /***
  2. * 商品上架
  3. * @param spuId
  4. */
  5. void put(Long spuId);

修改com.changgou.goods.service.impl.SpuServiceImpl,添加put方法实现,代码如下:

  1. /***
  2. * 商品上架
  3. * @param spuId
  4. */
  5. @Override
  6. public void put(Long spuId) {
  7. Spu spu = spuMapper.selectByPrimaryKey(spuId);
  8. //检查是否删除的商品
  9. if(spu.getIsDelete().equals("1")){
  10. throw new RuntimeException("此商品已删除!");
  11. }
  12. //上架状态
  13. spu.setIsMarketable("1");
  14. spuMapper.updateByPrimaryKeySelective(spu);
  15. }

(2)控制层
修改com.changgou.goods.controller.SpuController,添加put方法代码如下:

  1. /**
  2. * 商品上架
  3. * @param id
  4. * @return
  5. */
  6. @PutMapping("/put/{id}")
  7. public Result put(@PathVariable Long id){
  8. spuService.put(id);
  9. return new Result(true,StatusCode.OK,"上架成功");
  10. }

3.3.4 批量上架

前端传递一组商品ID,后端进行批量上下架处理
(1)业务层
修改com.changgou.goods.service.SpuService接口,代码如下:

  1. int putMany(Long[] ids);

修改com.changgou.goods.service.impl.SpuServiceImpl,添加批量上架方法实现,代码如下:

  1. /***
  2. * 批量上架
  3. * @param ids:需要上架的商品ID集合
  4. * @return
  5. */
  6. @Override
  7. public int putMany(Long[] ids) {
  8. Spu spu=new Spu();
  9. spu.setIsMarketable("1");//上架
  10. //批量修改
  11. Example example=new Example(Spu.class);
  12. Example.Criteria criteria = example.createCriteria();
  13. criteria.andIn("id", Arrays.asList(ids));//id
  14. //下架
  15. criteria.andEqualTo("isMarketable","0");
  16. //审核通过的
  17. criteria.andEqualTo("status","1");
  18. //非删除的
  19. criteria.andEqualTo("isDelete","0");
  20. return spuMapper.updateByExampleSelective(spu, example);
  21. }

(2)控制层
修改com.changgou.goods.controller.SpuController,添加批量上架方法,代码如下:

  1. /**
  2. * 批量上架
  3. * @param ids
  4. * @return
  5. */
  6. @PutMapping("/put/many")
  7. public Result putMany(@RequestBody Long[] ids){
  8. int count = spuService.putMany(ids);
  9. return new Result(true,StatusCode.OK,"上架"+count+"个商品");
  10. }

使用Postman测试:
第3章 商品发布 - 图14

3.3.5 批量下架

学员实现

4 删除与还原商品

4.1 需求分析

请看管理后台的静态原型
商品列表中的删除商品功能,并非真正的删除,而是将删除标记的字段设置为1,
在回收站中有恢复商品的功能,将删除标记的字段设置为0
在回收站中有删除商品的功能,是真正的物理删除。

4.2 实现思路

逻辑删除商品,修改spu表is_delete字段为1
商品回收站显示spu表is_delete字段为1的记录
回收商品,修改spu表is_delete字段为0

4.3 代码实现

4.3.1 逻辑删除商品

(1)业务层
修改com.changgou.goods.service.SpuService接口,增加logicDelete方法,代码如下:

  1. /***
  2. * 逻辑删除
  3. * @param spuId
  4. */
  5. void logicDelete(Long spuId);

修改com.changgou.goods.service.impl.SpuServiceImpl,添加logicDelete方法实现,代码如下:

  1. /***
  2. * 逻辑删除
  3. * @param spuId
  4. */
  5. @Override
  6. @Transactional
  7. public void logicDelete(Long spuId) {
  8. Spu spu = spuMapper.selectByPrimaryKey(spuId);
  9. //检查是否下架的商品
  10. if(!spu.getIsMarketable().equals("0")){
  11. throw new RuntimeException("必须先下架再删除!");
  12. }
  13. //删除
  14. spu.setIsDelete("1");
  15. //未审核
  16. spu.setStatus("0");
  17. spuMapper.updateByPrimaryKeySelective(spu);
  18. }

(2)控制层
修改com.changgou.goods.controller.SpuController,添加logicDelete方法,如下:

  1. /**
  2. * 逻辑删除
  3. * @param id
  4. * @return
  5. */
  6. @DeleteMapping("/logic/delete/{id}")
  7. public Result logicDelete(@PathVariable Long id){
  8. spuService.logicDelete(id);
  9. return new Result(true,StatusCode.OK,"逻辑删除成功!");
  10. }

4.3.2 还原被删除的商品

(1)业务层
修改com.changgou.goods.service.SpuService接口,添加restore方法代码如下:

  1. /***
  2. * 还原被删除商品
  3. * @param spuId
  4. */
  5. void restore(Long spuId);

修改com.changgou.goods.service.impl.SpuServiceImpl类,添加restore方法,代码如下:

  1. /**
  2. * 恢复数据
  3. * @param spuId
  4. */
  5. @Override
  6. public void restore(Long spuId) {
  7. Spu spu = spuMapper.selectByPrimaryKey(spuId);
  8. //检查是否删除的商品
  9. if(!spu.getIsDelete().equals("1")){
  10. throw new RuntimeException("此商品未删除!");
  11. }
  12. //未删除
  13. spu.setIsDelete("0");
  14. //未审核
  15. spu.setStatus("0");
  16. spuMapper.updateByPrimaryKeySelective(spu);
  17. }

(2)控制层
修改com.changgou.goods.controller.SpuController,添加restore方法,代码如下:

  1. /**
  2. * 恢复数据
  3. * @param id
  4. * @return
  5. */
  6. @PutMapping("/restore/{id}")
  7. public Result restore(@PathVariable Long id){
  8. spuService.restore(id);
  9. return new Result(true,StatusCode.OK,"数据恢复成功!");
  10. }

4.3.3 物理删除商品

修改com.changgou.goods.service.impl.SpuServiceImpl的delete方法,代码如下:

  1. /**
  2. * 删除
  3. * @param id
  4. */
  5. @Override
  6. public void delete(Long id){
  7. Spu spu = spuMapper.selectByPrimaryKey(id);
  8. //检查是否被逻辑删除 ,必须先逻辑删除后才能物理删除
  9. if(!spu.getIsDelete().equals("1")){
  10. throw new RuntimeException("此商品不能删除!");
  11. }
  12. spuMapper.deleteByPrimaryKey(id);
  13. }

5 商品列表

5.1 需求分析

如图所示 展示商品的列表。并实现分页。
第3章 商品发布 - 图15
思路:

  1. 根据查询的条件 分页查询 并返回分页结果即可。
  2. 分页查询 采用 pagehelper ,条件查询 通过map进行封装传递给后台即可。

5.2 代码实现

在代码生成器生成的代码中已经包含了该实现,这里就省略了。
控制层(SpuController):

  1. /***
  2. * Spu分页条件搜索实现
  3. * @param spu
  4. * @param page
  5. * @param size
  6. * @return
  7. */
  8. @PostMapping(value = "/search/{page}/{size}" )
  9. public Result<PageInfo> findPage(@RequestBody(required = false) Spu spu, @PathVariable int page, @PathVariable int size){
  10. //执行搜索
  11. PageInfo<Spu> pageInfo = spuService.findPage(spu, page, size);
  12. return new Result(true,StatusCode.OK,"查询成功",pageInfo);
  13. }

其他每层代码,代码生成器已经生成,这里就不再列出来了。