目录
- 6.3 属性服务
- 6.4 规格参数
- 6.5 平台属性
- 6.6 新增商品
- 6.7 商品管理
- 6.8 仓储服务之仓库管理
- 7 分布式基础总结
6.3 属性服务
6.3.1 spu和sku
- SPU:Standard Product Unit(标准化产品单元)
是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。


iphoneX 是SPU、MI 8 是SPU
iphoneX 64G 黑曜石是SKU
MI8 8+64G+黑色是SKU
- SKU:Stock Keeping Unit(库存量单位)
即库存进出计量的基本单元,可以是以件,盒,托盘等为单位。SKU 这是对于大型连锁超市DC(配送中心)物流管理的一个必要的方法。现在已经被引申为产品统一编号的简称,每种产品均对应有唯一的SKU 号。
个人理解:
-
商品介绍、规格包装这些都是spu,每一个spu下的所有的sku都具备同样的属性。
-
颜色和内存都是销售属性,因为这个是决定每一个sku的不同的价格的。决定sku的属性我们叫做销售属性。每一个sku的商品介绍的图片和文字这些都是一样的。
-
基本属性就是指的主体中的型号或者是长度等。这些都是基本属性。
6.3.2 基本属性【规格参数】与销售属性




6.3.3 属性分组准备工作
如下图,这个是属性分组的效果展示。

1、前端组件
从老师给的课件资源中找到sys_menus.sql,将其复制后,在数据库gulimall_admin中进行找到sys_menus这张表进行完善。即可出现。

注意这里有一个坑:就是看清楚sql文件中的语句是否和你数据库中对应的表的名称一样,否则不管运行多少次sql都不会生效。

- 在moudules下新建common/categroy.vue,这是一个公共组件,后面我们要引用他,即树形结构。这里我们可以直接将以前写过的category.vue复制,然后进行简单的删除即可。
<!-- -->
<template>
<el-tree :data="menus" :props="defaultProps" node-key="catId" ref="menuTree">
</el-tree>
</template>
<script>
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';
export default {
//import引入的组件需要注入到对象中才能使用
components: {},
data() {
//这里存放数据
return {
menus: [],
expandedKey: [],
defaultProps: {
children: "children", //子节点
label: "name", //name属性作为标签的值,展示出来
},
};
},
//监听属性 类似于data概念
computed: {},
//监控data中的数据变化
watch: {},
//方法集合
methods: {
getMenus() {
this.$http({
url: this.$http.adornUrl("/product/category/list/tree"),
method: "get",
}).then(({ data }) => {
console.log("成功了获取到菜单数据....", data.data);
this.menus = data.data;
});
},
},
//生命周期 - 创建完成(可以访问当前this实例)
created() {
this.getMenus();
},
//生命周期 - 挂载完成(可以访问DOM元素)
mounted() {},
beforeCreate() {}, //生命周期 - 创建之前
beforeMount() {}, //生命周期 - 挂载之前
beforeUpdate() {}, //生命周期 - 更新之前
updated() {}, //生命周期 - 更新之后
beforeDestroy() {}, //生命周期 - 销毁之前
destroyed() {}, //生命周期 - 销毁完成
activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style scoped>
</style>
- 将逆向生成的前端代码复制到product下面。

- 在modules/product/下创建attgroup.vue组件
- 左侧6 用来显示菜单,右侧18用来显示表格

- 引入公共组件Category, AddOrUpdate
- 剩下的复制生成的
attrgroup.vue
<!-- -->
<template>
<el-row :gutter="20">
<el-col :span="6"><category></category></el-col>
<el-col :span="18"
><div class="mod-config">
<el-form
:inline="true"
:model="dataForm"
@keyup.enter.native="getDataList()"
>
<el-form-item>
<el-input
v-model="dataForm.key"
placeholder="参数名"
clearable
></el-input>
</el-form-item>
<el-form-item>
<el-button @click="getDataList()">查询</el-button>
<el-button
v-if="isAuth('product:attrgroup:save')"
type="primary"
@click="addOrUpdateHandle()"
>新增</el-button
>
<el-button
v-if="isAuth('product:attrgroup:delete')"
type="danger"
@click="deleteHandle()"
:disabled="dataListSelections.length <= 0"
>批量删除</el-button
>
</el-form-item>
</el-form>
<el-table
:data="dataList"
border
v-loading="dataListLoading"
@selection-change="selectionChangeHandle"
style="width: 100%"
>
<el-table-column
type="selection"
header-align="center"
align="center"
width="50"
>
</el-table-column>
<el-table-column
prop="attrGroupId"
header-align="center"
align="center"
label="分组id"
>
</el-table-column>
<el-table-column
prop="attrGroupName"
header-align="center"
align="center"
label="组名"
>
</el-table-column>
<el-table-column
prop="sort"
header-align="center"
align="center"
label="排序"
>
</el-table-column>
<el-table-column
prop="descript"
header-align="center"
align="center"
label="描述"
>
</el-table-column>
<el-table-column
prop="icon"
header-align="center"
align="center"
label="组图标"
>
</el-table-column>
<el-table-column
prop="catelogId"
header-align="center"
align="center"
label="所属分类id"
>
</el-table-column>
<el-table-column
fixed="right"
header-align="center"
align="center"
width="150"
label="操作"
>
<template slot-scope="scope">
<el-button
type="text"
size="small"
@click="addOrUpdateHandle(scope.row.attrGroupId)"
>修改</el-button
>
<el-button
type="text"
size="small"
@click="deleteHandle(scope.row.attrGroupId)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<el-pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
:current-page="pageIndex"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
:total="totalPage"
layout="total, sizes, prev, pager, next, jumper"
>
</el-pagination>
<!-- 弹窗, 新增 / 修改 -->
<add-or-update
v-if="addOrUpdateVisible"
ref="addOrUpdate"
@refreshDataList="getDataList"
></add-or-update></div
></el-col>
</el-row>
</template>
<script>
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';
import Category from "../common/category.vue";
import AddOrUpdate from "./attrgroup-add-or-update.vue";
export default {
//import引入的组件需要注入到对象中才能使用
components: { Category, AddOrUpdate},
data() {
return {
dataForm: {
key: "",
},
dataList: [],
pageIndex: 1,
pageSize: 10,
totalPage: 0,
dataListLoading: false,
dataListSelections: [],
addOrUpdateVisible: false,
};
},
activated() {
this.getDataList();
},
methods: {
// 获取数据列表
getDataList() {
this.dataListLoading = true;
this.$http({
url: this.$http.adornUrl("/product/attrgroup/list"),
method: "get",
params: this.$http.adornParams({
page: this.pageIndex,
limit: this.pageSize,
key: this.dataForm.key,
}),
}).then(({ data }) => {
if (data && data.code === 0) {
this.dataList = data.page.list;
this.totalPage = data.page.totalCount;
} else {
this.dataList = [];
this.totalPage = 0;
}
this.dataListLoading = false;
});
},
// 每页数
sizeChangeHandle(val) {
this.pageSize = val;
this.pageIndex = 1;
this.getDataList();
},
// 当前页
currentChangeHandle(val) {
this.pageIndex = val;
this.getDataList();
},
// 多选
selectionChangeHandle(val) {
this.dataListSelections = val;
},
// 新增 / 修改
addOrUpdateHandle(id) {
this.addOrUpdateVisible = true;
this.$nextTick(() => {
this.$refs.addOrUpdate.init(id);
});
},
// 删除
deleteHandle(id) {
var ids = id
? [id]
: this.dataListSelections.map((item) => {
return item.attrGroupId;
});
this.$confirm(
`确定对[id=${ids.join(",")}]进行[${id ? "删除" : "批量删除"}]操作?`,
"提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
).then(() => {
this.$http({
url: this.$http.adornUrl("/product/attrgroup/delete"),
method: "post",
data: this.$http.adornData(ids, false),
}).then(({ data }) => {
if (data && data.code === 0) {
this.$message({
message: "操作成功",
type: "success",
duration: 1500,
onClose: () => {
this.getDataList();
},
});
} else {
this.$message.error(data.msg);
}
});
});
},
},
};
</script>
<style scoped>
</style>
踩坑:
Can't resolve './attrgroup-add-or-update' in 'C:\Users\hxld\Desktop\renren-fast-vue\src\views\modules\product'
解决办法:
原来是绝对路径,后面改为相对路径即可。错误原因是因为版本问题可能。
2、父子组件传递数据
我们要实现的功能是点击左侧,右侧表格对应显示。
父子组件传递数据:category.vue点击时,引用它的attgroup.vue能感知到, 然后通知到add-or-update。

- 子组件发送事件
-
在category.vue中的树形控件绑定点击事件@node-click=“nodeclick”
-
node-click方法中有三个参数(data, node, component),data表示当前数据,node为elementui封装的数据
-
点击之后向父组件发送事件:this.$emit(“tree-node-click”,…) …为参数
//组件绑定事件
<el-tree :data="menus" :props="defaultProps" node-key="catId" ref="menuTree" @node-click="nodeclick">
//methods中新增方法
nodeclick(data, node, component){
console.log("子组件categroy的节点被点击:", data,node,component);
this.$emit("tree-node-click", data,node,component); //向父组件发送tree-node-click事件
}
- 父组件接收事件
//引用的组件,可能会发散tree-node-click事件,当接收到时,触发父组件的treenodeclick方法
<category @tree-node-click="treenodeclick"></category>
//methods中新增treenodeclick方法,验证父组件是否接收到
treenodeclick(data, node, component){
console.log("attrgroup感知到category的节点被点击:",data, node, component);
console.log("刚才被点击的菜单Id", data.catId);
},
3、启动测试
6.3.4 获取分类属性分组
接口在线文档地址:https://easydoc.net/s/78237135/ZUqEdvA4/OXTgKobR

接口地址:/product/attrgroup/list/{catelogId}
@RequestParam:获取请求参数的值,和方法形参绑定,并自动赋值
@PathVariable:获取路径参数中的值,和方法形参绑定,并自动赋值
1、修改product下的controller
@RequestMapping("/list/{catelogId}")
public R list(@RequestParam Map<String, Object> params, @PathVariable Long catelogId){
//PageUtils page = attrGroupService.queryPage(params);
PageUtils page = attrGroupService.queryPage(params, catelogId);
return R.ok().put("page", page);
}
2、service新增接口,实现类新增方法
@Override
public PageUtils queryPage(Map<String, Object> params, Long catelogId) {
if (catelogId == 0){ //如果传过来的id是0,则查询所有属性
//this.page两个参数,第一个参数是查询页码信息,其中Query.getPage方法传入一个map,会自动封装成IPage
//第二个参数是查询条件,空的wapper就是查询全部
IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params),
new QueryWrapper<AttrGroupEntity>());
return new PageUtils(page);
}else{
String key = (String) params.get("key");
QueryWrapper<AttrGroupEntity> wapper = new QueryWrapper<AttrGroupEntity>().eq("catelog_id", catelogId);
if (!StringUtils.isEmpty(key)){
wapper.and((obj)->{
obj.like("attr_group_name", key).or().eq("attr_group_id", key);
});
}
IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params),
wapper);
return new PageUtils(page);
}
}
3、细节完善
我们不让一级和二级分类下,点击的时候也出现表格,只让三级分类的时候才出现相应的表格,所以我们可以设置一个判断。
修改前端代码
- 修改getDataList()中的请求路径

- data中新增
catId,设置默认为0 - methods中修改点击方法
treenodeclick(data, node, component) {
//必须是三级分类,才显示属性
if (data.catLevel == 3){
this.catId = data.catId;
this.getDataList();
}
},
4、数据库中新增数据,进行测试。
6.3.5 属性分组新增功能
新增时,父id改换为选择框
1、新增选择框,添加菜单数据

-
我们发现可以选择分类,但是分类显示的是空白,这个是因为 显示的属性是label,通过props属性进行绑定
<!--v-model 绑定要提交的数据,options绑定选择菜单, props绑定选择框的参数--> <el-form-item label="所属分类id" prop="catelogId"> <el-cascader v-model="dataForm.catelogIds" :options="categroys" :props="props"></el-cascader> </el-form-item>//data中新增属性,props用来绑定选择框的参数,categorys用来保存菜单 props:{ value:"catId", label:"name", children:"children" }, categroys:[], //方法中新增getCategorys(),用来获取选择菜单的值 getCategorys() { this.$http({ url: this.$http.adornUrl("/product/category/list/tree"), method: "get", }).then(({ data }) => { console.log("成功了获取到菜单数据....", data.data); this.categroys = data.data; }); }, //组件创建的时候就要获取菜单的值 created(){ this.getCategorys(); }
2、发现返回的数据,三级菜单下面也有children(为空)
- 解决方法:在CategoryEntity中设置相应的属性不为空既可。当children为空时,不返回children字段
在children字段上添加注解:当值为空时,不返回当前字段


3、修改之后仍然报错
原因是:el-cascader绑定的dataForm.catelogId是一个数组,其中包含选择框的父节点id和自己的id。而我们要提交的只是他自己的id。
//修改data中的dataFrom
dataForm: {
attrGroupId: 0,
attrGroupName: "",
sort: "",
descript: "",
icon: "",
catelogIds: [], //保存父节点和子节点的id
catelogId: 0 //保存要提交的子节点的id
},
//修改表单提交方法,要传送的数据,只传最后一个子id
catelogId: this.dataForm.catelogIds[this.dataForm.catelogIds.length - 1],
6.3.6 修改回显分类功能

我们要设置选择进行修改的时候将原来本属于这个原信息回显出来。

