商品管理
商品的管理不能像以前写的小demo一样,一个表解决所有的问题。
全品类的商品种类很多,商品的属性也不一样,存到一起商品又多又难管理。所以抽出来两个概念:spu和sku。
商品管理的难点就在于搞清楚商品spu和sku,搞清楚以后业务逻辑很容易实现。
SPU和SKU
SPU:Standard Product Unit (标准产品单位) ,一组具有共同属性的商品集
SKU:Stock Keeping Unit(库存量单位),SPU商品集因具体特性不同而细分的每个商品
下面从网上找到一个商品解释一下:
- 小米Redmi K20Pro 就是一个商品集(SPU)
- 因为颜色、内存等不同,而细分出不同的 小米Redmi K20Pro,比如冰川蓝8+128GB。(SKU)
所以spu是一个抽象的概念,我们实际买的某一个颜色某一个版本的手机是sku。
买的是sku。
数据库设计
弄清楚了SPU和SKU的概念,就该考虑一下数据库怎么设计了。
SKU的特有属性
SPU中会有一些特殊属性,用来区分不同的SKU,我们称为SKU特有属性。如小米手机的颜色、内存属性。
不同种类的商品,一个手机,一个衣服,其SKU属性不相同。
同一种类的商品,比如都是衣服,SKU属性基本是一样的,都是颜色、尺码等。
SKU的特有属性是商品规格参数的一部分:
所以可以不用对sku特有属性进行设计,因为他存在于规格参数中,是规格参数的一部分。
- 所有sku共享的规格属性(称为全局属性)
- 每个sku不同的规格属性(称为特有属性)
而搜索的时候也会用到规格参数的一部分,这些参数都可以在规格参数中找到,需要哪个字段做过滤可以给它做一个标识。
具体数据库表
我们观察页面的规格参数结构:
可以看到规格参数是分组的,每一组都有多个参数键值对。不过对于规格参数的模板而言,其值现在是不确定的,不同的商品值肯定不同,模板中只要保存组信息、组内参数信息即可。
设计两张表:
- tb_spec_group:规格参数组,与商品分类关联
- tb_spec_param:规格参数,与组关联,一对多(竖表设计,把参数名作为字段)
-
可以看到下图,group表里存的是那些组名称,它们都绑定到一个cid(分类的id),而param表里存的是每个组里面键值对的键(key),它们也都绑定到一个cid,这是为根据分类查找所有规格参数,页面展示,搜索过滤的时候会有用(而不用再通过tb_spec_group)。而键值对的值value是不确定的,它们存在spu,spu_detail,sku这些表中。
tb_spec_group:
tb_spec_param:
后面加的字段是对参数数值进行要求,比如上市年份只能输入几几年,而不能输入文字等乱七八糟的,还有是否要作为过滤条件(搜索过滤时候用)
代码实现
controller:
@RestController
@RequestMapping("spec")
public class SpecificationController {
@Autowired
private SpecificationService specificationService;
/**
* 根据分类id查询规格组
*
* @param cid
* @return
*/
@GetMapping("groups/{cid}")
public ResponseEntity<List<SpecGroup>> queryByCid(@PathVariable("cid") Long cid) {
List<SpecGroup> specGroups = specificationService.queryGroupByCid(cid);
return ResponseEntity.ok(specGroups);
}
/**
* 跟据组id查询参数
* 第二次改:查询参数集合
*
* @param gid
* @param gid 规格组ID
* @param cid 商品分类ID
* @param searching 是否是搜索字段
* @param generic 是否是通用字段
* @return
*/
@GetMapping("params")
public ResponseEntity<List<SpecParam>> queryParamByList(
@RequestParam(value = "gid", required = false) Long gid,
@RequestParam(value = "cid", required = false) Long cid,
@RequestParam(value = "searching", required = false) Boolean searching,
@RequestParam(value = "generic", required = false) Boolean generic) {
return ResponseEntity.ok(specificationService.queryParamByList(gid, cid, searching, generic));
}
/**
* 根据分类查询规格组及组内参数
*
* @param cid
* @return
*/
@GetMapping("group")
public ResponseEntity<List<SpecGroup>> queryListByCid(@RequestParam("cid") Long cid) {
return ResponseEntity.ok(specificationService.queryListByCid(cid));
}
}
service:
@Service
public class SpecificationService {
@Autowired
private SpecGroupMapper groupMapper;
@Autowired
private SpecParamMapper paramMapper;
/**
* 根据分类id查询规格组
*
* @param cid
* @return
*/
public List<SpecGroup> queryGroupByCid(Long cid) {
SpecGroup g = new SpecGroup();
g.setCid(cid);
List<SpecGroup> select = groupMapper.select(g);
if (CollectionUtils.isEmpty(select)) {
throw new LyException(ExceptionEnum.SPEC_GROUP_NOT_FOUND);
}
return select;
}
/**
* 跟据组id查询参数
*
* @param gid
* @return
*/
public List<SpecParam> queryParamByList(Long gid, Long cid, Boolean searching, Boolean generic) {
SpecParam p = new SpecParam();
p.setGroupId(gid);
p.setCid(cid);
p.setSearching(searching);
p.setGeneric(generic);
List<SpecParam> select1 = paramMapper.select(p);
if (CollectionUtils.isEmpty(select1)) {
throw new LyException(ExceptionEnum.SPEC_PARAM_NOT_FOUND);
}
return select1;
}
/**
* 根据分类查询规格组及组内参数
*
* @param cid
* @return
*/
public List<SpecGroup> queryListByCid(Long cid) {
// 查询规格组
List<SpecGroup> specGroups = queryGroupByCid(cid);
// 查询当前分类下的参数
List<SpecParam> specParams = queryParamByList(null, cid, null, null);
// 把规格参数变成map,key是规格组id,value是组内的所有参数
Map<Long, List<SpecParam>> map = new HashMap<>();
for (SpecParam param : specParams) {
if (!map.containsKey(param.getGroupId())) {
// 这个组id在map中不存在,新增一个list
map.put(param.getGroupId(), new ArrayList<>());
}
map.get(param.getGroupId()).add(param);
}
// 填充param到group
for (SpecGroup specGroup : specGroups) {
specGroup.setParams(map.get(specGroup.getId()));
}
return specGroups;
}
}
总结:
了解spu和sku,商品分类和规格组表,规格参数表的关系后,业务逻辑实现就会简单,因为要从哪张表查数据,需要查的spu还是sku就知道了,写代码的整套流程就会清晰。