lyshop学习笔记六-商品管理


title: 乐优商城学习笔记六-商品管理
date: 2019-04-16 09:19:03
tags:
- 乐优商城
- java
- springboot
categories:
- 乐优商城


1.SPU和SKU数据结构

规格确定以后,就可以添加商品了,先看下数据库表

1.1.SPU表

1.1.1.表结构

SPU表:

CREATE TABLE `tb_spu` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'spu id',
  `title` varchar(255) NOT NULL DEFAULT '' COMMENT '标题',
  `sub_title` varchar(255) DEFAULT '' COMMENT '子标题',
  `cid1` bigint(20) NOT NULL COMMENT '1级类目id',
  `cid2` bigint(20) NOT NULL COMMENT '2级类目id',
  `cid3` bigint(20) NOT NULL COMMENT '3级类目id',
  `brand_id` bigint(20) NOT NULL COMMENT '商品所属品牌id',
  `saleable` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否上架,0下架,1上架',
  `valid` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否有效,0已删除,1有效',
  `create_time` datetime DEFAULT NULL COMMENT '添加时间',
  `last_update_time` datetime DEFAULT NULL COMMENT '最后修改时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=208 DEFAULT CHARSET=utf8 COMMENT='spu表,该表描述的是一个抽象的商品,比如 iphone8';

与我们前面分析的基本类似,但是似乎少了一些字段,比如商品描述。

我们做了表的垂直拆分,将SPU的详情放到了另一张表:tb_spu_detail

