商品SKU与规格维护架构文档
概述
- 商品SKU需要在商品列表中的SKU管理中单独维护
- 新添加的商品都会创建一条默认的SKU数据
- SKU和规格是绑定的,关系为一对一
- 在SKU管理中可以根据不同规格的笛卡尔积算法生成SKU列表
SKU和规格关系
SKU和规格是绑定的,关系为一对一,举例如下:
规格有两种:
颜色:黑色、白色
尺码:M、L、XL
可以组合出6种SKU
SKU | 规格 |
---|---|
sku1 | 黑色、M |
sku2 | 黑色、L |
sku3 | 黑色、XL |
sku4 | 白色、M |
sku5 | 白色、L |
sku6 | 白色、XL |
相关API
核心API
实体类参数可参考下方的类图展示
请求方式 | API地址 | 参数说明 | 作用 |
---|---|---|---|
GET | https://{seller-api-domain}/seller/goods/{id} | Long id:商品主键ID | 根据商品ID获取商品信息 |
GET | https://{seller-api-domain}/seller/goods/spec/all | SpecGroupParam param:查询参数 | 查询商家所有商品规格信息 |
GET | https://{seller-api-domain}/seller/goods/{goods_id}/skus | Long goodsId:商品主键ID | 根据商品ID获取商品sku信息 |
POST | https://{seller-api-domain}/seller/goods/spec | SpecGroupDO specGroup:规格信息 | 保存常用规格信息 |
PUT | https://{seller-api-domain}/seller/goods/{goods_id}/skus | Long goodsId:商品主键ID,List<GoodsSkuVO> skuList :商品sku信息 | 更新商品的sku信息 |
核心数据
API查询的SKU数据格式如下:
[
{
"goods_name":"toys小店的连衣裙",
"seller_id":"10",
"seller_name":"母婴和玩具",
"shop_cat_id":"0",
"brand_id":null,
"category_id":"97",
"goods_type":"NORMAL",
"market_enable":1,
"point_disable":0,
"exchange_point":0,
"disabled":1,
"is_auth":1,
"sku_id":"1689832910424363009",
"goods_id":"1689455289266049025",
"sn":"TOY-0002",
"weight":0.1,
"price":80,
"cost":50,
"quantity":100,
"enable_quantity":100,
"thumbnail":"https://javashop-statics.oss-cn-beijing.aliyuncs.com/test/normal/1CC0AF18BBDB4FA28BA5E46684615BC6.jpg_300x300",
"sku_image":"https://javashop-statics.oss-cn-beijing.aliyuncs.com/test/normal/1CC0AF18BBDB4FA28BA5E46684615BC6.jpg",
"create_time":"1691722536",
"template_id":null,
"properties":"10040002,10050001",
"properties_name":"颜色:白色;尺码:XL",
"sku_sort":1,
"lock_version":0,
"spec_list":[
{
"id":"1689832910982205441",
"goods_id":"1689455289266049025",
"sku_id":"1689832910424363009",
"spec_group_sn":"10040000",
"spec_group_name":"颜色",
"spec_value_sn":"10040002",
"spec_value_name":"白色",
"spec_value_image":null,
"seller_id":"10",
"seller_name":"母婴和玩具"
},
{
"id":"1689832910990594050",
"goods_id":"1689455289266049025",
"sku_id":"1689832910424363009",
"spec_group_sn":"10050000",
"spec_group_name":"尺码",
"spec_value_sn":"10050001",
"spec_value_name":"XL",
"spec_value_image":null,
"seller_id":"10",
"seller_name":"母婴和玩具"
}
],
"free_freight":null,
"last_modify":null,
"expired_date":null
}
]
key | 说明 |
---|---|
spec_list | sku本身的规格数据,规格分组为维度,由sku的规格数据逆向生成的 |
spec_list
是sku的规格组合,前端需要通过这些信息形成sku编辑器
SKU数据保存
- 将sku数组传递给后端
- 其中排序值需要根据当前顺序形成,以便回显时保持一致
- 库存数据后端需要逻辑运算
SKU图片处理
1、图片的规格分类
处的批量处理sku编辑器
处的单个处理,都是前端的操作逻辑,只有点击保存按钮时,才会更新数据库。
2、商品相册中排序第一个为商品默认图片,使用缩略图(thumbnail
)路径
3、当在选取的规格值中上传图片,sku列表中含有当前规格值的sku上显示该图片
4、在会员端商品详情选中该规格时显示规格设置的图片
5、当在某个sku上传图片时,清空sku对应规格的图片
6、在会员端商品详情中选中该sku时显示sku上传的图片
数据库设计
结构图
商品sku表
参考文档商品核心数据说明
sku表中有个规格的字段如下:
字段名 | 类型与长度 | 备注 |
---|---|---|
properties | varchar(255) | 规格值编号组合字符串,以“,”分隔,如:10040002,10050002 |
properties_name | varchar(255) | 规格信息,如:颜色:白色;尺码:L |
商品sku规格表
表名:es_sku_spec
字段名 | 类型与长度 | 备注 |
---|---|---|
id | bigint(20) | 主键ID |
goods_id | bigint(20) | 商品ID |
sku_id | bigint(20) | 商品skuID |
spec_group_sn | varchar(20) | 规格组编号 |
spec_group_name | varchar(90) | 规格组名称 |
spec_value_sn | varchar(20) | 规格值编号 |
spec_value_name | varchar(90) | 规格值名称 |
spec_value_image | varchar(255) | 规格值图片 |
seller_id | bigint(20) | 商家ID |
seller_name | varchar(50) | 商家名称 |
规格组表
表名:es_spec_group
字段名 | 类型与长度 | 备注 |
---|---|---|
id | bigint(20) | 主键ID |
spec_group_name | varchar(90) | 规格组名称 |
spec_group_memo | varchar(255) | 规格组描述 |
seller_id | bigint(20) | 商家ID;值为0代表系统内置规格组,不为0代表商家自建 |
create_time | bigint(20) | 创建时间 |
spec_group_sn | varchar(20) | 规格组编号 |
规格值表
表名:es_spec_values
字段名 | 类型与长度 | 备注 |
---|---|---|
id | bigint(20) | 主键ID |
spec_group_id | bigint(20) | 规格组ID |
spec_group_name | varchar(90) | 规格组名称 |
spec_value_name | varchar(90) | 规格值名称 |
seller_id | bigint(20) | 商家ID;值为0代表系统内置规格组,不为0代表商家自建 |
spec_value_sn | varchar(20) | 规格值编号 |
spec_group_sn | varchar(20) | 规格组编号 |
类图展示
SKU相关
规格相关
实体类
DO类
DTO类
VO类
代码展示
更新sku数据
com.enation.app.javashop.service.goods.impl.GoodsSkuManagerImpl#editSkus(java.util.List<com.enation.app.javashop.model.goods.vo.GoodsSkuVO>
, java.lang.Long
)
/**
* 更新商品的sku,因为可能是添加,可能是修改 ,可能是删除
* 涉及到的场景比较复杂 所以这里采用先删除后添加的方式来更新sku
*
* @param skuList sku集合
* @param goodsId 商品id
*/
@Override
@Transactional(value = "goodsTransactionManager", propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void editSkus(List<GoodsSkuVO> skuList, Long goodsId) {
GoodsDO goods = goodsManager.getById(goodsId);
if (goods == null) {
throw new ServiceException(GoodsErrorCode.E307.code(), "商品不存在");
}
//1.获取原有的sku集合
//*****************************************************
List<Long> skuIdList = this.lambdaQuery()
.eq(GoodsSkuDO::getGoodsId, goodsId)
.list().stream().map(GoodsSkuDO::getSkuId).collect(Collectors.toList());
//如果调用者未定义排序,给默认排序
int sort = 0;
for (GoodsSkuVO goodsSku : skuList) {
sort++;
if (goodsSku.getSkuSort() == null) {
goodsSku.setSkuSort(sort);
}
}
//2.当无规格时,skuList为空 添加无规格的sku
//*****************************************************
if (CollUtil.isEmpty(skuList)) {
this.addNoSpecSku(goods);
}
//3.当有规格时,集合中有sku信息 更新sku信息
//*****************************************************
this.updateSkus(goods, skuList);
//4清除sku缓存存信息
//*****************************************************
this.cleanCache(goodsId, skuIdList);
//5.更新商品库存
//*****************************************************
this.reCountGoodsStock(goodsId);
//6.清除缓存中库存
//*****************************************************
this.goodsStockManager.deleteSkuQuantity(skuIdList);
//7.发送商品变化消息
//*****************************************************
SkuChangeMessage skuChangeMessage = new SkuChangeMessage(skuIdList, SkuChangeMessage.DEL_OPERATION);
// 发送商品sku变化消息
this.messageSender.send(skuChangeMessage);
}
规格维护
查询:com.enation.app.javashop.service.goods.impl.SpecGroupManagerImpl#specList
新增:com.enation.app.javashop.service.goods.impl.SpecGroupManagerImpl#add
/**
* 添加规格
*
* @param specGroup 规格组
* @return
*/
@Override
@Transactional(value = "goodsTransactionManager", rollbackFor = Exception.class)
public SpecGroupDO add(SpecGroupDO specGroup) {
String groupName = specGroup.getSpecGroupName();
//验证分组名称是否重复
this.checkGroupName(groupName, null, specGroup.getSellerId());
//获取规格组编码
Long groupSn = specGroupPrimaryKeyGenerator.generateTop(specGroup.getSellerId());
specGroup.setSpecGroupSn(groupSn);
specGroup.setId(null);
//添加规格组
baseMapper.insert(specGroup);
//添加规格值
specValuesManager.saveBatch(specGroup);
return specGroup;
}
/**
* 查询规格列表
*
* @param param 查询参数
* @return 规格组列表
*/
@Override
public List<SpecGroupDO> specList(SpecGroupParam param) {
List<SpecGroupDO> list = new LambdaQueryChainWrapper<>(baseMapper)
.eq(StringUtil.notEmpty(param.getSpecGroupName()), SpecGroupDO::getSpecGroupName, param.getSpecGroupName())
.eq(SpecGroupDO::getSellerId, param.getSellerId())
.orderByDesc(SpecGroupDO::getCreateTime)
.list();
//设置规格值信息
List<SpecValuesDO> specValueList = specValuesManager.lambdaQuery()
.eq(SpecValuesDO::getSellerId, param.getSellerId())
.eq(StringUtil.notEmpty(param.getSpecGroupName()), SpecValuesDO::getSpecGroupName, param.getSpecGroupName())
.list();
this.setSpecValue(list, specValueList);
return list;
}