赞
踩
目录
接口文档:谷粒商城:01、获取所有分类及子分类
分类维护接口文档:获取所有分类及子分类
GET: product/category/list/tree
接口描述
获取所有分类以及子分类,并返回json树形结构
响应参数
参数名 | 参数类型 | 描述 |
---|---|---|
code | int | 0-成功,其他业务码-失败 |
msg | string | 提示消息 |
data | array | 返回的所有菜单 |
响应示例
{
"code": 0,
"msg": "success",
"data": [{
"catId": 1,
"name": "图书、音像、电子书刊",
"parentCid": 0,
"catLevel": 1,
"showStatus": 1,
"sort": 0,
"icon": null,
"productUnit": null,
"productCount": 0,
"children": []
}]
}
在注册中心中“product”命名空间中,创建“gulimall-product.yml”配置文件:
将“application.yml”内容拷贝到该配置文件
#端口 server: port: 10001 spring: datasource: url: jdbc:mysql://192.168.119.127:3306/gulimall_pms?useUnicode=true&characterEncoding=UTF-8 driver-class-name: com.mysql.cj.jdbc.Driver username: root password: root cloud: nacos: discovery: server-addr: 127.0.0.1:8848 mybatis-plus: mapper-locations: classpath:/mapper/**/*.xml global-config: #配置每个实体类的主键自增长 db-config: id-type: auto logic-delete-value: 1 logic-not-delete-value: 0 logging: level: com.atguigu.gulimall: debug
在本地创建“bootstrap.properties”文件,指明配置中心的位置和使用到的配置文件:
- spring.application.name=gulimall-product
- spring.cloud.nacos.config.server-addr=127.0.0.1:8848
- spring.cloud.nacos.config.namespace=2128d268-4782-4ec8-88ee-ac5254696ea8
- spring.cloud.nacos.config.extension-configs[0].data-id=gulimall-product.yml
- spring.cloud.nacos.config.extension-configs[0].group=DEFAULT_GROUP
- spring.cloud.nacos.config.extension-configs[0].refresh=true
然后启动gulimall-product,查看到该服务已经出现在了nacos的注册中心中了
修改“com.atguigu.gulimall.product.entity.CategoryEntity”类,代码如下:
- @Data
- @TableName("pms_category")
- public class CategoryEntity implements Serializable {
- private static final long serialVersionUID = 1L;
-
- /**
- * 分类id
- */
- @TableId
- private Long catId;
- /**
- * 分类名称
- */
- private String name;
- /**
- * 父分类id
- */
- private Long parentCid;
- /**
- * 层级
- */
- private Integer catLevel;
- /**
- * 是否显示[0-不显示,1显示]
- */
- @TableLogic(value = "1",delval = "0")
- private Integer showStatus;
- /**
- * 排序
- */
- private Integer sort;
- /**
- * 图标地址
- */
- private String icon;
- /**
- * 计量单位
- */
- private String productUnit;
- /**
- * 商品数量
- */
- private Integer productCount;
- /**
- * 子分类
- */
- @JsonInclude(JsonInclude.Include.NON_EMPTY) //属性为NULL 不序列化
- @TableField(exist = false) //表示当前属性不是数据库的字段,但在项目中必须使用,这样在新增等使用bean的时候,mybatis-plus就会忽略这个,不会报错
- private List<CategoryEntity> children;
- }
修改“com.atguigu.gulimall.product.controller.CategoryController”类,添加如下代码:
- /**
- * 查出所有分类以及子分类,以树形结构组装起来
- */
- @RequestMapping("/list/tree")
- public R list(){
- List<CategoryEntity> list = categoryService.listWithTree();
-
- return R.ok().put("data", list);
- }
修改‘com.atguigu.gulimall.product.service.CategoryService’类,代码如下
List<CategoryEntity> listWithTree();
如何区别是哪种分类级别?
答:可以通过分类的parent_cid来进行判断,如果是一级分类,其值为0.
修改‘com.atguigu.gulimall.product.service.impl.CategoryServiceImpl’类,代码如下
- @Override
- public List<CategoryEntity> listWithTree() {
- //1、查出所有分类
- List<CategoryEntity> entities = baseMapper.selectList(null);
- //2、组装成父子的树形结构
- List<CategoryEntity> level1Menus = entities.stream().filter(categoryEntity -> categoryEntity.getParentCid() == 0)
- .map((menu) -> {
- menu.setChildren(getChildrens(menu, entities));
- return menu;
- }).sorted(Comparator.comparingInt(menu -> (menu.getSort() == null ? 0 : menu.getSort())))
- .collect(Collectors.toList());
- //2.1、找到所有的一级分类
- return level1Menus;
- }
-
- //递归查找所有菜单的子菜单
- private List<CategoryEntity> getChildrens(CategoryEntity root, List<CategoryEntity> all) {
- List<CategoryEntity> children = all.stream()
- .filter(categoryEntity -> categoryEntity.getParentCid() == root.getCatId())
- .map(categoryEntity -> {
- //1、找到子菜单
- categoryEntity.setChildren(getChildrens(categoryEntity, all));
- return categoryEntity;
- // 菜单的排序
- }).sorted(Comparator.comparingInt(menu -> (menu.getSort() == null ? 0 : menu.getSort())))
- .collect(Collectors.toList());
- return children;
- }
测试:http://localhost:10000/product/category/list/tree
下面是得到的部分JSON数据
[
{
"catId": 1,
"name": "图书、音像、电子书刊",
"parentCid": 0,
"catLevel": 1,
"showStatus": 1,
"sort": 0,
"icon": null,
"productUnit": null,
"productCount": 0,
"childCategoryEntity": [
{
"catId": 22,
"name": "电子书刊",
"parentCid": 1,
"catLevel": 2,
"showStatus": 1,
"sort": 0,
"icon": null,
"productUnit": null,
"productCount": 0,
"childCategoryEntity": [
{
"catId": 165,
"name": "电子书",
"parentCid": 22,
"catLevel": 3,
"showStatus": 1,
"sort": 0,
"icon": null,
"productUnit": null,
"productCount": 0,
"childCategoryEntity": []
},
{
"catId": 166,
"name": "网络原创",
"parentCid": 22,
"catLevel": 3,
"showStatus": 1,
"sort": 0,
"icon": null,
"productUnit": null,
"productCount": 0,
"childCategoryEntity": []
},
{
"catId": 167,
"name": "数字杂志",
"parentCid": 22,
"catLevel": 3,
"showStatus": 1,
"sort": 0,
"icon": null,
"productUnit": null,
"productCount": 0,
"childCategoryEntity": []
},
{
"catId": 168,
"name": "多媒体图书",
"parentCid": 22,
"catLevel": 3,
"showStatus": 1,
"sort": 0,
"icon": null,
"productUnit": null,
"productCount": 0,
"childCategoryEntity": []
}
]
},
{
"catId": 23,
"name": "音像",
"parentCid": 1,
"catLevel": 2,
"showStatus": 1,
"sort": 0,
"icon": null,
"productUnit": null,
"productCount": 0,
"childCategoryEntity": [
{
"catId": 169,
"name": "音乐",
"parentCid": 23,
"catLevel": 3,
"showStatus": 1,
"sort": 0,
"icon": null,
"productUnit": null,
"productCount": 0,
"childCategoryEntity": []
},
{
"catId": 170,
"name": "影视",
"parentCid": 23,
"catLevel": 3,
"showStatus": 1,
"sort": 0,
"icon": null,
"productUnit": null,
"productCount": 0,
"childCategoryEntity": []
},
{
"catId": 171,
"name": "教育音像",
"parentCid": 23,
"catLevel": 3,
"showStatus": 1,
"sort": 0,
"icon": null,
"productUnit": null,
"productCount": 0,
"childCategoryEntity": []
}
]
},
{
启动后端项目renren-fast
启动前端项目renren-fast-vue:
npm run dev
访问: http://localhost:8001/#/login
创建一级菜单:
创建完成后,在后台的管理系统中会创建一条记录:
然后创建子菜单:
创建renren-fast-vue\src\views\modules\product目录,之所以是这样来创建,是因为product/category,对应于product-category
在该目录下,新建“category.vue”文件:
- <!-- -->
- <template>
- <el-tree :data="menus" :props="defaultProps" @node-click="handleNodeClick"></el-tree>
- </template>
-
- <script>
- //这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
- //例如:import 《组件名称》 from '《组件路径》';
-
- export default {
- //import引入的组件需要注入到对象中才能使用
- components: {},
-
- //监听属性 类似于data概念
- computed: {},
- //监控data中的数据变化
- watch: {},
- data() {
- return {
- menus: [],
- defaultProps: {
- children: "childrens",
- label: "name"
- },
- }
- },
-
- methods: {
- handleNodeClick(data) {
- console.log(data);
- },
-
- getMenus() {
- this.dataListLoading = true;
- this.$http({
- url: this.$http.adornUrl("/product/category/list/tree"),
- method: "get"
- }).then(({ data }) => {
- console.log("获取到数据", data);
- this.menus=data;
- });
- }
- },
-
-
- //生命周期 - 创建完成(可以访问当前this实例)
- created() {
- this.getMenus();
- },
- //生命周期 - 挂载完成(可以访问DOM元素)
- mounted() {},
- beforeCreate() {}, //生命周期 - 创建之前
- beforeMount() {}, //生命周期 - 挂载之前
- beforeUpdate() {}, //生命周期 - 更新之前
- updated() {}, //生命周期 - 更新之后
- beforeDestroy() {}, //生命周期 - 销毁之前
- destroyed() {}, //生命周期 - 销毁完成
- activated() {} //如果页面有keep-alive缓存功能,这个函数会触发
- };
- </script>
- <style scoped>
刷新页面出现404异常,查看请求发现,请求的是“http://localhost:8080/renren-fast/product/category/list/tree”
这个请求是不正确的,正确的请求是:http://localhost:10000/product/category/list/tree,
修正这个问题:
替换“static\config\index.js”文件中的“window.SITE_CONFIG['baseUrl']”
替换前:
window.SITE_CONFIG['baseUrl'] = 'http://localhost:8080/renren-fast';
替换后:
window.SITE_CONFIG['baseUrl'] = 'http://localhost:88/api';
http://localhost:88,这个地址是我们网关微服务的接口。
这里我们需要通过网关来完成路径的映射,因此将renren-fast注册到nacos注册中心中,并添加配置中心
- application:
- name: renren-fast
- cloud:
- nacos:
- discovery:
- server-addr: 127.0.0.1:8848
-
- config:
- name: renren-fast
- server-addr: 127.0.0.1.8848
- namespace: ee409c3f-3206-4a3b-ba65-7376922a886d
配置网关路由,前台的所有请求都是经由“http://localhost:88/api”来转发的,在“gulimall-gateway”中添加路由规则:
- - id: admin_route
- uri: lb://renren-fast
- predicates:
- - Path=/api/**
但是这样做也引入了另外的一个问题,再次访问:http://localhost:8001/#/login,发现验证码不再显示:
分析原因:
在admin_route的路由规则下,在访问路径中包含了“api”,因此它会将它转发到renren-fast,网关在转发的时候,会使用网关的前缀信息,为了能够正常的取得验证码,我们需要对请求路径进行重写
关于请求路径重写:
6.16. The RewritePath GatewayFilter Factory
The RewritePath
GatewayFilter
factory takes a path regexp
parameter and a replacement
parameter. This uses Java regular expressions for a flexible way to rewrite the request path. The following listing configures a RewritePath
GatewayFilter
:
Example 41. application.yml
- spring:
- cloud:
- gateway:
- routes:
- - id: rewritepath_route
- uri: https://example.org
- predicates:
- - Path=/foo/**
- filters:
- - RewritePath=/red(?<segment>/?.*), $\{segment}
For a request path of /red/blue
, this sets the path to /blue
before making the downstream request. Note that the $
should be replaced with $\
because of the YAML specification.
修改“admin_route”路由规则:
- - id: admin_route
- uri: lb://renren-fast
- predicates:
- - Path=/api/**
- filters:
- - RewritePath=/api/(?<segment>/?.*), /renren-fast/$\{segment}
再次访问: http://localhost:8001/#/login 发现报503
分析原因:找不到有效的服务,也就是负载均衡不到对应的服务。由于springcloud2020弃用了Ribbon,因此Alibaba在2021版本nacos中删除了Ribbon的jar包,因此无法通过lb路由到指定微服务,出现了503情况。
解决办法:加入这个依赖
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-loadbalancer</artifactId>
- </dependency>
再次访问:http://localhost:8001/#/login,验证码能够正常的加载了。
但是很不幸新的问题又产生了,访问被拒绝了
问题描述:已拦截跨源请求:同源策略禁止读取位于 http://localhost:88/api/sys/login 的远程资源。(原因:CORS 头缺少 'Access-Control-Allow-Origin')。
问题分析:这是一种跨域问题。访问的域名和端口和原来的请求不同,请求就会被限制
跨域流程:
解决方法:在网关中定义“GulimallCorsConfiguration”类,该类用来做过滤,允许所有的请求跨域。
- @Configuration
- public class GulimallCorsConfiguration {
-
- @Bean
- public CorsWebFilter corsWebFilter(){
- UrlBasedCorsConfigurationSource source=new UrlBasedCorsConfigurationSource();
- CorsConfiguration corsConfiguration = new CorsConfiguration();
- corsConfiguration.addAllowedHeader("*");
- corsConfiguration.addAllowedMethod("*");
- corsConfiguration.addAllowedOrigin("*");
- corsConfiguration.setAllowCredentials(true);
-
- source.registerCorsConfiguration("/**",corsConfiguration);
- return new CorsWebFilter(source);
- }
- }
再次访问:http://localhost:8001/#/login
http://localhost:8001/renren已拦截跨源请求:同源策略禁止读取位于 http://localhost:88/api/sys/login 的远程资源。(原因:不允许有多个 ‘Access-Control-Allow-Origin’ CORS 头)n-fast/captcha.jpg?uuid=69c79f02-d15b-478a-8465-a07fd09001e6
出现了多个请求,并且也存在多个跨源请求。
为了解决这个问题,需要修改renren-fast项目,注释掉“io.renren.config.CorsConfig”类。然后再次进行访问。
在显示分类信息的时候,出现了404异常,请求的http://localhost:88/api/product/category/list/tree不存在
这是因为网关上所做的路径映射不正确,映射后的路径为http://localhost:8001/renren-fast/product/category/list/tree
但是只有通过http://localhost:10000/product/category/list/tree路径才能够正常访问,所以会报404异常。
解决方法就是定义一个product路由规则,进行路径重写:
- - id: product_route
- uri: lb://gulimall-product
- predicates:
- - Path=/api/product/**
- filters:
- - RewritePath=/api/(?<segment>/?.*),/$\{segment}
在路由规则的顺序上,将精确的路由规则放置到模糊的路由规则的前面,否则的话,精确的路由规则将不会被匹配到,类似于异常体系中try catch子句中异常的处理顺序。
添加delete和append标识,并且增加复选框
- <el-tree
- :data="menus"
- show-checkbox //显示复选框
- :props="defaultProps"
- :expand-on-click-node="false" //设置节点点击时不展开
- node-key="catId"
- >
- <span class="custom-tree-node" slot-scope="{ node, data }">
- <span>{{ node.label }}</span>
- <span>
- <el-button v-if="node.level <= 2" type="text" size="mini" @click="() => append(data)">Append</el-button>
- <el-button
- v-if="node.childNodes.length == 0"
- type="text"
- size="mini"
- @click="() => remove(node, data)"
- >Delete</el-button>
- </span>
- </span>
- </el-tree>
测试删除数据,打开postman输入“ http://localhost:88/api/product/category/delete ”,请求方式设置为POST,为了比对效果,可以在删除之前查询数据库的pms_category表:
由于delete请求接收的是一个数组,所以这里使用JSON方式,传入了一个数组:
再次查询数据库能够看到cat_id为1000的数据已经被删除了。
修改“com.atguigu.gulimall.product.controller.CategoryController”类,添加如下代码:
- /**
- * 删除
- * @RequestBody:获取请求体,必须发送post请求
- * springMVC自动将请求体的数据(json),转为对应的对象
- */
- @RequestMapping("/delete")
- public R delete(@RequestBody Long[] catIds){
- //检查当前删除的菜单,是否被别的地方引用
- //categoryService.removeByIds(Arrays.asList(catIds));
-
- categoryService.removeMenusByIds(Arrays.asList(catIds));
- return R.ok();
- }
com.atguigu.gulimall.product.service.impl.CategoryServiceImpl
- @Override
- public void removeMenusByIds(List<Long> asList) {
- //TODO 检查当前删除的菜单,是否被别的地方引用
- //逻辑删除
- baseMapper.deleteBatchIds(asList);
- }
然而多数时候,我们并不希望删除数据,而是标记它被删除了,这就是逻辑删除;
可以设置show_status为0,标记它已经被删除。
mybatis-plus的逻辑删除:
配置全局的逻辑删除规则,在“src/main/resources/application.yml”文件中添加如下内容:
- mybatis-plus:
- global-config:
- db-config:
- id-type: auto
- logic-delete-value: 1
- logic-not-delete-value: 0
修改“com.atguigu.gulimall.product.entity.CategoryEntity”类,添加上@TableLogic,表明使用逻辑删除:
- /**
- * 是否显示[0-不显示,1显示]
- */
- @TableLogic(value = "1",delval = "0")
- private Integer showStatus;
然后在Postman中测试一下是否能够满足需要。另外在“src/main/resources/application.yml”文件中,设置日志级别,打印出SQL语句:
- logging:
- level:
- com.bigdata.gulimall.product: debug
打印的日志:
- ==> Preparing: UPDATE pms_category SET show_status=0 WHERE cat_id IN ( ? ) AND show_status=1
- ==> Parameters: 1431(Long)
- <== Updates: 1
- get changedGroupKeys:[]
同一个菜单内拖动 | 正常 |
拖动到父菜单的前面或后面 | 正常 |
拖动到父菜单同级的另外一个菜单中 | 正常 |
关注的焦点在于,拖动到目标节点中,使得目标节点的catlevel+deep小于3即可。拖动到目标节点前后的条件是,使得
需要考虑两种类型节点的catLevel
一种关系是:如果是同一个节点下的子节点的前后移动,则不需要修改其catLevel
如果是拖动到另外一个节点内或父节点中,则要考虑修改其catLevel
如果拖动到与父节点平级的节点关系中,则要将该拖动的节点的catLevel,设置为兄弟节点Level,
先考虑parentCid还是先考虑catLevel?
两种关系在耦合
另外还有一种是前后拖动的情况
哪个范围最大?
肯定是拖动类型关系最大,
如果是前后拖动,则拖动后需要看待拖动节点的层级和设置待拖动节点的parentId,
如果待拖动节点和目标节点的层级相同,则认为是同级拖动,只需要修改节点的先后顺序即可;
否则认为是跨级拖动,则需要修改层级和重新设置parentID
如果以拖动类型来分,并不合适,比较合适的是跨级拖动和同级拖动
如何判断是跨级拖动还是同级拖动,根据拖动的层级来看,如果是同一级的拖动,只需要修改先后顺序即可,但是这样也会存在一个问题,就是当拖动到另外一个分组下的同级目录中,显然也需要修改parentID,究竟什么样的模型最好呢?
另外也可以判断在跨级移动时,跨级后的parentID是否相同,如果不相同,则认为是在不同目录下的跨级移动需要修改parentID。
顺序、catLevel和parentID
同级移动:
(1)首先判断待移动节点和目标节点的catLevel是否相同,
(2)相同则认为是同级移动,
如果此时移动后目标节点的parentID和待移动节点的相同,但是移动类型是前后移动,只需要调整顺序即可,此时移动类型是inner,则需要修改catLevel和parentId和顺序
如果此时移动后目标节点的parentID和待移动节点的不相同,但是移动类型是前后移动,则需要调整顺序和parentId,此时移动类型是inner,则需要修改catLevel和parentId和顺序
通过这两步的操作能看到一些共性,如果抽取移动类型作为大的分类,则在这种分类下,
如果是前后移动,则分为下面几种情况:
同级别下的前后移动:界定标准为catLevel相同,但是又可以分为parentID相同和parentID不同,parent相同时,只需要修改顺序即可;parentID不同时,需要修改parentID和顺序
不同级别下的前后移动:界定标准为catLevel不同,此时无论如何都要修改parentID,顺序和catLevel
如果是inner类型移动,则分为一下的几种情况。
此时不论是同级inner,还是跨级innner,都需要修改parentID,顺序和catLevel
哪种情况需要更新子节点呢?
那就要看要拖拽的节点是否含有子节点,如果有子节点,则需要更新子节点的catLevel,不需要更新它之间的顺序和parentId,只需要更新catLevel即可。这种更新子节点的Level应该归类,目前的目标是只要有子节点就更新它的catLevel,
(2)如果待移动节点和目标节点的catLevel不同,则认为是跨级移动。如果是移动到父节点中,则需要设置catLevel,parentID和顺序。此时需要分两种情况来考虑,如果是移动到父节点中,则需要设置catLevel,parentID和顺序,如果是移动到兄弟节点中,则需要设置
包含移动到父节点同级目录,兄弟节点中。
设置菜单拖动开关
<el-switch v-model="draggable" active-text="开启拖拽" inactive-text="关闭拖拽"></el-switch>
但是现在存在的一个问题是每次拖拽的时候,都会发送请求,更新数据库这样频繁的与数据库交互,现在想要实现一个拖拽过程中不更新数据库,拖拽完成后,统一提交拖拽后的数据。
现在还存在一个问题,如果是将一个菜单连续的拖拽,最终还放到了原来的位置,但updateNode中却出现了很多节点更新信息,这样显然也是一个问题。
批量删除
<el-button type="danger" plain size="small" @click="batchDelete">批量删除</el-button>
- //批量删除
- batchDelete() {
- let checkNodes = this.$refs.menuTree.getCheckedNodes();
-
- // console.log("被选中的节点:",checkNodes);
-
- let catIds = [];
- for (let i = 0; i < checkNodes.length; i++) {
- catIds.push(checkNodes[i].catId);
- }
-
- this.$confirm(`确定要删除?`, "提示", {
- confirmButtonText: "确定",
- cancelButtonText: "取消",
- type: "warning"
- })
- .then(() => {
- this.$http({
- url: this.$http.adornUrl("/product/category/delete"),
- method: "post",
- data: this.$http.adornData(catIds, false)
- }).then(({ data }) => {
- this.$message({
- message: "菜单批量删除成功",
- type: "success"
- });
-
- //重新刷新页面
- this.getMeus();
- });
-
-
- })
- .catch(() => {
- //取消删除
- });
- },
2)将“”逆向工程得到的resources\src\views\modules\product文件拷贝到gulimall/renren-fast-vue/src/views/modules/product目录下,也就是下面的两个文件
brand.vue brand-add-or-update.vue
但是显示的页面没有新增和删除功能,这是因为权限控制的原因,
- <el-button v-if="isAuth('product:brand:save')" type="primary" @click="addOrUpdateHandle()">新增</el-button>
- <el-button v-if="isAuth('product:brand:delete')" type="danger" @click="deleteHandle()" :disabled="dataListSelections.length <= 0">批量删除</el-button>
-
查看“isAuth”的定义位置:
它是在“index.js”中定义,现在将它设置为返回值为true,即可显示添加和删除功能。
再次刷新页面能够看到,按钮已经出现了:
brand.vue
- <template slot-scope="scope">
- <el-switch
- v-model="scope.row.showStatus"
- active-color="#13ce66"
- inactive-color="#ff4949"
- @change="updateBrandStatus(scope.row)"
- :active-value = "1"
- :inactive-value = "0"
- ></el-switch>
- </template>
brand-add-or-update.vue
- <el-form-item label="显示状态" prop="showStatus">
- <el-switch v-model="dataForm.showStatus" active-color="#13ce66" inactive-color="#ff4949"></el-switch>
- </el-form-item>
- //更新开关的状态
- updateBrandStatus(data) {
- console.log("最新状态", data);
- let {brandId,showStatus} = data;
- this.$http({
- url: this.$http.adornUrl("/product/brand/update"),
- method: "post",
- data: this.$http.adornData({brandId,showStatus}, false)
- }).then(({ data }) => {
-
- this.$message({
- message: "状态更新成功",
- type: "success"
- });
-
- });
- },
SpringCloud Alibaba-OSS
对象存储服务(Object Storage Service, OSS)是一种海量、安全、低成本、高可靠的云存储服务,适合存放任意类型的文件。容量和处理能力弹性扩展,多种存储类型供选择,全面优化存储成本。
和传统的单体应用不同,这里我们选择将数据上传到分布式文件服务器上。
这里我们选择将图片放置到阿里云上,使用对象存储。
阿里云上使使用对象存储方式:
创建Bucket
上传文件:
上传成功后,取得图片的URL
这种方式是手动上传图片,实际上我们可以在程序中设置自动上传图片到阿里云对象存储。
上传模型:
查看阿里云关于文件上传的帮助: https://help.aliyun.com/document_detail/32009.html?spm=a2c4g.11186623.6.768.549d59aaWuZMGJ
(1)、添加依赖包
在Maven项目中加入依赖项(推荐方式)
在 Maven 工程中使用 OSS Java SDK,只需在 pom.xml 中加入相应依赖即可。以 3.8.0 版本为例,在 内加入如下内容:
- <dependency>
- <groupId>com.aliyun.oss</groupId>
- <artifactId>aliyun-sdk-oss</artifactId>
- <version>3.8.0</version>
- </dependency>
(2)、上传文件流
以下代码用于上传文件流:
- // Endpoint以杭州为例,其它Region请按实际情况填写。
- String endpoint = "http://oss-cn-hangzhou.aliyuncs.com";
- // 云账号AccessKey有所有API访问权限,建议遵循阿里云安全最佳实践,创建并使用RAM子账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建。
- String accessKeyId = "<yourAccessKeyId>";
- String accessKeySecret = "<yourAccessKeySecret>";
-
- // 创建OSSClient实例。
- OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
-
- // 上传文件流。
- InputStream inputStream = new FileInputStream("<yourlocalFile>");
- ossClient.putObject("<yourBucketName>", "<yourObjectName>", inputStream);
-
- // 关闭OSSClient。
- ossClient.shutdown();
endpoint的取值:
accessKeyId和accessKeySecret需要创建一个RAM账号:
创建用户完毕后,会得到一个“AccessKey ID”和“AccessKeySecret”,然后复制这两个值到代码的“AccessKey ID”和“AccessKeySecret”。
另外还需要添加访问控制权限:
- @Test
- public void testUpload() throws FileNotFoundException {
- // Endpoint以杭州为例,其它Region请按实际情况填写。
- String endpoint = "oss-cn-shanghai.aliyuncs.com";
- // 云账号AccessKey有所有API访问权限,建议遵循阿里云安全最佳实践,创建并使用RAM子账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建。
- String accessKeyId = "自己申请的子用户"; //自己知道就好,我的暴露安全性被攻击了
- String accessKeySecret = "子用户的密码";
-
- // 创建OSSClient实例。
- OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
-
- // 上传文件流。
- InputStream inputStream = new FileInputStream("C:\\Users\\Administrator\\Pictures\\timg.jpg");
- ossClient.putObject("gulimall-images", "time.jpg", inputStream);
-
- // 关闭OSSClient。
- ossClient.shutdown();
- System.out.println("上传成功.");
- }
更为简单的使用方式,是使用SpringCloud Alibaba
详细使用方法,见: https://help.aliyun.com/knowledge_detail/108650.html
(1)添加依赖
- <dependency>
- <groupId>com.alibaba.cloud</groupId>
- <artifactId>spring-cloud-starter-alicloud-oss</artifactId>
- <version>2.2.0.RELEASE</version>
- </dependency>
(2)创建“AccessKey ID”和“AccessKeySecret”
(3)配置key,secret和endpoint相关信息
- access-key: 自己申请的子用户
- secret-key: 自己子用户的密码
- oss:
- endpoint: oss-cn-shanghai.aliyuncs.com
(4)注入OSSClient并进行文件上传下载等操作
但是这样来做还是比较麻烦,如果以后的上传任务都交给gulimall-product来完成,显然耦合度高。最好单独新建一个Module来完成文件上传任务。
1)新建gulimall-third-party
2)添加依赖,将原来gulimall-common中的“spring-cloud-starter-alicloud-oss”依赖移动到该项目中
- <?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>2.3.4.RELEASE</version>
- <relativePath/> <!-- lookup parent from repository -->
- </parent>
- <groupId>com.atguigu.gulimall</groupId>
- <artifactId>gulimall-thrid-party</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <name>gulimall-thrid-party</name>
- <description>第三方服务</description>
-
- <properties>
- <java.version>1.8</java.version>
- <spring-cloud.version>Hoxton.SR8</spring-cloud.version>
- </properties>
-
- <dependencies>
- <!--阿里云文件上传-->
- <dependency>
- <groupId>com.alibaba.cloud</groupId>
- <artifactId>spring-cloud-starter-alicloud-oss</artifactId>
- </dependency>
- <dependency>
- <groupId>com.auguigu.gulimall</groupId>
- <artifactId>gulimall-commom</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <exclusions>
- <exclusion>
- <groupId>com.baomidou</groupId>
- <artifactId>mybatis-plus-boot-starter</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-openfeign</artifactId>
- </dependency>
-
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- <exclusions>
- <exclusion>
- <groupId>org.junit.vintage</groupId>
- <artifactId>junit-vintage-engine</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
- </dependencies>
-
- <dependencyManagement>
- <dependencies>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-dependencies</artifactId>
- <version>${spring-cloud.version}</version>
- <type>pom</type>
- <scope>import</scope>
- </dependency>
- <dependency>
- <groupId>com.alibaba.cloud</groupId>
- <artifactId>spring-cloud-alibaba-dependencies</artifactId>
- <version>2.2.0.RELEASE</version>
- <type>pom</type>
- <scope>import</scope>
- </dependency>
- </dependencies>
- </dependencyManagement>
-
- <build>
- <plugins>
- <plugin>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-maven-plugin</artifactId>
- </plugin>
- </plugins>
- </build>
-
- </project>
3)在主启动类中开启服务的注册和发现
@EnableDiscoveryClient
4)在nacos中注册
(1)创建命名空间“ gulimall-third-party ”
2)在“ gulimall-third-party”命名空间中,创建“ gulimall-third-party.yml”文件
- spring:
- cloud:
- alicloud:
- access-key: 自己申请的子用户
- secret-key: 子用户的密码
- oss:
- endpoint: oss-cn-shanghai.aliyuncs.com
5)编写配置文件
application.yml
- server:
- port: 30000
-
- spring:
- application:
- name: gulimall-third-party
- cloud:
- nacos:
- discovery:
- server-addr: 192.168.137.14:8848
-
- logging:
- level:
- com.bigdata.gulimall.product: debug
-
bootstrap.properties
- spring.application.name=gulimall-thrid-party
- spring.cloud.nacos.config.server-addr=127.0.0.1:8848
- spring.cloud.nacos.config.namespace=555d020c-9b7e-4e8e-b625-65a3c4b6ff47
- spring.cloud.nacos.config.extension-configs[0].data-id=oss.yml
- spring.cloud.nacos.config.extension-configs[0].group=DEFAULT_GROUP
- spring.cloud.nacos.config.extension-configs[0].refresh=true
6) 编写测试类
- package com.bigdata.gulimall.thirdparty;
- import com.aliyun.oss.OSS;
- import com.aliyun.oss.OSSClient;
- import com.aliyun.oss.OSSClientBuilder;
- import org.junit.jupiter.api.Test;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.boot.test.context.SpringBootTest;
- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
- import java.io.InputStream;
-
- @SpringBootTest
- class GulimallThirdPartyApplicationTests {
-
- @Autowired
- OSSClient ossClient;
-
- @Test
- public void testUpload() throws FileNotFoundException {
- // Endpoint以杭州为例,其它Region请按实际情况填写。
- String endpoint = "oss-cn-shanghai.aliyuncs.com";
- // 云账号AccessKey有所有API访问权限,建议遵循阿里云安全最佳实践,创建并使用RAM子账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建。
- String accessKeyId = "自己申请的子用户";
- String accessKeySecret = "子用户的密码";
-
- // 创建OSSClient实例。
- OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
-
- //上传文件流。
- InputStream inputStream = new FileInputStream("C:\\Users\\Administrator\\Pictures\\timg.jpg");
- ossClient.putObject("gulimall-images", "time3.jpg", inputStream);
-
- // 关闭OSSClient。
- ossClient.shutdown();
- System.out.println("上传成功.");
- }
- }
-
背景
采用JavaScript客户端直接签名(参见JavaScript客户端签名直传)时,AccessKeyID和AcessKeySecret会暴露在前端页面,因此存在严重的安全隐患。因此,OSS提供了服务端签名后直传的方案。
原理介绍
服务端签名后直传的原理如下:
编写“com.atguigu.gulimall.thridparty.controller.OSSController”类:
- package com.atguigu.gulimall.thridparty.controller;
-
- import com.aliyun.oss.OSS;
- import com.aliyun.oss.common.utils.BinaryUtil;
- import com.aliyun.oss.model.MatchMode;
- import com.aliyun.oss.model.PolicyConditions;
- import com.atguigu.common.utils.R;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
-
- import java.text.SimpleDateFormat;
- import java.util.Date;
- import java.util.LinkedHashMap;
- import java.util.Map;
-
- /**
- * @author WangTianShun
- * @date 2020/10/10 16:06
- */
-
- @RestController
- public class OSSController {
-
- @Autowired
- OSS ossClient;
-
- @Value("${spring.cloud.alicloud.oss.endpoint}")
- private String endpoint;
-
- @Value("${spring.cloud.alicloud.oss.bucket}")
- private String bucket;
-
- @Value("${spring.cloud.alicloud.access-key}")
- private String accessId;
-
- @RequestMapping("/oss/policy")
- public R policy(){
- String host = "https://" + bucket + "." + endpoint; // host的格式为 bucketname.endpoint
- // callbackUrl为 上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实信息。
- //String callbackUrl = "http://88.88.88.88:8888";
- String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
- String dir = format+"/"; // 用户上传文件时指定的前缀。
- Map<String, String> respMap = null;
- try {
- long expireTime = 30;
- long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
- Date expiration = new Date(expireEndTime);
- // PostObject请求最大可支持的文件大小为5 GB,即CONTENT_LENGTH_RANGE为5*1024*1024*1024。
- PolicyConditions policyConds = new PolicyConditions();
- policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
- policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
-
- String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
- byte[] binaryData = postPolicy.getBytes("utf-8");
- String encodedPolicy = BinaryUtil.toBase64String(binaryData);
- String postSignature = ossClient.calculatePostSignature(postPolicy);
-
- respMap = new LinkedHashMap<String, String>();
- respMap.put("accessid", accessId);
- respMap.put("policy", encodedPolicy);
- respMap.put("signature", postSignature);
- respMap.put("dir", dir);
- respMap.put("host", host);
- respMap.put("expire", String.valueOf(expireEndTime / 1000));
- // respMap.put("expire", formatISO8601Date(expiration));
-
- } catch (Exception e) {
- // Assert.fail(e.getMessage());
- System.out.println(e.getMessage());
- } finally {
- ossClient.shutdown();
- }
- return R.ok().put("data",respMap);
- }
- }
测试: http://localhost:30000/oss/policy
{"accessid":"LTAI4G4W1RA4JXz2QhoDwHhi","policy":"eyJleHBpcmF0aW9uIjoiMjAyMC0wNC0yOVQwMjo1ODowNy41NzhaIiwiY29uZGl0aW9ucyI6W1siY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMTA0ODU3NjAwMF0sWyJzdGFydHMtd2l0aCIsIiRrZXkiLCIyMDIwLTA0LTI5LyJdXX0=","signature":"s42iRxtxGFmHyG40StM3d9vOfFk=","dir":"2020-04-29/","host":"https://gulimall-images.oss-cn-shanghai.aliyuncs.com","expire":"1588129087"}
以后在上传文件时的访问路径为“ http://localhost:88/api/thirdparty/oss/policy”,
在“gulimall-gateway”中配置路由规则:
- - id: third_party_route
- uri: lb://gulimall-gateway
- predicates:
- - Path=/api/thirdparty/**
- filters:
- - RewritePath=/api/thirdparty/(?<segment>/?.*),/$\{segment}
测试是否能够正常跳转: http://localhost:88/api/thirdparty/oss/policy
上传组件
放置项目提供的upload文件夹到components目录下,一个是单文件上传,另外一个是多文件上传
- PS D:\Project\gulimall\renren-fast-vue\src\components\upload> ls
-
-
- 目录: D:\Project\gulimall\renren-fast-vue\src\components\upload
-
-
- Mode LastWriteTime Length Name
- ---- ------------- ------ ----
- -a---- 2020/4/29 星期三 12:0 3122 multiUpload.vue
- 2
- -a---- 2019/11/11 星期一 21: 343 policy.js
- 20
- -a---- 2020/4/29 星期三 12:0 3053 singleUpload.vue
- 1
-
-
- PS D:\Project\gulimall\renren-fast-vue\src\components\upload>
修改这两个文件的配置后
开始执行上传,但是在上传过程中,出现了如下的问题:
- Access to XMLHttpRequest at 'http://gulimall-images.oss-cn-shanghai.aliyuncs.com/' from origin 'http://localhost:8001' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
这又是一个跨域的问题,解决方法就是在阿里云上开启跨域访问:
再次执行文件上传。
步骤1:使用校验注解
在Java中提供了一系列的校验方式,它这些校验方式在“javax.validation.constraints”包中,提供了如@Email,@NotNull等注解。
在非空处理方式上提供了@NotNull,@Blank和@
(1)@NotNull
The annotated element must not be null. Accepts any type.
注解元素禁止为null,能够接收任何类型
(2)@NotEmpty
the annotated element must not be null nor empty.
该注解修饰的字段不能为null或""
Supported types are:
支持以下几种类型
CharSequence (length of character sequence is evaluated)
字符序列(字符序列长度的计算)
Collection (collection size is evaluated)
集合长度的计算
Map (map size is evaluated)
map长度的计算
Array (array length is evaluated)
数组长度的计算
(3)@NotBlank
The annotated element must not be null and must contain at least one non-whitespace character. Accepts CharSequence.
该注解不能为null,并且至少包含一个非空白字符。接收字符序列。
步骤2:在请求方法中,使用校验注解@Valid,开启校验,
- @RequestMapping("/save")
- public R save(@Valid @RequestBody BrandEntity brand){
- brandService.save(brand);
-
- return R.ok();
- }
测试: http://localhost:88/api/product/brand/save
在postman种发送上面的请求
- {
- "timestamp": "2020-04-29T09:20:46.383+0000",
- "status": 400,
- "error": "Bad Request",
- "errors": [
- {
- "codes": [
- "NotBlank.brandEntity.name",
- "NotBlank.name",
- "NotBlank.java.lang.String",
- "NotBlank"
- ],
- "arguments": [
- {
- "codes": [
- "brandEntity.name",
- "name"
- ],
- "arguments": null,
- "defaultMessage": "name",
- "code": "name"
- }
- ],
- "defaultMessage": "不能为空",
- "objectName": "brandEntity",
- "field": "name",
- "rejectedValue": "",
- "bindingFailure": false,
- "code": "NotBlank"
- }
- ],
- "message": "Validation failed for object='brandEntity'. Error count: 1",
- "path": "/product/brand/save"
- }
能够看到"defaultMessage": “不能为空”,这些错误消息定义在“hibernate-validator”的“\org\hibernate\validator\ValidationMessages_zh_CN.properties”文件中。在该文件中定义了很多的错误规则:
- javax.validation.constraints.AssertFalse.message = 只能为false
- javax.validation.constraints.AssertTrue.message = 只能为true
- javax.validation.constraints.DecimalMax.message = 必须小于或等于{value}
- javax.validation.constraints.DecimalMin.message = 必须大于或等于{value}
- javax.validation.constraints.Digits.message = 数字的值超出了允许范围(只允许在{integer}位整数和{fraction}位小数范围内)
- javax.validation.constraints.Email.message = 不是一个合法的电子邮件地址
- javax.validation.constraints.Future.message = 需要是一个将来的时间
- javax.validation.constraints.FutureOrPresent.message = 需要是一个将来或现在的时间
- javax.validation.constraints.Max.message = 最大不能超过{value}
- javax.validation.constraints.Min.message = 最小不能小于{value}
- javax.validation.constraints.Negative.message = 必须是负数
- javax.validation.constraints.NegativeOrZero.message = 必须是负数或零
- javax.validation.constraints.NotBlank.message = 不能为空
- javax.validation.constraints.NotEmpty.message = 不能为空
- javax.validation.constraints.NotNull.message = 不能为null
- javax.validation.constraints.Null.message = 必须为null
- javax.validation.constraints.Past.message = 需要是一个过去的时间
- javax.validation.constraints.PastOrPresent.message = 需要是一个过去或现在的时间
- javax.validation.constraints.Pattern.message = 需要匹配正则表达式"{regexp}"
- javax.validation.constraints.Positive.message = 必须是正数
- javax.validation.constraints.PositiveOrZero.message = 必须是正数或零
- javax.validation.constraints.Size.message = 个数必须在{min}和{max}之间
-
- org.hibernate.validator.constraints.CreditCardNumber.message = 不合法的信用卡号码
- org.hibernate.validator.constraints.Currency.message = 不合法的货币 (必须是{value}其中之一)
- org.hibernate.validator.constraints.EAN.message = 不合法的{type}条形码
- org.hibernate.validator.constraints.Email.message = 不是一个合法的电子邮件地址
- org.hibernate.validator.constraints.Length.message = 长度需要在{min}和{max}之间
- org.hibernate.validator.constraints.CodePointLength.message = 长度需要在{min}和{max}之间
- org.hibernate.validator.constraints.LuhnCheck.message = ${validatedValue}的校验码不合法, Luhn模10校验和不匹配
- org.hibernate.validator.constraints.Mod10Check.message = ${validatedValue}的校验码不合法, 模10校验和不匹配
- org.hibernate.validator.constraints.Mod11Check.message = ${validatedValue}的校验码不合法, 模11校验和不匹配
- org.hibernate.validator.constraints.ModCheck.message = ${validatedValue}的校验码不合法, ${modType}校验和不匹配
- org.hibernate.validator.constraints.NotBlank.message = 不能为空
- org.hibernate.validator.constraints.NotEmpty.message = 不能为空
- org.hibernate.validator.constraints.ParametersScriptAssert.message = 执行脚本表达式"{script}"没有返回期望结果
- org.hibernate.validator.constraints.Range.message = 需要在{min}和{max}之间
- org.hibernate.validator.constraints.SafeHtml.message = 可能有不安全的HTML内容
- org.hibernate.validator.constraints.ScriptAssert.message = 执行脚本表达式"{script}"没有返回期望结果
- org.hibernate.validator.constraints.URL.message = 需要是一个合法的URL
-
- org.hibernate.validator.constraints.time.DurationMax.message = 必须小于${inclusive == true ? '或等于' : ''}${days == 0 ? '' : days += '天'}${hours == 0 ? '' : hours += '小时'}${minutes == 0 ? '' : minutes += '分钟'}${seconds == 0 ? '' : seconds += '秒'}${millis == 0 ? '' : millis += '毫秒'}${nanos == 0 ? '' : nanos += '纳秒'}
- org.hibernate.validator.constraints.time.DurationMin.message = 必须大于${inclusive == true ? '或等于' : ''}${days == 0 ? '' : days += '天'}${hours == 0 ? '' : hours += '小时'}${minutes == 0 ? '' : minutes += '分钟'}${seconds == 0 ? '' : seconds += '秒'}${millis == 0 ? '' : millis += '毫秒'}${nanos == 0 ? '' : nanos += '纳秒'}
-
想要自定义错误消息,可以覆盖默认的错误提示信息,如@NotBlank的默认message是
- public @interface NotBlank {
-
- String message() default "{javax.validation.constraints.NotBlank.message}";
可以在添加注解的时候,修改message:
- @NotBlank(message = "品牌名必须非空")
- private String name;
当再次发送请求时,得到的错误提示信息:
- {
- "timestamp": "2020-04-29T09:36:04.125+0000",
- "status": 400,
- "error": "Bad Request",
- "errors": [
- {
- "codes": [
- "NotBlank.brandEntity.name",
- "NotBlank.name",
- "NotBlank.java.lang.String",
- "NotBlank"
- ],
- "arguments": [
- {
- "codes": [
- "brandEntity.name",
- "name"
- ],
- "arguments": null,
- "defaultMessage": "name",
- "code": "name"
- }
- ],
- "defaultMessage": "品牌名必须非空",
- "objectName": "brandEntity",
- "field": "name",
- "rejectedValue": "",
- "bindingFailure": false,
- "code": "NotBlank"
- }
- ],
- "message": "Validation failed for object='brandEntity'. Error count: 1",
- "path": "/product/brand/save"
- }
但是这种返回的错误结果并不符合我们的业务需要。
步骤3:给校验的Bean后,紧跟一个BindResult,就可以获取到校验的结果。拿到校验的结果,就可以自定义的封装。
- @RequestMapping("/save")
- public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){
- if( result.hasErrors()){
- Map<String,String> map=new HashMap<>();
- //1.获取错误的校验结果
- result.getFieldErrors().forEach((item)->{
- //获取发生错误时的message
- String message = item.getDefaultMessage();
- //获取发生错误的字段
- String field = item.getField();
- map.put(field,message);
- });
- return R.error(400,"提交的数据不合法").put("data",map);
- }else {
-
- }
- brandService.save(brand);
-
- return R.ok();
- }
-
这种是针对于该请求设置了一个内容校验,如果针对于每个请求都单独进行配置,显然不是太合适,实际上可以统一的对于异常进行处理。
步骤4:统一异常处理
可以使用SpringMvc所提供的@ControllerAdvice,通过“basePackages”能够说明处理哪些路径下的异常。
(1)抽取一个异常处理类
- package com.atguigu.gulimall.product.exception;
-
- import com.atguigu.common.exception.BizCodeEnume;
- import com.atguigu.common.utils.R;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.validation.BindingResult;
- import org.springframework.web.bind.MethodArgumentNotValidException;
- import org.springframework.web.bind.annotation.ControllerAdvice;
- import org.springframework.web.bind.annotation.ExceptionHandler;
- import org.springframework.web.bind.annotation.ResponseBody;
- import org.springframework.web.bind.annotation.RestControllerAdvice;
- import org.springframework.web.servlet.ModelAndView;
-
- import java.util.HashMap;
- import java.util.Map;
-
- /**
- * 集中处理所有异常
- */
- @Slf4j
- //@ResponseBody
- //@ControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
- @RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
- public class GulimallExceptionControllerAdvice {
-
- @ExceptionHandler(value= MethodArgumentNotValidException.class)
- public R handleVaildException(MethodArgumentNotValidException e){
- log.error("数据校验出现问题{},异常类型:{}",e.getMessage(),e.getClass());
- BindingResult bindingResult = e.getBindingResult();
-
- Map<String,String> errorMap = new HashMap<>();
- bindingResult.getFieldErrors().forEach((fieldError)->{
- errorMap.put(fieldError.getField(),fieldError.getDefaultMessage());
- });
- return R.error(BizCodeEnume.VAILD_EXCEPTION.getCode(),BizCodeEnume.VAILD_EXCEPTION.getMsg()).put("data",errorMap);
- }
-
- @ExceptionHandler(value = Throwable.class)
- public R handleException(Throwable throwable){
-
- log.error("错误:",throwable);
- return R.error(BizCodeEnume.UNKNOW_EXCEPTION.getCode(),BizCodeEnume.UNKNOW_EXCEPTION.getMsg());
- }
- }
(2)测试: http://localhost:88/api/product/brand/save
(3)默认异常处理
- @ExceptionHandler(value = Throwable.class)
- public R handleException(Throwable throwable){
- log.error("未知异常{},异常类型{}",throwable.getMessage(),throwable.getClass());
- return R.error(BizCodeEnum.UNKNOW_EXEPTION.getCode(),BizCodeEnum.UNKNOW_EXEPTION.getMsg());
- }
(4)错误状态码
上面代码中,针对于错误状态码,是我们进行随意定义的,然而正规开发过程中,错误状态码有着严格的定义规则,如该在项目中我们的错误状态码定义
为了定义这些错误状态码,我们可以单独定义一个常量类,用来存储这些错误状态码
- package com.bigdata.common.exception;
-
- /***
- * 错误码和错误信息定义类
- * 1. 错误码定义规则为5为数字
- * 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知异常
- * 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式
- * 错误码列表:
- * 10: 通用
- * 001:参数格式校验
- * 11: 商品
- * 12: 订单
- * 13: 购物车
- * 14: 物流
- */
- public enum BizCodeEnum {
-
- UNKNOW_EXEPTION(10000,"系统未知异常"),
-
- VALID_EXCEPTION( 10001,"参数格式校验失败");
-
- private int code;
- private String msg;
-
- BizCodeEnum(int code, String msg) {
- this.code = code;
- this.msg = msg;
- }
-
- public int getCode() {
- return code;
- }
-
- public String getMsg() {
- return msg;
- }
- }
-
(5)测试: http://localhost:88/api/product/brand/save
分组校验功能(完成多场景的复杂校验)
1、给校验注解,标注上groups,指定什么情况下才需要进行校验
如:指定在更新和添加的时候,都需要进行校验
- @NotEmpty
- @NotBlank(message = "品牌名必须非空",groups = {UpdateGroup.class,AddGroup.class})
- private String name;
在这种情况下,没有指定分组的校验注解,默认是不起作用的。想要起作用就必须要加groups。
2、业务方法参数上使用@Validated注解
@Validated的value方法:
Specify one or more validation groups to apply to the validation step kicked off by this annotation.
指定一个或多个验证组以应用于此注释启动的验证步骤。
JSR-303 defines validation groups as custom annotations which an application declares for the sole purpose of using
them as type-safe group arguments, as implemented in SpringValidatorAdapter.
JSR-303 将验证组定义为自定义注释,应用程序声明的唯一目的是将它们用作类型安全组参数,如 SpringValidatorAdapter 中实现的那样。
Other SmartValidator implementations may support class arguments in other ways as well.
其他SmartValidator 实现也可以以其他方式支持类参数。
3、默认情况下,在分组校验情况下,没有指定分组的校验注解,将不会生效,它只会在分组的情况下生效。
自定义校验功能
1、编写一个自定义的校验注解
- @Documented
- @Constraint(validatedBy = { ListValueConstraintValidator.class})
- @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
- @Retention(RUNTIME)
- public @interface ListValue {
- String message() default "{com.bigdata.common.valid.ListValue.message}";
-
- Class<?>[] groups() default { };
-
- Class<? extends Payload>[] payload() default { };
-
- int[] value() default {};
- }
2、编写一个自定义的校验器
- public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
- private Set<Integer> set=new HashSet<>();
- @Override
- public void initialize(ListValue constraintAnnotation) {
- int[] value = constraintAnnotation.value();
- for (int i : value) {
- set.add(i);
- }
- }
-
- @Override
- public boolean isValid(Integer value, ConstraintValidatorContext context) {
- return set.contains(value);
- }
- }
3、关联自定义的校验器和自定义的校验注解
@Constraint(validatedBy = { ListValueConstraintValidator.class})
4、使用实例
- /**
- * 显示状态[0-不显示;1-显示]
- */
- @ListValue(value = {0,1},groups ={AddGroup.class})
- private Integer showStatus;
POST product/categorybrandrelation/save
请求参数
{"brandId":1,"catelogId":2}
响应数据
{
"msg": "success",
"code": 0
}
修改 “com.atguigu.gulimall.product.controller.CategoryBrandRelationController”类,代码如下:
- /**
- * 保存
- */
- @RequestMapping("/save")
- public R save(@RequestBody CategoryBrandRelationEntity categoryBrandRelation){
- categoryBrandRelationService.saveDetail(categoryBrandRelation);
-
- return R.ok();
- }
修改“com.atguigu.gulimall.product.service.CategoryBrandRelationService”接口,代码如下:
void saveDetail(CategoryBrandRelationEntity categoryBrandRelation);
修改“com.atguigu.gulimall.product.service.impl.CategoryBrandRelationServiceImpl”类,代码如下:
- @Override
- public void saveDetail(CategoryBrandRelationEntity categoryBrandRelation) {
- Long brandId = categoryBrandRelation.getBrandId();
- Long catelogId = categoryBrandRelation.getCatelogId();
- //1、查询详细名字
- BrandEntity brandEntity = brandDao.selectById(brandId);
- CategoryEntity categoryEntity = categoryDao.selectById(catelogId);
- categoryBrandRelation.setBrandName(brandEntity.getName());
- categoryBrandRelation.setCatelogName(categoryEntity.getName());
- this.save(categoryBrandRelation);
- }
<el-button type="text" size="small" @click="updateCatelogHandle(scope.row.brandId)">关联分类</el-button>
- updateCatelogHandle(brandId) {
- this.cateRelationDialogVisible = true;
- this.brandId = brandId;
- this.getCateRelation();
- },
GET /product/categorybrandrelation/catelog/list
请求参数
参数名 | 参数类型 | 描述 |
---|---|---|
brandId | long | 品牌id |
响应数据
{
"msg": "success",
"code": 0,
"data": [{
"catelogId": 0,
"catelogName": "string",
}]
}
修改 “com.atguigu.gulimall.product.controller.CategoryBrandRelationController”类,代码如下:
- /**
- * 获取品牌关联的所有分类列表
- */
- @RequestMapping(value = "/catelog/list", method = RequestMethod.GET)
- 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);
- }
重新执行“sys_menus.sql”
接口文档:
GET /product/attrgroup/list/{catelogId}
请求参数
{
page: 1,//当前页码
limit: 10,//每页记录数
sidx: 'id',//排序字段
order: 'asc/desc',//排序方式
key: '华为'//检索关键字
}
分页数据
响应数据
{
"msg": "success",
"code": 0,
"page": {
"totalCount": 0,
"pageSize": 10,
"totalPage": 0,
"currPage": 1,
"list": [{
"attrGroupId": 0, //分组id
"attrGroupName": "string", //分组名
"catelogId": 0, //所属分类
"descript": "string", //描述
"icon": "string", //图标
"sort": 0 //排序
"catelogPath": [2,45,225] //分类完整路径
}]
}
}
现在想要实现点击菜单的左边,能够实现在右边展示数据
父子组件传递数据:
1)子组件给父组件传递数据,事件机制;
在category中绑定node-click事件,
<el-tree :data="menus" :props="defaultProps" node-key="catId" ref="menuTree" @node-click="nodeClick" ></el-tree>
2)子组件给父组件发送一个事件,携带上数据;
- nodeClick(data,Node,component){
- console.log("子组件",data,Node,component);
- this.$emit("tree-node-click",data,Node,component);
- },
this.$emit(事件名,“携带的数据”);
3)父组件中的获取发送的事件
<category @tree-node-click="treeNodeClick"></category>
- //获取发送的事件数据
- treeNodeClick(data,Node,component){
- console.log("attgroup感知到的category的节点被点击",data,Node,component);
- console.log("刚才被点击的菜单ID",data.catId);
- },
修改:“com.atguigu.gulimall.product.controller.AttrGroupController”类,代码如下:
- /**
- * 列表
- */
- @RequestMapping("/list/{catelogId}")
- public R list(@RequestParam Map<String, Object> params, @PathVariable("catelogId") Long catelogId){
- // PageUtils page = attrGroupService.queryPage(params);
- PageUtils page = attrGroupService.queryPage(params,catelogId);
- return R.ok().put("page", page);
- }
修改:“com.atguigu.gulimall.product.service.AttrGroupService”类,代码如下:
PageUtils queryPage(Map<String, Object> params, Long catelogId);
修改:“com.atguigu.gulimall.product.service.impl.AttrGroupServiceImpl”类,代码如下:
- @Override
- public PageUtils queryPage(Map<String, Object> params, Long catelogId) {
- String key = (String) params.get("key");
- //select * from pms_attr_group where catelog_id = ? and (attr_group_id = key or attr_group_name like %key%)
- QueryWrapper<AttrGroupEntity> wrapper = new QueryWrapper<AttrGroupEntity>();
- if (!StringUtils.isEmpty(key)){
- wrapper.and((obj)->{
- obj.eq("attr_group_id",key).or().like("attr_group_name",key);
- });
- }
- if (catelogId == 0){
- IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params), wrapper);
- return new PageUtils(page);
- }else{
- // String key = (String) params.get("key");
- // //select * from pms_attr_group where catelog_id = ? and (attr_group_id = key or attr_group_name like %key%)
- // QueryWrapper<AttrGroupEntity> wrapper = new QueryWrapper<AttrGroupEntity>().eq("catelog_id",catelogId);
- // if (!StringUtils.isEmpty(key)){
- // wrapper.and((obj)->{
- // obj.eq("attr_group_id",key).or().like("attr_group_name",key);
- // });
- // }
- wrapper.eq("catelog_id",catelogId);
- IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params), wrapper);
- return new PageUtils(page);
- }
-
- }
GET /product/attrgroup/info/{attrGroupId}
响应数据
{
"code": 0,
"msg": "success",
"attrGroup": {
"attrGroupId": 1,
"attrGroupName": "主体",
"sort": 0,
"descript": null,
"icon": null,
"catelogId": 225,
"catelogPath": [
2,
34,
225
] //完整分类路径
}
}
修改:“com.atguigu.gulimall.product.controller.AttrGroupController”类,代码如下:
- /**
- * 信息
- */
- @RequestMapping("/info/{attrId}")
- //@RequiresPermissions("product:attr:info")
- public R info(@PathVariable("attrId") Long attrId){
- //AttrEntity attr = attrService.getById(attrId);
- AttrResponseVo respVo = attrService.getAttrInfo(attrId);
-
- return R.ok().put("attr", respVo);
- }
修改:“com.atguigu.gulimall.product.service.AttrService”类,代码如下:
PageUtils queryPage(Map<String, Object> params, Long catelogId);
修改:“com.atguigu.gulimall.product.service.impl.AttrServiceImpl”类,代码如下:
- @Override
- public AttrResponseVo getAttrInfo(Long attrId) {
- AttrResponseVo responseVo = new AttrResponseVo();
- AttrEntity attrEntity = this.getById(attrId);
- BeanUtils.copyProperties(attrEntity,responseVo);
- //1、设置分组信息
- AttrAttrgroupRelationEntity relationEntity = attrAttrgroupRelationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrId));
- if (relationEntity != null){
- responseVo.setAttrGroupId(relationEntity.getAttrGroupId());
- AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(relationEntity.getAttrGroupId());
- if (attrGroupEntity != null){
- responseVo.setGroupName(attrGroupEntity.getAttrGroupName());
- }
- }
- //2、设置分类信息
- Long catelogId = attrEntity.getCatelogId();
- Long[] catelogPath = categoryService.findCatelogPath(catelogId);
- responseVo.setCatelogPath(catelogPath);
- CategoryEntity categoryEntity = categoryDao.selectById(catelogId);
- if (categoryEntity != null){
- responseVo.setCatelogName(categoryEntity.getName());
- }
-
- return responseVo;
- }
获取属性分组的关联的所有属性
GET:/product/attrgroup/{attrgroupId}/attr/relation
接口描述
获取当前属性分组所关联的属性
请求参数
响应数据
{
"msg": "success",
"code": 0,
"data": [
{
"attrId": 4,
"attrName": "aad",
"searchType": 1,
"valueType": 1,
"icon": "qq",
"valueSelect": "v;q;w",
"attrType": 1,
"enable": 1,
"catelogId": 225,
"showDesc": 1
}
]
}
如何查找:既然给出了attr_group_id,那么到中间表中查询出来所关联的attr_id,然后得到最终的所有属性即可。
可能出现null值的问题
修改“com.atguigu.gulimall.product.controller.AttrGroupController”类,代码如下:
- @GetMapping("{attrgroupId}/attr/relation")
- public R attrRelation(@PathVariable("attrgroupId") Long attrgroupId){
- List<AttrEntity> attrEntities = attrService.getRelationAttr(attrgroupId);
- return R.ok().put("data",attrEntities);
- }
修改“com.atguigu.gulimall.product.service.impl.AttrServiceImpl”类,代码如下:
- /**
- * 根据分组id查找所有关联的基本属性
- * @param attrgroupId
- * @return
- */
- @Override
- public List<AttrEntity> getRelationAttr(Long attrgroupId) {
- List<AttrAttrgroupRelationEntity> entities = attrAttrgroupRelationDao.selectList(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_group_id", attrgroupId));
- List<Long> addrIds = entities.stream().map((attr) -> {
- return attr.getAttrId();
- }).collect(Collectors.toList());
- if (attrIds == null || attrIds.size() == 0){
- return null;
- }
- List<AttrEntity> attrEntities = this.listByIds(addrIds);
- return attrEntities;
- }
POST /product/attrgroup/attr/relation/delete
请求参数
[{"attrId":1,"attrGroupId":2}]
响应数据
{
"msg": "success",
"code": 0
}
修改“com.atguigu.gulimall.product.controller.AttrGroupController”类,代码如下:
- // /product/attrgroup/attr/relation/delete
- @PostMapping("/attr/relation/delete")
- public R deleteRelation(@RequestBody AttrGroupRelationVo[] vos){
- attrService.deletRelation(vos);
- return R.ok();
- }
修改“com.atguigu.gulimall.product.service.AttrService”类,代码如下:
void deletRelation(AttrGroupRelationVo[] vos);
修改“com.atguigu.gulimall.product.service.impl.AttrServiceImpl”类,代码如下:
- @Override
- public void deletRelation(AttrGroupRelationVo[] vos) {
- //attrAttrgroupRelationDao.delete(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id",1).eq("attr_group_id",1));
- List<AttrAttrgroupRelationEntity> entities = Arrays.asList(vos).stream().map((item) -> {
- AttrAttrgroupRelationEntity entity = new AttrAttrgroupRelationEntity();
- BeanUtils.copyProperties(item, entity);
- return entity;
- }).collect(Collectors.toList());
- attrAttrgroupRelationDao.deleteBatchRelation(entities);
- }
修改“com.atguigu.gulimall.product.dao.AttrAttrgroupRelationDao”类,代码如下:
void deleteBatchRelation(@Param("entities") List<AttrAttrgroupRelationEntity> entities);
注意如果遍历的是一个实体类里的某一个参数,分隔符 separator=" or "
- <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>
GET /product/attrgroup/{attrgroupId}/noattr/relation
接口描述
获取属性分组里面还没有关联的本分类里面的其他基本属性,方便添加新的关联
请求参数
{
page: 1,//当前页码
limit: 10,//每页记录数
sidx: 'id',//排序字段
order: 'asc/desc',//排序方式
key: '华为'//检索关键字
}
分页数据
响应数据
{
"msg": "success",
"code": 0,
"page": {
"totalCount": 3,
"pageSize": 10,
"totalPage": 1,
"currPage": 1,
"list": [{
"attrId": 1,
"attrName": "aaa",
"searchType": 1,
"valueType": 1,
"icon": "aa",
"valueSelect": "aa;ddd;sss;aaa2",
"attrType": 1,
"enable": 1,
"catelogId": 225,
"showDesc": 1
}]
}
}
POST /product/attrgroup/attr/relation
请求参数
[{
"attrGroupId": 0, //分组id
"attrId": 0, //属性id
}]
响应数据
{
"msg": "success",
"code": 0
}
修改“com.atguigu.gulimall.product.controller.AttrGroupController”类,代码如下:
- // /product/attrgroup/attr/relation
- @PostMapping("/attr/relation")
- public R addRelation(@RequestBody List<AttrGroupRelationVo> vos){
- relationService.saveBatch(vos);
- return R.ok();
- }
修改“com.atguigu.gulimall.product.service.AttrAttrgroupRelationService”类,代码如下:
void saveBatch(List<AttrGroupRelationVo> vos);
修改“com.atguigu.gulimall.product.service.impl.AttrAttrgroupRelationServiceImpl”类,代码如下:
- @Override
- public void saveBatch(List<AttrGroupRelationVo> vos) {
- List<AttrAttrgroupRelationEntity> collect = vos.stream().map(item -> {
- AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
- BeanUtils.copyProperties(item, relationEntity);
- return relationEntity;
- }).collect(Collectors.toList());
- this.saveBatch(collect);
- }
POST /product/attr/save
请求参数
{
"attrGroupId": 0, //属性分组id
"attrName": "string",//属性名
"attrType": 0, //属性类型
"catelogId": 0, //分类id
"enable": 0, //是否可用
"icon": "string", //图标
"searchType": 0, //是否检索
"showDesc": 0, //快速展示
"valueSelect": "string", //可选值列表
"valueType": 0 //可选值模式
}
分页数据
响应数据
{
"msg": "success",
"code": 0
}
规格参数新增时,请求的URL:Request URL:
http://localhost:88/api/product/attr/base/list/0?t=1588731762158&page=1&limit=10&key=
当有新增字段时,我们往往会在entity实体类中新建一个字段,并标注数据库中不存在该字段,然而这种方式并不规范
比较规范的做法是,新建一个vo文件夹,将每种不同的对象,按照它的功能进行了划分。在java中,涉及到了这几种类型
Request URL: http://localhost:88/api/product/attr/save,现在的情况是,它在保存的时候,只是保存了attr,并没有保存attrgroup,为了解决这个问题,我们新建了一个vo/AttrVo,在原AttrEntity基础上增加了attrGroupId字段,使得保存新增数据的时候,也保存了它们之间的关系。
通过" BeanUtils.copyProperties(attr,attrEntity);"能够实现在两个Bean之间拷贝数据,但是两个Bean的字段要相同
修改“com.atguigu.gulimall.product.controller.AttrController”类,代码如下:
- /**
- * 保存
- */
- @RequestMapping("/save")
- public R save(@RequestBody AttrVo attr){
- attrService.saveAttr(attr);
-
- return R.ok();
- }
修改“com.atguigu.gulimall.product.service.AttrService”类,代码如下:
void saveAttr(AttrVo attr);
修改“com.atguigu.gulimall.product.service.impl.AttrServiceImpl”类,代码如下:
- @Transactional
- @Override
- public void saveAttr(AttrVo attr) {
- AttrEntity attrEntity = new AttrEntity();
- BeanUtils.copyProperties(attr,attrEntity);
- //1、保存基本数据
- this.save(attrEntity);
- //2、保存关联关系
- AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
- relationEntity.setAttrGroupId(attr.getAttrGroupId());
- relationEntity.setAttrId(attrEntity.getAttrId());
- attrAttrgroupRelationDao.insert(relationEntity);
- }
问题:现在有两个查询,一个是查询部分,另外一个是查询全部,但是又必须这样来做吗?还是有必要的,但是可以在后台进行设计,两种查询是根据catId是否为零进行区分的。
GET /product/attr/sale/list/{catelogId}
请求参数
{
page: 1,//当前页码
limit: 10,//每页记录数
sidx: 'id',//排序字段
order: 'asc/desc',//排序方式
key: '华为'//检索关键字
}
分页数据
响应数据
{
"msg": "success",
"code": 0,
"page": {
"totalCount": 0,
"pageSize": 10,
"totalPage": 0,
"currPage": 1,
"list": [{
"attrId": 0, //属性id
"attrName": "string", //属性名
"attrType": 0, //属性类型,0-销售属性,1-基本属性
"catelogName": "手机/数码/手机", //所属分类名字
"groupName": "主体", //所属分组名字
"enable": 0, //是否启用
"icon": "string", //图标
"searchType": 0,//是否需要检索[0-不需要,1-需要]
"showDesc": 0,//是否展示在介绍上;0-否 1-是
"valueSelect": "string",//可选值列表[用逗号分隔]
"valueType": 0//值类型[0-为单个值,1-可以选择多个值]
}]
}
}
修改“com.atguigu.gulimall.product.controller.AttrController”类,代码如下:
- public class AttrController {
- @Autowired
- private AttrService attrService;
- // /product/attr/sale/list/{catelogId}
- // /product/attr/base/list/{catelogId}
- @GetMapping("/{attrType}/list/{catelogId}")
- public R baseAttrList(@RequestParam Map<String,Object> params,
- @PathVariable("catelogId") Long catelogId,
- @PathVariable("attrType") String type){
- PageUtils page = attrService.queryBaseAttrPage(params,catelogId,type);
- return R.ok().put("page",page);
- }
修改“com.atguigu.gulimall.product.service.impl.AttrServiceImpl”类,代码如下:
- @Override
- public PageUtils queryBaseAttrPage(Map<String, Object> params, Long catelogId, String type) {
- QueryWrapper<AttrEntity> queryWrapper = new QueryWrapper<AttrEntity>().eq("attr_type",
- "base".equalsIgnoreCase(type)?ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode():ProductConstant.AttrEnum.ATTR_TYPE_SALE.getCode());
- 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<AttrResponseVo> responseVos = records.stream().map(attrEntity -> {
- AttrResponseVo responseVo = new AttrResponseVo();
- BeanUtils.copyProperties(attrEntity, responseVo);
- //1、设置分类和分组的名字(只有基本属性显示)
- if ("base".equalsIgnoreCase(type)){
- AttrAttrgroupRelationEntity attrId = attrAttrgroupRelationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrEntity.getAttrId()));
- if (attrId != null) {
- AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrId.getAttrGroupId());
- responseVo.setGroupName(attrGroupEntity.getAttrGroupName());
- }
- }
- CategoryEntity categoryEntity = categoryDao.selectById(attrEntity.getCatelogId());
- if (categoryEntity != null) {
- responseVo.setCatelogName(categoryEntity.getName());
- }
- return responseVo;
- }).collect(Collectors.toList());
-
- pageUtils.setList(responseVos);
- return pageUtils;
- }
在gulimall-common添加常量的类“com.atguigu.common.constant.ProductConstant”
- package com.atguigu.common.constant;
-
- /**
- * @author WangTianShun
- * @date 2020/10/14 15:11
- */
- public class ProductConstant {
- public enum AttrEnum{
- ATTR_TYPE_BASE(1,"基本属性"),
- ATTR_TYPE_SALE(0,"销售属性");
-
- AttrEnum(int code,String msg){
- this.code = code;
- this.msg = msg;
- }
-
- private int code;
-
- private String msg;
-
- public int getCode() {
- return code;
- }
-
- public String getMsg() {
- return msg;
- }
- }
- }
因为基本属性和销售属性共用的一张表,所以保存和修改的方法也是共用的,只根据attr_type来判断,所以保存和修改的方法也要修改销售(关于属性和分组的关联,销售属性模块不需要添加到数据库)
修改“com.atguigu.gulimall.product.service.impl.AttrServiceImpl”类,代码如下:
保存
- @Transactional
- @Override
- public void saveAttr(AttrVo attr) {
- AttrEntity attrEntity = new AttrEntity();
- BeanUtils.copyProperties(attr,attrEntity);
- //1、保存基本数据
- this.save(attrEntity);
- //2、保存关联关系(只有基本信息才保存关联关系)
- if (attr.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()){
- AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
- relationEntity.setAttrGroupId(attr.getAttrGroupId());
- relationEntity.setAttrId(attrEntity.getAttrId());
- attrAttrgroupRelationDao.insert(relationEntity);
- }
-
- }
查看详情
- @Override
- public AttrResponseVo getAttrInfo(Long attrId) {
- AttrResponseVo responseVo = new AttrResponseVo();
- AttrEntity attrEntity = this.getById(attrId);
- BeanUtils.copyProperties(attrEntity,responseVo);
- //基本类型才查询分组
- if (attrEntity.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()){
- //1、设置分组信息
- AttrAttrgroupRelationEntity relationEntity = attrAttrgroupRelationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrId));
- if (relationEntity != null){
- responseVo.setAttrGroupId(relationEntity.getAttrGroupId());
- AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(relationEntity.getAttrGroupId());
- if (attrGroupEntity != null){
- responseVo.setGroupName(attrGroupEntity.getAttrGroupName());
- }
- }
- }
- //2、设置分类信息
- Long catelogId = attrEntity.getCatelogId();
- Long[] catelogPath = categoryService.findCatelogPath(catelogId);
- responseVo.setCatelogPath(catelogPath);
- CategoryEntity categoryEntity = categoryDao.selectById(catelogId);
- if (categoryEntity != null){
- responseVo.setCatelogName(categoryEntity.getName());
- }
-
- return responseVo;
- }
修改
- @Transactional
- @Override
- public void updateAttr(AttrVo attr) {
- AttrEntity attrEntity = new AttrEntity();
- BeanUtils.copyProperties(attr,attrEntity);
- this.updateById(attrEntity);
- //基本类型才修改分组
- if (attrEntity.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()){
- //1、修改分组关联
- AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
- relationEntity.setAttrGroupId(attr.getAttrGroupId());
- relationEntity.setAttrId(attr.getAttrId());
- Integer count = attrAttrgroupRelationDao.selectCount(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrEntity.getAttrId()));
- //有数据说明是修改,没数据是新增
- if (count>0){
- attrAttrgroupRelationDao.update(relationEntity,new UpdateWrapper<AttrAttrgroupRelationEntity>().eq("attr_id",attrEntity.getAttrId()));
- }else {
- attrAttrgroupRelationDao.insert(relationEntity);
- }
- }
-
- }
GET /product/attr/base/listforspu/{spuId}
响应数据
{
"msg": "success",
"code": 0,
"data": [{
"id": 43,
"spuId": 11,
"attrId": 7,
"attrName": "入网型号",
"attrValue": "LIO-AL00",
"attrSort": null,
"quickShow": 1
}]
}
修改“com.atguigu.gulimall.product.controller.AttrController”类,代码如下:
- @GetMapping("/base/listforspu/{spuId}")
- public R baseAttrListForSpu(@PathVariable("spuId") Long spuId){
- List<ProductAttrValueEntity> entities = productAttrValueService.baseAttrListForSpu(spuId);
- return R.ok().put("data",entities);
-
- }
修改“com.atguigu.gulimall.product.service.ProductAttrValueService”类,代码如下:
List<ProductAttrValueEntity> baseAttrListForSpu(Long spuId);
修改“com.atguigu.gulimall.product.service.impl.ProductAttrValueServiceImpl”类,代码如下:
- @Override
- public List<ProductAttrValueEntity> baseAttrListForSpu(Long spuId) {
- List<ProductAttrValueEntity> entities = this.baseMapper.selectList(new QueryWrapper<ProductAttrValueEntity>().eq("spu_id", spuId));
- return entities;
- }
POST /product/attr/update/{spuId}
请求参数
[{
"attrId": 7,
"attrName": "入网型号",
"attrValue": "LIO-AL00",
"quickShow": 1
}, {
"attrId": 14,
"attrName": "机身材质工艺",
"attrValue": "玻璃",
"quickShow": 0
}, {
"attrId": 16,
"attrName": "CPU型号",
"attrValue": "HUAWEI Kirin 980",
"quickShow": 1
}]
响应数据
{
"msg": "success",
"code": 0
}
修改“com.atguigu.gulimall.product.controller.AttrController”类,代码如下:
- @PostMapping("/update/{spuId}")
- public R updateSpuAttr(@PathVariable("spuId") Long spuId,
- @RequestBody List<ProductAttrValueEntity> entities){
- productAttrValueService.updateSpuAttr(spuId,entities);
- return R.ok();
-
- }
修改“com.atguigu.gulimall.product.controller.AttrController”类,代码如下:
void updateSpuAttr(Long spuId, List<ProductAttrValueEntity> entities);
修改“com.atguigu.gulimall.product.controller.AttrController”类,代码如下:
- @Transactional
- @Override
- public void updateSpuAttr(Long spuId, List<ProductAttrValueEntity> entities) {
- //1、删除spu之前对应的所有属性
- this.baseMapper.delete(new QueryWrapper<ProductAttrValueEntity>().eq("spu_id",spuId));
- List<ProductAttrValueEntity> collect = entities.stream().map(item -> {
- item.setSpuId(spuId);
- return item;
- }).collect(Collectors.toList());
- this.saveBatch(collect);
- }
商品管理需要会员等级,先把资料前端文件夹里的modules里的文件导入vsCode里重新运行,添加几个会员
POST /member/memberlevel/list
请求参数
{
page: 1,//当前页码
limit: 10,//每页记录数
sidx: 'id',//排序字段
order: 'asc/desc',//排序方式
key: '华为'//检索关键字
}
分页数据
响应数据
{
"msg": "success",
"code": 0,
"page": {
"totalCount": 0,
"pageSize": 10,
"totalPage": 0,
"currPage": 1,
"list": [{
"id": 1,
"name": "aaa",
"growthPoint": null,
"defaultStatus": null,
"freeFreightPoint": null,
"commentGrowthPoint": null,
"priviledgeFreeFreight": null,
"priviledgeMemberPrice": null,
"priviledgeBirthday": null,
"note": null
}]
}
}
修改“com.atguigu.gulimall.member.controller.MemberLevelController”类,代码如下:
- @RequestMapping("/list")
- public R list(@RequestParam Map<String, Object> params){
- PageUtils page = memberLevelService.queryPage(params);
-
- return R.ok().put("page", page);
- }
修改“com.atguigu.gulimall.member.service.MemberLevelService”类,代码如下:
PageUtils queryPage(Map<String, Object> params);
修改“com.atguigu.gulimall.member.service.impl.MemberLevelServiceImpl”类,代码如下:
- @Override
- public PageUtils queryPage(Map<String, Object> params) {
- IPage<MemberLevelEntity> page = this.page(
- new Query<MemberLevelEntity>().getPage(params),
- new QueryWrapper<MemberLevelEntity>()
- );
-
- return new PageUtils(page);
- }
GET /product/categorybrandrelation/brands/list
请求参数
参数名 | 参数类型 | 描述 |
---|---|---|
catId | long | 分类id |
响应数据
{
"msg": "success",
"code": 0,
"data": [{
"brandId": 0,
"brandName": "string",
}]
}
修改“com.atguigu.gulimall.product.controller.CategoryBrandRelationController”类,代码如下:
- /**
- * 获取三级分类下的所有品牌
- * /product/categorybrandrelation/brands/list
- *
- * 1、Controller: 处理请求,接收和校验数据
- * 2、service接收controller数据,进行业务处理
- * 3、Controller接收Service处理完的数据,封装页面指定的vo
- */
- @GetMapping("brands/list")
- public R relationBrandList(@RequestParam(value = "catId", required = true) Long catId){
- List<BrandEntity> list = categoryBrandRelationService.getBrandsByCatId(catId);
- List<BrandVo> collect = list.stream().map(item -> {
- BrandVo brandVo = new BrandVo();
- brandVo.setBrandId(item.getBrandId());
- brandVo.setBrandName(item.getName());
- return brandVo;
- }).collect(Collectors.toList());
- return R.ok().put("data",collect);
- }
修改“com.atguigu.gulimall.product.service.CategoryBrandRelationService”类,代码如下:
List<BrandEntity> getBrandsByCatId(Long catId);
修改“com.atguigu.gulimall.product.service.impl.CategoryBrandRelationServiceImpl”类,代码如下:
- @Override
- public List<BrandEntity> getBrandsByCatId(Long catId) {
- List<CategoryBrandRelationEntity> catelogId = categoryBrandRelationDao.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;
- }
GET /product/attrgroup/{catelogId}/withattr
响应数据
{
"msg": "success",
"code": 0,
"data": [{
"attrGroupId": 1,
"attrGroupName": "主体",
"sort": 0,
"descript": "主体",
"icon": "dd",
"catelogId": 225,
"attrs": [{
"attrId": 7,
"attrName": "入网型号",
"searchType": 1,
"valueType": 0,
"icon": "xxx",
"valueSelect": "aaa;bb",
"attrType": 1,
"enable": 1,
"catelogId": 225,
"showDesc": 1,
"attrGroupId": null
}, {
"attrId": 8,
"attrName": "上市年份",
"searchType": 0,
"valueType": 0,
"icon": "xxx",
"valueSelect": "2018;2019",
"attrType": 1,
"enable": 1,
"catelogId": 225,
"showDesc": 0,
"attrGroupId": null
}]
},
{
"attrGroupId": 2,
"attrGroupName": "基本信息",
"sort": 0,
"descript": "基本信息",
"icon": "xx",
"catelogId": 225,
"attrs": [{
"attrId": 11,
"attrName": "机身颜色",
"searchType": 0,
"valueType": 0,
"icon": "xxx",
"valueSelect": "黑色;白色",
"attrType": 1,
"enable": 1,
"catelogId": 225,
"showDesc": 1,
"attrGroupId": null
}]
}]
}
修改“com.atguigu.gulimall.product.controller.AttrGroupController”类,代码如下:
- @GetMapping("/{catelogId}/withattr")
- public R getAttrGroupWithAttrs(@PathVariable("catelogId") Long catelogId){
- //1、查出当前分类下的所有分组
-
- //2、查出每个分组下的所有属性
- List<AttrGroupWithAttrsVo> list = attrGroupService.getAttrGroupWithAttrsByCatelogId(catelogId);
- return R.ok().put("data",list);
- }
修改“com.atguigu.gulimall.product.service.AttrGroupService”类,代码如下:
List<AttrGroupWithAttrsVo> getAttrGroupWithAttrsByCatelogId(Long catelogId);
修改“com.atguigu.gulimall.product.service.impl.AttrGroupServiceImpl”类,代码如下:
- /**
- * 根据分类id查出所有的分组以及这些组里面的属性
- * @param catelogId
- * @return
- */
- @Override
- public List<AttrGroupWithAttrsVo> getAttrGroupWithAttrsByCatelogId(Long catelogId) {
- //1、查询分组信息
- List<AttrGroupEntity> attrGroupEntities = this.list(new QueryWrapper<AttrGroupEntity>().eq("catelog_id", catelogId));
- //2、查询所有属性
- List<AttrGroupWithAttrsVo> collect = attrGroupEntities.stream().map(item -> {
- AttrGroupWithAttrsVo attrsVo = new AttrGroupWithAttrsVo();
- BeanUtils.copyProperties(item, attrsVo);
- List<AttrEntity> attrs = attrService.getRelationAttr(attrsVo.getAttrGroupId());
- attrsVo.setAttrs(attrs);
- return attrsVo;
- }).collect(Collectors.toList());
- return collect;
- }
POST /product/spuinfo/save
请求参数
{
"spuName": "Apple XR",
"spuDescription": "Apple XR",
"catalogId": 225,
"brandId": 12,
"weight": 0.048,
"publishStatus": 0,
"decript": ["https://gulimall-hello.oss-cn-beijing.aliyuncs.com/2019-11-22//66d30b3f-e02f-48b1-8574-e18fdf454a32_f205d9c99a2b4b01.jpg"],
"images": ["https://gulimall-hello.oss-cn-beijing.aliyuncs.com/2019-11-22//dcfcaec3-06d8-459b-8759-dbefc247845e_5b5e74d0978360a1.jpg", "https://gulimall-hello.oss-cn-beijing.aliyuncs.com/2019-11-22//5b15e90a-a161-44ff-8e1c-9e2e09929803_749d8efdff062fb0.jpg"],
"bounds": {
"buyBounds": 500,
"growBounds": 6000
},
"baseAttrs": [{
"attrId": 7,
"attrValues": "aaa;bb",
"showDesc": 1
}, {
"attrId": 8,
"attrValues": "2019",
"showDesc": 0
}],
"skus": [{
"attr": [{
"attrId": 9,
"attrName": "颜色",
"attrValue": "黑色"
}, {
"attrId": 10,
"attrName": "内存",
"attrValue": "6GB"
}],
"skuName": "Apple XR 黑色 6GB",
"price": "1999",
"skuTitle": "Apple XR 黑色 6GB",
"skuSubtitle": "Apple XR 黑色 6GB",
"images": [{
"imgUrl": "https://gulimall-hello.oss-cn-beijing.aliyuncs.com/2019-11-22//dcfcaec3-06d8-459b-8759-dbefc247845e_5b5e74d0978360a1.jpg",
"defaultImg": 1
}, {
"imgUrl": "https://gulimall-hello.oss-cn-beijing.aliyuncs.com/2019-11-22//5b15e90a-a161-44ff-8e1c-9e2e09929803_749d8efdff062fb0.jpg",
"defaultImg": 0
}],
"descar": ["黑色", "6GB"],
"fullCount": 5,
"discount": 0.98,
"countStatus": 1,
"fullPrice": 1000,
"reducePrice": 10,
"priceStatus": 0,
"memberPrice": [{
"id": 1,
"name": "aaa",
"price": 1998.99
}]
}, {
"attr": [{
"attrId": 9,
"attrName": "颜色",
"attrValue": "黑色"
}, {
"attrId": 10,
"attrName": "内存",
"attrValue": "12GB"
}],
"skuName": "Apple XR 黑色 12GB",
"price": "2999",
"skuTitle": "Apple XR 黑色 12GB",
"skuSubtitle": "Apple XR 黑色 6GB",
"images": [{
"imgUrl": "",
"defaultImg": 0
}, {
"imgUrl": "",
"defaultImg": 0
}],
"descar": ["黑色", "12GB"],
"fullCount": 0,
"discount": 0,
"countStatus": 0,
"fullPrice": 0,
"reducePrice": 0,
"priceStatus": 0,
"memberPrice": [{
"id": 1,
"name": "aaa",
"price": 1998.99
}]
}, {
"attr": [{
"attrId": 9,
"attrName": "颜色",
"attrValue": "白色"
}, {
"attrId": 10,
"attrName": "内存",
"attrValue": "6GB"
}],
"skuName": "Apple XR 白色 6GB",
"price": "1998",
"skuTitle": "Apple XR 白色 6GB",
"skuSubtitle": "Apple XR 黑色 6GB",
"images": [{
"imgUrl": "",
"defaultImg": 0
}, {
"imgUrl": "",
"defaultImg": 0
}],
"descar": ["白色", "6GB"],
"fullCount": 0,
"discount": 0,
"countStatus": 0,
"fullPrice": 0,
"reducePrice": 0,
"priceStatus": 0,
"memberPrice": [{
"id": 1,
"name": "aaa",
"price": 1998.99
}]
}, {
"attr": [{
"attrId": 9,
"attrName": "颜色",
"attrValue": "白色"
}, {
"attrId": 10,
"attrName": "内存",
"attrValue": "12GB"
}],
"skuName": "Apple XR 白色 12GB",
"price": "2998",
"skuTitle": "Apple XR 白色 12GB",
"skuSubtitle": "Apple XR 黑色 6GB",
"images": [{
"imgUrl": "",
"defaultImg": 0
}, {
"imgUrl": "",
"defaultImg": 0
}],
"descar": ["白色", "12GB"],
"fullCount": 0,
"discount": 0,
"countStatus": 0,
"fullPrice": 0,
"reducePrice": 0,
"priceStatus": 0,
"memberPrice": [{
"id": 1,
"name": "aaa",
"price": 1998.99
}]
}]
}
分页数据
响应数据
{
"msg": "success",
"code": 0
}
修改“com.atguigu.gulimall.product.controller.SpuInfoController”类,代码如下:
- /**
- * 保存
- */
- @RequestMapping("/save")
- public R save(@RequestBody SpuSaveVo vo){
- //spuInfoService.save(spuInfo);
- spuInfoService.save(vo);
-
- return R.ok();
- }
修改“com.atguigu.gulimall.product.service.SpuInfoService”类,代码如下:
- void save(SpuSaveVo vo);
-
- void saveBaseSpuInfo(SpuInfoEntity infoEntity);
修改“com.atguigu.gulimall.product.service.impl.SpuInfoServiceImpl”类,代码如下:
- /**
- * //TODO 高级部分完善
- * @param vo
- */
- @Transactional
- @Override
- public void save(SpuSaveVo vo) {
-
- //1、保存spu基本信息 pms_spu_info
- SpuInfoEntity infoEntity = new SpuInfoEntity();
- BeanUtils.copyProperties(vo,infoEntity);
- infoEntity.setCreateTime(new Date());
- infoEntity.setUpdateTime(new Date());
- this.saveBaseSpuInfo(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();
- spuImagesService.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());
- AttrEntity byId = attrService.getById(attr.getAttrId());
- valueEntity.setAttrName(byId.getAttrName());
- valueEntity.setAttrValue(attr.getAttrValues());
- valueEntity.setQuickShow(attr.getShowDesc());
- valueEntity.setSpuId(infoEntity.getId());
- return valueEntity;
- }).collect(Collectors.toList());
- productAttrValueService.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信息
- //6.1)、sku的基本信息 pms_sku_info
- List<Skus> skus = vo.getSkus();
- if (skus != null && skus.size() >0){
- skus.forEach(item->{
- String defaultImg = "";
- for (Images image : item.getImages()) {
- if (image.getDefaultImg() == 1){
- defaultImg = image.getImgUrl();
- }
- }
- SkuInfoEntity skuInfoEntity = new SkuInfoEntity();
- BeanUtils.copyProperties(item,skuInfoEntity);
- skuInfoEntity.setBrandId(infoEntity.getBrandId());
- skuInfoEntity.setCatalogId(infoEntity.getCatalogId());
- skuInfoEntity.setSaleCount(0L);
- skuInfoEntity.setSpuId(infoEntity.getId());
- skuInfoEntity.setSkuDefaultImg(defaultImg);
- skuInfoService.saveSkuInfo(skuInfoEntity);
-
- //6.2)、sku的图片信息 pms_sku_images
- Long skuId = skuInfoEntity.getSkuId();
- List<SkuImagesEntity> imagesEntities = item.getImages().stream().map(img -> {
- SkuImagesEntity imagesEntity = new SkuImagesEntity();
- imagesEntity.setSkuId(skuId);
- imagesEntity.setImgUrl(img.getImgUrl());
- imagesEntity.setDefaultImg(img.getDefaultImg());
- return imagesEntity;
- }).filter(entity->{
- //返回true就是需要,返回false就是剔除
- return !StringUtils.isEmpty(entity.getImgUrl());
- }).collect(Collectors.toList());
- // TODO 没有图片;路径的无需保存
- skuImagesService.saveBatch(imagesEntities);
-
- //6.3)、sku的销售属性信息 pms_sku_sale_attr_value
- List<Attr> attr = item.getAttr();
- List<SkuSaleAttrValueEntity> skuSaleAttrValueEntities = attr.stream().map(a -> {
- SkuSaleAttrValueEntity saleAttrValueEntity = new SkuSaleAttrValueEntity();
- BeanUtils.copyProperties(a, saleAttrValueEntity);
- saleAttrValueEntity.setSkuId(skuId);
- return saleAttrValueEntity;
- }).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优惠信息失败");
- }
- }
-
- });
- }
-
- }
//1、保存spu基本信息 pms_spu_info
修改“com.atguigu.gulimall.product.service.impl.SpuInfoServiceImpl”类,代码如下:
- @Override
- public void saveBaseSpuInfo(SpuInfoEntity infoEntity) {
- this.baseMapper.insert(infoEntity);
- }
//2、保存spu的描述图片 pms_spu_info_desc
修改“com.atguigu.gulimall.product.service.SpuInfoDescService”类,代码如下:
void saveSpuInfoDesc(SpuInfoDescEntity descEntity);
修改“com.atguigu.gulimall.product.service.impl.SpuInfoDescServiceImpl”类,代码如下:
- @Override
- public void saveSpuInfoDesc(SpuInfoDescEntity descEntity) {
- this.baseMapper.insert(descEntity);
- }
//3、保存spu的图片集 pms_spu_images
修改“com.atguigu.gulimall.product.service.SpuImagesService”类,代码如下:
void saveImages(Long id, List<String> images);
修改“com.atguigu.gulimall.product.service.impl.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 entity = new SpuImagesEntity();
- entity.setSpuId(id);
- entity.setImgUrl(img);
- return entity;
- }).collect(Collectors.toList());
- this.saveBatch(collect);
- }
- }
//4、保存spu的规格参数 pms_product_attr_value
修改“com.atguigu.gulimall.product.service.ProductAttrValueService”类,代码如下:
void saveProductAttr(List<ProductAttrValueEntity> collect);
修改“com.atguigu.gulimall.product.service.impl.ProductAttrValueServiceImpl”类,代码如下:
- @Override
- public void saveProductAttr(List<ProductAttrValueEntity> collect) {
- this.saveBatch(collect);
- }
//5、保存spu的积分信息 gulimall_sms --> sms_spu_bounds
注意:
重点:需要远程调用第三方服务
1、创建openFeign配置(前提第三方服务已经注册和配置到注册中心了)
2、在主程序类中加上@EnableFeignClients(basePackages = "com.atguigu.gulimall.product.feign")
- @EnableFeignClients(basePackages = "com.atguigu.gulimall.product.feign")
- @EnableDiscoveryClient
- @MapperScan("com.atguigu.gulimall.product.dao")
- @SpringBootApplication
- public class GulimallProductApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(GulimallProductApplication.class, args);
- }
-
- }
3、在gulimall-common添加服务与服务之间调用的to类“com.atguigu.common.to.SpuBoundTo”类,代码如下:
- @Data
- public class SpuBoundTo {
- private Long SpuId;
- private BigDecimal buyBounds;
- private BigDecimal growBounds;
- }
修改“com.atguigu.gulimall.product.feign.CouponFeignService”类,代码如下:
- package com.atguigu.gulimall.product.feign;
-
- import com.atguigu.common.to.SpuBoundTo;
- import com.atguigu.common.utils.R;
- import org.springframework.cloud.openfeign.FeignClient;
- import org.springframework.web.bind.annotation.PostMapping;
- import org.springframework.web.bind.annotation.RequestBody;
- import org.springframework.web.bind.annotation.ResponseBody;
-
- /**
- * @author WangTianShun
- * @date 2020/10/16 8:54
- */
- @FeignClient("gulimall-coupon")
- public interface CouponFeignService {
- /**
- * 1、couponFeignService.saveSpuBounds(spuBoundTo)
- * 1)、@RequestBody将这个对象转为json
- * 2)、找到gulimall-coupon服务,给/coupon/spubounds/save发送请求。将上一步转的json放在请求体的位置发送请求
- * 3)、对方服务收到请求请求体有json数据。
- * (@RequestBody SpuBoundsEntity spuBoundTo)将请求体里的json转为SpuBoundsEntity
- * 只要json数据模型是兼容的。对方服务无需使用同一个to
- * @param spuBoundTo
- * @return
- */
- @PostMapping("/coupon/spubounds/save")
- R saveSpuBounds(@RequestBody SpuBoundTo spuBoundTo);
- }
4、第三方服务接口
- /**
- * 保存
- */
- @PostMapping("/save")
- public R save(@RequestBody SpuBoundsEntity spuBounds){
- spuBoundsService.save(spuBounds);
-
- return R.ok();
- }
//6、保存当前spu对应的所有sku信息
//6.1)、sku的基本信息 pms_sku_info
修改“com.atguigu.gulimall.product.service.SkuInfoServicel”类,代码如下:
void saveSkuInfo(SkuInfoEntity skuInfoEntity);
修改“com.atguigu.gulimall.product.service.impl.SkuInfoServiceImpl”类,代码如下:
- @Override
- public void saveSkuInfo(SkuInfoEntity skuInfoEntity) {
- this.baseMapper.insert(skuInfoEntity);
- }
//6.4)、sku的优惠、满减等信息 gulimall_sms -> sms_sku_ladder/sms_sku_full_reduction/sms_member_price
在gulimall-common添加服务与服务之间调用的to类“com.atguigu.common.to.MemberPrice”,“com.atguigu.common.to.SkuReductionTo”类,代码如下:
- /**
- * Copyright 2019 bejson.com
- */
- package com.atguigu.common.to;
-
- import lombok.Data;
-
- import java.math.BigDecimal;
-
- /**
- * Auto-generated: 2019-11-26 10:50:34
- *
- * @author bejson.com (i@bejson.com)
- * @website http://www.bejson.com/java2pojo/
- */
- @Data
- public class MemberPrice {
-
- private Long id;
- private String name;
- private BigDecimal price;
-
- }
- package com.atguigu.common.to;
-
- import lombok.Data;
-
- import java.math.BigDecimal;
- import java.util.List;
-
- /**
- * @author WangTianShun
- * @date 2020/10/16 9:53
- */
- @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;
- }
修改“com.atguigu.gulimall.coupon.controller.SkuFullReductionController”类,代码如下:
- /**
- * 保存
- */
- @RequestMapping("/saveInfo")
- public R saveInfo(@RequestBody SkuReductionTo skuReductionTo){
- skuFullReductionService.saveReduction(skuReductionTo);
-
- return R.ok();
- }
修改“com.atguigu.gulimall.coupon.service.SkuFullReductionService”类,代码如下:
void saveReduction(SkuReductionTo skuReductionTo);
修改“com.atguigu.gulimall.coupon.service.impl.SkuFullReductionServiceImpl”类,代码如下:
- @Override
- public void saveReduction(SkuReductionTo reductionTo) {
- //1、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);
- }
-
- //2、ms_sku_full_reduction
- SkuFullReductionEntity reductionEntity = new SkuFullReductionEntity();
- BeanUtils.copyProperties(reductionTo,reductionEntity);
- if (reductionEntity.getFullPrice().compareTo(new BigDecimal("0")) == 1 ){
- this.save(reductionEntity);
- }
-
GET /product/spuinfo/list
请求参数
{
page: 1,//当前页码
limit: 10,//每页记录数
sidx: 'id',//排序字段
order: 'asc/desc',//排序方式
key: '华为',//检索关键字
catelogId: 6,//三级分类id
brandId: 1,//品牌id
status: 0,//商品状态
}
分页数据
响应数据
{
"msg": "success",
"code": 0,
"page": {
"totalCount": 0,
"pageSize": 10,
"totalPage": 0,
"currPage": 1,
"list": [{"brandId": 0, //品牌id
"brandName": "品牌名字",
"catalogId": 0, //分类id
"catalogName": "分类名字",
"createTime": "2019-11-13T16:07:32.877Z", //创建时间
"id": 0, //商品id
"publishStatus": 0, //发布状态
"spuDescription": "string", //商品描述
"spuName": "string", //商品名字
"updateTime": "2019-11-13T16:07:32.877Z", //更新时间
"weight": 0 //重量}]
}
}
修改“com.atguigu.gulimall.product.controller.SpuInfoController”类,代码如下:
- /**
- * 列表
- */
- @RequestMapping("/list")
- public R list(@RequestParam Map<String, Object> params){
- PageUtils page = spuInfoService.queryPageByCondition(params);
-
- return R.ok().put("page", page);
- }
修改“com.atguigu.gulimall.product.service.SpuInfoService”类,代码如下:
PageUtils queryPageByCondition(Map<String, Object> params);
修改“com.atguigu.gulimall.product.service.impl.SpuInfoServiceImpl”类,代码如下:
- @Override
- public PageUtils queryPageByCondition(Map<String, Object> params) {
-
- QueryWrapper<SpuInfoEntity> wrapper = new QueryWrapper<>();
-
- String key = (String) params.get("key");
- if(!StringUtils.isEmpty(key)){
- wrapper.and((w)->{w.eq("id",key).or().like("spu_name",key);});
- }
- // status=1 and (id=1 or spu_name like xxx)
- 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)&&!"0".equalsIgnoreCase(brandId)){
- wrapper.eq("brand_id",brandId);
- }
-
- String catelogId = (String) params.get("catelogId");
- if(!StringUtils.isEmpty(catelogId)&&!"0".equalsIgnoreCase(catelogId)){
- wrapper.eq("catalog_id",catelogId);
- }
-
- /**
- * status: 2
- * key:
- * brandId: 9
- * catelogId: 225
- */
-
- IPage<SpuInfoEntity> page = this.page(
- new Query<SpuInfoEntity>().getPage(params),
- wrapper
- );
-
- return new PageUtils(page);
- }
GET /product/skuinfo/list
请求参数
{
page: 1,//当前页码
limit: 10,//每页记录数
sidx: 'id',//排序字段
order: 'asc/desc',//排序方式
key: '华为',//检索关键字
catelogId: 0,
brandId: 0,
min: 0,
max: 0
}
分页数据
响应数据
{
"msg": "success",
"code": 0,
"page": {
"totalCount": 26,
"pageSize": 10,
"totalPage": 3,
"currPage": 1,
"list": [{
"skuId": 1,
"spuId": 11,
"skuName": "华为 HUAWEI Mate 30 Pro 星河银 8GB+256GB",
"skuDesc": null,
"catalogId": 225,
"brandId": 9,
"skuDefaultImg": "https://gulimall-hello.oss-cn-beijing.aliyuncs.com/2019-11-26/60e65a44-f943-4ed5-87c8-8cf90f403018_d511faab82abb34b.jpg",
"skuTitle": "华为 HUAWEI Mate 30 Pro 星河银 8GB+256GB麒麟990旗舰芯片OLED环幕屏双4000万徕卡电影四摄4G全网通手机",
"skuSubtitle": "【现货抢购!享白条12期免息!】麒麟990,OLED环幕屏双4000万徕卡电影四摄;Mate30系列享12期免息》",
"price": 6299.0000,
"saleCount": 0
}]
}
}
修改“com.atguigu.gulimall.product.controller.SkuInfoController”类,代码如下:
- @RequestMapping("/list")
- public R list(@RequestParam Map<String, Object> params){
- PageUtils page = skuInfoService.queryPageByCondition(params);
-
- return R.ok().put("page", page);
- }
修改“com.atguigu.gulimall.product.service.SkuInfoService”类,代码如下:
PageUtils queryPageByCondition(Map<String, Object> params);
修改“com.atguigu.gulimall.product.service.impl.SkuInfoServiceImpl”类,代码如下:
- @Override
- public PageUtils queryPageByCondition(Map<String, Object> params) {
- QueryWrapper<SkuInfoEntity> queryWrapper = new QueryWrapper<>();
-
- String key = (String) params.get("key");
- if (!StringUtils.isEmpty(key)){
- queryWrapper.and((w)->{
- w.eq("sku_id",key).or().like("sku_name",key);
- });
- }
-
- String catelogId = (String) params.get("catelogId");
- if (!StringUtils.isEmpty(catelogId) && !"0".equalsIgnoreCase(catelogId)){
- queryWrapper.eq("catalog_id",catelogId);
- }
-
- String brandId = (String) params.get("brandId");
- if (!StringUtils.isEmpty(brandId) && !"0".equalsIgnoreCase(brandId)){
- queryWrapper.eq("brand_id",brandId);
- }
-
- String min = (String) params.get("min");
- if (!StringUtils.isEmpty(min)){
- queryWrapper.ge("price",min);
- }
-
- String max = (String) params.get("max");
- if (!StringUtils.isEmpty(max)){
- try{
- BigDecimal bigDecimal = new BigDecimal(max);
- if (bigDecimal.compareTo(new BigDecimal("0"))==1){
- queryWrapper.le("price",max);
- }
- }catch (Exception e){
-
- }
- }
-
- IPage<SkuInfoEntity> page = this.page(
- new Query<SkuInfoEntity>().getPage(params),
- queryWrapper
- );
- return new PageUtils(page);
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。