1、前端 attrgroup-add-or-update.vue新增完整路径
init(id) {
this.dataForm.attrGroupId = id || 0;
this.visible = true;
this.$nextTick(() => {
this.$refs["dataForm"].resetFields();
if (this.dataForm.attrGroupId) {
this.$http({
url: this.$http.adornUrl(
`/product/attrgroup/info/${this.dataForm.attrGroupId}`
),
method: "get",
params: this.$http.adornParams(),
}).then(({ data }) => {
if (data && data.code === 0) {
this.dataForm.attrGroupName = data.attrGroup.attrGroupName;
this.dataForm.sort = data.attrGroup.sort;
this.dataForm.descript = data.attrGroup.descript;
this.dataForm.icon = data.attrGroup.icon;
this.dataForm.catelogId = data.attrGroup.catelogId;
//查出catelogId的完整路径
this.dataForm.catelogPath = data.attrGroup.catelogPath;
}
});
}
});
},
2、后端AttrGroupEntity新增完整路径属性
@TableField(exist = false)
private Long[] catelogPath;
3、修改AttrGroupController
@Autowired
private CategoryService categoryService;
/**
* 信息
*/
@RequestMapping("/info/{attrGroupId}")
public R info(@PathVariable("attrGroupId") Long attrGroupId){
AttrGroupEntity attrGroup = attrGroupService.getById(attrGroupId);
Long catelogId = attrGroup.getCatelogId();
//根据id查询完整路径
Long[] path = categoryService.findCatelogPath(catelogId);
// 查询到的传入回去
attrGroup.setCatelogPath(path);
//返回
return R.ok().put("attrGroup", attrGroup);
}
4、修改categoryService以及categoryServiceImpl的方法,新增接口,实现方法
//categoryService接口
/*
*找到catelogId的完整路径
*/
Long[] findCatelogPath(Long catelogId);
//categoryServiceImpl实现方法
//查找完整路径方法
@Override
public Long[] findCatelogPath(Long catelogId) {
List<Long> paths = new ArrayList<>();
List<Long> parentPath = findParentPath(catelogId, paths);
// list.toArray list对象转换为数组对象
return parentPath.toArray(new Long[parentPath.size()]);
}
//递归查找父节点id
public List<Long> findParentPath(Long catelogId,List<Long> paths){
//1、收集当前节点id
CategoryEntity byId = this.getById(catelogId);
if (byId.getParentCid() != 0){
findParentPath(byId.getParentCid(), paths);
}
paths.add(catelogId);
return paths;
}
5、 attrgroup-add-or-update.vue 当对话框关闭时,清空数据,防止不合理回显
<el-dialog
:title="!dataForm.attrGroupId ? '新增' : '修改'"
:close-on-click-modal="false"
:visible.sync="visible"
@closed="dialogClose" //关闭时,绑定方法dialogClose
>
//新增方法
dialogClose(){
this.dataForm.catelogPath = [];
},
6、选择框加上搜索功能:filterable, 显示提示信息placeholder="试试搜索:手机"
<el-cascader
placeholder="试试搜索:手机"
filterable
v-model="dataForm.catelogPath"
:options="categroys"
:props="props"
></el-cascader>
6.3.7 实现分页-引入插件
发现自动生成的分页条不好使,原因是没有引入mybatis-plus的分页插件。新建配置类,引入如下配置


1、新建mybatisplus配置文件,引入并配置分页插件
@Configuration
public class MybatisPlusConfig {
// 最新版
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));
return interceptor;
}
}
上面这个是最新版本的分页插件的配置信息。
下面这个是老师课件上的配置信息
@Configuration
@EnableTransactionManagement //开启事务
@MapperScan("com.atguigu.gulimall.product.dao")
public class MyBatisConfig {
//引入分页插件 显示页码
@Bean
public PaginationInterceptor paginationInterceptor(){
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
//设置请求的页面大于最大页后操作,true调回到首页,false继续请求,默认false
paginationInterceptor.setOverflow(true);
//设置最大单页限制数量,默认500条,-1不受限制
paginationInterceptor.setLimit(1000);
return paginationInterceptor;
}
}
6.3.8 关联分类
1、前端代码拷贝
这一部分前端代码我们直接将老师发的课件的全部复制到我们自己的renren-fast-vue文件夹下.


- 一个手机品牌下可能包含手机、电器等分类,同理手机分类下可能包含小米、华为等多个品牌,即品牌与分类之间是多对多的关系,表
pms_category_brand_relation保存品牌与分类的多对多关系。

2、获取品牌关联的分类
根据传过来的brandId,查找所有的分类信息

CategoryBrandRelationController
/**
* 获取品牌关联的分类
*/
@GetMapping("/catelog/list")
public R catelogList(@RequestParam("brandId") Long brandId){
List<CategoryBrandRelationEntity> data = categoryBrandRelationService.list(
new QueryWrapper<CategoryBrandRelationEntity>().eq("brand_id", brandId)
);
return R.ok().put("data",data);
}
3、新增品牌与分类关联关系:

保存的时候,前端传过来brandid和categoryid,存储的时候还要存brandName和categoryName,所以在保存之前进行查找.
- CategoryBrandRelationController
/**
* 保存
*/
@RequestMapping("/save")
public R save(@RequestBody CategoryBrandRelationEntity categoryBrandRelation){
categoryBrandRelationService.saveDetail(categoryBrandRelation);
return R.ok();
}
- CategoryBrandRelationService
void saveDetail(CategoryBrandRelationEntity categoryBrandRelation);
- CategoryBrandRelationServiceImpl
@Override
public void saveDetail(CategoryBrandRelationEntity categoryBrandRelation) {
Long brandId = categoryBrandRelation.getBrandId();
Long catelogId = categoryBrandRelation.getCatelogId();
//查询详细名字和分类名字
BrandEntity brandEntity = brandDao.selectById(brandId);
CategoryEntity categoryEntity = categoryDao.selectById(catelogId);
categoryBrandRelation.setBrandName(brandEntity.getName());
categoryBrandRelation.setCatelogName(categoryEntity.getName());
this.save(categoryBrandRelation);
}
4、要对品牌(分类)名字进行修改时,品牌分类关系表之中的名字也要进行修改
- 品牌名字修改同时修改关系表数据
BrandController
@RequestMapping("/update")
//@RequiresPermissions("product:brand:update")
public R update(@Validated(value = {UpdateGroup.class})@RequestBody BrandEntity brand){
brandService.updateByIdDetail(brand);
return R.ok();
}
brandServiceImpl
@Autowired
CategoryBrandRelationService categoryBrandRelationService;
@Transactional
@Override
public void updateByIdDetail(BrandEntity brand) {
//保证冗余字段的数据一致
this.updateById(brand);
if (!StringUtils.isEmpty(brand.getName())){
//如果修改了名字,则品牌分类关系表之中的名字也要修改
categoryBrandRelationService.updateBrand(brand.getBrandId(), brand.getName());
//TODO 更新其他关联
}
}
CategoryBrandRelationServiceImpl
@Override
public void updateBrand(Long brandId, String name) {
CategoryBrandRelationEntity categoryBrandRelationEntity = new CategoryBrandRelationEntity();
categoryBrandRelationEntity.setBrandName(name);
categoryBrandRelationEntity.setBrandId(brandId);
this.update(categoryBrandRelationEntity, new QueryWrapper<CategoryBrandRelationEntity>().eq("brand_id", brandId));
}
- 分类名字修改同时修改关系表数据
CategoryController
/**
* 修改
*/
@RequestMapping("/update")
//@RequiresPermissions("product:category:update")
public R update(@RequestBody CategoryEntity category){
categoryService.updateCascade(category);
return R.ok();
}
CategroyServiceImpl
@Autowired
CategoryBrandRelationService categoryBrandRelationService;
@Transactional
@Override
public void updateCascade(CategoryEntity category) {
this.updateById(category);
categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
}
CategoryBrandRelationDao
void updateCategroy(@Param("catId") Long catId,@Param("name") String name);
CateBrandRelationDao.xml
<update id="updateCategroy">
UPDATE `pms_category_brand_relation` SET catelog_name=#{name} WHERE catelog_id=#{catId}
</update>
6.4 规格参数
模糊查询全部

6.4.1 规格参数新增
1、流程梳理
-
规格参数新增时,请求的URL:Request URL:/product/attr/save 现在的情况是,它在保存的时候,只是保存了attr,并没有保存attrgroup,为了解决这个问题,我们新建了一个vo/AttrVo,在原AttrEntity基础上增加了attrGroupId字段,使得保存新增数据的时候,也保存了它们之间的关系。
通过" BeanUtils.copyProperties(attr,attrEntity);"能够实现在两个Bean之间拷贝数据,但是两个Bean的字段要相同
-
当有新增字段时,我们往往会在entity实体类中新建一个字段,并标注数据库中不存在该字段,然而这种方式并不规范。比较规范的做法是,新建一个vo文件夹,将每种不同的对象,按照它的功能进行了划分。
-
查看前端返回的数据,发现比数据库中的attr多了attrGroupId字段, 所以新建AttrVo




上面的保存的save方法只是逆向生成代码中最简单的,没有任何附加作用,所以我们进行修改。
2、创建Vo 包
@Data
public class AttrVo extends AttrEntity {
private Long attrGroupId;
}
3、修改AttrController
@RequestMapping("/save")
public R save(@RequestBody AttrVo attr){
attrService.saveAttr(attr);
return R.ok();
}
4、AttrServiceImpl
@Autowired
private AttrAttrgroupRelationDao attrAttrgroupRelationDao;
@Override
public void saveAttr(AttrVo attr) {
AttrEntity attrEntity = new AttrEntity();
//1.保存基本数据 保存attrEntity
//利用attr的属性给attrEntity的属性赋值,前提是他俩的属性名一致
BeanUtils.copyProperties(attr,attrEntity); //因为一个一个属性设置太麻烦了
this.save(attrEntity);
//2.保存关联关系 保存AttrGroupId信息
AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
relationEntity.setAttrGroupId(attr.getAttrGroupId());
relationEntity.setAttrId(attr.getAttrId());
attrAttrgroupRelationDao.insert(relationEntity);
}
5、知识点补充:


6.4.2 获取分类规格参数

1、新建AttrRespVo
@Data
public class AttrRespVo extends AttrVo {
private String catelogName;
private String groupName;
}
2、AttrController
@RequestMapping("/base/list/{catelogId}")
public R baseList(@RequestParam Map<String, Object> params, @PathVariable("catelogId") Long catelogId){
PageUtils page = attrService.queryBaseAttrPage(params, catelogId);
return R.ok().put("page", page);
}
3、AttrServiceImpl
@Autowired
private CategoryDao categoryDao;
@Autowired
private AttrGroupDao attrGroupDao;
@Override
public PageUtils queryBaseAttrPage(Map<String, Object> params, Long catelogId) {
QueryWrapper<AttrEntity> queryWrapper = new QueryWrapper<>();
//模糊查询
if (catelogId != 0){
queryWrapper.eq("catelog_id", catelogId);
}
String key = (String) params.get("key");
if (!StringUtils.isEmpty(key)){
//attr_id attr_name
queryWrapper.and((wrapper) -> {
wrapper.eq("attr_id", key).or().like("attr_name", key);
});
}
IPage<AttrEntity> page = this.page(
new Query<AttrEntity>().getPage(params),
queryWrapper
);
//封装分页数据
PageUtils pageUtils = new PageUtils(page);
//查出新增的属性
List<AttrEntity> records = page.getRecords();
List<AttrRespVo> respVos = records.stream().map((attrEntity) -> {
AttrRespVo attrRespVo = new AttrRespVo();
BeanUtils.copyProperties(attrEntity, attrRespVo);
CategoryEntity categoryEntity = categoryDao.selectOne(new QueryWrapper<CategoryEntity>().eq("cat_id", attrEntity.getCatelogId()));
if (categoryEntity != null) {
attrRespVo.setCatelogName(categoryEntity.getName());
}
AttrAttrgroupRelationEntity attrgroupRelationEntity = attrAttrgroupRelationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrEntity.getAttrId()));
if (attrgroupRelationEntity != null) {
AttrGroupEntity attrGroupEntity = attrGroupDao.selectOne(new QueryWrapper<AttrGroupEntity>().eq("attr_group_id", attrgroupRelationEntity.getAttrGroupId()));
attrRespVo.setGroupName(attrGroupEntity.getAttrGroupName());
}
return attrRespVo;
}).collect(Collectors.toList());
// 把新的数据传送过去
pageUtils.setList(respVos);
return pageUtils;
}
6.4.3 查询属性详情

1、修改AttrRespVo
@Data
public class AttrRespVo extends AttrVo {
private String catelogName;
private String groupName;
private Long[] catelogPath;
}
2、AttrController
@RequestMapping("/info/{attrId}")
public R info(@PathVariable("attrId") Long attrId){
AttrRespVo attrRespVo = attrService.getAttrInfo(attrId);
return R.ok().put("attr", attrRespVo);
}
3、AttrServiceImpl
@Override
public AttrRespVo getAttrInfo(Long attrId) {
AttrRespVo respVo = new AttrRespVo();
AttrEntity attrEntity = this.getById(attrId);
BeanUtils.copyProperties(attrEntity,respVo);
//1.设置分组信息
AttrAttrgroupRelationEntity attrAttrgroupRelation = attrAttrgroupRelationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>()
.eq("attr_id", attrId));
if(attrAttrgroupRelation != null){
respVo.setAttrGroupId(attrAttrgroupRelation.getAttrGroupId());
AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrAttrgroupRelation.getAttrId());
if(attrGroupEntity!= null){
respVo.setGroupName(attrGroupEntity.getAttrGroupName());
}
}
//2.设置分类信息
Long catelogId = attrEntity.getCatelogId();
Long[] catelogPath = categoryService.findCatelogPath(catelogId);
respVo.setCatelogPath(catelogPath);
CategoryEntity categoryEntity = categoryDao.selectById(catelogId);
if(categoryEntity!=null){
respVo.setCatelogName(categoryEntity.getName());
}
return respVo;
}
4、未解决问题:
规格参数处不显示所属分组。数据库中已经修改。如下图:


