商品系统接口
商城的核心自然是商品,而商品多了以后,肯定要进行分类,并且不同的商品会有不同的品牌信息,我们需要依次去完成:品牌、商品分类、商品的开发。
品牌在系统搭建后,增删改查甚至文件上传都可以正常使用。
接下来完善商品分类及商品功能的增删改查功能。
参考课前资料中的《后台管理系统接口文档.md》
程序源码论坛-1024,网址 www.cx1314.cn 仅分享最流行最优质的IT资源!
不同于其他论坛平台,这里只有精品、稀有资源,已泛滥、已过时、垃圾资源不录入!
Java,前端,python,人工智能,大数据,云计算...持续更新资源-最新完整且均不加密、、、
活动线报,宅男福利,最新大片…
程序员的新大陆-更新最快的IT资源社区!开发者必备平台!
欢迎访问:www.cx1314.cn 百度搜索-> 程序源码论坛
1. 商品分类
数据结构:
1 2 3 4 5 6 7 8 9 10
| CREATE TABLE `pms_category` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '分类id', `name` char(50) DEFAULT NULL COMMENT '分类名称', `parent_id` bigint(20) DEFAULT NULL COMMENT '父分类id', `status` tinyint(4) DEFAULT NULL COMMENT '是否显示[0-不显示,1显示]', `sort` int(11) DEFAULT NULL COMMENT '排序', `icon` char(255) DEFAULT NULL COMMENT '图标地址', `unit` char(50) DEFAULT NULL COMMENT '计量单位', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1433 DEFAULT CHARSET=utf8mb4 COMMENT='商品三级分类';
|
因为商品分类会有层级关系,因此这里我们加入了parent_id
字段,对本表中的其它分类进行自关联。
![1584591973794](/assets/1584591973794.png)
查看控制台,浏览器会发出如下请求
![1584592964081](/assets/1584592964081.png)
参照接口文档,开发接口
1.1. CategoryController
请求地址:parent/{parentId},因为/pms/category已经配置在CategoryController上
请求方式:GET
请求参数:parentId
响应:ResponseVO<List<CategoryEntity>>
给CategoryController添加一个查询方法:
1 2 3 4 5 6
| @ApiOperation("根据父id查询分类") @GetMapping("parent/{parentId}") public ResponseVo<List<CategoryEntity>> queryCategory(@PathVariable("parentId")Long parentId){ List<CategoryEntity> categoryEntityList = this.categoryService.queryCategory(parentId); return ResponseVo.ok(categoryEntityList); }
|
1.2. CategoryService
在CategoryService接口中添加接口方法
1
| List<CategoryEntity> queryCategory(Long parentId);
|
在CategoryServiceImpl实现类中实现该方法:
1 2 3 4 5 6 7 8 9 10 11 12 13
| private CategoryMapper categoryMapper;
@Override public List<CategoryEntity> queryCategory(Long parentId) { QueryWrapper<CategoryEntity> wrapper = new QueryWrapper<>(); if (parentId != -1){ wrapper.eq("parent_id", parentId); }
return this.categoryMapper.selectList(wrapper); }
|
1.3. 重启测试
![1567475835910](/assets/1567475835910.png)
1.4. 根据父节点查询
新增商品时,先根据parentId=0查询一级节点,选择一级分类后,需要根据父节点查询子节点。
![1567476312461](/assets/1567476312461.png)
2. 商品信息
2.1. SPU和SKU设计
SPU:Standard Product Unit (标准产品单位) ,一组具有共同属性的商品集
SKU:Stock Keeping Unit(库存量单位),SPU商品集因具体特性不同而细分的每个商品
以图为例来看:
![1526085541996](/assets/1526085541996.png)
- 本页的 华为Mate10 就是一个商品集(SPU)
- 因为颜色、内存等不同,而细分出不同的Mate10,如亮黑色128G版。(SKU)
可以看出:
- SPU是一个抽象的商品集概念,为了方便后台的管理。
- SKU才是具体要销售的商品,每一个SKU的价格、库存可能会不一样,用户购买的是SKU而不是SPU
表结构设计:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| CREATE TABLE `pms_spu` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '商品id', `name` varchar(200) DEFAULT NULL COMMENT '商品名称', `category_id` bigint(20) DEFAULT NULL COMMENT '所属分类id', `brand_id` bigint(20) DEFAULT NULL COMMENT '品牌id', `publish_status` tinyint(4) DEFAULT NULL COMMENT '上架状态[0 - 下架,1 - 上架]', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='spu信息';
CREATE TABLE `pms_spu_desc` ( `spu_id` bigint(20) NOT NULL COMMENT '商品id', `decript` longtext COMMENT '商品介绍', PRIMARY KEY (`spu_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='spu信息介绍';
CREATE TABLE `pms_sku` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'skuId', `spu_id` bigint(20) DEFAULT NULL COMMENT 'spuId', `name` varchar(255) DEFAULT NULL COMMENT 'sku名称', `category_id` bigint(20) DEFAULT NULL COMMENT '所属分类id', `brand_id` bigint(20) DEFAULT NULL COMMENT '品牌id', `default_image` varchar(255) DEFAULT NULL COMMENT '默认图片', `title` varchar(255) DEFAULT NULL COMMENT '标题', `subtitle` varchar(2000) DEFAULT NULL COMMENT '副标题', `price` decimal(18,4) DEFAULT NULL COMMENT '价格', `weight` int(20) DEFAULT NULL COMMENT '重量(克)', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='sku信息';
CREATE TABLE `pms_sku_images` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', `sku_id` bigint(20) DEFAULT NULL COMMENT 'sku_id', `url` varchar(255) DEFAULT NULL COMMENT '图片地址', `sort` int(11) DEFAULT NULL COMMENT '排序', `default_status` int(11) DEFAULT NULL COMMENT '默认图[0 - 不是默认图,1 - 是默认图]', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='sku图片';
|
2.2. 规格参数设计
![1567485141477](/assets/1567485141477.png)
商品规格参数有以下特点:
- 规格参数都有分组
- 同一类SPU(三级分类,如手机)的规格参数名是统一的。例如:iphone X,galaxy S10等。
- 同一spu,有些规格参数值是不变的,如:摄像头像素、摄像头数量等;而有些规格参数的值,会随着你选择的sku发生变化,如:内存、机身颜色、存储等。
- 有些规格参数需要作为sku选项(销售属性),例如:机身颜色、存储、版本等
- 有些规格参数需要参与检索(检索属性),例如:屏幕尺寸、分辨率、操作系统等
- 有些规格参数需要快速展示在商品介绍的上方
综上,表设计如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| CREATE TABLE `pms_attr_group` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '分组id', `name` char(20) DEFAULT NULL COMMENT '组名', `sort` int(11) DEFAULT NULL COMMENT '排序', `icon` varchar(255) DEFAULT NULL COMMENT '组图标', `category_id` bigint(20) DEFAULT NULL COMMENT '所属分类id', `remark` varchar(255) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='属性分组';
CREATE TABLE `pms_attr` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '属性id', `name` char(30) DEFAULT NULL COMMENT '属性名', `search_type` tinyint(4) DEFAULT NULL COMMENT '是否需要检索[0-不需要,1-需要]', `icon` varchar(255) DEFAULT NULL COMMENT '属性图标', `value_select` char(255) DEFAULT NULL COMMENT '可选值列表[用逗号分隔]', `type` tinyint(4) DEFAULT NULL COMMENT '属性类型[0-销售属性,1-基本属性,2-既是销售属性又是基本属性]', `enable` bigint(20) DEFAULT NULL COMMENT '启用状态[0 - 禁用,1 - 启用]', `show_desc` tinyint(4) DEFAULT NULL COMMENT '快速展示【是否展示在介绍上;0-否 1-是】,在sku中仍然可以调整', `category_id` bigint(20) DEFAULT NULL COMMENT '所属分类', `group_id` bigint(20) DEFAULT NULL COMMENT '规格分组id', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品属性';
CREATE TABLE `pms_spu_attr_value` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', `spu_id` bigint(20) DEFAULT NULL COMMENT '商品id', `attr_id` bigint(20) DEFAULT NULL COMMENT '属性id', `attr_name` varchar(200) DEFAULT NULL COMMENT '属性名', `attr_value` varchar(200) DEFAULT NULL COMMENT '属性值', `sort` int(11) DEFAULT NULL COMMENT '顺序', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='spu属性值';
CREATE TABLE `pms_sku_attr_value` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', `sku_id` bigint(20) DEFAULT NULL COMMENT 'sku_id', `attr_id` bigint(20) DEFAULT NULL COMMENT 'attr_id', `attr_name` varchar(200) DEFAULT NULL COMMENT '销售属性名', `attr_value` varchar(200) DEFAULT NULL COMMENT '销售属性值', `sort` int(11) DEFAULT NULL COMMENT '顺序', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='sku销售属性&值';
|
2.3. 规格属性查询
![1584621122426](/assets/1584621122426.png)
2.3.1. 查询规格分组
查看控制台,对应的发送了3个请求:
![1584621550824](/assets/1584621550824.png)
其中有一个是查询分组。结合接口文档:
请求地址:category/{cid},Controller上已经标记了/pms/attrgroup
请求方式:GET
请求参数:一些基本分页条件已经封装到PageParamVo,然后有一个占位符参数(商品分类:225)
正确响应:ResponseVo<PageResultVO>
AttrGroupController中添加Controller方法:
1 2 3 4 5 6 7
| @ApiOperation("根据三级分类id查询") @GetMapping("category/{cid}") public ResponseVo<List<AttrGroupEntity>> queryByCidPage(@PathVariable("cid")Long cid){
List<AttrGroupEntity> groupEntities = this.attrGroupService.list(new QueryWrapper<AttrGroupEntity>().eq("category_id", cid)); return ResponseVo.ok(groupEntities); }
|
测试效果:
![1584621584538](/assets/1584621584538.png)
2.3.2. 查询分组下的规格参数
点击属性分组下的维护关联关系,可以查看该分组下的所有属性,并可以添加、删除关联关系。
![1584669844142](/assets/1584669844142.png)
参考接口文档:
请求地址:group/{gid},已经在AttrController上配置/pms/attr
请求方式:GET
请求参数:gid
正确响应:Response<List>
AttrController添加方法:
1 2 3 4 5 6
| @GetMapping("group/{gid}") @ApiOperation("分页查询") public ResponseVo<List<AttrEntity>> queryAttrsByGid(@PathVariable("gid")Long gid){ List<AttrEntity> attrEntities = this.attrService.list(new QueryWrapper<AttrEntity>().eq("group_id", gid)); return ResponseVo.ok(attrEntities); }
|
测试:
![1584670473377](/assets/1584670473377.png)
2.4. 商品查询
2.4.1. 查询商品列表
![1567513319657](/assets/1567513319657.png)
查看控制台:
![1584674738425](/assets/1584674738425.png)
结合接口文档
请求地址:category/{categoryId},SpuInfoController类上的路径是/pms/spuinfo
请求方式:GET
请求参数:PageParamVo+ categoryId
正确响应:ResponseVo
SpuInfoController添加方法:
1 2 3 4 5 6 7
| @ApiOperation("spu商品信息查询") @GetMapping("category/{categoryId}") public ResponseVo<PageResultVo> querySpuInfo(PageParamVo pageParamVo, @PathVariable("categoryId")Long categoryId){
PageResultVo pageResultVo = this.spuService.querySpuInfo(pageParamVo, categoryId); return ResponseVo.ok(pageResultVo); }
|
SpuInfoService接口方法:
1
| PageResultVo querySpuInfo(PageParamVo pageParamVo, Long categoryId);
|
SpuInfoServiceImpl实现类实现该方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Override public PageResultVo querySpuInfo(PageParamVo pageParamVo, Long categoryId) {
QueryWrapper<SpuEntity> wrapper = new QueryWrapper<>(); if (categoryId != 0){ wrapper.eq("category_id", categoryId); } String key = pageParamVo.getKey(); if (StringUtils.isNotBlank(key)){ wrapper.and(t -> t.like("name", key).or().like("id", key)); }
return new PageResultVo(this.page(pageParamVo.getPage(), wrapper)); }
|
测试:
![1567520074569](/assets/1567520074569.png)
2.4.2. 查询SPU下的SKU
找到库存管理
下的商品库存
![1567521129311](/assets/1567521129311.png)
点击一个商品的库存维护
:
![1584771702197](/assets/1584771702197.png)
结合接口文档:
请求地址:spu/{spuId}
请求方式:GET
请求参数:spuId
正确响应:Resp<List<SkuEntity>>
给SkuInfoController添加方法:
1 2 3 4 5 6 7 8 9 10
|
@GetMapping("spu/{spuId}") @ApiOperation("根据spuId查询sku列表") public ResponseVo<List<SkuEntity>> list(@PathVariable("spuId")Long spuId){ List<SkuEntity> skuEntities = skuService.list(new QueryWrapper<SkuEntity>().eq("spu_id", spuId));
return ResponseVo.ok(skuEntities); }
|
测试:
![1567521804729](/assets/1567521804729.png)
2.4.3. 查询SKU的库存信息
sku列表中的库存维护
![1567523065617](/assets/1567523065617.png)
![1584775206306](/assets/1584775206306.png)
参照接口文档:
请求地址:/sku/{skuId} 在gmall-wms系统的WareSkuController中已经配置了/wms/waresku/
请求方式:GET
请求参数:skuId
正确响应:ResponseVo<List<WareSkuEntity>>
WareSkuController添加方法:
1 2 3 4 5 6 7
| @ApiOperation("根据skuId查询库存信息") @GetMapping("sku/{skuId}") public ResponseVo<List<WareSkuEntity>> queryWareSkuBySkuId(@PathVariable("skuId")Long skuId){
List<WareSkuEntity> wareSkuEntities = this.wareSkuService.list(new QueryWrapper<WareSkuEntity>().eq("sku_id", skuId)); return ResponseVo.ok(wareSkuEntities); }
|
测试:
![1584779661541](/assets/1584779661541.png)
2.5. 商品新增
![1567560984539](/assets/1567560984539.png)
点击添加spu
按钮,跳转到新增spu页面
![1567561565352](/assets/1567561565352.png)
填写完表单,点击下一步
![1584697495113](/assets/1584697495113.png)
进入录入spu属性信息
,但是没有显示spu属性(即规格参数)。
2.5.1. 查询分类下的分组及其规格参数
当添加商品时,应该根据用户选择的商品分类,显示不同规格参数名,让用户填写对应的参数值。上图控制台发送的这个请求,就是根据分类id查询分组及组下的属性,并动态生成一个表单,让用户填写参数值
参照接口文档:
请求地址:/withattrs/{catId}
请求方式:GET
请求参数:catId
正确响应:ResponseVo<List<?>>
这里的响应数据比AttrGroupEntity多出来一个字段,就是组下的属性列表attrEntities。所以要给AttrGroupEntity扩展一个字段,有两种扩展方式:
- 直接在AttrGroupEntity中添加一个字段attrEntities
- 编写一个扩展类GroupVo继承AttrGroupEntity,然后扩展一个attrEntities
推荐使用第二种方式,因为AttrGroupEntity作为一个基础实体类和数据库操作密切相关,很多人都会用到。
GroupVo实体类:
![1584698223491](/assets/1584698223491.png)
AttrGroupController添加方法:
1 2 3 4 5 6 7
| @ApiOperation("根据三级分类id查询分组及组下的规格参数") @GetMapping("/withattrs/{catId}") public ResponseVo<List<GroupVo>> queryByCid(@PathVariable("catId")Long cid){
List<GroupVo> groupVos = this.attrGroupService.queryByCid(cid); return ResponseVo.ok(groupVos); }
|
AttrGroupService的接口方法:
1
| List<GroupVo> queryByCid(Long cid);
|
AttrGroupServiceImpl实现类实现接口方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Override public List<GroupVo> queryByCid(Long cid) { List<AttrGroupEntity> attrGroupEntities = this.list(new QueryWrapper<AttrGroupEntity>().eq("category_id", cid));
return attrGroupEntities.stream().map(attrGroupEntity -> { GroupVo groupVo = new GroupVo(); BeanUtils.copyProperties(attrGroupEntity, groupVo); List<AttrEntity> attrEntities = this.attrMapper.selectList(new QueryWrapper<AttrEntity>().eq("group_id", attrGroupEntity.getId()).eq("type", 1)); groupVo.setAttrEntities(attrEntities); return groupVo; }).collect(Collectors.toList()); }
|
测试:
![1584714398957](/assets/1584714398957.png)
2.5.2. 查询分类下的销售属性
点击“下一步”进入第三步,页面没有数据。查看控制台发送了查询请求:
![1584714509858](/assets/1584714509858.png)
参照接口文档:
请求地址:category/{cid}?type=0&searchType=1
请求方式:GET
请求参数:cid, type, searchType
正确响应:ResponseVo<List<AttrEntity>>
AttrController:
1 2 3 4 5 6 7 8
| @GetMapping("category/{cid}") @ApiOperation("分类查询") public ResponseVo<List<AttrEntity>> queryAttrsByCid(@PathVariable("cid")Long cid, @RequestParam(value = "type", required = false)Integer type, @RequestParam(value = "searchType", required = false) Integer searchType){ List<AttrEntity> attrEntities = this.attrService.queryAttrsByCid(cid, type, searchType); return ResponseVo.ok(attrEntities); }
|
AttrService:
1
| List<AttrEntity> queryAttrsByCid(Long cid, Integer type, Integer searchType);
|
AttrServiceImpl:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Override public List<AttrEntity> queryAttrsByCid(Long cid, Integer type, Integer searchType) { QueryWrapper<AttrEntity> queryWrapper = new QueryWrapper<>(); if (cid != 0) { queryWrapper.eq("category_id", cid); } if (type != null) { queryWrapper.eq("type", type); } if (searchType != null) { queryWrapper.eq("search_type", searchType); } return this.list(queryWrapper); }
|
2.5.3. 为页面提交的数据构建数据模型
![1584718209086](/assets/1584718209086.png)
每个sku都可以再次展开,sku基本信息,优惠信息,以及图片上传
![1567576282861](/assets/1567576282861.png)
最后点击确定,显示保存成功!?查看数据库你会发现它只保存了spu的基本信息。
查看控制台,可以看到:
![1584718286014](/assets/1584718286014.png)
提交的数据非常复杂,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| { "name": "华为mate30pro", "brandId": 2, "categoryId": 225, "publishStatus": 1, "spuImages": ["https://ggmall.oss-cn-shanghai.aliyuncs.com/2020-03-20/2e3c0156-a8a5-4ae2-8e5d-e3061069fa95_eda8c4c43653595f (1).jpg", "https://ggmall.oss-cn-shanghai.aliyuncs.com/2020-03-20/41af1ab5-6625-4ef3-8991-ddece4933fe8_eda8c4c43653595f (1).jpg"], "baseAttrs": [{ "id": 1, "name": "上市年份", "valueSelected": ["2019"] }, { "id": 2, "name": "产品名称", "valueSelected": ["HUAWEI Mate 30"] }, { "id": 6, "name": "CPU品牌", "valueSelected": ["麒麟"] }, { "id": 7, "name": "CPU型号", "valueSelected": ["麒麟990 5G"] }, { "id": 8, "name": "分辨率", "valueSelected": ["FHD+ 2340×1080 像素"] }, { "id": 9, "name": "屏幕尺寸", "valueSelected": ["7"] }], "skus": [{ "attr_3": "黑色", "price": "5000", "stock": 0, "growBounds": "2000", "buyBounds": "1000", "work": [1, 0, 1, 0], "fullCount": "2", "discount": "90", "fullPrice": "5000", "reducePrice": "500", "fullAddOther": 1, "images": ["https://ggmall.oss-cn-shanghai.aliyuncs.com/2020-03-20/d877660d-93a3-4d9a-8c8d-ce5f5e5e1904_eda8c4c43653595f (1).jpg", "https://ggmall.oss-cn-shanghai.aliyuncs.com/2020-03-20/01f785b4-b984-4246-8724-5a1d2c423fd0_e9ad9735fc3f0686.jpg"], "name": "华为mate30pro 黑色,8G,256G", "title": "华为 HUAWEI Mate 30 5G 麒麟990 4000万超感光徕卡影像双超级快充8GB+256GB亮黑色5G全网通游戏手机", "subTitle": "华为mate30pro 黑色,8G,256G", "weight": "500", "attr_4": "8G", "attr_5": "256G", "ladderAddOther": 1, "subtitle": "【直降500元!到手价4999元!享12期免息!低至13.7元/天】双4000万徕卡电影四摄;Mate30直降500元享12期》", "saleAttrs": [{ "attrId": "3", "attrValue": "黑色" }, { "attrId": "4", "attrValue": "8G" }, { "attrId": "5", "attrValue": "256G" }] }, { "attr_3": "黑色", "price": "6000", "stock": 0, "growBounds": "1000", "buyBounds": "1000", "work": [1, 0, 1, 0], "fullCount": "3", "discount": "80", "fullPrice": "6000", "reducePrice": "2000", "fullAddOther": 1, "images": ["https://ggmall.oss-cn-shanghai.aliyuncs.com/2020-03-20/7cf5c7d2-f9a2-4767-8e90-d41b67a5ffd2_e9ad9735fc3f0686.jpg", "https://ggmall.oss-cn-shanghai.aliyuncs.com/2020-03-20/8e7189ce-5043-4d4c-8a8b-e3728360e451_802254cca298ae79 (1).jpg"], "name": "华为mate30pro 黑色,8G,512G", "title": "华为 HUAWEI Mate 30 5G 麒麟990 4000万超感光徕卡影像双超级快充8GB+128GB亮黑色5G全网通游戏手机", "subTitle": "华为mate30pro 黑色,8G,512G", "weight": "510", "attr_4": "8G", "attr_5": "512G", "ladderAddOther": 1, "subtitle": "【直降500元!到手价4499元!享12期免息!低至12.3元/天】双4000万徕卡电影四摄;Mate30直降500元享12期》", "saleAttrs": [{ "attrId": "3", "attrValue": "黑色" }, { "attrId": "4", "attrValue": "8G" }, { "attrId": "5", "attrValue": "512G" }] }] }
|
由于提交的是json数据,所以只能通过一个对象来进行接收,这就需要咱们来构建一个VO对象。涉及的表肯定也不只spu一张表。
这么复杂的数据结构,我们一层层的剥茧抽丝式的查看:
2.5.2.1. 先看json数据的最外层
对应pms_spu表的信息,还多了三个字段:spuImages、baseAttrs、skus。也就是说需要对SpuInfoEntity进行扩展。首选可以确定这三个字段是集合,因为值都有方括号。
![1584718471261](/assets/1584718471261.png)
创建SpuVO对象:
![1584718569421](/assets/1584718569421.png)
内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
@Data public class SpuVO extends SpuInfoEntity {
private List<?> spuImages;
private List<?> baseAttrs;
private List<?> skus; }
|
接下来就分析这三个集合中,都是什么数据类型的数据。
2.5.2.2. spuImages
首先spuImages很简单,就是字符串数组:
![1567582227035](/assets/1567582227035.png)
也就是说,SpuInfoVO对象是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
@Data public class SpuVO extends SpuInfoEntity {
private List<String> spuImages;
private List<?> baseAttrs;
private List<?> skus; }
|
2.5.2.3. baseAttrs
对应的表是pms_spu_attr_value,对应的实体类是SpuAttrValueEntity
![1567582744402](/assets/1567582744402.png)
注意:attrValue的值在实体类中是String,而传过来时参数名是valueSelected,值为集合类型。
所以,必须扩展SpuAttrValueEntity类:
![1567584097320](/assets/1567584097320.png)
重写setter方法,因为参数接收时的本质是调用setter方法接收的:
1 2 3 4 5 6 7 8 9 10
| public class SpuAttrValueVo extends SpuAttrValueEntity {
public void setValueSelected(List<Object> valueSelected){ if (CollectionUtils.isEmpty(valueSelected)){ return; } this.setAttrValue(StringUtils.join(valueSelected, ",")); } }
|
现在SpuInfoVO中的第二参数的集合泛型也搞定了:
1 2 3 4 5 6 7 8 9 10 11 12
| @Data public class SpuVo extends SpuEntity {
private List<String> spuImages;
private List<SpuAttrValueVo> baseAttrs;
private List<?> skus; }
|
2.5.2.4. skus
一部分字段对应的是pms_sku这张表,对应的实体类是SkuEntity。
![1584719056119](/assets/1584719056119.png)
images这个字段保存到pms_sku_images这张表。
一部分字段对应的是sms_sku_bounds表,对应的实体类是gmall-sms服务中的SkuBoundsEntity
![1567585469132](/assets/1567585469132.png)
一部分字段对应的表是sms_sku_full_reduction,对应的实体类是gmall-sms微服务中的SkuFullReductionEntity
![1567585679204](/assets/1567585679204.png)
一部分字段对应的表是sms_sku_ladder,对应的实体类是gmall-sms微服务的SkuLadderEntity
![1567586094915](/assets/1567586094915.png)
最后一个字段就是saleAttrs,对应的是pms_sku_attr_value,实体类是List<SkuAttrValueEntity>
![1567586459917](/assets/1567586459917.png)
由于java是单继承,所以这里只能选择一个继承,其他的字段在从对应的实体类中copy过来。这里选择扩展SkuEntity这个实体类:(注意:带下划线的字段为垃圾字段,不用接收)
![1584719555226](/assets/1584719555226.png)
内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| @Data public class SkuVo extends SkuEntity { private List<String> images;
private BigDecimal growBounds; private BigDecimal buyBounds;
private List<Integer> work;
private BigDecimal fullPrice; private BigDecimal reducePrice; private Integer fullAddOther;
private Integer fullCount; private BigDecimal discount;
private Integer addOther;
private List<SkuAttrValueEntity> saleAttrs; }
|
最终的SpuVO:
1 2 3 4 5 6 7 8 9 10 11 12
| @Data public class SpuVo extends SpuEntity {
private List<String> spuImages;
private List<SpuAttrValueVo> baseAttrs;
private List<SkuVo> skus; }
|
2.5.3. 完成SPU新增功能
SpuController:改造之前的新增方法
1 2 3 4 5 6 7 8 9 10
|
@PostMapping @ApiOperation("保存") public ResponseVo<Object> save(@RequestBody SpuVo spuVo){ spuService.bigSave(spuVo);
return ResponseVo.ok(); }
|
SpuService接口添加方法:
1
| void bigSave(SpuVo spu);
|
SpuServiceImpl实现类实现新增方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| @Autowired private SpuDescMapper descMapper;
@Autowired private SpuAttrValueService baseService;
@Override public void bigSave(SpuVo spuVo) { spuVo.setPublishStatus(1); spuVo.setCreateTime(new Date()); spuVo.setUpdateTime(spuVo.getCreateTime()); this.save(spuVo); Long spuId = spuVo.getId();
SpuDescEntity spuInfoDescEntity = new SpuDescEntity(); spuInfoDescEntity.setSpuId(spuId); spuInfoDescEntity.setDecript(StringUtils.join(spuVo.getSpuImages(), ",")); this.descMapper.insert(spuInfoDescEntity);
List<SpuAttrValueVo> baseAttrs = spuVo.getBaseAttrs(); if (!CollectionUtils.isEmpty(baseAttrs)) { List<SpuAttrValueEntity> spuAttrValueEntities = baseAttrs.stream().map(spuAttrValueVO -> { spuAttrValueVO.setSpuId(spuId); spuAttrValueVO.setSort(0); return spuAttrValueVO; }).collect(Collectors.toList()); this.baseService.saveBatch(spuAttrValueEntities); }
}
|
注意:SpuDescEntity对象的主键需要设置为手动设置,无法手动设置。
![1584722502507](/assets/1584722502507.png)
测试效果:3张表的值均可正常插入
![1584722548563](/assets/1584722548563.png)
![1584722595999](/assets/1584722595999.png)
![1584722625124](/assets/1584722625124.png)
2.5.4. 完成SKU新增功能
继续完善SpuServiceImpl实现类新增方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
| @Autowired private SpuDescMapper descMapper;
@Autowired private SpuAttrValueService baseService;
@Autowired private SkuMapper skuMapper;
@Autowired private SkuImagesService skuImagesService;
@Autowired private SkuAttrValueService skuAttrValueService;
@Override public void bigSave(SpuVo spuVo) { spuVo.setPublishStatus(1); spuVo.setCreateTime(new Date()); spuVo.setUpdateTime(spuVo.getCreateTime()); this.save(spuVo); Long spuId = spuVo.getId();
SpuDescEntity spuInfoDescEntity = new SpuDescEntity(); spuInfoDescEntity.setSpuId(spuId); spuInfoDescEntity.setDecript(StringUtils.join(spuVo.getSpuImages(), ",")); this.descMapper.insert(spuInfoDescEntity);
List<SpuAttrValueVo> baseAttrs = spuVo.getBaseAttrs(); if (!CollectionUtils.isEmpty(baseAttrs)) { List<SpuAttrValueEntity> spuAttrValueEntities = baseAttrs.stream().map(spuAttrValueVO -> { spuAttrValueVO.setSpuId(spuId); spuAttrValueVO.setSort(0); return spuAttrValueVO; }).collect(Collectors.toList()); this.baseService.saveBatch(spuAttrValueEntities); }
List<SkuVo> skuVos = spuVo.getSkus(); if (CollectionUtils.isEmpty(skuVos)){ return; } skuVos.forEach(skuVo -> { SkuEntity skuEntity = new SkuEntity(); BeanUtils.copyProperties(skuVo, skuEntity); skuEntity.setBrandId(spuVo.getBrandId()); skuEntity.setCatagoryId(spuVo.getCategoryId()); List<String> images = skuVo.getImages(); if (!CollectionUtils.isEmpty(images)){ skuEntity.setDefaultImage(skuEntity.getDefaultImage()==null ? images.get(0) : skuEntity.getDefaultImage()); } skuEntity.setSpuId(spuId); this.skuMapper.insert(skuEntity); Long skuId = skuEntity.getId();
if (!CollectionUtils.isEmpty(images)){ String defaultImage = images.get(0); List<SkuImagesEntity> skuImageses = images.stream().map(image -> { SkuImagesEntity skuImagesEntity = new SkuImagesEntity(); skuImagesEntity.setDefaultStatus(StringUtils.equals(defaultImage, image) ? 1 : 0); skuImagesEntity.setSkuId(skuId); skuImagesEntity.setSort(0); skuImagesEntity.setUrl(image); return skuImagesEntity; }).collect(Collectors.toList()); this.skuImagesService.saveBatch(skuImageses); }
List<SkuAttrValueEntity> saleAttrs = skuVo.getSaleAttrs(); saleAttrs.forEach(saleAttr -> { saleAttr.setSort(0); saleAttr.setSkuId(skuId); }); this.skuAttrValueService.saveBatch(saleAttrs);
}); }
|
测试:sku相关的3张表保存成功
![1584723319043](/assets/1584723319043.png)
![1584723350642](/assets/1584723350642.png)
![1584723375965](/assets/1584723375965.png)
2.5.5. sms提供营销保存接口
SkuVo中剩余没有保存的字段都是营销信息的字段,需要把这些数据传输给营销系统,接下来首先创建一个数据模型
在gmall-sms中添加VO对象
![1584764077728](/assets/1584764077728.png)
内容如下:营销信息 + skuId
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @Data public class SkuSaleVo {
private Long skuId;
private BigDecimal growBounds; private BigDecimal buyBounds;
private List<Integer> work;
private BigDecimal fullPrice; private BigDecimal reducePrice; private Integer fullAddOther;
private Integer fullCount; private BigDecimal discount;
private Integer addOther; }
|
在SkuBoundsController中添加方法:
1 2 3 4 5 6 7
| @ApiOperation("新增sku的营销信息") @PostMapping("/skusale/save") public ResponseVo<Object> saveSkuSaleInfo(@RequestBody SkuSaleVo skuSaleVo){ this.skuBoundsService.saveSkuSaleInfo(skuSaleVo);
return ResponseVo.ok(null); }
|
给SkuBoundsService接口添加方法:
1
| void saveSkuSaleInfo(SkuSaleVo skuSaleVo);
|
给SkuBoundsServiceImpl实现类添加实现方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @Autowired private SkuFullReductionMapper skuFullReductionMapper;
@Autowired private SkuLadderMapper skuLadderMapper;
@Override public void saveSkuSaleInfo(SkuSaleVo skuSaleVo) { SkuBoundsEntity skuBoundsEntity = new SkuBoundsEntity(); BeanUtils.copyProperties(skuSaleVo, skuBoundsEntity); List<Integer> work = skuSaleVo.getWork(); if (!CollectionUtils.isEmpty(work)){ skuBoundsEntity.setWork(work.get(0) * 8 + work.get(1) * 4 + work.get(2) * 2 + work.get(3)); } this.save(skuBoundsEntity);
SkuFullReductionEntity skuFullReductionEntity = new SkuFullReductionEntity(); BeanUtils.copyProperties(skuSaleVo, skuFullReductionEntity); skuFullReductionEntity.setAddOther(skuSaleVo.getFullAddOther()); this.skuFullReductionMapper.insert(skuFullReductionEntity);
SkuLadderEntity skuLadderEntity = new SkuLadderEntity(); BeanUtils.copyProperties(skuSaleVo, skuLadderEntity); this.skuLadderMapper.insert(skuLadderEntity); }
|
2.5.6. pms调用营销保存接口
feign的玩法分以下三步:
- 引入openfeign的启动器,已添加
- 在引导类上添加@EnableFeignClients
- 编写feign接口
在gmall-pms的引导类(GmallPmsApplication)上添加@EnableFeignClients注解
![1584764490061](/assets/1584764490061.png)
添加SkuSaleVo实体类和GmallSmsClient接口
![1584766226032](/assets/1584766226032.png)
SkuSaleVo的内容参照gmall-sms中的SkuSaleVo的内容
GmallSmsClient的内容:
1 2 3 4 5 6
| @FeignClient("sms-service") public interface GmallSmsClient {
@PostMapping("/sms/skubounds/skusale/save") public ResponseVo<Object> saveSkuSaleInfo(@RequestBody SkuSaleVo skuSaleVo); }
|
完成商品新增:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
| @Autowired private SpuDescMapper descMapper;
@Autowired private SpuAttrValueService baseService;
@Autowired private SkuMapper skuMapper;
@Autowired private SkuImagesService skuImagesService;
@Autowired private SkuAttrValueService skuAttrValueService;
@Autowired private GmallSmsClient smsClient;
@Override public void bigSave(SpuVo spuVo) { Long spuId = saveSpu(spuVo);
saveSpuDesc(spuVo, spuId);
saveBaseAttr(spuVo, spuId);
saveSku(spuVo, spuId); }
private void saveSku(SpuVo spuVo, Long spuId) { List<SkuVo> skuVos = spuVo.getSkus(); if (CollectionUtils.isEmpty(skuVos)){ return; } skuVos.forEach(skuVo -> { SkuEntity skuEntity = new SkuEntity(); BeanUtils.copyProperties(skuVo, skuEntity); skuEntity.setBrandId(spuVo.getBrandId()); skuEntity.setCatagoryId(spuVo.getCategoryId()); List<String> images = skuVo.getImages(); if (!CollectionUtils.isEmpty(images)){ skuEntity.setDefaultImage(skuEntity.getDefaultImage()==null ? images.get(0) : skuEntity.getDefaultImage()); } skuEntity.setSpuId(spuId); this.skuMapper.insert(skuEntity); Long skuId = skuEntity.getId();
if (!CollectionUtils.isEmpty(images)){ String defaultImage = images.get(0); List<SkuImagesEntity> skuImageses = images.stream().map(image -> { SkuImagesEntity skuImagesEntity = new SkuImagesEntity(); skuImagesEntity.setDefaultStatus(StringUtils.equals(defaultImage, image) ? 1 : 0); skuImagesEntity.setSkuId(skuId); skuImagesEntity.setSort(0); skuImagesEntity.setUrl(image); return skuImagesEntity; }).collect(Collectors.toList()); this.skuImagesService.saveBatch(skuImageses); }
List<SkuAttrValueEntity> saleAttrs = skuVo.getSaleAttrs(); saleAttrs.forEach(saleAttr -> { saleAttr.setSort(0); saleAttr.setSkuId(skuId); }); this.skuAttrValueService.saveBatch(saleAttrs);
SkuSaleVo skuSaleVo = new SkuSaleVo(); BeanUtils.copyProperties(skuVo, skuSaleVo); skuSaleVo.setSkuId(skuId); this.smsClient.saveSkuSaleInfo(skuSaleVo); }); }
private void saveBaseAttr(SpuVo spuVo, Long spuId) { List<SpuAttrValueVo> baseAttrs = spuVo.getBaseAttrs(); if (!CollectionUtils.isEmpty(baseAttrs)) { List<SpuAttrValueEntity> spuAttrValueEntities = baseAttrs.stream().map(spuAttrValueVO -> { spuAttrValueVO.setSpuId(spuId); spuAttrValueVO.setSort(0); return spuAttrValueVO; }).collect(Collectors.toList()); this.baseService.saveBatch(spuAttrValueEntities); } }
private void saveSpuDesc(SpuVo spuVo, Long spuId) { SpuDescEntity spuInfoDescEntity = new SpuDescEntity(); spuInfoDescEntity.setSpuId(spuId); spuInfoDescEntity.setDecript(StringUtils.join(spuVo.getSpuImages(), ",")); this.descMapper.insert(spuInfoDescEntity); }
private Long saveSpu(SpuVo spuVo) { spuVo.setPublishStatus(1); spuVo.setCreateTime(new Date()); spuVo.setUpdateTime(spuVo.getCreateTime()); this.save(spuVo); return spuVo.getId(); }
|
3. feign的最佳实践
![1584766480880](/assets/1584766480880.png)
以上的这些代码直接从营销微服务中拷贝而来,完全一致。差别就是没有方法的具体实现。大家觉得这样有没有问题?
而FeignClient代码遵循SpringMVC的风格,因此与商品微服务的Controller完全一致。这样就存在一定的问题:
- 代码冗余。尽管不用写实现,只是写接口,但服务调用方要写与服务controller一致的代码,有几个消费者就要写几次。另外DTO对象,在每个需要feign的工程里面都要维护一份。
- 增加开发成本。调用方还得清楚知道接口的路径,才能编写正确的FeignClient。
解决方案:
- 我们的服务提供方不仅提供实体类,还要提供api接口声明
- 调用方不用自己编写接口方法声明,直接继承提供方给的Api接口即可
第一步:服务的提供方在gmall-sms-interface
中提供API接口,并编写接口声明:
第二步:在调用方gmall-pms
中编写FeignClient,但不要写方法声明了,直接继承gmall-sms-interface
提供的api接口:
3.1. 服务提供方添加接口工程
服务提供方需要创建接口工程:
![1567616497727](/assets/1567616497727.png)
![1567616540202](/assets/1567616540202.png)
![1567616575235](/assets/1567616575235.png)
工程结构如下:
![1584766903411](/assets/1584766903411.png)
pom.xml中的内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>gmall</artifactId> <groupId>com.atguigu</groupId> <version>0.0.1-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion>
<artifactId>gmall-sms-interface</artifactId>
<dependencies> <dependency> <groupId>com.atguigu</groupId> <artifactId>gmall-common</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.4.RELEASE</version> </dependency> </dependencies>
</project>
|
SkuSaleVo的内容跟之前没有变化,只是移动到接口工程了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| @Data public class SkuSaleVO { private Long skuId;
private BigDecimal growBounds; private BigDecimal buyBounds;
private List<Integer> work;
private BigDecimal fullPrice; private BigDecimal reducePrice; private Integer fullAddOther;
private Integer fullCount; private BigDecimal discount;
private Integer addOther; }
|
SkuSaleApi接口:
1 2 3 4 5
| public interface GmallSmsApi {
@PostMapping("/sms/skubounds/skusale/save") public Resp<Object> saveSkuSaleInfo(@RequestBody SkuSaleVO skuSaleVO); }
|
3.2. 服务提供方微服务
只要引入接口工程,重新到一下包即可
1 2 3 4 5
| <dependency> <groupId>com.atguigu</groupId> <artifactId>gmall-sms-interface</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
|
之前微服务中定义的Vo可以删掉了:
![1567617868566](/assets/1567617868566.png)
3.3. 服务消费方
![1584767441863](/assets/1584767441863.png)
也需要依赖营销的接口工程即可:
1 2 3 4 5
| <dependency> <groupId>com.atguigu</groupId> <artifactId>gmall-sms-interface</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
|
也不需要自己定义Vo了,另外GmallSmsClient内容变的及其简单:
1 2 3 4
| @FeignClient("sms-service") public interface GmallSmsClient extends GmallSmsApi {
}
|
3.4. 小结
![1567618674250](/assets/1567618674250.png)