一、电商商品SPU和SKU的概念
SPU和SKU的概念,在电商领域非常重要,SPU描述的是商品的集合,以手机为例,例如 华为手机有不同颜色,不同内存大小等相关属性信息。如果只说华为荣誉X60 就是SPU的信息。
SKU是商品最小销售单元,可以通过SKU描述具体特定的商品华为荣誉X60黑色 128G ,根据这句话特点的描述出了某一个商品。这个就是SPU和SKU。
商品详情页面 ,加载的是SPU ,商品详情页面是以SPU进行当前商品信息的描述。随着规格信息的切换,颜色的切换,具体细化到了SKU上,随着规格改变价格会变。
添加购物车的时候添加的是SKU的信息。
SPU:是一组商品集合,在这组商品集合里,会包含一个或多个SKU的信息,每一个SKU是一个具体的,某个商品的 最小销售单元,可以通过一个SKU集体细化到商品上,从而确定商品的价钱。SPU与SKU 是一对多 或一对一的关系。
SPU = Standard Product Unit (标准产品单位)
-概念 : SPU 是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。
-通俗点讲,属性值、特性相同的货品就可以称为一个 SPU
例如:华为荣誉X60 就是一个 SPU
SKU=stock keeping unit( 库存量单位)
-SKU 即库存进出计量的单位, 可以是以件、盒、托盘等为单位。
-SKU 是物理上不可分割的最小存货单元。在使用时要根据不同业态,不同管理模式来处理。
-在服装、鞋类商品中使用最多最普遍。
例如:华为荣誉X60 黑色 128G 就是一个 SKU
二、需求分析
1)表结构分析
tb_spu 表 (SPU表)
字段名称 | 字段含义 | 字段类型 | 字段长度 | 备注 |
---|---|---|---|---|
id | 主键 | VARCHAR | ||
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 | VARCHAR | ||
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)实现思路
前端传递给后端的数据格式 是一个spu对象和sku列表组成的对象
{
"spu": {
"name": "这个是商品名称",
"caption": "这个是副标题",
"brandId": 12,
"category1Id": 558,
"category2Id": 559,
"category3Id": 560,
"freightId": 10,
"image": "http://www.shangcheng.com/image/1.jpg",
"images": "http://www.shangcheng.com/image/1.jpg,http://www.shangcheng.com/image/2.jpg",
"introduction": "这个是商品详情,html代码",
"paraItems": "{'出厂年份':'2023','赠品':'充电器'}",
"saleService": "七天包退,闪电退货",
"sn": "020102331",
"specItems": "{'颜色':['红','绿'],'机身内存':['128G','8G']}",
"templateId": 42
},
"skuList": [{
"sn": "10192010292",
"num": 100,
"alertNum": 20,
"price": 900000,
"spec": "{'颜色':'红','机身内存':'128G'}",
"image": "http://www.shangcheng.com/image/1.jpg",
"images": "http://www.shangcheng.com/image/1.jpg,http://www.shangcheng.com/image/2.jpg",
"status": "1",
"weight": 130
},
{
"sn": "10192010293",
"num": 100,
"alertNum": 20,
"price": 600000,
"spec": "{'颜色':'蓝','机身内存':'256G'}",
"image": "http://www.shangcheng.com/image/1.jpg",
"images": "http://www.shangcheng.com/image/1.jpg,http://www.shangcheng.com/image/2.jpg",
"status": "1",
"weight": 130
}
]
}
三、代码实现
商品数据库 ,表有 tb_album ,tb_brand, tb_category, tb_category_brand, tb_para,tb_pref,tb_sku,tb_spu,tb_spec
SPU与SKU的保存涉及到的业务表有,品牌与分类关联,规格数据,分布式ID
注:基于网关调用具体服务,应该携带令牌。添加操作首先需要进行认证登录,需要获取JWT令牌,携带令牌才能进行添加。
3.1 SPU与SKU列表的保存
代码实现:
(1)shangcheng_service_goods_api工程创建组合实体类Goods
/**
* 商品组合实体类
*/
public class Goods implements Serializable {
private Spu spu;
private List<Sku> skuList;
public Spu getSpu() {
return spu;
}
public void setSpu(Spu spu) {
this.spu = spu;
}
public List<Sku> getSkuList() {
return skuList;
}
public void setSkuList(List<Sku> skuList) {
this.skuList = skuList;
}
}
(2)shangcheng_service_goods工程SpuService新增方法add(Goods goods)
/***
* 新增
* @param goods
*/
void add(Goods goods);
(3)shangcheng_service_goods工程SpuServiceImpl实现此方法
@Autowired
private CategoryMapper categoryMapper;
@Autowired
private SkuMapper skuMapper;
@Autowired
private BrandMapper brandMapper;
@Autowired
private IdWorker idWorker;
/**
* 保存商品 SPU+SKU列表
* @param goods 商品组合实体类
*/
@Transactional
@Override
public void add(Goods goods) {
//1.添加spu
Spu spu = goods.getSpu();
//设置分布式id
long spuId = idWorker.nextId();
spu.setId(String.valueOf(spuId));
//设置删除状态.
spu.setIsDelete("0");
//上架状态
spu.setIsMarketable("0");
//审核状态
spu.setStatus("0");
spuMapper.insertSelective(spu);
//2.添加sku集合
this.saveSkuList(goods);
}
/**
* 保存sku列表
* @param goods
*/
private void saveSkuList(Goods goods){
Spu spu = goods.getSpu();
//查询分类对象
Category category = categoryMapper.selectByPrimaryKey(spu.getCategory3Id());
//查询品牌对象
Brand brand = brandMapper.selectByPrimaryKey(spu.getBrandId());
//设置品牌与分类的关联关系
//查询关联表
CategoryBrand categoryBrand = new CategoryBrand();
categoryBrand.setBrandId(spu.getBrandId());
categoryBrand.setCategoryId(spu.getCategory3Id());
int count = categoryBrandMapper.selectCount(categoryBrand);
if (count == 0){
//品牌与分类还没有关联关系
categoryBrandMapper.insert(categoryBrand);
}
//获取sku集合
List<Sku> skuList = goods.getSkuList();
if (skuList != null){
//遍历sku集合,循环填充数据并添加到数据库中
for (Sku sku : skuList) {
//设置skuId
sku.setId(String.valueOf(idWorker.nextId()));
//设置sku规格数据
if (StringUtils.isEmpty(sku.getSpec())){
sku.setSpec("{}");
}
//设置sku名称(spu名称+规格)
String name = spu.getName();
//将规格json转换为map,将map中的value进行名称的拼接
Map<String,String> specMap = JSON.parseObject(sku.getSpec(), Map.class);
if (specMap != null && specMap.size()>0){
for (String value : specMap.values()) {
name+=" "+value;
}
}
sku.setName(name);
//设置spuid
sku.setSpuId(spu.getId());
//设置创建与修改时间
sku.setCreateTime(new Date());
sku.setUpdateTime(new Date());
//商品分类id
sku.setCategoryId(category.getId());
//设置商品分类名称
sku.setCategoryName(category.getName());
//设置品牌名称
sku.setBrandName(brand.getName());
//将sku添加到数据库
skuMapper.insertSelective(sku);
}
}
}
(4)修改SpuController的add方法
/**
* 新增数据
* @param goods
* @return
*/
@PostMapping
public Result add(@RequestBody Goods goods){
spuService.add(goods);
return new Result(true,StatusCode.OK,"添加成功");
}
3.2 品牌与分类关联
实现思路:
将分类ID与SPU的品牌ID 一起插入到关系表tb_category_brand中
(1)创建实体类
@Table(name="tb_category_brand")
public class CategoryBrand implements Serializable {
@Id
private Integer categoryId;
@Id
private Integer brandId;
public Integer getCategoryId() {
return categoryId;
}
public void setCategoryId(Integer categoryId) {
this.categoryId = categoryId;
}
public Integer getBrandId() {
return brandId;
}
public void setBrandId(Integer brandId) {
this.brandId = brandId;
}
}
这个表是联合主键,所以templateId和brandId都有@Id注解
(2)新建数据访问接口
public interface CategoryBrandMapper extends Mapper<CategoryBrand> {
}
(3)SpuServiceImpl引入
@Autowired
private CategoryBrandMapper categoryBrandMapper;
(4)修改SpuServiceImpl的saveSkuList方法,添加分类与品牌之间的关联,
/**
* 保存sku列表
* @param goods
*/
private void saveSkuList(Goods goods){
Spu spu = goods.getSpu();
//查询分类对象
Category category = categoryMapper.selectByPrimaryKey(spu.getCategory3Id());
//查询品牌对象
Brand brand = brandMapper.selectByPrimaryKey(spu.getBrandId());
//设置品牌与分类的关联关系
//查询关联表
CategoryBrand categoryBrand = new CategoryBrand();
categoryBrand.setBrandId(spu.getBrandId());
categoryBrand.setCategoryId(spu.getCategory3Id());
int count = categoryBrandMapper.selectCount(categoryBrand);
if (count == 0){
//品牌与分类还没有关联关系
categoryBrandMapper.insert(categoryBrand);
}
//获取sku集合
List<Sku> skuList = goods.getSkuList();
if (skuList != null){
//遍历sku集合,循环填充数据并添加到数据库中
for (Sku sku : skuList) {
//设置skuId
sku.setId(String.valueOf(idWorker.nextId()));
//设置sku规格数据
if (StringUtils.isEmpty(sku.getSpec())){
sku.setSpec("{}");
}
//设置sku名称(spu名称+规格)
String name = spu.getName();
//将规格json转换为map,将map中的value进行名称的拼接
Map<String,String> specMap = JSON.parseObject(sku.getSpec(), Map.class);
if (specMap != null && specMap.size()>0){
for (String value : specMap.values()) {
name+=" "+value;
}
}
sku.setName(name);
//设置spuid
sku.setSpuId(spu.getId());
//设置创建与修改时间
sku.setCreateTime(new Date());
sku.setUpdateTime(new Date());
//商品分类id
sku.setCategoryId(category.getId());
//设置商品分类名称
sku.setCategoryName(category.getName());
//设置品牌名称
sku.setBrandName(brand.getName());
//将sku添加到数据库
skuMapper.insertSelective(sku);
}
}
}