所属分组修改成功,数据库中已经成功,但是页面不显示的问题?
目前暂时解决办法是在:规格参数那新增的时候选择到销售属性。在规格参数中新增的时候选择到销售属性即可。

6.4.4 修改属性


1、在AttrController中我们进行新增相应的方法
@RequestMapping("/update")
public R update(@RequestBody AttrVo attr){
attrService.updateAttr(attr);
return R.ok();
}
2、AttrServiceImpl
//保存时,要修改两张表 attr这张表和attrattrgroupRelationEntity这两个表
@Transactional
@Override
public void updateAttr(AttrVo attr) {
AttrEntity attrEntity = new AttrEntity();
BeanUtils.copyProperties(attr, attrEntity);
this.updateById(attrEntity);
AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
relationEntity.setAttrGroupId(attr.getAttrGroupId());
relationEntity.setAttrId(attr.getAttrId());
//判断是新增还是删除
Integer count = attrAttrgroupRelationDao.selectCount(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attr.getAttrId()));
if (count > 0){
attrAttrgroupRelationDao.update(relationEntity, new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attr.getAttrId()));
}else{
attrAttrgroupRelationDao.insert(relationEntity);
}
}
6.5 平台属性
6.5.1 获取分类销售属性

我们不难发现获取销售属性和规格参数的差别就是sale和base的区别,其他后面的都是差不多的,所以我们可以在路径变量中添加{attrType},使得同时用一个方法查询销售属性和规格参数。

**注意点:**销售属性,没有分组信息,所以我们在复用方法的时候要进行判断到底是销售属性还是规格参数。
1、AttrController
@RequestMapping("/{attrType}/list/{catelogId}")
public R baseList(@RequestParam Map<String, Object> params, @PathVariable("catelogId") Long catelogId,
@PathVariable("attrType") String attrType){
PageUtils page = attrService.queryBaseAttrPage(params, catelogId, attrType);
return R.ok().put("page", page);
}
2、AttrServiceImpl
@Override
public PageUtils queryBaseAttrPage(Map<String, Object> params, Long catelogId, String attrType) {
QueryWrapper<AttrEntity> queryWrapper = new QueryWrapper<AttrEntity>().eq("attr_type", "base".equalsIgnoreCase(attrType)?1:0);
if (catelogId != 0){
queryWrapper.eq("catelog_id", catelogId);
}
String key = (String) params.get("key");
if (!StringUtils.isEmpty(key)){
//attr_id attr_name
queryWrapper.and((wrapper) -> {
//根据id或者名称进行查询
wrapper.eq("attr_id", key).or().like("attr_name", key);
});
}
IPage<AttrEntity> page = this.page(
new Query<AttrEntity>().getPage(params),
queryWrapper
);
//封装分页数据
PageUtils pageUtils = new PageUtils(page);
// 查出新增的属性
List<AttrEntity> records = page.getRecords();
List<AttrRespVo> respVos = records.stream().map((attrEntity) -> {
AttrRespVo attrRespVo = new AttrRespVo();
BeanUtils.copyProperties(attrEntity, attrRespVo);
//1.设置分类和分组的名字
if ("base".equalsIgnoreCase(attrType)){
AttrAttrgroupRelationEntity attrgroupRelationEntity = attrAttrgroupRelationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrEntity.getAttrId()));
if (attrgroupRelationEntity != null) {
AttrGroupEntity attrGroupEntity = attrGroupDao.selectOne(new QueryWrapper<AttrGroupEntity>().eq("attr_group_id", attrgroupRelationEntity.getAttrGroupId()));
attrRespVo.setGroupName(attrGroupEntity.getAttrGroupName());
}
}
CategoryEntity categoryEntity = categoryDao.selectOne(new QueryWrapper<CategoryEntity>().eq("cat_id", attrEntity.getCatelogId()));
if (categoryEntity != null) {
attrRespVo.setCatelogName(categoryEntity.getName());
}
return attrRespVo;
}).collect(Collectors.toList());
// 把新的数据传送过去
pageUtils.setList(respVos);
return pageUtils;
}
通过中间表pms_attr-attrgroup_relation去查pms_attr表。

3、判断
-
当新增/修改规格参数时,会在attrAttrGroupRelation表之中新增数据,但是销售属性没有分组信息。所以在新增/修改时,进行判断
-
在Common中新建类ProductConstant,用来商品服务中的保存常量
public class ProductConstant {
public enum AttrEnum{
ATTR_TYPE_BASE(1,"基本属性"), ATTR_TYPE_SALE(0,"销售属性");
private int code;
private String msg;
AttrEnum(int code, String msg){
this.code = code;
this.msg = msg;
}
}
}
- 在
saveAttr,getAttrInfo,updateAttr中,设计分组信息之前做判断
if (attrEntity.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode())//如果是规格参数,再操作分组信息
6.5.2 获取属性分组的关联的所有属性

1、attrgroupController
@GetMapping("/{attrgroupId}/attr/relation")
public R attrRelation(@PathVariable("attrgroupId") Long attrgroupId){
List<AttrEntity> data = attrService.getRelationAttr(attrgroupId);
return R.ok().put("data", data);
}
2、attrServiceImpl
/**
* 根据分组id查找关联的所有基本属性
* @param attrgroupId
* @return
*/
@Override
public List<AttrEntity> getRelationAttr(Long attrgroupId) {
//通过pms_attr_attrgroup_relation中间表中的attr_group_id找到attrgroupId,然后去attr表中找到对应信息即可
List<AttrAttrgroupRelationEntity> entities = relationDao.selectList(
new QueryWrapper<AttrAttrgroupRelationEntity>().
eq("attr_group_id", attrgroupId));
//找到所有的attrId的集合
List<Long> attrIds = entities.stream().map((attr) -> {
return attr.getAttrId();
}).collect(Collectors.toList());
//解决空指针异常问题
if(attrIds == null || attrIds.size() == 0){
return null;
}
//通过listByIds找到所有的attr实体类
Collection<AttrEntity> attrEntities = this.listByIds(attrIds);
//返回
return (List<AttrEntity>) attrEntities;
}
6.5.3 删除属性与分组的关联关系

1、新建AttrGroupRelationVo
@Data
public class AttrGroupRelationVo {
private Long attrId;
private Long attrGroupId;
}
2、AttrGroupController
@PostMapping("/attr/relation/delete")
public R attrRelationDelete(@RequestBody AttrGroupRelationVo[] vos){
attrService.deleteRelation(vos);
return R.ok();
}
3、AttrServiceImpl
@Override
public void deleteRelation(AttrGroupRelationVo[] vos) {
//将数组转换为集合list
List<AttrGroupRelationVo> relationVos = Arrays.asList(vos);
// AttrAttrgroupRelationDao relationDao 我们希望使用的是relationDao中使用的AttrAttrgroupRelationEntity这个实体类,所以我们转换
List<AttrAttrgroupRelationEntity> entities = relationVos.stream().map((relationVo) -> {
AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
BeanUtils.copyProperties(relationVo, relationEntity);
return relationEntity;
}).collect(Collectors.toList());
//根据attrId,attrGroupId批量删除关联关系
relationDao.deleteBatchRelation(entities);
}
4、AttrAttrgroupRelationDao.xml
<delete id="deleteBatchRelation">
DELETE FROM `pms_attr_attrgroup_relation` WHERE
<!--遍历循环来实现动态取值删除-->
<foreach collection="entities" item="item" separator=" OR ">
(attr_id=#{item.attrId} AND attr_group_id=#{item.attrGroupId})
</foreach>
</delete>
6.5.4 获取属性分组没有关联的其他属性

1、AttrGroupController
@GetMapping("/{attrgroupId}/noattr/relation")
public R attrNoRelation(@PathVariable("attrgroupId") Long attrgroupId, @RequestParam Map<String, Object> params){
PageUtils page = attrService.getNoRelationAttr(params, attrgroupId);
return R.ok().put("page", page);
}
2、AttrServiceImpl
- 当前分组只能关联自己所属分类里面的所有属性
- 当前分组只能关联别的分组没有引用的属性
- 当前分类下的其他分组
- 这些分组关联的属性
- 从当前分类的所有属性中移除这些属性
/**
* 获取当前分组没有关联的所有属性
* @param params
* @param attrgroupId
* @return
*/
@Override
public PageUtils getNoRelationAttr(Map<String, Object> params, Long attrgroupId) {
//1.当前分组只能关联自己所属分类里面的所有属性
AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrgroupId);
//得到分类id
Long catelogId = attrGroupEntity.getCatelogId();
//2.当前分组只能关联别的分组没有引用的属性
//2.1当前分类下的其他分组
List<AttrGroupEntity> attrGroupEntities = attrGroupDao.selectList(new QueryWrapper<AttrGroupEntity>().eq("catelog_id", catelogId));
List<Long> collect = attrGroupEntities.stream().map(attrGroupEntity1 -> {
return attrGroupEntity1.getAttrGroupId();
}).collect(Collectors.toList());
//2.2 这些分组关联的属性
List<AttrAttrgroupRelationEntity> relationEntities = relationDao.selectList(new QueryWrapper<AttrAttrgroupRelationEntity>().in("attr_group_id", collect));
//属性id的集合
List<Long> attrIds = relationEntities.stream().map((relationEntity) -> {
return relationEntity.getAttrId();
}).collect(Collectors.toList());
//从当前分类的所有属性中移除这些属性
QueryWrapper<AttrEntity> wrapper = new QueryWrapper<AttrEntity>().eq("catelog_id", catelogId).eq("attr_type", ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode());
if(attrIds != null && attrIds.size() > 0){
wrapper.notIn("attr_id",attrIds);
}
//模糊查询
String key = (String) params.get("key");
if(!StringUtils.isEmpty(key)){
wrapper.and((w) ->{
w.eq("attr_id",key).or().like("attr_name",key);
});
}
IPage<AttrEntity> page = this.page(new Query<AttrEntity>().getPage(params), wrapper);
return new PageUtils(page);
}
6.5.5 添加属性与分组关联关系

1、AttrGroupController
@PostMapping("/attr/relation")
public R attrRelation(@RequestBody List<AttrGroupRelationVo> vos){
relationService.saveBatch(vos);
return R.ok();
}
2、AttrAttrgroupRelationServiceImpl
@Override
public void saveBatch(List<AttrGroupRelationVo> vos) {
List<AttrAttrgroupRelationEntity> entities = vos.stream().map((vo) -> {
AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
//属性对拷
BeanUtils.copyProperties(vo, relationEntity);
return relationEntity;
}).collect(Collectors.toList());
this.saveBatch(entities);
}
6.6 新增商品
6.6.1 调试会员等级相关接口
1、前端代码拷贝
- 我们将老师给的课件中有关于前端的所有代码复制到前端项目中,其实后面很多的操作中最难的部分是前端页面的编写。我们最主要的还是后端,所以说直接将老师所给的代码直接复制,关注后端代码的编写即可。