CREATE TABLE `tb_spu_detail` (
  `spu_id` bigint(20) NOT NULL,
  `description` text COMMENT '商品描述信息',
  `specifications` varchar(3000) NOT NULL DEFAULT '' COMMENT '全部规格参数数据',
  `spec_template` varchar(1000) NOT NULL COMMENT '特有规格参数及可选值信息,json格式',
  `packing_list` varchar(1000) DEFAULT '' COMMENT '包装清单',
  `after_service` varchar(1000) DEFAULT '' COMMENT '售后服务',
  PRIMARY KEY (`spu_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

这张表中的数据都比较大,为了不影响主表的查询效率我们拆分出这张表。

需要注意的是这两个字段:specifications和spec_template。

1.1.2.spu中的规格参数

前面讲过规格参数与商品分类绑定,一个分类下的所有SPU具有类似的规格参数。SPU下的SKU可能会有不同的规格参数,因此我们计划是这样:

  • SPU中保存全局的规格参数信息。
  • SKU中保存特有规格参数。

以手机为例,品牌、操作系统等肯定是全局属性,内存、颜色等肯定是特有属性。

当你确定了一个SPU,比如小米的:红米4X

全局属性举例:

品牌:小米
型号:红米4X

特有属性举例:

颜色:[香槟金, 樱花粉, 磨砂黑]
内存:[2G, 3G]
机身存储:[16GB, 32GB]

来看下我们的 表如何存储这些信息:

1.1.2.1.specifications字段

首先是specifications,其中保存全部规格参数信息,因此也是一个json格式:

整体来看:

1526262279963

展开一组来看

[图片上传失败...(image-4c9665-1555379703367)]

可以看到,与规格参数表中的模板相比,最大的区别就是,这里指定了具体的值,因为商品确定了,其参数值肯定也确定了。

特有属性

刚才看到的是全局属性,那么特有属性在这个字段中如何存储呢?

[图片上传失败...(image-b59bbc-1555379703367)]

我们发现特有属性也是有的,但是,注意看这里是不确定具体值的,因为特有属性只有在SKU中才能确定。这里只是保存了options,所有SKU属性的可选项。

在哪里会用到这个字段的值呢,商品详情页的规格参数信息中:

[图片上传失败...(image-a7671-1555379703367)]

1.1.2.2.spec_template字段

既然specifications已经包含了所有的规格参数,那么为什么又多出了一个spec_template呢?

里面又有哪些内容呢?

来看数据格式:

[图片上传失败...(image-4a20bf-1555379703367)]

可以看出,里面只保存了规格参数中的特有属性,而且格式进行了大大的简化,只有属性的key,和待选项。

为什么要冗余保存一份?

因为很多场景下我们只需要查询特有规格属性,如果放在一起,每次查询再去分离比较麻烦。

比如,商品详情页展示可选的规格参数时:

1526267828817

2.页面实现

2.1.页面实现代码






主要的改动点:

  • 页面的v-data-table中的属性绑定修改。items指向goodsList,totalItems指向totalGoods

  • 页面渲染的字段名修改:字段改成商品的SPU字段:id、title,cname(商品分类名称),bname(品牌名称)

  • data属性修改了以下属性:

    • goodsList:当前页商品数据
    • totalGoods:商品总数
    • headers:头信息,需要修改头显示名称
    • oldGoods:准备要修改的商品
  • 加载数据的函数:getDataFromServer,请求的路径进行了修改,另外去除了跟排序相关的查询。SPU查询不排序

  • 新增商品的事件函数:清除了一些数据查询接口,只保留弹窗

查看效果:

1526272476614

2.3.后台提供接口

页面已经准备好,接下来在后台提供分页查询SPU的功能:

实体类

spu

@Data
@Table(name = "tb_spu")
public class Spu {
    @Id
    @KeySql(useGeneratedKeys = true)
//    @GeneratedValue(strategy = GenerationType.IDENTITY)
    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;// 最后修改时间
    @Transient
    private String cname;
    @Transient
    private String bname;
}

spu详情表

@Table(name="tb_spu_detail")
public class SpuDetail {
    @Id
    private Long spuId;// 对应的SPU的id
    private String description;// 商品描述
    private String specTemplate;// 商品特殊规格的名称及可选值模板
    private String specifications;// 商品的全局规格属性
    private String packingList;// 包装清单
    private String afterService;// 售后服务
    // 省略getter和setter
}

controller

先分析:

  • 请求方式:GET

  • 请求路径:/spu/page

  • 请求参数:

    • page:当前页
    • rows:每页大小
    • key:过滤条件
    • saleable:上架或下架
  • 返回结果:商品SPU的分页信息。

    • 要注意,页面展示的是商品分类和品牌名称,而数据库中保存的是id,怎么办?

      我们可以在spu类,拓展cname和bname属性,

      @Data
      @Table(name = "tb_spu")
      public class Spu {
      
          @Transient
          private String cname;
          @Transient
          private String bname;
          
      }
    

代码

@RestController
public class GoodsController {

    @Autowired
    private GoodsService goodsService;
    @GetMapping("/spu/page")
    public ResponseEntity> querySpuByPage(
            @RequestParam(value = "page",defaultValue = "1") Integer page,
            @RequestParam(value = "rows",defaultValue = "5") Integer rows,
            @RequestParam(value = "sortBy", required = false) String sortBy,
            @RequestParam(value = "desc", defaultValue = "false") Boolean desc,
            @RequestParam(value = "key",required = false) String key,
            @RequestParam(value = "saleable",defaultValue = "true") Boolean saleable


    ){
        return ResponseEntity.ok(goodsService.querySpuByPage(page,rows,saleable,key));
    }
}

service

所有商品相关的业务(包括SPU和SKU)放到一个业务下:GoodsService。

@Service
public class GoodsService {
    @Autowired
    private SpuMapper spuMapper;

    @Autowired
    private SpuDetailMapper spuDetailMapper;

    @Autowired
    private CategoryService categoryService;

    @Autowired
    private BrandService brandService;

    public PageResult querySpuByPage(Integer page, Integer rows, Boolean saleable, String key) {
        //分页
        PageHelper.startPage(page,rows);
        //过滤
        Example example = new Example(Spu.class);
        Example.Criteria criterion = example.createCriteria();
        //搜索字段过滤
        if (StringUtils.isNotBlank(key)){
            criterion.andLike("title","%"+key+"%");
        }
        //上下架过滤
        if (saleable != null){
            criterion.orEqualTo("saleable",saleable);
        }

        //排序
        example.setOrderByClause("last_update_time DESC");

        //查询
        List spus = spuMapper.selectByExample(example);
        //判断
        if (CollectionUtils.isEmpty(spus)){
            throw new LyException(ExceptionEnum.GOODS_NOT_FOOD);

        }
        //解析分类和品牌的名称
        loadCategoryAndBrandName(spus);
        //解析分页结果
        PageInfo info = new PageInfo<>(spus);

        return new PageResult<>(info.getTotal(),spus);
    }

    private void loadCategoryAndBrandName(List spus) {
        for (Spu spu:spus){
            //处理分类名称
            List names = categoryService.queryByIds(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()))
                    .stream().map(Category::getName).collect(Collectors.toList());
            spu.setCname(StringUtils.join(names,"/"));
            //处理品牌信息
            spu.setBname(brandService.queryById(spu.getBrandId()).getName());

        }
    }
}

mapper

public interface SpuMapper extends Mapper {
}

Category中拓展查询名称的功能

页面需要商品的分类名称需要在这里查询,因此要额外提供查询分类名称的功能,

在CategoryService中添加功能:

    public List queryByIds(List ids){
        List categories = categoryMapper.selectByIdList(ids);
        if (CollectionUtils.isEmpty(categories)){
            throw new LyException(ExceptionEnum.CATEGORY_NOT_FOND);
        }
        return categories;
    }

mapper的selectByIDList方法是来自于通用mapper。不过需要我们在mapper上继承一个通用mapper接口:

public interface CategoryMapper extends Mapper, IdListMapper {
}

测试

1526280777975

你可能感兴趣的:(lyshop学习笔记六-商品管理)