2、微服务注册及相应网关配置
- 其实逆向生成的代码中已经有这个接口的完整方法,我们要做的事就是将gulimall-member这个微服务注册到nacos服务注册中心,并且配置相应的网关路由
注册到服务注册中心的套路和配置相应的网关路由前面已经详细讲解过,这里不再赘述。
- id: member_route
uri: lb://gulimall-member
predicates:
- Path=/api/member/**
filters:
- RewritePath=/api/(?<segment>.*),/$\{segment}
3、添加一些数据进行测试

在用户系统中的会员等级中添加上面的几个等级名称。
6.6.2 获取分类关联的品牌
1、思路梳理
- 新增商品时,点击商品的分类,要获取与该分类关联的所有品牌

-
请求参数catId不是路径变量,所以我们使用@RequestParam(“catId”)从请求中获取。
-
因为响应数据不是page分页数据,所以我们直接使用list即可。
-
返回的数据不是entity中的所有数据,我们就可以通过封装为vo来进行解决返回数据的问题。
2、新建BrandVo
@Data
public class BrandVo {
private Long brandId;
private String brandName;
}
3、CategoryBrandRelationController
/**
* value="catId",required = true 这样写即为设置必须要传入这个参数
*
* controller套路: --- 接收处理数据,传给service,接受service处理完的数据
* 1.controller:处理请求,接受和校验数据
* 2.service接受controller传来的数据,进行业务处理
* 3.controller接受service处理完的数据,封装页面指定的vo
*
* @param catId
* @return
*/
@GetMapping("/brands/list")
public R relationBrandsList(@RequestParam(value="catId",required = true) Long catId){
List<BrandEntity> vos = categoryBrandRelationService.getBrandsByCatId(catId);
/**
* 有一个疑问,这个地方的item为什么是BrandEntity?
* 解释:java.util.stream.Stream<T>
* public abstract <R> Stream<R> map(java.util.function.Function<? super T, ? extends R> mapper)
*/
List<BrandVo> collect = vos.stream().map(item -> {
BrandVo brandVo = new BrandVo();
brandVo.setBrandId(item.getBrandId());
//因为brandentity中的品牌名是name,不是BrandName,所以不能进行属性对拷
brandVo.setBrandName(item.getName());
return brandVo;
}).collect(Collectors.toList());
//返回的数据是"key:data",这个地方put(key,value).
return R.ok().put("data",collect);
4、CategoryBrandRelationServiceImpl
@Autowired
BrandService brandService;
@Override
public List<BrandEntity> getBrandsByCatId(Long catId) {
List<CategoryBrandRelationEntity> catelogId = this.baseMapper.selectList(new QueryWrapper<CategoryBrandRelationEntity>().eq("catelog_id", catId));
List<BrandEntity> collect = catelogId.stream().map(item -> {
Long brandId = item.getBrandId();
BrandEntity byId = brandService.getById(brandId);
return byId;
}).collect(Collectors.toList());
return collect;
}
5、测试
我们上面的代码已经完全没有问题了,但是为什么进行测试的收回出现错误呢?
前端项目中没有引入pubsub.js,这个是只要按照老师进度,都会范的错误,接下来进行解决。

6、踩坑及解决办法
下图是翻自b站评论区的一个解决方法, 注意这个地方仍然会有坑,那就是node.js以及node-sass这个版本的问题。

- vscode终端中执行 npm install --save pubsub-js报错

- 报错原因是:node.js版本太高,我们需要降级为和老师课件一致的版本才行。即node.js:10.16.3

- 发现是Nodejs的版本问题导致的一直安装失败,我们选择最简单的使用nvm来管理nodejs版本,方便切换。
- https://blog.csdn.net/weixin_48947842/article/details/122177655 安装nvm 使用 nvm来进行nodejs版本切换
附:nvm最新下载地址:https://github.com/coreybutler/nvm-windows/releases
-
下载.exe安装包进行安装
-

-
查看nvm版本

- 使用nvm进行nodejs版本切换

-
切换版本之后将本地前端项目renren-fast-vue中的node-modules这个文件夹给删除就好了。后面这个node-modules文件夹中的内容会在你进行任何Npm操作时自动生成。
-
进行npm install --save pubsub-js 之后继续报错,查看报错原因:node-sass版本问题。

- 解决办法:

- 先进行npm uninstall node-sass,然后安装指定版本: npm install node-sass@4.9.0 ,最后进行npm install --save pubsub-js.

再次执行安装之后不报错了。
- 在前端项目renren-fast-vue的src的main.js中引用:
- import PubSub from ‘pubsub-js’
- Vue.prototype.PubSub = PubSub
如下图:

- 重新运行前端项目,成功解决。

- 还有一个坑,就是在前面我们复制的前端组件中有一些方法是缺失的,缺少某些方法,我们可以重新进行复制。


- 将老师课件中的压缩包gulimall-admin-vue解压,将里面的modules下的文件全部重新复制到前端项目中。


6.6.3 获取分类下所有分组以及属性


1、数据处理
- 在这个地方老师的课件的数据是如下图的这些数据,所以我们可以尝试将这一部分的数据进行修改。




这里面的快速展示我们可以不需要全部都选,因为有一些无关紧要的设置我们可以不在商品介绍页面进行展示。还有一些销售属性的attrType是2,既是销售属性也是规格参数,所以有些可以选,有些可以不选。
- 将数据库中的部分数据换成和老师课件中一样的。
注意
因为我们在数据库中设计某些表的时候都是设置了主键的,我们拿pms_attr这张表为例。如果我们后期想要进行数据的添加(直接导入老师sql中的数据,避免我们自己手动添加出错),但是因为设置了主键的原因,你的attr_id这个主键是会不连续的。这个即使删除数据也没办法。那么如何解决下面的这个问题呢?


- sql文件
//从表中去掉这个主键这一列
ALTER TABLE `pms_attr` DROP attr_id;
//重新加上主键这一列并设置 为主键
ALTER TABLE `pms_attr` ADD attr_id INT NOT NULL PRIMARY KEY AUTO_INCREMENT FIRST;
-- ----------------------------
-- Records of pms_attr
-- ----------------------------
INSERT INTO `pms_attr` VALUES ('7', '入网型号', '0', '0', 'xxx', 'A2217;C3J;以官网信息为准', '1', '1', '225', '0');
INSERT INTO `pms_attr` VALUES ('8', '上市年份', '0', '0', 'xxx', '2018;2019', '1', '1', '225', '0');
INSERT INTO `pms_attr` VALUES ('9', '颜色', '0', '0', 'xxx', '黑色;白色;蓝色', '0', '1', '225', '0');
INSERT INTO `pms_attr` VALUES ('10', '内存', '0', '0', 'xxx', '4GB;6GB;8GB;12GB', '0', '1', '225', '0');
INSERT INTO `pms_attr` VALUES ('11', '机身颜色', '0', '0', 'xxx', '黑色;白色', '1', '1', '225', '1');
INSERT INTO `pms_attr` VALUES ('12', '版本', '0', '0', 'xxx', '', '0', '1', '225', '0');
INSERT INTO `pms_attr` VALUES ('13', '机身长度(mm)', '0', '0', 'xx', '158.3;135.9', '1', '1', '225', '0');
INSERT INTO `pms_attr` VALUES ('14', '机身材质工艺', '0', '1', 'xxx', '以官网信息为准;陶瓷;玻璃', '1', '1', '225', '0');
INSERT INTO `pms_attr` VALUES ('15', 'CPU品牌', '1', '0', 'xxx', '高通(Qualcomm);海思(Hisilicon);以官网信息为准', '1', '1', '225', '1');
INSERT INTO `pms_attr` VALUES ('16', 'CPU型号', '1', '0', 'xxx', '骁龙665;骁龙845;骁龙855;骁龙730;HUAWEI Kirin 980;HUAWEI Kirin 970', '1', '1', '225', '0');
-- ----------------------------
-- Records of pms_attr_attrgroup_relation
-- ----------------------------
INSERT INTO `pms_attr_attrgroup_relation` VALUES ('23', '7', '1', NULL);
INSERT INTO `pms_attr_attrgroup_relation` VALUES ('24', '8', '1', NULL);
INSERT INTO `pms_attr_attrgroup_relation` VALUES ('26', '11', '2', NULL);
INSERT INTO `pms_attr_attrgroup_relation` VALUES ('27', '13', '2', NULL);
INSERT INTO `pms_attr_attrgroup_relation` VALUES ('28', '14', '2', NULL);
INSERT INTO `pms_attr_attrgroup_relation` VALUES ('29', '15', '7', NULL);
INSERT INTO `pms_attr_attrgroup_relation` VALUES ('30', '16', '7', NULL);
-- ----------------------------
-- Records of pms_attr_group
-- ----------------------------
INSERT INTO `pms_attr_group` VALUES ('1', '主体', '0', '主体', 'dd', '225');
INSERT INTO `pms_attr_group` VALUES ('2', '基本信息', '0', '基本信息', 'xx', '225');
INSERT INTO `pms_attr_group` VALUES ('4', '屏幕', '0', '屏幕', 'xx', '233');
INSERT INTO `pms_attr_group` VALUES ('7', '主芯片', '0', '主芯片', 'xx', '225');
-- ----------------------------
-- Records of pms_category_brand_relation
-- ----------------------------
INSERT INTO `pms_category_brand_relation` VALUES ('13', '9', '225', '华为', '手机');
INSERT INTO `pms_category_brand_relation` VALUES ('15', '9', '250', '华为', '平板电视');
INSERT INTO `pms_category_brand_relation` VALUES ('16', '9', '449', '华为', '笔记本');
INSERT INTO `pms_category_brand_relation` VALUES ('17', '10', '449', '小米', '笔记本');
INSERT INTO `pms_category_brand_relation` VALUES ('18', '10', '225', '小米', '手机');
INSERT INTO `pms_category_brand_relation` VALUES ('19', '10', '231', '小米', '移动电源');
INSERT INTO `pms_category_brand_relation` VALUES ('20', '10', '233', '小米', '蓝牙耳机');
INSERT INTO `pms_category_brand_relation` VALUES ('21', '10', '250', '小米', '平板电视');
INSERT INTO `pms_category_brand_relation` VALUES ('22', '10', '449', '小米', '笔记本');
INSERT INTO `pms_category_brand_relation` VALUES ('23', '11', '225', 'oppo', '手机');
INSERT INTO `pms_category_brand_relation` VALUES ('24', '11', '227', 'oppo', '合约机');
INSERT INTO `pms_category_brand_relation` VALUES ('25', '12', '225', 'Apple', '手机');
INSERT INTO `pms_category_brand_relation` VALUES ('26', '12', '243', 'Apple', 'iPhone 配件');
INSERT INTO `pms_category_brand_relation` VALUES ('27', '12', '366', 'Apple', '智能手表');
-- ----------------------------
-- Records of pms_product_attr_value
-- ----------------------------
INSERT INTO `pms_product_attr_value` VALUES ('55', '13', '7', '入网型号', 'A2217', NULL, '0');
INSERT INTO `pms_product_attr_value` VALUES ('56', '13', '8', '上市年份', '2018', NULL, '0');
INSERT INTO `pms_product_attr_value` VALUES ('57', '13', '13', '机身长度(mm)', '158.3', NULL, '0');
INSERT INTO `pms_product_attr_value` VALUES ('58', '13', '14', '机身材质工艺', '以官网信息为准', NULL, '0');
INSERT INTO `pms_product_attr_value` VALUES ('59', '13', '15', 'CPU品牌', '以官网信息为准', NULL, '1');
INSERT INTO `pms_product_attr_value` VALUES ('60', '13', '16', 'CPU型号', 'A13仿生', NULL, '1');
INSERT INTO `pms_product_attr_value` VALUES ('61', '11', '7', '入网型号', 'LIO-AL00', NULL, '1');
INSERT INTO `pms_product_attr_value` VALUES ('62', '11', '8', '上市年份', '2019', NULL, '0');
INSERT INTO `pms_product_attr_value` VALUES ('63', '11', '11', '机身颜色', '黑色', NULL, '1');
INSERT INTO `pms_product_attr_value` VALUES ('64', '11', '13', '机身长度(mm)', '158.3', NULL, '1');
INSERT INTO `pms_product_attr_value` VALUES ('65', '11', '14', '机身材质工艺', '玻璃;陶瓷', NULL, '0');
INSERT INTO `pms_product_attr_value` VALUES ('66', '11', '15', 'CPU品牌', '海思(Hisilicon)', NULL, '1');
INSERT INTO `pms_product_attr_value` VALUES ('67', '11', '16', 'CPU型号', 'HUAWEI Kirin 970', NULL, '1');
上面这些都是可以自己按照老师课件来进行设置的,但是对于图集这些是因为使用的我们自己的oss云存储服务,所以我们这块需要自己进行设置了。
2、新建AttrGroupWithAttrsVo
从接口文档中的响应数据来看,我们发现返回的data数据中有一个attrs[]数组列表,返回的数据是AttrGroupEntity上增加了一个attrEntity.所以我们新建一个AttrGroupWithAttrsVo
@Data
public class AttrGroupWithAttrsVo {
/**
* 分组id
*/
private Long attrGroupId;
/**
* 组名
*/
private String attrGroupName;
/**
* 排序
*/
private Integer sort;
/**
* 描述
*/
private String descript;
/**
* 组图标
*/
private String icon;
/**
* 所属分类id
*/
private Long catelogId;
private List<AttrEntity> attrs;
}
3、AttrGroupController
@GetMapping("/{catelogId}/withattr")
public R getAttrGroupWithAttrs(@PathVariable("catelogId") Long catelogId){
//1、查出当前分类下的所有属性分组
//2、查出每个属性分组的所有属性
List<AttrGroupWithAttrsVo> vos = attrGroupService.getAttrGroupWithAttrsByCatelogId(catelogId);
return R.ok().put("data", vos);
}
4、AttrGroupServiceImpl
/*
*根据分类id查出所有的分组以及这些分组里面的属性
*@param:[catelogId]
*@return:java.util.List<com.xmh.gulimall.product.vo.AttrGroupWithAttrsVo>
*@date: 2021/8/16 21:13
*/
@Override
public List<AttrGroupWithAttrsVo> getAttrGroupWithAttrsByCatelogId(Long catelogId) {
//所有分组
List<AttrGroupEntity> attrGroupEntities = this.baseMapper.selectList(new QueryWrapper<AttrGroupEntity>().eq("catelog_id", catelogId));
List<AttrGroupWithAttrsVo> collect = attrGroupEntities.stream().map(item -> {
AttrGroupWithAttrsVo attrsVo = new AttrGroupWithAttrsVo();
BeanUtils.copyProperties(item, attrsVo);
//属性
List<AttrEntity> relationAttr = attrService.getRelationAttr(item.getAttrGroupId());
attrsVo.setAttrs(relationAttr);
return attrsVo;
}).collect(Collectors.toList());
return collect;
}
6.6.4 新增商品
1、举例
在商品系统–商品维护–发布商品中添加商品。本次课堂案例以华为 HUAWEI Mate 30 PRO为例

我们可以在发布商品的时候设置一些折扣优惠信息。

2、json转java实体类
当发布商品的所有信息都选择好之后,我们查看控制台,得到一串json数据。将其拷贝,通过json工具将我们得到的数据进行抽取,按照我们指定的包名和总的vo名,生成相应的java实体类。
json格式化工具:
https://www.bejson.com/,json生成java类:https://www.bejson.com/json2javapojo/new/

{"spuName":"华为 HUAWEI Mate 30 PRO","spuDescription":"华为 HUAWEI Mate 30 PRO","catalogId":225,"brandId":9,"weight":0.198,"publishStatus":0,"decript":["https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/cc346fa8-da32-47e4-8e17-34a4581022ee_b094601548ddcb1b.jpg","https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/e3ab0c02-cc48-4405-89a2-7e9141ff2b41_528211b97272d88a.jpg"],"images":["https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/18137163-649c-4120-86a4-e1a8b4c6f4c6_1f15cdbcf9e1273c.jpg","https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/e80ada95-24cf-4cbf-8320-9e5049b67627_1f15cdbcf9e1273c.jpg","https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/8851510f-3925-462d-87ce-8c1305f8a609_2b1837c6c50add30.jpg","https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/25b7a3a3-9f1c-4516-8b90-8ccf6b861733_3c24f9cd69534030.jpg","https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/87da3ec8-1e1c-4166-8644-01f88f5f7368_8bf441260bffa42f.jpg","https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/f8268208-13cf-43c6-8898-8bc441ee3cf2_23d9fbb256ea5d4a.jpg","https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/d85785f0-7d3f-4902-81a9-89bdea984c94_73ab4d2e818d2211.jpg","https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/bbd19dac-5258-4d05-8c34-34535e330bfc_919c850652e98031.jpg","https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/016564c5-e143-4171-8d79-c1dcda706b1c_b5c6b23d01dcdf81.jpg","https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/123a9df0-ca69-4b20-8bb3-07d057b58027_a83bf5250e14caf2.jpg","https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/56f7d57e-0cc1-4999-8a4c-fc4b63a32e35_a0b8774404ae8a2c.jpg","https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/18c5ab23-5be5-4080-87d8-0aae35fd9039_f753fc5c13707e7e.jpg","https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/49daceaa-13de-4a7d-8ff5-db3e37c8f209_335b2c690e43a8f8.jpg","https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/c0666be8-70a0-4106-878b-3a27bdf190a8_28f296629cca865e.jpg"],"bounds":{"buyBounds":500,"growBounds":500},"baseAttrs":[{"attrId":7,"attrValues":"LIO-AL00","showDesc":1},{"attrId":8,"attrValues":"2019","showDesc":1},{"attrId":13,"attrValues":"158.3","showDesc":0},{"attrId":14,"attrValues":"其他","showDesc":0},{"attrId":15,"attrValues":"海思(Hisilicon)","showDesc":1},{"attrId":16,"attrValues":"HUAWEI Kirin 980","showDesc":1}],"skus":[{"attr":[{"attrId":9,"attrName":"颜色","attrValue":"亮黑色"},{"attrId":12,"attrName":"版本","attrValue":"8GB+128GB"}],"skuName":"华为 HUAWEI Mate 30 PRO 亮黑色 8GB+128GB","price":"5799","skuTitle":"华为 HUAWEI Mate 30 Pro 亮黑色 8GB+128GB麒麟990旗舰芯片OLED环幕屏双4000万徕卡电影四摄4G全网通手机","skuSubtitle":"【现货抢购!享白条12期免息!】麒麟990,OLED环幕屏双4000万徕卡电影四摄;Mate30系列享12期免息》","images":[{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/87da3ec8-1e1c-4166-8644-01f88f5f7368_8bf441260bffa42f.jpg","defaultImg":1},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/49daceaa-13de-4a7d-8ff5-db3e37c8f209_335b2c690e43a8f8.jpg","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/c0666be8-70a0-4106-878b-3a27bdf190a8_28f296629cca865e.jpg","defaultImg":0}],"descar":["亮黑色","8GB+128GB"],"fullCount":0,"discount":0,"countStatus":0,"fullPrice":0,"reducePrice":0,"priceStatus":0,"memberPrice":[{"id":2,"name":"铜牌会员","price":0},{"id":3,"name":"银牌会员","price":0}]},{"attr":[{"attrId":9,"attrName":"颜色","attrValue":"亮黑色"},{"attrId":12,"attrName":"版本","attrValue":"8GB+256GB"}],"skuName":"华为 HUAWEI Mate 30 PRO 亮黑色 8GB+256GB","price":"6299","skuTitle":"华为 HUAWEI Mate 30 Pro 亮黑色 8GB+256GB麒麟990旗舰芯片OLED环幕屏双4000万徕卡电影四摄4G全网通手机","skuSubtitle":"【现货抢购!享白条12期免息!】麒麟990,OLED环幕屏双4000万徕卡电影四摄;Mate30系列享12期免息》","images":[{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/87da3ec8-1e1c-4166-8644-01f88f5f7368_8bf441260bffa42f.jpg","defaultImg":1},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/49daceaa-13de-4a7d-8ff5-db3e37c8f209_335b2c690e43a8f8.jpg","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/c0666be8-70a0-4106-878b-3a27bdf190a8_28f296629cca865e.jpg","defaultImg":0}],"descar":["亮黑色","8GB+256GB"],"fullCount":0,"discount":0,"countStatus":0,"fullPrice":0,"reducePrice":0,"priceStatus":0,"memberPrice":[{"id":2,"name":"铜牌会员","price":0},{"id":3,"name":"银牌会员","price":0}]},{"attr":[{"attrId":9,"attrName":"颜色","attrValue":"翡翠冷"},{"attrId":12,"attrName":"版本","attrValue":"8GB+128GB"}],"skuName":"华为 HUAWEI Mate 30 PRO 翡翠冷 8GB+128GB","price":"5799","skuTitle":"华为 HUAWEI Mate 30 Pro 翡冷翠 8GB+128GB麒麟990旗舰芯片OLED环幕屏双4000万徕卡电影四摄4G全网通手机","skuSubtitle":"【现货抢购!享白条12期免息!】麒麟990,OLED环幕屏双4000万徕卡电影四摄;Mate30系列享12期免息》","images":[{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/f8268208-13cf-43c6-8898-8bc441ee3cf2_23d9fbb256ea5d4a.jpg","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/016564c5-e143-4171-8d79-c1dcda706b1c_b5c6b23d01dcdf81.jpg","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/123a9df0-ca69-4b20-8bb3-07d057b58027_a83bf5250e14caf2.jpg","defaultImg":1},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0}],"descar":["翡翠冷","8GB+128GB"],"fullCount":0,"discount":0,"countStatus":0,"fullPrice":0,"reducePrice":0,"priceStatus":0,"memberPrice":[{"id":2,"name":"铜牌会员","price":0},{"id":3,"name":"银牌会员","price":0}]},{"attr":[{"attrId":9,"attrName":"颜色","attrValue":"翡翠冷"},{"attrId":12,"attrName":"版本","attrValue":"8GB+256GB"}],"skuName":"华为 HUAWEI Mate 30 PRO 翡翠冷 8GB+256GB","price":"6299","skuTitle":"华为 HUAWEI Mate 30 Pro 罗兰紫 8GB+256GB麒麟990旗舰芯片OLED环幕屏双4000万徕卡电影四摄4G全网通手机","skuSubtitle":"【现货抢购!享白条12期免息!】麒麟990,OLED环幕屏双4000万徕卡电影四摄;Mate30系列享12期免息》","images":[{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/f8268208-13cf-43c6-8898-8bc441ee3cf2_23d9fbb256ea5d4a.jpg","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/016564c5-e143-4171-8d79-c1dcda706b1c_b5c6b23d01dcdf81.jpg","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/123a9df0-ca69-4b20-8bb3-07d057b58027_a83bf5250e14caf2.jpg","defaultImg":1},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0}],"descar":["翡翠冷","8GB+256GB"],"fullCount":0,"discount":0,"countStatus":0,"fullPrice":0,"reducePrice":0,"priceStatus":0,"memberPrice":[{"id":2,"name":"铜牌会员","price":0},{"id":3,"name":"银牌会员","price":0}]},{"attr":[{"attrId":9,"attrName":"颜色","attrValue":"罗兰紫"},{"attrId":12,"attrName":"版本","attrValue":"8GB+128GB"}],"skuName":"华为 HUAWEI Mate 30 PRO 罗兰紫 8GB+128GB","price":"5799","skuTitle":"华为 HUAWEI Mate 30 Pro 罗兰紫 8GB+128GB麒麟990旗舰芯片OLED环幕屏双4000万徕卡电影四摄4G全网通手机","skuSubtitle":"【现货抢购!享白条12期免息!】麒麟990,OLED环幕屏双4000万徕卡电影四摄;Mate30系列享12期免息》","images":[{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/d85785f0-7d3f-4902-81a9-89bdea984c94_73ab4d2e818d2211.jpg","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/bbd19dac-5258-4d05-8c34-34535e330bfc_919c850652e98031.jpg","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/18c5ab23-5be5-4080-87d8-0aae35fd9039_f753fc5c13707e7e.jpg","defaultImg":1},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0}],"descar":["罗兰紫","8GB+128GB"],"fullCount":0,"discount":0,"countStatus":0,"fullPrice":0,"reducePrice":0,"priceStatus":0,"memberPrice":[{"id":2,"name":"铜牌会员","price":0},{"id":3,"name":"银牌会员","price":0}]},{"attr":[{"attrId":9,"attrName":"颜色","attrValue":"罗兰紫"},{"attrId":12,"attrName":"版本","attrValue":"8GB+256GB"}],"skuName":"华为 HUAWEI Mate 30 PRO 罗兰紫 8GB+256GB","price":"6299","skuTitle":"华为 HUAWEI Mate 30 Pro 罗兰紫 8GB+256GB麒麟990旗舰芯片OLED环幕屏双4000万徕卡电影四摄4G全网通手机","skuSubtitle":"【现货抢购!享白条12期免息!】麒麟990,OLED环幕屏双4000万徕卡电影四摄;Mate30系列享12期免息》","images":[{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/d85785f0-7d3f-4902-81a9-89bdea984c94_73ab4d2e818d2211.jpg","defaultImg":1},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/bbd19dac-5258-4d05-8c34-34535e330bfc_919c850652e98031.jpg","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/18c5ab23-5be5-4080-87d8-0aae35fd9039_f753fc5c13707e7e.jpg","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0}],"descar":["罗兰紫","8GB+256GB"],"fullCount":0,"discount":0,"countStatus":0,"fullPrice":0,"reducePrice":0,"priceStatus":0,"memberPrice":[{"id":2,"name":"铜牌会员","price":0},{"id":3,"name":"银牌会员","price":0}]},{"attr":[{"attrId":9,"attrName":"颜色","attrValue":"星河银"},{"attrId":12,"attrName":"版本","attrValue":"8GB+128GB"}],"skuName":"华为 HUAWEI Mate 30 PRO 星河银 8GB+128GB","price":"5799","skuTitle":"华为 HUAWEI Mate 30 Pro 星河银 8GB+128GB麒麟990旗舰芯片OLED环幕屏双4000万徕卡电影四摄4G全网通手机","skuSubtitle":"【现货抢购!享白条12期免息!】麒麟990,OLED环幕屏双4000万徕卡电影四摄;Mate30系列享12期免息》","images":[{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/d85785f0-7d3f-4902-81a9-89bdea984c94_73ab4d2e818d2211.jpg","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/bbd19dac-5258-4d05-8c34-34535e330bfc_919c850652e98031.jpg","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/18c5ab23-5be5-4080-87d8-0aae35fd9039_f753fc5c13707e7e.jpg","defaultImg":1},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0}],"descar":["星河银","8GB+128GB"],"fullCount":0,"discount":0,"countStatus":0,"fullPrice":0,"reducePrice":0,"priceStatus":0,"memberPrice":[{"id":2,"name":"铜牌会员","price":0},{"id":3,"name":"银牌会员","price":0}]},{"attr":[{"attrId":9,"attrName":"颜色","attrValue":"星河银"},{"attrId":12,"attrName":"版本","attrValue":"8GB+256GB"}],"skuName":"华为 HUAWEI Mate 30 PRO 星河银 8GB+256GB","price":"6299","skuTitle":"华为 HUAWEI Mate 30 Pro 星河银 8GB+256GB麒麟990旗舰芯片OLED环幕屏双4000万徕卡电影四摄4G全网通手机","skuSubtitle":"【现货抢购!享白条12期免息!】麒麟990,OLED环幕屏双4000万徕卡电影四摄;Mate30系列享12期免息》","images":[{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/d85785f0-7d3f-4902-81a9-89bdea984c94_73ab4d2e818d2211.jpg","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/bbd19dac-5258-4d05-8c34-34535e330bfc_919c850652e98031.jpg","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0},{"imgUrl":"https://gulimall-2022-11.oss-cn-hangzhou.aliyuncs.com/2022-11-17/18c5ab23-5be5-4080-87d8-0aae35fd9039_f753fc5c13707e7e.jpg","defaultImg":1},{"imgUrl":"","defaultImg":0},{"imgUrl":"","defaultImg":0}],"descar":["星河银","8GB+256GB"],"fullCount":0,"discount":0,"countStatus":0,"fullPrice":0,"reducePrice":0,"priceStatus":0,"memberPrice":[{"id":2,"name":"铜牌会员","price":0},{"id":3,"name":"银牌会员","price":0}]}]}


- 解压复制到项目中。


-
微调vo:我们查看生成的代码,发现有get和set方法,我们这个地方可以将这些方法进行去掉,然后使用lombok中的@Data注解即可。把所有id字段改成Long类型,把所有double类型改成BigDecimal类型。
-
真实项目中需要进行校验,校验是当传过来的数据不符合之后,给前端返回相应的异常错误代码。
-
这个地方为了防止和老师所讲代码有差别,直接将老师课件中的代码复制到product包下的vo包中即可。
3、接口文档

4、思路梳理
保存商品涉及到多个表之间的关系。我们先要搞清楚到底需要保存哪些东西?
-
保存spu基本信息 — pms_spu_info
-
保存spu的描述图片 — pms_spu_info_desc
-
保存spu的图片集 — pms_spu_images
-
保存spu的规格参数 — pms_product_attr_value
-
保存spu的积分信息 — gulimall_sms->sms_spu_bounds
-
保存spu对应的所有sku信息
-
- sku的基本信息 — pms_sku_info
- sku的图片信息 — pms_sku_images
- sku的销售属性信息 — pms_sku_sale_attr_value
- sku的优惠、满减等信息 — gulimall_sms->sms_sku_ladder/sms_sku_full_reduction/sms_member_price
5、具体实现
这些基础是要将数据库中表的字段要熟悉
SpuInfoController
/**
* 保存
*/
@RequestMapping("/save")
public R save(@RequestBody SpuSaveVo vo){
// spuInfoService.save(spuInfo);
spuInfoService.saveSpuInfo(vo);
return R.ok();
}
6、SpuInfoServiceImpl
(此处代码是下一步debug之后完整可正常运行的代码,和老师讲课正常章节有所出入)
/**
* //TODO 高级部分再来完善
*/
@Service("spuInfoService")
public class SpuInfoServiceImpl extends ServiceImpl<SpuInfoDao, SpuInfoEntity> implements SpuInfoService {
@Autowired
SpuInfoDescService spuInfoDescService;
@Autowired
SpuImagesService imagesService;
@Autowired
AttrService attrService;
@Autowired
ProductAttrValueService attrValueService;
@Autowired
SkuInfoService skuInfoService;
@Autowired
SkuImagesService skuImagesService;
@Autowired
SkuSaleAttrValueService skuSaleAttrValueService;
@Autowired
CouponFeignService couponFeignService;
@Transactional
@Override
public void saveSpuInfo(SpuSaveVo vo) {
//1、保存spu基本信息`pms_spu_info`
SpuInfoEntity infoEntity = new SpuInfoEntity();
//将vo中携带的数据复制给infoEntity 属性对拷
BeanUtils.copyProperties(vo,infoEntity);
//infoEntity中还需要有CreateTime,UpdateTime,属性对拷未复制完全的我们可以自己设置
infoEntity.setCreateTime(new Date());
infoEntity.setUpdateTime(new Date());
this.save(infoEntity);
//2、保存spu的描述图片`pms_spu_info_desc`
List<String> decript = vo.getDecript();
SpuInfoDescEntity descEntity = new SpuInfoDescEntity();
descEntity.setSpuId(infoEntity.getId());
//使用,进行分割拼接,因为图片都被转换为了链接
descEntity.setDecript(String.join(",",decript));
spuInfoDescService.saveSpuInfoDesc(descEntity);
//3、保存spu的图片集`pms_spu_images`
List<String> images = vo.getImages();
imagesService.saveImages(infoEntity.getId(),images);
//4、保存spu的规格参数`pms_product_attr_value`
List<BaseAttrs> baseAttrs = vo.getBaseAttrs();
List<ProductAttrValueEntity> collect = baseAttrs.stream().map(attr -> {
ProductAttrValueEntity valueEntity = new ProductAttrValueEntity();
valueEntity.setAttrId(attr.getAttrId());
// spu_id :13 attr_id:7 attr_name:入网型号 attr_value:A2217 quick_show :0
AttrEntity id = attrService.getById(attr.getAttrId());
valueEntity.setAttrName(id.getAttrName());
valueEntity.setAttrValue(attr.getAttrValues());
valueEntity.setQuickShow(attr.getShowDesc());
valueEntity.setSpuId(infoEntity.getId());
return valueEntity;
}).collect(Collectors.toList());
attrValueService.saveProductAttr(collect);
//5、保存spu的积分信息`gulimall_sms`->`sms_spu_bounds`
Bounds bounds = vo.getBounds();
SpuBoundTo spuBoundTo = new SpuBoundTo();
BeanUtils.copyProperties(bounds,spuBoundTo);
spuBoundTo.setSpuId(infoEntity.getId());
R r = couponFeignService.saveSpuBounds(spuBoundTo);
if(r.getCode() != 0){
log.error("远程保存spu积分信息失败");
}
//6、保存spu对应的所有sku信息
List<Skus> skus = vo.getSkus();
if(skus != null && skus.size()>0){
skus.forEach(item ->{
String defaultImage = "";
//查找出默认图片
for (Images image: item.getImages()) {
//0 不是默认图 1:是默认图
if(image.getDefaultImg() == 1){
defaultImage = image.getImgUrl();
}
}
//private String skuName;
//private BigDecimal price;
//private string skuTitle;
//private string skuSubtitle;
SkuInfoEntity skuInfoEntity = new SkuInfoEntity();
BeanUtils.copyProperties(item,skuInfoEntity);
skuInfoEntity.setSpuId(infoEntity.getId());
skuInfoEntity.setBrandId(infoEntity.getBrandId());
skuInfoEntity.setCatalogId(infoEntity.getCatalogId());
skuInfoEntity.setSaleCount(0L);
skuInfoEntity.setSkuDefaultImg(defaultImage);
//6.1、sku的基本信息`pms_sku_info`
skuInfoService.saveSkuInfo(skuInfoEntity);
//6.2、sku的图片信息`pms_sku_images`
Long skuId = skuInfoEntity.getSkuId();
List<SkuImagesEntity> imagesEntities = item.getImages().stream().map(img -> {
SkuImagesEntity skuImagesEntity = new SkuImagesEntity();
skuImagesEntity.setSkuId(skuId);
skuImagesEntity.setImgUrl(img.getImgUrl());
skuImagesEntity.setDefaultImg(img.getDefaultImg());
return skuImagesEntity;
}).filter(entity ->{
//返回true就是需要,false就是剔除 这个地方是鉴别有些没有图片路径的就不需要进行保存
return !StringUtils.isEmpty(entity.getImgUrl());
}).collect(Collectors.toList());
skuImagesService.saveBatch(imagesEntities);
//6.3、sku的销售属性信息`pms_sku_sale_attr_value`
List<Attr> attr = item.getAttr();
List<SkuSaleAttrValueEntity> skuSaleAttrValueEntities = attr.stream().map(a -> {
SkuSaleAttrValueEntity attrValueEntity = new SkuSaleAttrValueEntity();
BeanUtils.copyProperties(a, attrValueEntity);
attrValueEntity.setSkuId(skuId);
return attrValueEntity;
}).collect(Collectors.toList());
skuSaleAttrValueService.saveBatch(skuSaleAttrValueEntities);
//6.4、sku的优惠、满减等信息`gulimall_sms`->`sms_sku_ladder`/`sms_sku_full_reduction`/`sms_member_price`
SkuReductionTo skuReductionTo = new SkuReductionTo();
BeanUtils.copyProperties(item,skuReductionTo);
skuReductionTo.setSkuId(skuId);
if(skuReductionTo.getFullCount() > 0 || skuReductionTo.getFullPrice().compareTo(new BigDecimal("0")) == 1){
R r1 = couponFeignService.saveSkuReduction(skuReductionTo);
if(r1.getCode() != 0){
log.error("远程保存sku优惠信息失败");
}
}
});
}
}
}
其中一些自动注入的service方法的实现,主要是使用的保存方法。我们使用service方法自动注入,因为其比dao更加全一些。
1. SpuInfoDescServiceImpl
@Override
public void saveSpuInfoDesc(SpuInfoDescEntity descEntity) {
this.baseMapper.insert(descEntity);
}
2. SpuImagesServiceImpl
@Override
public void saveImages(Long id, List<String> images) {
if(images == null || images.size() == 0){
}else {
List<SpuImagesEntity> collect = images.stream().map(img -> {
SpuImagesEntity spuImagesEntity = new SpuImagesEntity();
spuImagesEntity.setSpuId(id);
spuImagesEntity.setImgUrl(img);
return spuImagesEntity;
}).collect(Collectors.toList());
this.saveBatch(collect);
}
}
3. ProductAttrValueServiceImpl
@Override
public void saveProductAttr(List<ProductAttrValueEntity> collect) {
this.saveBatch(collect);
}
4. SkuInfoServiceImpl
@Override
public void saveSkuInfo(SkuInfoEntity skuInfoEntity) {
this.baseMapper.insert(skuInfoEntity);
}
7、创建TO
对于远程调用,其实就是跨表操作。我们可以创建一个TO来做远程调用

- 在common微服务中创建一个SkuReductionTo用作远程调用
@Data
public class SkuReductionTo {
private Long skuId;
private int fullCount;
private BigDecimal discount;
private int countStatus;
private BigDecimal fullPrice;
private BigDecimal reducePrice;
private int priceStatus;
private List<MemberPrice> memberPrice;
}
-
同时将json自动生成的memberPrice这个vo也复制到common中
@Data public class MemberPrice { private Long id; private String name; private BigDecimal price; }
8、新建CouponFeignService
在product包中新建fegin.CouponFeignService用来远程调用Coupon服务
一共调用了两个服务
"coupon/spubounds/save"和"coupon/skufullreduction/saveInfo"

- 第一个服务使用自动生成,直接调用即可
@FeignClient("gulimall-coupon")
public interface CouponFeignService {
/**
*1、CouponFeginService.saveSpuBounds(spuBoudnTo)的实现步骤:
* 1)、@RequestBody 将传入的这个对象转化为json
* 2)、找到gulimall-coupon服务,给coupon/spubounds/save发送请求
* 将上一步转的json放在请求体位置,发送数据
* 3)、对方服务接受请求,请求体里面有json数据
* public R save(@RequestBody SpuBoundsEntity spuBounds);
* 将请求体的json转化为SpuBoundsEntity;
* 只要json数据模型是兼容的。双方无需使用同一个to
*@param:[spuBoundTo]
*@return:com.xmh.common.utils.R
*@date: 2021/8/18 1:28
*/
@PostMapping("coupon/spubounds/save")
R saveSpuBounds(@RequestBody SpuBoundTo spuBoundTo);
@PostMapping("coupon/skufullreduction/saveInfo")
R saveSkuReduction(@RequestBody SkuReductionTo skuReductionTo);
}

- 第二个服务在
SkuFullReductionController中新建方法
@PostMapping("/saveInfo")
public R saveInfo(@RequestBody SkuReductionTo skuReductionTo){
skuFullReductionService.saveSkuReduction(skuReductionTo);
return R.ok();
}
- 在
SkuFullReductionServiceImpl中实现
@Service("skuFullReductionService")
public class SkuFullReductionServiceImpl extends ServiceImpl<SkuFullReductionDao, SkuFullReductionEntity> implements SkuFullReductionService {
@Autowired
SkuLadderService skuLadderService;
@Autowired
MemberPriceService memberPriceService;
@Override
public void saveSkuReduction(SkuReductionTo reductionTo) {
//6.4、sku的优惠、满减等信息`gulimall_sms`->`sms_sku_ladder`/`sms_sku_full_reduction`/`sms_member_price`
//sms_sku_ladder 保存满减打折,会员价
SkuLadderEntity skuLadderEntity = new SkuLadderEntity();
skuLadderEntity.setSkuId(reductionTo.getSkuId());
skuLadderEntity.setFullCount(reductionTo.getFullCount());
skuLadderEntity.setDiscount(reductionTo.getDiscount());
skuLadderEntity.setAddOther(reductionTo.getCountStatus());
if(reductionTo.getFullCount() > 0){
skuLadderService.save(skuLadderEntity);
}
skuLadderService.save(skuLadderEntity);
//sms_sku_full_reduction 保存满减信息
SkuFullReductionEntity reductionEntity = new SkuFullReductionEntity();
BeanUtils.copyProperties(reductionTo,reductionEntity);
if(reductionEntity.getFullPrice().compareTo(new BigDecimal("0")) == 1){
this.save(reductionEntity);
}
//sms_member_price 保存会员价格
List<MemberPrice> memberPrice = reductionTo.getMemberPrice();
List<MemberPriceEntity> collect = memberPrice.stream().map(item -> {
MemberPriceEntity priceEntity = new MemberPriceEntity();
priceEntity.setSkuId(reductionTo.getSkuId());
priceEntity.setMemberLevelId(item.getId());
priceEntity.setMemberLevelName(item.getName());
priceEntity.setMemberPrice(item.getPrice());
//设置默认叠加优惠
priceEntity.setAddOther(1);
return priceEntity;
}).filter(item -> {
return item.getMemberPrice().compareTo(new BigDecimal("0")) == 1;
}).collect(Collectors.toList());
memberPriceService.saveBatch(collect);
}
}
- 在给前端返回的R这个类中添加一个getCode方法,方便判断远程调用是否成功(注意这里有坑,后面debug的时候修正)
public Integer getCode(){
return Integer.parseInt((String) this.get("code"));
}
6.6.5 内存调优及一键启停
1、新建Compound


2、把服务添加到新建的compound里

3、设置每个项目最大占用内存为100M

这样可以大大减少内存占用。
6.6.6 商品保存debug
1、隔离级别
我们在进行debug的时候,因为我们在上面设置了事务的原因,而mysql默认是可重复读(REPEATABLE READ),所以我们可以暂时设置隔离级别。
如果我们使用@Transactional,不指定隔离级别,就会使用数据库的默认隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
2、问题及解决
- 出现问题
SpuInfoDescEntity,mybatis默认主键为自增的,而SpuInfoDescEntity中的主键为自己输入的,所以修改主键注释

- 抛出异常,修改
R中的getCode方法
public Integer getCode(){
return (Integer) this.get("code");
}
- 出现问题,保存sku图片时,有些图片是没有路径的,没有路径的图片,无需保存。
解决办法:在收集图片的时候进行过滤
List<SkuImagesEntity> skuImagesEntities = item.getImages().stream().map(img -> {
SkuImagesEntity skuImagesEntity = new SkuImagesEntity();
skuImagesEntity.setSkuId(skuId);
skuImagesEntity.setImgUrl(img.getImgUrl());
skuImagesEntity.setDefaultImg(img.getDefaultImg());
return skuImagesEntity;
}).filter(entity -> {
//返回true是需要,返回false是过滤掉
return !StringUtils.isNullOrEmpty(entity.getImgUrl());
}).collect(Collectors.toList());
skuImagesService.saveBatch(skuImagesEntities);
- 保存折扣信息的时候,满0元打0折这种都是无意义的,要过滤掉

解决方法:在保存之前做判断,过滤掉小于等于0的无意义信息(不贴代码了),要注意的是判断BigDecimal进行判断时,要用compareTo函数。如果是普通的,就不需要做判断。
下面举例:
if(reductionTo.getFullCount() > 0){
skuLadderService.save(skuLadderEntity);
}
if (skuReductionTo.getFullCount() > 0 || skuReductionTo.getFullPrice().compareTo(new BigDecimal("0")) == 1){
R r1 = couponFeignService.saveSkuReduction(skuReductionTo);
if (r1.getCode() != 0){
log.error("远程保存优惠信息失败");
}
}
- 保存失败,原因【系统未知异常】的原因及解决办法


保存的时候出现上面这个原因,我们去控制台中查看得知是调用远程服务超时导致。因为会去nacos中进行寻找,我们所要做的就是等待feign稳定即可。
3、新增apple 11 完善数据库
按照华为mate30pro新增方法,新增一个apple 11到数据库中。

6.7 商品管理
6.7.1 spu检索

1、SpuInfoController.java
@RequestMapping("/list")
public R list(@RequestParam Map<String, Object> params){
PageUtils page = spuInfoService.queryPageByCondition(params);
return R.ok().put("page", page);
}
2、SpuInfoServiceImpl
@Override
public PageUtils queryPageByCondition(Map<String, Object> params) {
QueryWrapper<SpuInfoEntity> wrapper = new QueryWrapper<>();
/**
* status:2
* key:
* brandId:9
* catelogId:225
*/
String key = (String) params.get("key");
if(!StringUtils.isEmpty(key)){
wrapper.and((w)->{
w.eq("id",key).or().like("spu_name",key);
});
}
String status = (String) params.get("status");
if(!StringUtils.isEmpty(status)){
wrapper.eq("publish_status",status);
}
String brandId = (String) params.get("brandId");
if(!StringUtils.isEmpty(brandId)){
wrapper.eq("brand_id",brandId);
}
String catelogId = (String) params.get("catelogId");
if(!StringUtils.isEmpty(catelogId)){
wrapper.eq("catalog_id",catelogId);
}
IPage<SpuInfoEntity> page = this.page(
new Query<SpuInfoEntity>().getPage(params),
wrapper
);
return new PageUtils(page);
}
3、测试

成功。
2022-11-18 17:07:36.366 DEBUG 44276 --- [io-11000-exec-8] c.a.g.product.dao.SpuInfoDao.selectPage : ==> Preparing: SELECT COUNT(1) FROM pms_spu_info WHERE (((id = ? OR spu_name LIKE ?)) AND publish_status = ? AND brand_id = ? AND catalog_id = ?)
2022-11-18 17:07:36.366 DEBUG 44276 --- [io-11000-exec-8] c.a.g.product.dao.SpuInfoDao.selectPage : ==> Parameters: 华为(String), %华为%(String), 0(String), 9(String), 225(String)
2022-11-18 17:07:36.368 DEBUG 44276 --- [io-11000-exec-8] c.a.g.product.dao.SpuInfoDao.selectPage : ==> Preparing: SELECT id,spu_description,spu_name,catalog_id,create_time,brand_id,weight,update_time,publish_status FROM pms_spu_info WHERE (( (id = ? OR spu_name LIKE ?) ) AND publish_status = ? AND brand_id = ? AND catalog_id = ?) LIMIT ?,?
2022-11-18 17:07:36.368 DEBUG 44276 --- [io-11000-exec-8] c.a.g.product.dao.SpuInfoDao.selectPage : ==> Parameters: 华为(String), %华为%(String), 0(String), 9(String), 225(String), 0(Long), 10(Long)
2022-11-18 17:07:36.370 DEBUG 44276 --- [io-11000-exec-8] c.a.g.product.dao.SpuInfoDao.selectPage : <== Total: 1
4、时间格式问题解决
测试发现时间格式不对,如下图:

我们可以在配置文件中进行设置:

重启进行测试,结果如下,时间格式正确。

6.7.2 sku检索

1、SkuInfoController
/**
* 列表
*/
@RequestMapping("/list")
public R list(@RequestParam Map<String, Object> params){
PageUtils page = skuInfoService.queryPageByCondition(params);
return R.ok().put("page", page);
}
2、SkuInfoServiceImpl(这里的代码对老师代码进行简写,测试可以正常通过)
@Override
public PageUtils queryPageByCondition(Map<String, Object> params) {
QueryWrapper<SkuInfoEntity> wrapper = new QueryWrapper<>();
String key = (String) params.get("key");
if (!StringUtils.isNullOrEmpty(key)){
wrapper.and((w) -> {
w.eq("sku_id", key).or().like("sku_name", key);
});
}
String catelogId = (String) params.get("catelogId");
if (!StringUtils.isNullOrEmpty(catelogId) && !"0".equalsIgnoreCase(catelogId)){
wrapper.eq("catalog_id", catelogId);
}
String brandId = (String) params.get("brandId");
if (!StringUtils.isNullOrEmpty(brandId) && !"0".equalsIgnoreCase(brandId)){
wrapper.eq("brand_id", brandId);
}
String min = (String) params.get("min");
if (!StringUtils.isNullOrEmpty(min) && !"0".equalsIgnoreCase(min)){
wrapper.ge("price", min);
}
String max = (String) params.get("max");
if (!StringUtils.isNullOrEmpty(max) && !"0".equalsIgnoreCase(max)){
wrapper.le("price", max);
}
IPage<SkuInfoEntity> page = this.page(
new Query<SkuInfoEntity>().getPage(params),
wrapper
);
return new PageUtils(page);
}
6.8 仓储服务之仓库管理
6.8.1 整合ware服务获取仓库列表
1、数据库表的说明
- 仓库维护 wms_ware_info


- 商品库存 wms_ware_sku


2、将仓库服务注册到nacos中

3、微服务网关路由重写
否则点击相应的菜单报404错误
- id: ware_route
uri: lb://gulimall-ware
predicates:
- Path=/api/ware/**
filters:
- RewritePath=/api/(?<segment>.*),/$\{segment}
4、启动进行测试
- 浏览器输入
127.0.0.1:8848/nacos/进行测试访问,我们查看到确实已经在nacos中有相应服务了。

5、完成仓库模糊查询功能
我们打开控制台,点击查询,查看发出的url
http://localhost:88/api/ware/wareinfo/list?t=1633696575331&page=1&limit=10&key=
然后根据这个url我们进行仓库模糊查询功能的实现。
- WareInfoController.java
@RequestMapping("/list")
//@RequiresPermissions("ware:wareinfo:list")
public R list(@RequestParam Map<String, Object> params){
PageUtils page = wareInfoService.queryPageByCondition(params);
return R.ok().put("page", page);
}
- WareInfoServiceImpl.java(模糊查询)
@Override
public PageUtils queryPageByCondition(Map<String, Object> params) {
QueryWrapper<WareInfoEntity> wrapper = new QueryWrapper<>();
String key = (String) params.get("key");
if (!StringUtils.isNullOrEmpty(key)){
wrapper.eq("id", key).or().like("name", key).or().like("address", key).or().like("areacode", key);
}
IPage<WareInfoEntity> page = this.page(
new Query<WareInfoEntity>().getPage(params),
wrapper
);
return new PageUtils(page);
}
- 设置日志输出级别,方便查看sql语句
logging:
level:
com.xmh: debug
6、进行测试
测试成功,控制台中的sql如下:
2022-11-18 19:18:08.476 DEBUG 49824 --- [io-12000-exec-4] c.a.g.ware.dao.WareInfoDao.selectPage : ==> Preparing: SELECT id,address,name,areacode FROM wms_ware_info WHERE (id = ? OR name LIKE ? OR address LIKE ? OR areacode LIKE ?)
2022-11-18 19:18:08.476 DEBUG 49824 --- [io-12000-exec-4] c.a.g.ware.dao.WareInfoDao.selectPage : ==> Parameters: 上海(String), %上海%(String), %上海%(String), %上海%(String)
2022-11-18 19:18:08.477 DEBUG 49824 --- [io-12000-exec-4] c.a.g.ware.dao.WareInfoDao.selectPage : <== Total: 1
6.8.2 查询库存的模糊查询
1、代码实现

-
WareSkuServiceImpl
/** * skuId : 1 * wareId : 2 * @param params * @return */ @Override public PageUtils queryPage(Map<String, Object> params) { QueryWrapper<WareSkuEntity> queryWrapper = new QueryWrapper<>(); String skuId = (String) params.get("skuId"); if(!StringUtils.isEmpty(skuId)){ queryWrapper.eq("sku_id",skuId); } String wareId = (String) params.get("wareId"); if(!StringUtils.isEmpty(wareId)){ queryWrapper.eq("ware_id",wareId); } IPage<WareSkuEntity> page = this.page( new Query<WareSkuEntity>().getPage(params), queryWrapper ); return new PageUtils(page); }这个模糊查询比较简单,自动生成的方法不需要重新修改,只需要在方法实现类中添加一些查询即可。
6.8.3 采购需求的模糊查询

1、PurchaseDetailController
/**
* 列表
*/
@RequestMapping("/list")
public R list(@RequestParam Map<String, Object> params){
PageUtils page = purchaseDetailService.queryPage(params);
return R.ok().put("page", page);
}
2、PurchaseDetailServiceImpl
@Service("purchaseDetailService")
public class PurchaseDetailServiceImpl extends ServiceImpl<PurchaseDetailDao, PurchaseDetailEntity> implements PurchaseDetailService {
/**
* status: 0,//状态
* wareId: 1,//仓库id
*
* @param params
* @return
*/
@Override
public PageUtils queryPage(Map<String, Object> params) {
QueryWrapper<PurchaseDetailEntity> queryWrapper = new QueryWrapper<>();
//key是模糊检索的字段
String key = (String) params.get("key");
if (!StringUtils.isEmpty(key)) {
//purchase_id sku_id
queryWrapper.and(w -> {
w.eq("purchase_id", key).or().eq("sku_id", key);
});
}
String status = (String) params.get("status");
if (!StringUtils.isEmpty(status)) {
//purchase_id sku_id
queryWrapper.eq("status", status);
}
String wareId = (String) params.get("wareId");
if (!StringUtils.isEmpty(wareId)) {
//purchase_id sku_id
queryWrapper.eq("ware_id", wareId);
}
IPage<PurchaseDetailEntity> page = this.page(
new Query<PurchaseDetailEntity>().getPage(params),
queryWrapper
);
return new PageUtils(page);
}
}
6.8.5 合并采购流程
1、思路梳理

采购单可以通过采购需求进行创建,一种是指定某个采购人员(在系统管理-管理员中创建),一种是不指定,直接将多个采购需求自动合并成采购单。


6.8.6 查询未领取的采购单


1、PurchaseController
///ware/purchase/unreceive/list
/**
* 查询未领取的采购单
*/
@RequestMapping("/unreceive/list")
public R unreceivelist(@RequestParam Map<String, Object> params){
PageUtils page = purchaseService.queryPageUnreceivePurchase(params);
return R.ok().put("page", page);
}
2、新建常量枚举类constant.WareConstant
public class WareConstant {
/** 采购单状态枚举 */
public enum PurchaseStatusEnum{
CREATED(0,"新建"),ASSIGNED(1,"已分配"),
RECEIVE(2,"已领取"),FINISH(3,"已完成"),
HASERROR(4,"有异常");
private int code;
private String msg;
PurchaseStatusEnum(int code, String msg){
this.code = code;
this.msg = msg;
}
public int getCode(){
return code;
}
public String getMsg(){
return msg;
}
}
/** 采购需求枚举 */
public enum PurchaseDetailStatusEnum{
CREATED(0,"新建"),ASSIGNED(1,"已分配"),
BUYING(2,"正在采购"),FINISH(3,"已完成"),
HASERROR(4,"采购失败");
private int code;
private String msg;
PurchaseDetailStatusEnum(int code,String msg){
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
}
3、PurchaseServiceImpl
@Override
public PageUtils queryPageUnreceivePurchase(Map<String, Object> params) {
// IPage<PurchaseEntity> page = this.page(
// new Query<PurchaseEntity>().getPage(params),
// new QueryWrapper<PurchaseEntity>().eq("status",0).eq("status",1)
// );
QueryWrapper<PurchaseEntity> wrapper = new QueryWrapper<>();
wrapper.eq("status",WareConstant.PurchaseStatusEnum.CREATED.getCode())
.or()
.eq("status",WareConstant.PurchaseStatusEnum.ASSIGNED.getCode());
IPage<PurchaseEntity> page = this.page(
new Query<PurchaseEntity>().getPage(params),
wrapper
);
return new PageUtils(page);
}
4、测试

测试成功
6.8.7 合并采购需求

可以选择需要合并的采购需求,合并到整单,也可以不选择整单id,自动创建新的采购单。
1、新建MergerVo
@Data
public class MergerVo {
private Long purchaseId; //整单id
private List<Long> items; //合并项集合
}
2、思路梳理
分配,就是修改【采购需求】里对应的【采购单id、采购需求状态】,即purchase_detail表
并且不能重复分配采购需求给不同的采购单,如果还没去采购,或者采购失败,就可以修改
- PurchaseController
@PostMapping("/merge")
//@RequiresPermissions("ware:purchase:list")
public R merge(@RequestBody MergeVo mergeVo){
purchaseService.mergePurchase(mergeVo);
return R.ok();
}
- PurchaseServiceImpl
@Transactional
@Override
public void mergePurchase(MergeVo mergeVo) {
//获取采购单id
Long purchaseId = mergeVo.getPurchaseId();
//如果采购id为null 说明没选择采购单
if(purchaseId == null){
//没有选择就新建采购单
PurchaseEntity purchaseEntity = new PurchaseEntity();
purchaseEntity.setStatus(WareConstant.PurchaseStatusEnum.CREATED.getCode());
this.save(purchaseEntity);
purchaseId = purchaseEntity.getId();
}
//合并采购需求 (多个采购需求的集合 items[])
List<Long> items = mergeVo.getItems();
Long finalPurchaseId = purchaseId;
// PurchaseDetailEntity 采购需求
List<PurchaseDetailEntity> list = detailService.getBaseMapper().selectBatchIds(items).stream().filter(entity -> {
//如果还没去采购,或者采购失败,就可以修改 <= 4
return entity.getStatus() < WareConstant.PurchaseDetailStatusEnum.HASERROR.getCode()
|| entity.getStatus() == WareConstant.PurchaseDetailStatusEnum.HASERROR.getCode();
}).map(entity -> {
//修改状态,以及采购id
entity.setStatus(WareConstant.PurchaseDetailStatusEnum.ASSIGNED.getCode());
entity.setPurchaseId(finalPurchaseId);
return entity;
}).collect(Collectors.toList());
detailService.updateBatchById(list);
//设置创建时间和更新时间
PurchaseEntity purchaseEntity1 = new PurchaseEntity();
purchaseEntity1.setId(purchaseId);
purchaseEntity1.setCreateTime(new Date());
purchaseEntity1.setUpdateTime(new Date());
this.updateById(purchaseEntity1);
}
在修改测试后,发现时间如下图对这样,我们可以重新设置下时间。

在配置文件中对json进行格式化
jackson:
date-format: yyyy-MM-dd HH:mm:ss
测试成功:

6.8.8 领取采购单
1、思路梳理
采购单分配给了采购人员,采购人员在手机端领取采购单,此时的采购单应该为新建或已分配状态,在采购人员领取后采购单的状态变为已领取,采购需求的状态变为正在采购。

我们可以使用自己的接口调试工具进行测试。本次调试使用apifox.

2、PurchaseController
/**
* 领取采购单/ware/purchase/received
*/
@PostMapping("/received")
//@RequiresPermissions("ware:purchase:list")
public R received(@RequestBody List<Long> ids){
purchaseService.received(ids);
return R.ok();
}
3、PurchaseServiceImpl
@Transactional
@Override
public void received(List<Long> ids) {
//没有采购需求直接返回,否则会破坏采购单
if(ids == null || ids.size() == 0){
return ;
}
List<PurchaseEntity> collect = this.getBaseMapper().selectBatchIds(ids).stream().filter(entity -> {
//确保采购单的状态是新建或者已分配
return entity.getStatus() <= WareConstant.PurchaseStatusEnum.ASSIGNED.getCode();
}).map(entity -> {
//修改采购单的状态为已领取
entity.setStatus(WareConstant.PurchaseStatusEnum.RECEIVE.getCode());
return entity;
}).collect(Collectors.toList());
this.updateBatchById(collect);
//修改该采购单下的所有采购需求的状态为正在采购
UpdateWrapper<PurchaseDetailEntity> updateWrapper = new UpdateWrapper<>();
updateWrapper.in("purchase_id",ids);
PurchaseDetailEntity purchaseDetailEntity = new PurchaseDetailEntity();
purchaseDetailEntity.setStatus(WareConstant.PurchaseDetailStatusEnum.BUYING.getCode());
detailService.update(purchaseDetailEntity,updateWrapper);
}
4、测试


测试成功。
6.8.9 完成采购

1、完成采购的步骤:
- 判断所有采购需求的状态,采购需求全部完成时,采购单状态才为完成
- 采购项完成的时候,增加库存(调用远程获取skuName)
- 加上分页插件
2、新建PurchaseItemDoneVo
@Data
public class PurchaseItemDoneVo {
private Long itemId;
private Integer status;
private String reason;
}
PurchaseDoneVo
@Data
public class PurchaseDoneVo {
private Long id;
private List<PurchaseItemDoneVo> items;
}
3、PurchaseController
/**
* 完成采购
*/
@PostMapping("/done")
//@RequiresPermissions("ware:purchase:list")
public R received(@RequestBody PurchaseDoneVo vo){
purchaseService.done(vo);
return R.ok();
}
4、PurchaseServiceImpl
@Autowired
private WareSkuService wareSkuService;
@Autowired
private ProductFeignService feignService;
@Override
public void done(PurchaseDoneVo vo) {
//1.根据前端发过来的信息,更新采购需求的状态
List<PurchaseItemDoneVo> items = vo.getItems();
ArrayList<PurchaseDetailEntity> updateList = new ArrayList<>();
boolean flag = true;
for (PurchaseItemDoneVo item:items) {
Long detailId = item.getItemId();
PurchaseDetailEntity detailEntity = detailService.getById(detailId);
detailEntity.setStatus(item.getStatus());
//采购需求失败
if (item.getStatus() == WareConstant.PurchaseDetailStatusEnum.HASERROR.getCode()){
flag = false;
}else{
//3.根据采购需求的状态,更新库存
/**
* sku_id,sku_num,ware_id,
* sku_id,ware_id,stock sku_name(调用远程服务获取),stock_locked(先获取已经有的库存,在加上新购买的数量)
*
*/
String skuName = "";
try{
R info = feignService.info(detailEntity.getSkuId());
if(info.getCode() == 0){
Map<String,Object> data = (Map<String, Object>) info.get("skuInfo");
skuName = (String) data.get("skuName");
}
}catch (Exception e){
}
//更新库存
wareSkuService.addStock(detailEntity.getSkuId(),detailEntity.getWareId(),skuName,detailEntity.getSkuNum());
}
updateList.add(detailEntity);
}
//保存采购需求
detailService.updateBatchById(updateList);
//2.根据采购需求的状态,更新采购单的状态
PurchaseEntity purchaseEntity = new PurchaseEntity();
purchaseEntity.setId(vo.getId());
purchaseEntity.setStatus(flag ? WareConstant.PurchaseStatusEnum.FINISH.getCode() : WareConstant.PurchaseStatusEnum.HASERROR.getCode());
this.updateById(purchaseEntity);
}
5、新建feign.ProductFeignService接口,用来远程获取skuName
- ProductFeignService
@FeignClient("gulimall-product")
public interface ProductFeignService {
@RequestMapping("/product/skuinfo/info/{skuId}")
R info(@PathVariable("skuId") Long skuId);
}

- 对于远程调用微服务,路径写法有两种,一种是过网关,则路径前面加api,而去微服务是网关;一种是正常的商品服务,商品服务完整路径。
因为这个方法我们在gulimall-product微服务中已经写过了,所以我们直接使用最简单的方法(直接复制controller中的方法)进行远程调用。
- 要想使用远程调用,我们还需要在主启动类上加入@EnableFeignClients注解,否则是无法调用远程服务的。
6、新增addStock
对于第四步中的更新库存这个地方,我们使用需要新增一个addStock方法。
- WareSkuService
void addStock(Long skuId, Long wareId, String skuName, Integer skuNum);
- WareSkuDao
void addStock(@Param("skuId") Long skuId, @Param("wareId") Long wareId, @Param("skuNum") Integer skuNum);
- WareSkuDao.xml
<update id="addStock">
UPDATE `wms_ware_sku` SET stock = stock+#{skuNum} WHERE sku_id=#{skuId} AND ware_id = #{wareId}
</update>
- WareSkuServiceImpl 实现入库操作
@Autowired
private WareSkuDao wareSkuDao;
@Autowired
private ProductFeignService productFeignService;
@Override
public void addStock(Long skuId, Long wareId, String skuName, Integer skuNum) {
//1.判断如果还没有这个库存记录 就新增
List<WareSkuEntity> entities = wareSkuDao.selectList(new QueryWrapper<WareSkuEntity>().eq("sku_id", skuId).eq("ware_id", wareId));
if(entities == null || entities.size() == 0){
WareSkuEntity skuEntity = new WareSkuEntity();
skuEntity.setSkuId(skuId);
skuEntity.setStock(skuNum);
skuEntity.setWareId(wareId);
skuEntity.setStockLocked(0); //设置一个默认锁定库存
//TODO 远程查询sku的名字,如果失败,整个事务无需回滚,因为就简简单单的因为名字没有获取到,就将整个事务进行回滚的话,太浪费数据库性能了。
// 使用catch异常
//TODO 还可以使用什么办法让异常出现后不回滚 高级篇
try {
R info = productFeignService.info(skuId);
Map<String,Object> data = (Map<String, Object>) info.get("skuInfo");
if(info.getCode() == 0){
//设置skuName
skuEntity.setSkuName((String) data.get("skuName"));
}
} catch (Exception e) {
}
wareSkuDao.insert(skuEntity);
}else {
wareSkuDao.addStock(skuId,wareId,skuNum);
}
}
7、添加分页插件
- 这个可以直接复制product中的即可。(我们可以将@EnableTransactionManagement //开启事务 @MapperScan(“com.atguigu.gulimall.ware.dao”)这两个在主启动类中加上的注解都放到分页插件的类中)
package com.atguigu.gulimall.ware.config;
@Configuration
@EnableTransactionManagement //开启事务
@MapperScan("com.atguigu.gulimall.ware.dao")
public class MyBatisConfig {
//引入分页插件 显示页码
@Bean
public PaginationInterceptor paginationInterceptor(){
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
//设置请求的页面大于最大页后操作,true调回到首页,false继续请求,默认false
paginationInterceptor.setOverflow(true);
//设置最大单页限制数量,默认500条,-1不受限制
paginationInterceptor.setLimit(1000);
return paginationInterceptor;
}
}
8、进行测试
- 先新增几个采购需求,然后不进行任何操作,自动合并成一个新的采购单。我们可以从下图中看出采购单id为12.
- 为采购单进行人员分配,然后让这个采购人员领取采购单。





- 让采购人员完成采购


- 完成采购

- 来到商品库存菜单,查看商品库存,如图,库存已增加

6.8.10 获取spu规格



1、AttrController
@Autowired
private ProductAttrValueService productAttrValueService;
@GetMapping("/base/listforspu/{spuId}")
public R baseListforspu(@PathVariable("spuId") Long spuId){
List<ProductAttrValueEntity> entityList = productAttrValueService.baseAttrlistForSpu(spuId);
return R.ok().put("data", entityList);
}
2、ProductAttrValueServiceImpl
@Override
public List<ProductAttrValueEntity> baseAttrlistForSpu(Long spuId) {
List<ProductAttrValueEntity> entities = this.baseMapper.selectList(new QueryWrapper<ProductAttrValueEntity>().eq("spu_id", spuId));
return entities;
}
3、测试(踩坑及解决办法)
- 当我们点击规格的时候出现400页面。

- 解决办法如下图:



- 再次进行测试,测试成功。
6.8.11 修改商品规格
当我们点击规格后,不仅会回显原来的规格参数,我们可能还需要队某些规格参数进行修改。

1、AttrController
@PostMapping("/update/{spuId}")
public R updateSpuAttr(@PathVariable("spuId") Long spuId, @RequestBody List<ProductAttrValueEntity> entities){
productAttrValueService.updateSpuAttr(spuId, entities);
return R.ok();
}
2、ProductAttrValueServiceImpl
因为修改的时候,有新增有修改有删除。 所以就先把spuId对应的所有属性都删了,再新增.
@Override
public void updateSpuAttr(Long spuId, List<ProductAttrValueEntity> entities) {
//1、删除这个spuId对应的所有属性
this.baseMapper.delete(new QueryWrapper<ProductAttrValueEntity>().eq("spu_id", spuId));
//2、新增回去
for (ProductAttrValueEntity entity : entities){
entity.setSpuId(spuId);
}
this.saveBatch(entities);
}
3、测试

修改成功。
7 分布式基础总结

本文档详细记录了谷粒商城项目中关于分布式基础的实现,包括属性服务的各个部分,如SPU和SKU的概念,属性分组的管理,规格参数的新增、查询和修改,平台属性的获取,商品的新增、检索以及仓储服务中的仓库管理、采购流程等。内容涵盖前端组件、数据库操作、服务间交互等多个方面,涉及到了大量的接口设计、数据处理和问题解决。
8150

被折叠的 条评论
为什么被折叠?



