谷粒商城笔记(详细版) IDEA2021 基础篇(2/3)(已完结 2022.5.12)

11 篇文章 1 订阅
5 篇文章 12 订阅

文章目录


前言

谷粒商城的基础部分2


四 全笔记链接(链接给出)

谷粒商城笔记(详细版) IDEA2021 基础篇(1/3).

谷粒商城笔记(详细版) IDEA2021 基础篇(2/3).

ES6 VUE 基础篇前端笔记

谷粒商城笔记(详细版) IDEA2021 基础篇(3/3).

五 基础篇开发部分

0 lambda表达式基础

可以看我的这篇博客 学习自mooc网
Lambda表达式学习.

一 商品服务-API

1 三级分类-查询 递归树形结构获取(查询所有目录)

1首先更改category实体类

在这里插入图片描述

package com.atguigu.gulimall.product.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;

/**
 * 商品三级分类
 *
 * @author yyl
 * @email sunlightcs@gmail.com
 * @date 2022-04-24 19:01:01
 */
@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显示]
	 */
	private Integer showStatus;
	/**
	 * 排序
	 */
	private Integer sort;
	/**
	 * 图标地址
	 */
	private String icon;
	/**
	 * 计量单位
	 */
	private String productUnit;
	/**
	 * 商品数量
	 */
	private Integer productCount;

	//标注在数据库中不是实际存在
	@TableField(exist=false)
	private List<CategoryEntity> children;

}
2 controller层

在这里插入图片描述

    /**
     * 查出所有分类以及子分类,以树形结构组装起来
     */
    @RequestMapping("/list/tree")
    public R list(){

        List<CategoryEntity> entities = categoryService.listWithTree();


        return R.ok().put("data", entities);
    }
3 serviceImpl层

在这里插入图片描述

@Override
    public List<CategoryEntity> listWithTree() {
        //1、查出所有分类  因为这里使用了mybatis-plus 所以可以使用baseMapper
        List<CategoryEntity> entities = baseMapper.selectList(null);

        //2、组装成父子的树形结构

        //2.1)、找到所有的一级分类
        List<CategoryEntity> level1Menus = entities.stream().filter(categoryEntity ->
                categoryEntity.getParentCid() == 0
        ).map((menu)->{
            menu.setChildren(getChildrens(menu,entities));
            return menu;
        }).sorted((menu1,menu2)->{
            return (menu1.getSort()==null?0:menu1.getSort()) - (menu2.getSort()==null?0:menu2.getSort());
        }).collect(Collectors.toList());
        return level1Menus;
    }
    //递归查找所有菜单的子菜单
    //这个方法传入的是一个一级目录root 和全部的目录all
    private List<CategoryEntity> getChildrens(CategoryEntity root,List<CategoryEntity> all){

        //1 使用全部的目录all首先找出传入目录root的所有下一层目录
        //2 使用map对root的所有下一层目录 的 子目录进行设置
        //3 利用sorted对所有下一层目录进行排序
        //4  collect集合返回listWithTree()函数 这时候一个一级目录root的子目录已经设置成功
        List<CategoryEntity> children = all.stream().filter(categoryEntity -> {
            return categoryEntity.getParentCid() == root.getCatId();
        }).map(categoryEntity -> {
            //1、找到子菜单
            categoryEntity.setChildren(getChildrens(categoryEntity,all));
            return categoryEntity;
        }).sorted((menu1,menu2)->{
            //2、菜单的排序
            return (menu1.getSort()==null?0:menu1.getSort()) - (menu2.getSort()==null?0:menu2.getSort());
        }).collect(Collectors.toList());

        return children;
    }

2 三级分类-配置网关路由和路径重写

基本按照视频一步一步来

0 基本步骤

1 让我们vue前端项目去访问网关

2 前端base URL 如下

‘http://localhost:88/api’
位于static的index.js之中
在这里插入图片描述

这样我们验证码访问url为
http://localhost:88/api/captcha.jpg
3 后端要求访问路径如下
在这里插入图片描述
http://localhost:8080/renren-fast/captcha.jpg

4 所以我们编写路由规则

- id: admin_route
          uri: lb://renren-fast
          predicates:
            - Path=/api/**
          filters:
            - RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}
0.5 思路总结

1 首先变更前端vue访问的baseurl 让其访问gateway网关

2 把renren-fast后端注册到Nacos

3 利用网关编写路由规则使其访问到renren-fast项目

4 vue -> gateway -> renren-fast

1 (重要)新版本的renren-fast注册报错问题

附上官方版本对应关系链接

https://github.com/spring-cloud-incubator/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E

这里来解决一下新版本的renren-fast注册到Nacos配置中心的问题
因为对maven和依赖管理概念不明确
解决了很长时间
在这里插入图片描述
有两个概念
alibaba-cloud版本
spring-cloud版本
如果你还使用guli-common模块中的cloud版本
很可能会报错 (springboot版本与common模块中老的cloud版本冲突)
我们这里重新设置一个
使用2021.0.1 官方cloud版本

<properties>
<!--		配置cloud-->
		<spring-cloud.version>2021.0.1</spring-cloud.version>
	</properties>

<!--	统一管理cloud版本-->
	<dependencyManagement>
		<dependencies>
		 这个管理spring-cloud版本
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
			这个管理alibaba-cloud版本
			其实这个已经在common模块中定义了
			<dependency>
				<groupId>com.alibaba.cloud</groupId>
				<artifactId>spring-cloud-alibaba-dependencies</artifactId>
				<version>2.2.7.RELEASE</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

还有记得引入这个依赖来避免一个报错

		<dependency>
			<groupId>com.google.guava</groupId>
			<artifactId>guava</artifactId>
			<version>30.1-jre</version>
		</dependency>

总结
尽量把依赖版本(dependencyManagement)都在common中定义好
同时每个模块使用相似的springboot版本
避免版本相差过大无法使用共同common
解决成功
在这里插入图片描述
![在这里插入图片描述](https://img-blog.csdnimg.cn/e87a1f66d13d4e4e960e0b01ce04ee62.png

3 三级分类-网关统一配置跨域

1 跨域简介

在这里插入图片描述
在这里插入图片描述

2 跨域解决方案

在这里插入图片描述在这里插入图片描述
在你的gateway网关下编写如下配置
参考结构
注意你的springboot版本
如果新版使用addAllowedOriginPattern
否则不生效
在这里插入图片描述

package com.atguigu.gulimall.gateway.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;

@Configuration
public class GulimallCorsConfiguration {

    @Bean
    public CorsWebFilter corsWebFilter(){
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

        CorsConfiguration corsConfiguration = new CorsConfiguration();

        //1、配置跨域
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        
        //老版本方法
//        corsConfiguration.addAllowedOrigin("*");
        
        //注意我这里springboot版本新 2.6.7所以要使用以下方法允许源头=访问
        corsConfiguration.addAllowedOriginPattern("*");
        
        corsConfiguration.setAllowCredentials(true);

        source.registerCorsConfiguration("/**",corsConfiguration);
        return new CorsWebFilter(source);
    }
}

4 三级分类-查询 树形显示三级目录

1 首先把gulimall-product注册Nacos中

如果不懂可以到这篇博客前面的
Nacos做注册中心看一看
这里简单列出配置文件内容
参考结构
在这里插入图片描述
1 appliction,yml

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/gulimall_pms?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2B8
  application:
    name: gulimall-product
mybatis-plus:
  mapper-locations: classpath:/mapper/**/*.xml
  global-config:
    db-config:
      id-type: auto
server:
  port: 10000

2 bootsrap.properties


spring.application.name=gulimall-product
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=4f0331c3-576a-44ad-bf4e-8af996e13d9f
#spring.cloud.nacos.config.group=prop

2 编写前端vue内容

在这里插入图片描述
代码如下

<template>
 <el-tree :data="tableData" :props="defaultProps" @node-click="handleNodeClick"></el-tree>
</template>

<script>
export default {
 data () {
   return {
     tableData: [],
     defaultProps: {
       children: 'children',
       label: 'name'
     }
   }
 },
 methods: {
   handleNodeClick (data) {
     console.log(data)
   },

   // 获取数据列表
   getDataList () {
     this.dataListLoading = true
     this.$http({
       url: this.$http.adornUrl('/product/category/list/tree'),
       method: 'get'
     }).then(({data}) => {
       console.log('成功获取到菜单数据', data.data)
       this.tableData = data.data
     })
   }
 },
 created () {
   // 获取数据列表
   this.getDataList()
 }
}
</script>

<style scoped>

</style>
3 前端的一个小坑

后端的朋友可以看一看
consle.log打印出来的都是object无法查看.

5 三级分类-页面展示效果

1 增加append和delete

我们首先为页面增加删除和添加按钮
我们去到element-ui的树形控件当中
在这里插入图片描述
在这里插入图片描述
把这段代码和对应的函数粘贴到tree里面
完整示例如下

<template>
  <el-tree :data="tableData" :props="defaultProps" :expand-on-click-node="false" show-checkbox 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>
</template>

<script>
export default {
  data () {
    return {
      tableData: [],
      defaultProps: {
        children: 'children',
        label: 'name'
      }
    }
  },
  methods: {
    append (data) {
      console.log('append', data)
    },

    remove (node, data) {
      console.log('node', node, data)
    },

    // 获取数据列表
    getDataList () {
      this.dataListLoading = true
      this.$http({
        url: this.$http.adornUrl('/product/category/list/tree'),
        method: 'get'
      }).then(({data}) => {
        console.log('成功获取到菜单数据', data.data)
        this.tableData = data.data
      })
    }
  },
  created () {
    // 获取数据列表
    this.getDataList()
  }
}
</script>

<style scoped>

</style>
2 为append和delete设置条件

思考:
1 我们要让一级目录和二级目录可以添加子目录
2 只有没有子节点的目录才可以进行目录删除在这里插入图片描述

3 最终查看页面效果

在这里插入图片描述

6 三级分类-删除-逻辑删除

Mybatis-plus官方文档.
首先更改procut模块的配置文件
在这里插入图片描述

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/gulimall_pms?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2B8
  application:
    name: gulimall-product
mybatis-plus:
  mapper-locations: classpath:/mapper/**/*.xml
  global-config:
    db-config:
      id-type: auto
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
server:
  port: 10001

接着对实体类字段进行设置
在这里插入图片描述

	/**
	 * 是否显示[0-不显示,1显示] 注解表示这个字段为逻辑删除字段
	 */
	@TableLogic(value = "1",delval = "0")
	private Integer showStatus;

接着启动进行测试就可以了
如果你使用的是navicat数据库
在你操作完成后可能需要翻到下一页进行查看
而不是操作失败了
在这里插入图片描述

7 三级分类-删除-删除效果细化

首先前后端联调代码如下
这样做已经可以删除成功了
由于逻辑删除的缘故
我们查不到逻辑删除的数据

remove (node, data) {
      var ids = [data.catId]
      // 1 index发送请求
      // 'http://localhost:88/api'

      // 2 拼装请求
      // http://localhost:88/api/product/category/delete

      // 3 网关转发后请求
      // http://localhost:88/product/category/delete
      this.$http({
        url: this.$http.adornUrl('/product/category/delete'),
        method: 'post',
        data: this.$http.adornData(ids, false)
      }).then(({data}) => {
        // 获取数据列表
        this.getDataList()
        console.log('删除成功')
      })
    },

接着进行页面效果细化操作
1 确认删除操作
思路 增加一个MessageBox 把你的操作放在确认后进行
2 点击删除页面不收起
思路 el-tree中增加 :default-expanded-keys=“expandedKey”
同时在data中定义expandedKey
在这里插入图片描述
接着进行逻辑的编写
代码示例如下

remove (node, data) {
      // 1 index发送请求
      // 'http://localhost:88/api'

      // 2 拼装请求
      // http://localhost:88/api/product/category/delete

      // 3 网关转发后请求
      // http://localhost:88/product/category/delete
      var ids = [data.catId]
      this.$confirm(`此操作将永久删除${data.name}目录, 是否继续?`, '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        this.$http({
          url: this.$http.adornUrl('/product/category/delete'),
          method: 'post',
          data: this.$http.adornData(ids, false)
        }).then(({data}) => {
          // 获取数据列表
          this.getDataList()
          // 设置需要默认展开的菜单
          this.expandedKey = [node.parent.data.catId]
          console.log('删除成功')
        })
        this.$message({
          type: 'success',
          message: '删除成功!'
        })
      }).catch(() => {
        this.$message({
          type: 'info',
          message: '已取消删除'
        })
      })
    },

8 三级分类-新增

思路:
1 使用dialog对话框 里面嵌套一个el-form表单 设置dialogVisible

<el-dialog
      :title="title"
      :visible.sync="dialogVisible"
      width="30%"
      :close-on-click-modal="false"
    >
      <el-form :model="category">
        <el-form-item label="分类名称">
          <el-input v-model="category.name" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="图标">
          <el-input v-model="category.icon" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="计量单位">
          <el-input v-model="category.productUnit" autocomplete="off"></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="addCategory">确 定</el-button>
      </span>
    </el-dialog>

2 点击append打开对话框 对我们要上传目录的parentCid catLevel 进行赋值
把一些数据进行重置

append (data) {
      console.log('append', data)
      // this.dialogType = "add";
      // this.title = "添加分类";
      this.dialogVisible = true
      // 获得当前点击节点的id 其实是我们要添加目录的父id
      this.category.parentCid = data.catId
      // 获取添加目录的层级 为当前层级+1
      this.category.catLevel = data.catLevel * 1 + 1

      // 重置之前的数值 变为默认值
      this.category.catId = null
      this.category.name = ''
      this.category.icon = ''
      this.category.productUnit = ''
      this.category.sort = 0
      this.category.showStatus = 1
      console.log('append', data)
    },

3 编写addCategory函数 点击对话框中的确认时调用
里面编写代码连接后端进行数据添加
并设置默认展开

// 添加三级分类
    addCategory () {
      console.log('提交的三级分类数据', this.category)
      this.$http({
        url: this.$http.adornUrl('/product/category/save'),
        method: 'post',
        data: this.$http.adornData(this.category, false)
      }).then(({ data }) => {
        this.$message({
          message: '菜单保存成功',
          type: 'success'
        })
        // 关闭对话框
        this.dialogVisible = false
        // 刷新出新的菜单
        this.getDataList()
        // 设置需要默认展开的菜单
        this.expandedKey = [this.category.parentCid]
      })
    },

4 script中的data数据

  data () {
    return {
      category: {
        name: '',
        parentCid: 0,
        catLevel: 0,
        showStatus: 1,
        sort: 0,
        productUnit: '',
        icon: '',
        catId: null
      },
      dialogVisible: false,
      expandedKey: [],
      tableData: [],
      defaultProps: {
        children: 'children',
        label: 'name'
      }
    }
  },

8 三级分类-修改-基本修改效果完成

注意:具体的细节请看我写的代码注释

思路:

1 我们的增加和修改共用一个dialog进行操作
所以在确定的时候应该调用一个函数进行选择
在这里插入图片描述
在这里插入图片描述
2 修改的时候我们从数据库拿出最新的节点数据
拿到数据后为data()中的catagory属性进行赋值
因为我们的dialog绑定的就是category中的数值
所以会进行回显

在这里插入图片描述
在这里插入图片描述
3 当我们点击dialog的确认更改时提交这些数据
在这里插入图片描述

最终代码
<template>
  <div>
  <el-tree :data="tableData" :props="defaultProps" :expand-on-click-node="false"
           show-checkbox node-key="catId" :default-expanded-keys="expandedKey">
    <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 type="text" size="mini" @click="edit(data)">edit</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>


    <el-dialog
      :title="title"
      :visible.sync="dialogVisible"
      width="30%"
      :close-on-click-modal="false"
    >
      <el-form :model="category">
        <el-form-item label="分类名称">
          <el-input v-model="category.name" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="图标">
          <el-input v-model="category.icon" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="计量单位">
          <el-input v-model="category.productUnit" autocomplete="off"></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="submitData">确 定</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
export default {
  data () {
    return {
      // 标题名称
      title: '',
      // dialog类型
      dialogType: '', // edit,add
      // category所需数据
      category: {
        name: '',
        parentCid: 0,
        catLevel: 0,
        showStatus: 1,
        sort: 0,
        productUnit: '',
        icon: '',
        catId: null
      },
      // dialog默认不展开
      dialogVisible: false,
      // 默认展开哪些节点
      expandedKey: [],
      // 表格的数据
      tableData: [],

      // 树形结构的子节点是哪个字段 Label是树形结构展示哪个标签
      defaultProps: {
        children: 'children',
        label: 'name'
      }
    }
  },
  methods: {
    // 修改三级分类数据
    editCategory () {
      // 只把我们需要更新的数据进行上传
      var { catId, name, icon, productUnit } = this.category
      this.$http({
        url: this.$http.adornUrl('/product/category/update'),
        method: 'post',
        data: this.$http.adornData({ catId, name, icon, productUnit }, false)
      }).then(({ data }) => {
        this.$message({
          message: '菜单修改成功',
          type: 'success'
        })
        // 关闭对话框
        this.dialogVisible = false
        // 刷新出新的菜单
        this.getDataList()
        // 设置需要默认展开的菜单
        this.expandedKey = [this.category.parentCid]
      })
    },
    edit (data) {
      console.log('要修改的数据', data)
      // 设置DiglogType方便调用editCategory函数
      this.dialogType = 'edit'
      // 设置diglog名称
      this.title = '修改分类'
      // 打开Diglog
      this.dialogVisible = true

      // 发送请求获取当前节点最新的数据
      this.$http({
        url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
        method: 'get'
      }).then(({ data }) => {
        // 请求成功
        console.log('要回显的数据', data)
        this.category.name = data.data.name
        this.category.catId = data.data.catId
        this.category.icon = data.data.icon
        this.category.productUnit = data.data.productUnit
        this.category.parentCid = data.data.parentCid
        this.category.catLevel = data.data.catLevel
        this.category.sort = data.data.sort
        this.category.showStatus = data.data.showStatus
        /**
         *         parentCid: 0,
         catLevel: 0,
         showStatus: 1,
         sort: 0,
         */
      })
    },
    submitData () {
      if (this.dialogType === 'add') {
        this.addCategory()
      }
      if (this.dialogType === 'edit') {
        this.editCategory()
      }
    },
    // 添加三级分类
    addCategory () {
      console.log('提交的三级分类数据', this.category)
      this.$http({
        url: this.$http.adornUrl('/product/category/save'),
        method: 'post',
        data: this.$http.adornData(this.category, false)
      }).then(({ data }) => {
        this.$message({
          message: '菜单保存成功',
          type: 'success'
        })
        // 关闭对话框
        this.dialogVisible = false
        // 刷新出新的菜单
        this.getDataList()
        // 设置需要默认展开的菜单
        this.expandedKey = [this.category.parentCid]
      })
    },
    append (data) {
      // 打印点击append获取的节点数据
      console.log('append', data)
      // 把diglog框的类型设置为add便于调用addcategoty函数
      this.dialogType = 'add'
      // 设置表格头
      this.title = '添加分类'
      // 打开dialog
      this.dialogVisible = true
      // 获得当前点击节点的id 其实是我们要添加目录的父id
      this.category.parentCid = data.catId
      // 获取添加目录的层级 为当前层级+1
      this.category.catLevel = data.catLevel * 1 + 1

      // 把数据置为默认数据 避免因为update操作影响append
      this.category.catId = null
      this.category.name = ''
      this.category.icon = ''
      this.category.productUnit = ''
      this.category.sort = 0
      this.category.showStatus = 1
    },

    remove (node, data) {
      // 1 index发送请求
      // 'http://localhost:88/api'

      // 2 拼装请求
      // http://localhost:88/api/product/category/delete

      // 3 网关转发后请求
      // http://localhost:88/product/category/delete
      var ids = [data.catId]
      this.$confirm(`此操作将永久删除${data.name}目录, 是否继续?`, '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        this.$http({
          url: this.$http.adornUrl('/product/category/delete'),
          method: 'post',
          data: this.$http.adornData(ids, false)
        }).then(({data}) => {
          // 获取数据列表
          this.getDataList()
          // 设置需要默认展开的菜单
          this.expandedKey = [node.parent.data.catId]
          console.log('删除成功')
        })
        this.$message({
          type: 'success',
          message: '删除成功!'
        })
      }).catch(() => {
        this.$message({
          type: 'info',
          message: '已取消删除'
        })
      })
    },

    // 获取数据列表
    getDataList () {
      this.dataListLoading = true
      this.$http({
        url: this.$http.adornUrl('/product/category/list/tree'),
        method: 'get'
      }).then(({data}) => {
        console.log('成功获取到菜单数据', data.data)
        this.tableData = data.data
      })
    }
  },
  created () {
    // 获取数据列表
    this.getDataList()
  }
}
</script>

<style scoped>

</style>

剩下的三级分类暂且跳过

14 品牌管理 逆向生成前后端代码

首先去你代码生成的product代码下找到这两个文件
在这里插入图片描述
把它粘贴到你前端对应的product下
在这里插入图片描述
接着放行权限 让新增能够在页面中显示出来
在这里插入图片描述
全局搜索isAuth
发现在index.js中
我们注释掉原有代码
一直让其返回True
在这里插入图片描述
最后打开页面效果如下
![在这里插入图片描述](https://img-blog.csdnimg.cn/8d4cb4a0ca07459c810063931bc18f6e.png

14 品牌管理 效果优化 快速展示开关

思路
我们定义一个开关来控制显示状态
最终效果如下
在这里插入图片描述
那我们首先自定义表格列
去element-ui找到
在这里插入图片描述
拿取其中代码
我们要的只是这个template>
带column是方便你们理解

<el-table-column
      label="日期"
      width="180">
      <template slot-scope="scope">
        <i class="el-icon-time"></i>
        <span style="margin-left: 10px">{{ scope.row.date }}</span>
      </template>
    </el-table-column>

把这个template粘贴到显示状态那一列
在这里插入图片描述
在这里插入图片描述
思路:
当1 时显示 0时不显示
当我们的switch状态更改时实时改变数据库中的showstaus状态
于是我们编写的swtich代码如下

      <el-table-column
        prop="showStatus"
        header-align="center"
        align="center"
        label="显示状态">
        <template slot-scope="scope">
          <el-switch
            v-model="scope.row.showStatus"
            active-color="#13ce66"
            inactive-color="#ff4949"
            :active-value="1"
            :inactive-value="0"
            @change="updateBrandStatus(scope.row)"
          ></el-switch>
        </template>
      </el-table-column>

在这里插入图片描述
监听函数代码如下
这样我们一点击数据库中的值也会实时更新

      updateBrandStatus (data) {
        console.log('最新信息', data)
        let { brandId, showStatus } = data
      // 发送请求修改状态
        this.$http({
          // url: this.$http.adornUrl('/product/brand/update/status'),
          url: this.$http.adornUrl('/product/brand/update'),
          method: 'post',
          data: this.$http.adornData({ brandId, showStatus }, false)
        }).then(({ data }) => {
          this.$message({
            type: 'success',
            message: '状态更新成功'
          })
        })
      },

15 品牌管理 云储存开通和使用

1 云存储简介

在这里插入图片描述
在这里插入图片描述
首先去到阿里云官网
登录上你的账号
然后开通对象存储oss服务
这个项目总共花费也就几元钱
配置参照
选多了估计要付费多 孩子
在这里插入图片描述

2 上传方式的选择

我们选择服务器签名后直传的方式
避免了普通上传方式的繁琐
在这里插入图片描述
在这里插入图片描述

16 品牌管理 OSS整合测试

1 导入SDK依赖
<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.10.2</version>
</dependency>
2 创建RAM用户

这个子用户来操作OSS对象存储系统
避免了主账号密码丢失造成的巨大风险
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

3 使用代码进行文件上传
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import java.io.FileInputStream;
import java.io.InputStream;

public class Demo {

    public static void main(String[] args) throws Exception {
        // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
        String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
        // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
        String accessKeyId = "yourAccessKeyId";
        String accessKeySecret = "yourAccessKeySecret";
        // 填写Bucket名称,例如examplebucket。
        String bucketName = "examplebucket";
        // 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
        String objectName = "exampledir/exampleobject.txt";
        // 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
        // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。
        String filePath= "D:\\localpath\\examplefile.txt";

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        try {
            InputStream inputStream = new FileInputStream(filePath);            
            // 创建PutObject请求。
            ossClient.putObject(bucketName, objectName, inputStream);
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }
}                    
4 优化文件上传 --------------------------------------------------------(推荐使用这种方式而非传统的SDK,如果你之前已经学习过这个项目 请直接参照这个部分进行代码编写)

spring-cloud-oss官方文档.
1
首先引入依赖
因为可能别的模块也会用到文件上传
所以我们把这个简化依赖引入到guli-common模块当中

<!--alibaba-oss启动器-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alicloud-oss</artifactId>
            <version>2.1.0.RELEASE</version>
        </dependency>
    </dependencies>

如果报错not found可以参考我的这个博客进行解决
Cannot resolve com.alibaba.cloud:spring-cloud-starter-alicloud-oss:unknown.

2 在配置文件中进行oss的配置
在这里插入图片描述
参考代码如下

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    alicloud:
      access-key: cccc
      secret-key: ccccc
      oss:
        endpoint: oss-cn-shanghai.aliyuncs.com
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/gulimall_pms?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2B8
  application:
    name: gulimall-product
mybatis-plus:
  mapper-locations: classpath:/mapper/**/*.xml
  global-config:
    db-config:
      id-type: auto
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
server:
  port: 10001

3
测试使用
可以参照我的代码
编写代码进行上传测试
首先依赖注入OSSclient

    @Resource
    OSSClient ossClient;
     @GetMapping("/oss")
    public String oss(){
//        // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
//        String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
//        // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
//        String accessKeyId = "yourAccessKeyId";
//        String accessKeySecret = "yourAccessKeySecret";
        // 填写Bucket名称,例如examplebucket。
        String bucketName = "gulimall-yyla1";
        // 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
        String objectName = "0d40c24b264aa511.jpg";
        // 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
        // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。
        String filePath= "H:\\谷\\官方资料\\1.分布式基础(全栈开发篇)\\docs\\pics\\0d40c24b264aa511.jpg";

//        // 创建OSSClient实例。
//        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        try {
            InputStream inputStream = new FileInputStream(filePath);
            // 创建PutObject请求。
            ossClient.putObject(bucketName, objectName, inputStream);
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
        return "oss ok";
    }

上传成功
在这里插入图片描述

17 品牌管理 OSS获取服务端签名

思路:
因为我们之前的方式文件上传还是经过服务器了
所以我们这次准备加以改进
使用如下的这种方式
在这里插入图片描述

1首先再来创建一个新的项目模块来进行第三方服务的操作

参考如下
在这里插入图片描述
在这里插入图片描述
接着转移依赖
把gulicommon中的spring-cloud-alibaba-oss转移到这个模块当中
不这样的话我们每个引入common的模块都要进行oss的配置
不然会报错
在这里插入图片描述
记得配置你的allibaba-cloud-dependencymaneger
完整依赖参考

<?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.6.7</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.atguigu.gulimall</groupId>
    <artifactId>yyl</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gulimall-third-party</name>
    <description>gulimall-third-party</description>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>2021.0.2</spring-cloud.version>
    </properties>
    <dependencies>
<!--        gulimall-common模块-->
        <dependency>
            <groupId>com.atguigu.gulimall</groupId>
            <artifactId>gulimall_common</artifactId>
            <version>0.0.1-SNAPSHOT</version>
<!--            这个模块不使用数据库 排除-->
            <exclusions>
                <exclusion>
                    <groupId>com.baomidou</groupId>
                    <artifactId>mybatis-plus-boot-starter</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--alibaba-oss启动器-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alicloud-oss</artifactId>
            <version>2.1.0.RELEASE</version>
        </dependency>
<!--web模块-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
<!--        oppenfeign服务间调用-->
        <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>
        </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.7.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>

2接着把这个模块注册到配置中心当中

完整代码参考
结构参考
在这里插入图片描述
1 appilicaiton.yml

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
# in nacos
#    alicloud:
#      access-key: LTAI5tLTqoGq426o9HB89xnn
#      secret-key: dZdfBwxGaA7wmwDPiVctIqVpSl0o2t
#      oss:
#        endpoint: oss-cn-shanghai.aliyuncs.com
  application:
    name: gulimall-third-party
server:
  port: 30000

2 bootstrap.properties

# ??Nacos-?????????
spring.application.name=gulimall-third-party
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=c392fc56-4da0-4ab3-9540-0d0c621721be
#spring.cloud.nacos.config.group=prop
#加载配置集
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

3 启动测试

已经注册到了服务中心当中
在这里插入图片描述

4 进行获取oss签名的操作

参考结构如下
在这里插入图片描述

1 新建一个controller来进行获取签名操作

服务端签名直传文档.
在这里插入图片描述

参考代码如下
OSScontroller

package com.atguigu.gulimall.thirdparty.controller;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;
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 javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;

@RestController
public class OssController {
    @Autowired
    OSS client;


    @Value("${spring.cloud.alicloud.oss.endpoint}")
    private String endpoint;

    @Value("${spring.cloud.alicloud.access-key}")
    private String accessId;

    @RequestMapping("/oss/policy")
    public Map<String, String> policy(){

/**
 * 因为配置了引入了Pom依赖 并且配置了aplliciton.yml文件 所以这几行用不到了
  */
//        // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
//        String accessId = "yourAccessKeyId";
//        String accessKey = "yourAccessKeySecret";
//        // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
//        String endpoint = "oss-cn-hangzhou.aliyuncs.com";
//        OSSClient client = new OSSClient(endpoint, accessId, accessKey);

        //https://gulimall-hello.oss-cn-beijing.aliyuncs.com/hahaha.jpg
        // 填写Bucket名称,例如examplebucket。
        String bucket = "gulimall-yyla1";
        // 填写Host地址,格式为https://bucketname.endpoint。
        String host = "https://" + bucket + "." + endpoint; // host的格式为 bucketname.endpoint
//        String host = "https://examplebucket.oss-cn-hangzhou.aliyuncs.com";

        /**
         * 上传毁回调暂时用不到
         */
        // 设置上传回调URL,即回调服务器地址,用于处理应用服务器与OSS之间的通信。OSS会在文件上传完成后,把文件上传信息通过此回调URL发送给应用服务器。
//        String callbackUrl = "https://192.168.0.0:8888";


        /**
         * 这里设置成当天存储当天的
         */
        // 设置上传到OSS文件的前缀,可置空此项。置空后,文件将上传至Bucket的根目录下。
        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);
            PolicyConditions policyConds = new PolicyConditions();
            policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
            policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);

            String postPolicy = client.generatePostPolicy(expiration, policyConds);
            byte[] binaryData = postPolicy.getBytes("utf-8");
            String encodedPolicy = BinaryUtil.toBase64String(binaryData);
            String postSignature = client.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));

//            JSONObject jasonCallback = new JSONObject();
//            jasonCallback.put("callbackUrl", callbackUrl);
//            jasonCallback.put("callbackBody",
//                    "filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}");
//            jasonCallback.put("callbackBodyType", "application/x-www-form-urlencoded");
//            String base64CallbackBody = BinaryUtil.toBase64String(jasonCallback.toString().getBytes());
//            respMap.put("callback", base64CallbackBody);
//
//            JSONObject ja1 = JSONObject.fromObject(respMap);
//            // System.out.println(ja1.toString());
//            response.setHeader("Access-Control-Allow-Origin", "*");
//            response.setHeader("Access-Control-Allow-Methods", "GET, POST");
//            response(request, response, ja1.toString());

        } catch (Exception e) {
            // Assert.fail(e.getMessage());
            System.out.println(e.getMessage());
        }

        return respMap;
    }
}

2 配置文件如下(controller中注入了很多依赖中定义的数据)

1 application.yml

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    alicloud:
      access-key: xxxxxx
      secret-key: xxxxxx
      oss:
        endpoint: oss-cn-shanghai.aliyuncs.com
  application:
    name: gulimall-third-party
server:
  port: 30000

2 bootstrap.yml

# ??Nacos-?????????
spring.application.name=gulimall-third-party
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=c392fc56-4da0-4ab3-9540-0d0c621721be
#spring.cloud.nacos.config.group=prop
#加载配置集
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

测试发现返回签名成功
在这里插入图片描述

3 配置网关转发路由

我们配置网关转发路由 访问网关来获取签名
代码如下
找到third_party_route

#Router And Predicates And Nacos discovery
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    gateway:
      routes:
        - id: test_route
          uri: https://www.baidu.com
          predicates:
            - Query=url,baidu

        - id: qq_route
          uri: https://www.qq.com
          predicates:
            - Query=url,qq

        - id: product_route
          uri: lb://gulimall-product
          predicates:
            - Path=/api/product/**
          filters:
            - RewritePath=/api/(?<segment>.*),/$\{segment}


        - id: third_party_route
          uri: lb://gulimall-third-party
          predicates:
            - Path=/api/thirdparty/**
          filters:
            - RewritePath=/api/thirdparty/(?<segment>.*),/$\{segment}

        - id: admin_route
          uri: lb://renren-fast
          predicates:
            - Path=/api/**
          filters:
            - RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}





              ## 前端项目,/api
            ## http://localhost:88/api/captcha.jpg   http://localhost:8080/renren-fast/captcha.jpg
            ## http://localhost:88/api/product/category/list/tree http://localhost:10000/product/category/list/tree


  application:
    name: gulimall-gateway
server:
  port: 88
logging:
  level:
    com.atguigu.gulimall: debug

配置好后重启网关
我们发送请求进行测试
http://localhost:88/api/thirdparty/oss/policy
在这里插入图片描述

18 品牌管理 OSS前后联调测试上传

把upload文件夹粘贴到自己前端项目的目录下
在这里插入图片描述

更改其中的
action 文件上传位置
这个位置设成自己bucket外网访问链接
在这里插入图片描述
接站在brand-add-or-update作为组件引入并进行使用

![在这里插入图片描述](https://img-blog.csdnimg.cn/47e11305d6a94f1ca5c4e6b408cc7fab.png在这里插入图片描述
brand-add-or-update
完整代码示例如下

<template>
  <el-dialog
    :title="!dataForm.brandId ? '新增' : '修改'"
    :close-on-click-modal="false"
    :visible.sync="visible">
    <el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="80px">
    <el-form-item label="品牌名" prop="name">
      <el-input v-model="dataForm.name" placeholder="品牌名"></el-input>
    </el-form-item>
    <el-form-item label="品牌logo地址" prop="logo">
<!--      <el-input v-model="dataForm.logo" placeholder="品牌logo地址"></el-input>-->
      <single-upload v-model="dataForm.logo"></single-upload>
    </el-form-item>
    <el-form-item label="介绍" prop="descript">
      <el-input v-model="dataForm.descript" placeholder="介绍"></el-input>
    </el-form-item>
    <el-form-item label="显示状态" prop="showStatus">
      <el-input v-model="dataForm.showStatus" placeholder="显示状态[0-不显示;1-显示]"></el-input>
    </el-form-item>
    <el-form-item label="检索首字母" prop="firstLetter">
      <el-input v-model="dataForm.firstLetter" placeholder="检索首字母"></el-input>
    </el-form-item>
    <el-form-item label="排序" prop="sort">
      <el-input v-model="dataForm.sort" placeholder="排序"></el-input>
    </el-form-item>
    </el-form>
    <span slot="footer" class="dialog-footer">
      <el-button @click="visible = false">取消</el-button>
      <el-button type="primary" @click="dataFormSubmit()">确定</el-button>
    </span>
  </el-dialog>
</template>

<script>
import SingleUpload from "@/components/upload/singleUpload";

  export default {
    components: { SingleUpload },
    data () {
      return {
        visible: false,
        dataForm: {
          brandId: 0,
          name: '',
          logo: '',
          descript: '',
          showStatus: '',
          firstLetter: '',
          sort: ''
        },
        dataRule: {
          name: [
            { required: true, message: '品牌名不能为空', trigger: 'blur' }
          ],
          logo: [
            { required: true, message: '品牌logo地址不能为空', trigger: 'blur' }
          ],
          descript: [
            { required: true, message: '介绍不能为空', trigger: 'blur' }
          ],
          showStatus: [
            { required: true, message: '显示状态[0-不显示;1-显示]不能为空', trigger: 'blur' }
          ],
          firstLetter: [
            { required: true, message: '检索首字母不能为空', trigger: 'blur' }
          ],
          sort: [
            { required: true, message: '排序不能为空', trigger: 'blur' }
          ]
        }
      }
    },
    methods: {
      init (id) {
        this.dataForm.brandId = id || 0
        this.visible = true
        this.$nextTick(() => {
          this.$refs['dataForm'].resetFields()
          if (this.dataForm.brandId) {
            this.$http({
              url: this.$http.adornUrl(`/product/brand/info/${this.dataForm.brandId}`),
              method: 'get',
              params: this.$http.adornParams()
            }).then(({data}) => {
              if (data && data.code === 0) {
                this.dataForm.name = data.brand.name
                this.dataForm.logo = data.brand.logo
                this.dataForm.descript = data.brand.descript
                this.dataForm.showStatus = data.brand.showStatus
                this.dataForm.firstLetter = data.brand.firstLetter
                this.dataForm.sort = data.brand.sort
              }
            })
          }
        })
      },
      // 表单提交
      dataFormSubmit () {
        this.$refs['dataForm'].validate((valid) => {
          if (valid) {
            this.$http({
              url: this.$http.adornUrl(`/product/brand/${!this.dataForm.brandId ? 'save' : 'update'}`),
              method: 'post',
              data: this.$http.adornData({
                'brandId': this.dataForm.brandId || undefined,
                'name': this.dataForm.name,
                'logo': this.dataForm.logo,
                'descript': this.dataForm.descript,
                'showStatus': this.dataForm.showStatus,
                'firstLetter': this.dataForm.firstLetter,
                'sort': this.dataForm.sort
              })
            }).then(({data}) => {
              if (data && data.code === 0) {
                this.$message({
                  message: '操作成功',
                  type: 'success',
                  duration: 1500,
                  onClose: () => {
                    this.visible = false
                    this.$emit('refreshDataList')
                  }
                })
              } else {
                this.$message.error(data.msg)
              }
            })
          }
        })
      }
    }
  }
</script>

设置自己bucket的跨域权限
(让浏览器能给你的oss对象存储发数据)
在这里插入图片描述
最后上传测试
在这里插入图片描述

19 品牌管理 表单校验,自定义校验器

1 首先更新你前端项目使用的组件库

在这里插入图片描述
更新如下

/**
 * UI组件, 统一使用饿了么桌面端组件库(https://github.com/ElemeFE/element)
 *
 * 使用:
 *  1. 项目中需要的组件进行释放(解开注释)
 *
 * 注意:
 *  1. 打包只会包含释放(解开注释)的组件, 减少打包文件大小
 */
import Vue from 'vue'
import {
  Pagination,
  Dialog,
  Autocomplete,
  Dropdown,
  DropdownMenu,
  DropdownItem,
  Menu,
  Submenu,
  MenuItem,
  MenuItemGroup,
  Input,
  InputNumber,
  Radio,
  RadioGroup,
  RadioButton,
  Checkbox,
  CheckboxButton,
  CheckboxGroup,
  Switch,
  Select,
  Option,
  OptionGroup,
  Button,
  ButtonGroup,
  Table,
  TableColumn,
  DatePicker,
  TimeSelect,
  TimePicker,
  Popover,
  Tooltip,
  Breadcrumb,
  BreadcrumbItem,
  Form,
  FormItem,
  Tabs,
  TabPane,
  Tag,
  Tree,
  Alert,
  Slider,
  Icon,
  Row,
  Col,
  Upload,
  Progress,
  Spinner,
  Badge,
  Card,
  Rate,
  Steps,
  Step,
  Carousel,
  CarouselItem,
  Collapse,
  CollapseItem,
  Cascader,
  ColorPicker,
  Transfer,
  Container,
  Header,
  Aside,
  Main,
  Footer,
  Timeline,
  TimelineItem,
  Link,
  Divider,
  Image,
  Calendar,
  Loading,
  MessageBox,
  Message,
  Notification
} from 'element-ui'

Vue.use(Pagination)
Vue.use(Dialog)
Vue.use(Autocomplete)
Vue.use(Dropdown)
Vue.use(DropdownMenu)
Vue.use(DropdownItem)
Vue.use(Menu)
Vue.use(Submenu)
Vue.use(MenuItem)
Vue.use(MenuItemGroup)
Vue.use(Input)
Vue.use(InputNumber)
Vue.use(Radio)
Vue.use(RadioGroup)
Vue.use(RadioButton)
Vue.use(Checkbox)
Vue.use(CheckboxButton)
Vue.use(CheckboxGroup)
Vue.use(Switch)
Vue.use(Select)
Vue.use(Option)
Vue.use(OptionGroup)
Vue.use(Button)
Vue.use(ButtonGroup)
Vue.use(Table)
Vue.use(TableColumn)
Vue.use(DatePicker)
Vue.use(TimeSelect)
Vue.use(TimePicker)
Vue.use(Popover)
Vue.use(Tooltip)
Vue.use(Breadcrumb)
Vue.use(BreadcrumbItem)
Vue.use(Form)
Vue.use(FormItem)
Vue.use(Tabs)
Vue.use(TabPane)
Vue.use(Tag)
Vue.use(Tree)
Vue.use(Alert)
Vue.use(Slider)
Vue.use(Icon)
Vue.use(Row)
Vue.use(Col)
Vue.use(Upload)
Vue.use(Progress)
Vue.use(Spinner)
Vue.use(Badge)
Vue.use(Card)
Vue.use(Rate)
Vue.use(Steps)
Vue.use(Step)
Vue.use(Carousel)
Vue.use(CarouselItem)
Vue.use(Collapse)
Vue.use(CollapseItem)
Vue.use(Cascader)
Vue.use(ColorPicker)
Vue.use(Transfer)
Vue.use(Container)
Vue.use(Header)
Vue.use(Aside)
Vue.use(Main)
Vue.use(Footer)
Vue.use(Timeline)
Vue.use(TimelineItem)
Vue.use(Link)
Vue.use(Divider)
Vue.use(Image)
Vue.use(Calendar)

Vue.use(Loading.directive)

Vue.prototype.$loading = Loading.service
Vue.prototype.$msgbox = MessageBox
Vue.prototype.$alert = MessageBox.alert
Vue.prototype.$confirm = MessageBox.confirm
Vue.prototype.$prompt = MessageBox.prompt
Vue.prototype.$notify = Notification
Vue.prototype.$message = Message

Vue.prototype.$ELEMENT = { size: 'medium' }
2 接着引入在项目中展示你的Logo图片

在这里插入图片描述

      <el-table-column
        prop="logo"
        header-align="center"
        align="center"
        label="品牌logo地址">
        <template slot-scope="scope">
<!--          <i class="el-icon-time"></i>-->
<!--          <span style="margin-left: 10px">{{ scope.row.date }}</span>-->
<!--          <el-image-->
<!--            style="width: 100px; height: 80px"-->
<!--            :src="scope.row.logo"-->
<!--            :fit="fit"></el-image>-->
          <img :src="scope.row.logo" style="width: 100px; height: 80px" />
        </template>
      </el-table-column>
3最后在新增中增加前端表单校验

在这里插入图片描述

        dataRule: {
          name: [
            { required: true, message: '品牌名不能为空', trigger: 'blur' }
          ],
          logo: [
            { required: true, message: '品牌logo地址不能为空', trigger: 'blur' }
          ],
          descript: [
            { required: true, message: '介绍不能为空', trigger: 'blur' }
          ],
          showStatus: [
            { required: true, message: '显示状态[0-不显示;1-显示]不能为空', trigger: 'blur' }
          ],
          firstLetter: [
            {
              validator: (rule, value, callback) => {
                if (value == "") {
                  callback(new Error("首字母必须填写"));
                } else if (!/^[a-zA-Z]$/.test(value)) {
                  callback(new Error("首字母必须a-z或者A-Z之间"));
                } else {
                  callback();
                }
              },
              trigger: "blur"
            }
          ],
          sort: [
            {
              validator: (rule, value, callback) => {
                if (value === '') {
                  callback(new Error("排序字段必须填写"));
                } else if (!/^[0-9]$/.test(value) || value<0 ) {
                  callback(new Error("排序必须是一个大于等于0的整数"));
                } else {
                  callback();
                }
              },
              trigger: "blur"
            }
          ]
        }

完整代码

<template>
  <el-dialog
    :title="!dataForm.brandId ? '新增' : '修改'"
    :close-on-click-modal="false"
    :visible.sync="visible">
    <el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="80px">
    <el-form-item label="品牌名" prop="name">
      <el-input v-model="dataForm.name" placeholder="品牌名"></el-input>
    </el-form-item>
    <el-form-item label="品牌logo地址" prop="logo">
<!--      <el-input v-model="dataForm.logo" placeholder="品牌logo地址"></el-input>-->
      <single-upload v-model="dataForm.logo"></single-upload>
    </el-form-item>
    <el-form-item label="介绍" prop="descript">
      <el-input v-model="dataForm.descript" placeholder="介绍"></el-input>
    </el-form-item>
    <el-form-item label="显示状态" prop="showStatus">
<!--      <el-input v-model="dataForm.showStatus" placeholder="显示状态[0-不显示;1-显示]"></el-input>-->
      <el-switch
        v-model="dataForm.showStatus"
        active-color="#13ce66"
        inactive-color="#ff4949"
        :active-value="1"
        :inactive-value="0"
      ></el-switch>
    </el-form-item>
    <el-form-item label="检索首字母" prop="firstLetter">
      <el-input v-model="dataForm.firstLetter" placeholder="检索首字母"></el-input>
    </el-form-item>
    <el-form-item label="排序" prop="sort">
      <el-input v-model="dataForm.sort" placeholder="排序"></el-input>
    </el-form-item>
    </el-form>
    <span slot="footer" class="dialog-footer">
      <el-button @click="visible = false">取消</el-button>
      <el-button type="primary" @click="dataFormSubmit()">确定</el-button>
    </span>
  </el-dialog>
</template>

<script>
import SingleUpload from "@/components/upload/singleUpload";

  export default {
    components: { SingleUpload },
    data () {
      return {
        visible: false,
        dataForm: {
          brandId: 0,
          name: '',
          logo: '',
          descript: '',
          showStatus: '',
          firstLetter: '',
          sort: ''
        },
        dataRule: {
          name: [
            { required: true, message: '品牌名不能为空', trigger: 'blur' }
          ],
          logo: [
            { required: true, message: '品牌logo地址不能为空', trigger: 'blur' }
          ],
          descript: [
            { required: true, message: '介绍不能为空', trigger: 'blur' }
          ],
          showStatus: [
            { required: true, message: '显示状态[0-不显示;1-显示]不能为空', trigger: 'blur' }
          ],
          firstLetter: [
            {
              validator: (rule, value, callback) => {
                if (value == "") {
                  callback(new Error("首字母必须填写"));
                } else if (!/^[a-zA-Z]$/.test(value)) {
                  callback(new Error("首字母必须a-z或者A-Z之间"));
                } else {
                  callback();
                }
              },
              trigger: "blur"
            }
          ],
          sort: [
            {
              validator: (rule, value, callback) => {
                if (value === '') {
                  callback(new Error("排序字段必须填写"));
                } else if (!/^[0-9]$/.test(value) || value<0 ) {
                  callback(new Error("排序必须是一个大于等于0的整数"));
                } else {
                  callback();
                }
              },
              trigger: "blur"
            }
          ]
        }
      }
    },
    methods: {
      init (id) {
        this.dataForm.brandId = id || 0
        this.visible = true
        this.$nextTick(() => {
          this.$refs['dataForm'].resetFields()
          if (this.dataForm.brandId) {
            this.$http({
              url: this.$http.adornUrl(`/product/brand/info/${this.dataForm.brandId}`),
              method: 'get',
              params: this.$http.adornParams()
            }).then(({data}) => {
              if (data && data.code === 0) {
                this.dataForm.name = data.brand.name
                this.dataForm.logo = data.brand.logo
                this.dataForm.descript = data.brand.descript
                this.dataForm.showStatus = data.brand.showStatus
                this.dataForm.firstLetter = data.brand.firstLetter
                this.dataForm.sort = data.brand.sort
              }
            })
          }
        })
      },
      // 表单提交
      dataFormSubmit () {
        this.$refs['dataForm'].validate((valid) => {
          if (valid) {
            this.$http({
              url: this.$http.adornUrl(`/product/brand/${!this.dataForm.brandId ? 'save' : 'update'}`),
              method: 'post',
              data: this.$http.adornData({
                'brandId': this.dataForm.brandId || undefined,
                'name': this.dataForm.name,
                'logo': this.dataForm.logo,
                'descript': this.dataForm.descript,
                'showStatus': this.dataForm.showStatus,
                'firstLetter': this.dataForm.firstLetter,
                'sort': this.dataForm.sort
              })
            }).then(({data}) => {
              if (data && data.code === 0) {
                this.$message({
                  message: '操作成功',
                  type: 'success',
                  duration: 1500,
                  onClose: () => {
                    this.visible = false
                    this.$emit('refreshDataList')
                  }
                })
              } else {
                this.$message.error(data.msg)
              }
            })
          }
        })
      }
    }
  }
</script>

20 品牌管理 JSR303校验

我们先给后端加上简单的JSR303校验

0 思路:
  • JSR303
  • 1)、给Bean添加校验注解:javax.validation.constraints,并定义自己的message提示
  • 2)、开启校验功能@Valid
  •  效果:校验错误以后会有默认的响应;
    
  • 3)、给校验的bean后紧跟一个BindingResult,就可以获取到校验的结果
1 我们这里给brand实体类来加上注解
package com.atguigu.gulimall.product.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import java.io.Serializable;
import java.util.Date;

import lombok.Data;
import org.hibernate.validator.constraints.URL;

import javax.validation.constraints.*;

/**
 * 品牌
 *
 * @author yyl
 * @email sunlightcs@gmail.com
 * @date 2022-04-24 19:01:01
 */
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * 品牌id
	 */
	@TableId
	private Long brandId;
	/**
	 * 品牌名
	 */
	@NotBlank(message = "品牌名必须提交")
	private String name;
	/**
	 * 品牌logo地址
	 */
	@NotBlank
	@URL(message = "logo必须是一个合法的url地址")
	private String logo;
	/**
	 * 介绍
	 */
	private String descript;
	/**
	 * 显示状态[0-不显示;1-显示]
	 */
	@NotNull(message = "展示状态不能为空")
	private Integer showStatus;
	/**
	 * 检索首字母
	 */
	@NotEmpty
	@Pattern(regexp="^[a-zA-Z]$",message = "检索首字母必须是一个字母")
	private String firstLetter;
	/**
	 * 排序
	 */
	@NotNull
	@Min(value = 0,message = "排序必须大于等于0")
	private Integer sort;

}

2 错误解决 注解不生效问题

首先看看你导的是不是java.javax.validation.constraints.*;
在这里插入图片描述
如果不是引入一下依赖

        <!--        校验注解-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
            <version>2.6.7</version>
        </dependency>

更新依赖引入后如果重启还不生效
maven
先clean 再 compile试试

3 controller层开启校验功能

代码如下

    /**
     * 保存
     */
    @RequestMapping("/save")
    //@RequiresPermissions("product:brand:save")
    public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){
        if(result.hasErrors()){
            Map<String,String> map = new HashMap<>();
            //1、获取校验的错误结果
            result.getFieldErrors().forEach((item)->{
                //FieldError 获取到错误提示
                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();
        }
    }

21 品牌管理- 统一异常处理

0 思路:
  • 4、统一的异常处理
  • @ControllerAdvice
  • 1)、编写异常处理类,使用@ControllerAdvice。
  • 2)、使用@ExceptionHandler标注方法可以处理的异常。
1 首先在product模块下建一个全局异常捕获类

这样我们再product.controller下的异常都能被这个类捕获到进行统一处理

在这里插入图片描述

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.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

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 我们在common模块下定义一个全局枚举

这样进行统一状态码的返回更加规范
我们在product模块下的全局异常捕获类中已经使用它了
在这里插入图片描述

/**
 * Copyright (c) 2016-2019 人人开源 All rights reserved.
 *
 * https://www.renren.io
 *
 * 版权所有,侵权必究!
 */

package com.atguigu.common.exception;

/**
 * 自定义异常
 *
 * @author Mark sunlightcs@gmail.com
 */
public class RRException extends RuntimeException {
	private static final long serialVersionUID = 1L;

    private String msg;
    private int code = 500;

    public RRException(String msg) {
		super(msg);
		this.msg = msg;
	}

	public RRException(String msg, Throwable e) {
		super(msg, e);
		this.msg = msg;
	}

	public RRException(String msg, int code) {
		super(msg);
		this.msg = msg;
		this.code = code;
	}

	public RRException(String msg, int code, Throwable e) {
		super(msg, e);
		this.msg = msg;
		this.code = code;
	}

	public String getMsg() {
		return msg;
	}

	public void setMsg(String msg) {
		this.msg = msg;
	}

	public int getCode() {
		return code;
	}

	public void setCode(int code) {
		this.code = code;
	}


}

22 品牌管理 JSR303分组校验

思路:

  • 4)、分组校验(多场景的复杂校验)
  •     1)、	@NotBlank(message = "品牌名必须提交",groups = {AddGroup.class,UpdateGroup.class})
    
  •      给校验注解标注什么情况需要进行校验
    
  •     2)、@Validated({AddGroup.class})
    
  •     3)、默认没有指定分组的校验注解@NotBlank,在分组校验情况@Validated({AddGroup.class})下不生效,只会在@Validated生效;
    
1 在 common模块下创建一个vaild包

里面定义AddGroup接口和UpdateGroup接口
在这里插入图片描述

2 在实体类上定义组分类

形式(groups = {组接口.class})

package com.atguigu.gulimall.product.entity;

import com.atguigu.common.valid.AddGroup;
import com.atguigu.common.valid.ListValue;
import com.atguigu.common.valid.UpdateGroup;
import com.atguigu.common.valid.UpdateStatusGroup;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import java.io.Serializable;

import lombok.Data;
import org.hibernate.validator.constraints.URL;

import javax.validation.constraints.*;

/**
 * 品牌
 *
 * @author leifengyang
 * @email leifengyang@gmail.com
 * @date 2019-10-01 21:08:49
 */
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * 品牌id
	 */
	@NotNull(message = "修改必须指定品牌id",groups = {UpdateGroup.class})
	@Null(message = "新增不能指定id",groups = {AddGroup.class})
	@TableId
	private Long brandId;
	/**
	 * 品牌名
	 */
	@NotBlank(message = "品牌名必须提交",groups = {AddGroup.class,UpdateGroup.class})
	private String name;
	/**
	 * 品牌logo地址
	 */
	@NotBlank(groups = {AddGroup.class})
	@URL(message = "logo必须是一个合法的url地址",groups={AddGroup.class,UpdateGroup.class})
	private String logo;
	/**
	 * 介绍
	 */
	private String descript;
	/**
	 * 显示状态[0-不显示;1-显示]
	 */
//	@Pattern()
	@NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
	@ListValue(vals={0,1},groups = {AddGroup.class, UpdateStatusGroup.class})
	private Integer showStatus;
	/**
	 * 检索首字母
	 */
	@NotEmpty(groups={AddGroup.class})
	@Pattern(regexp="^[a-zA-Z]$",message = "检索首字母必须是一个字母",groups={AddGroup.class,UpdateGroup.class})
	private String firstLetter;
	/**
	 * 排序
	 */
	@NotNull(groups={AddGroup.class})
	@Min(value = 0,message = "排序必须大于等于0",groups={AddGroup.class,UpdateGroup.class})
	private Integer sort;

}

3 在controller层上使用@vaildated(接口.class)开启分组校验(这时候如果实体类上没有指定组的校验将会失效)
    /**
     * 修改
     */
    @RequestMapping("/update")
    //@RequiresPermissions("product:brand:update")
    public R update(@Validated(UpdateGroup.class) @RequestBody BrandEntity brand){
		brandService.updateById(brand);

        return R.ok();
    }

22 JSR303 自定义校验注解

思路:

  • 5)、自定义校验
  •  1)、编写一个自定义的校验注解
    
  •  2)、编写一个自定义的校验器 ConstraintValidator
    
  •  3)、关联自定义的校验器和自定义的校验注解
      *      @Documented
      * @Constraint(validatedBy = { ListValueConstraintValidator.class【可以指定多个不同的校验器,适配不同类型的校验】 })
      * @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
      * @Retention(RUNTIME)
      * public @interface ListValue {
    
1 在自己对应实体类上编写自定义注解

在这里插入图片描述

	/**
	 * 显示状态[0-不显示;1-显示]
	 */
//	@Pattern()
	@NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
	@ListValue(vals={0,1},groups = {AddGroup.class, UpdateStatusGroup.class})
	private Integer showStatus;
2 编写注解接口

参考结构
在这里插入图片描述

package com.atguigu.common.valid;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Documented
//指定校验器 这里指定我们自定义的ListValueConstraintValidator
@Constraint(validatedBy = { ListValueConstraintValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
    //使用我们自己定义的错误信息 在common模块下的resources中
//    String message() default "{com.atguigu.common.valid.ListValue.message}";
    String message() default "值必须是0或者1";

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };

    //预先准备的值 vals={0,1}
    int[] vals() default { };
}

//对应实体类的注解
//@ListValue(vals={0,1},groups = {AddGroup.class, UpdateStatusGroup.class})
3 编写注解校验器

在这里插入图片描述

package com.atguigu.common.valid;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;


//对应实体类的注解
//@ListValue(vals={0,1},groups = {AddGroup.class, UpdateStatusGroup.class})
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {

    private Set<Integer> set = new HashSet<>();
    //初始化方法
    @Override
    public void initialize(ListValue constraintAnnotation) {

        //从注解上获取值 遍历将其存入一个set
        int[] vals = constraintAnnotation.vals();
        for (int val : vals) {
            set.add(val);
        }

    }

    //判断是否校验成功

    /**
     *
     * @param value 需要校验的值
     * @param context
     * @return
     */
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {

        //拿到传入的值 判断set中是否包含这个值
        //不包含返回false
        return set.contains(value);
    }
}

4 controller层开启
    /**
     * 修改状态
     */
    @RequestMapping("/update/status")
    //@RequiresPermissions("product:brand:update")
    public R updateStatus(@Validated(UpdateStatusGroup.class) @RequestBody BrandEntity brand){
        brandService.updateById(brand);

        return R.ok();
    }
5 流程总结

controller开启Vailid或者Validated进行校验 -> 到实体类的校验注解查看校验策略(分组 和注解内容,如notnull等) ->分支1 非定义注解 验证后通过或是返回错误信息——>分支2 自定义注解 去自定义注解中找到校验器
然后进行校验判断

23 商品服务-概念-SPU SKU 规格参数 销售属性

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

24 商品服务-API-属性分组-前端组件抽取&父子组件交互

1 前端组件抽取

1 首先更新gulimall-admin的sys-menu表
更新前端菜单展示的数据
跟着视频来导入sql

2 接着我们创建下面图片对应的前端目录
在这里插入图片描述
在这里插入图片描述
接着我们在其中引入element-ui的分栏间隔组件分栏间隔
3 把我们的目录树组件抽出为一个common方法供后续使用
在这里插入图片描述

<template>
  <div>
    <el-tree
      :data="menus"
      :props="defaultProps"
      node-key="catId"
      ref="menuTree"
    ></el-tree>
  </div>
</template>

<script>


export default {
  components: {},
  props: {},
  data() {
    //这里存放数据
    return {
      filterText: "",
      menus: [],
      expandedKey: [],
      defaultProps: {
        children: "children",
        label: "name"
      }
    };
  },

  computed: {},
  //方法集合
  methods: {
    getMenus() {
      this.$http({
        url: this.$http.adornUrl("/product/category/list/tree"),
        method: 'get'
      }).then(({ data }) => {
        this.menus = data.data;
      });
    },
  },
  created () {
    this.getMenus();
  },
};
</script>
<style>

</style>

4 接着在我们的属性分组下导入这个组件
在这里插入图片描述
在这里插入图片描述

<template>
  <el-row :gutter="20">
    <el-col :span="6">
      <category></category>
      <div class="grid-content bg-purple"></div></el-col>
    <el-col :span="18">表格<div class="grid-content bg-purple"></div></el-col>
  </el-row>
</template>

<script>

import Category from '../common/category';
export default {
  components: {Category},
  name: 'attrgroup',
  data () {
    return {}
  },
  methods: {}
}
</script>

<style scoped>

</style>

5 最终效果
在这里插入图片描述

2 对attrgroup.vue进行进一步完善

我们使用代码生成器自动生成的代码对这个
attrgroup.vue的页面进行进一步的完善
在这里插入图片描述
attrgroup.vue具体代码如下

<template>
  <el-row :gutter="20">
    <el-col :span="6">
      <category></category>
      <div class="grid-content bg-purple"></div></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>
      <div class="grid-content bg-purple"></div></el-col>
  </el-row>
</template>

<script>
import AddOrUpdate from './attrgroup-add-or-update'
import Category from '../common/category'
export default {
  components: {Category, AddOrUpdate},
  name: 'attrgroup',
  data () {
    return {
      dataForm: {
        key: ''
      },
      dataList: [],
      pageIndex: 1,
      pageSize: 10,
      totalPage: 0,
      dataListLoading: false,
      dataListSelections: [],
      addOrUpdateVisible: false
    }
  },
  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>

3 点击attrgroup.vue中的子组件让父组件获取子组件信息

1 首先子组件category.vue上加上事件
@node-click 节点被点击时回调

<template>
  <div>
    <el-tree
      :data="menus"
      :props="defaultProps"
      node-key="catId"
      @node-click="nodeclick"
      ref="menuTree"
    ></el-tree>
  </div>
</template>

2 接着在回调函数nodeclick给父组件发送信息

回调参数
node-click 节点被点击时的回调 共三个参数,依次为:传递给 data 属性的数组中该节点所对应的对象、节点对应的 Node、节点组件本身。

    nodeclick(data, node, component) {
      console.log("子组件category的节点被点击", data, node, component);
      //向父组件发送事件;
      this.$emit("tree-node-click", data, node, component);
    },

3 父组件接收到发送的事件并展示事件发来的数据

    <el-col :span="6">
      <category @tree-node-click="treenodeclick"></category>
      <div class="grid-content bg-purple"></div></el-col>
    <el-col :span="18">
    //感知树节点被点击
    treenodeclick(data, node, component) {
      // if (node.level == 3) {
      //   this.catId = data.catId;
      //   this.getDataList(); //重新查询
      // }
      console.log("传来的数据是",data);
    },

最终页面效果
在这里插入图片描述

25 属性分组 获取分类属性分组

1 后端调试

1 controller

    /**
     * 列表 传一个三级目录id查出其下的数据
     */
    @RequestMapping("/list/{catelogId}")
    //接受一个map 的 参数params 和catelogId
    public R list(@RequestParam Map<String, Object> params,
                  @PathVariable("catelogId") Long catelogId){

        //调用service层方法
        PageUtils page = attrGroupService.queryPage(params,catelogId);

        return R.ok().put("page", page);
    }

2 service

public interface AttrGroupService extends IService<AttrGroupEntity> {

    PageUtils queryPage(Map<String, Object> params);

    PageUtils queryPage(Map<String, Object> params, Long catelogId);
}

3 serviceimpl

    // 请求参数    params
//    {
//        page: 1,//当前页码
//                limit: 10,//每页记录数
//            sidx: 'id',//排序字段
//            order: 'asc/desc',//排序方式
//            key: '华为'//检索关键字
//    }
    @Override
    public PageUtils queryPage(Map<String, Object> params, Long catelogId) {
        //1 首先拿到传过来参数中的key对应的参数
        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%)

        //2 创造一个对应实体类的queryWrapper
        QueryWrapper<AttrGroupEntity> wrapper = new QueryWrapper<AttrGroupEntity>();

        //3 如果key关键字对应的不为空的话
        if(!StringUtils.isEmpty(key)){
            //要满足and (attr_group_id=key or attr_group_name like %key%)
            wrapper.and((obj)->{
                obj.eq("attr_group_id",key).or().like("attr_group_name",key);
            });
        }

        //4 如果传来的catelogId为0
        if( catelogId == 0){

            //直接根据params和wrapper生成page
            //Query utils不过是把params 变为 page
            IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params),
                    wrapper);

            //把Ipage对象通过PageUtils进行处理 处理成我们自定义的字段返回给前端
            return new PageUtils(page);
        }else {

            // 5 如果传来的cateLogId不为0 wrapper再次匹配这个字段
            //Query utils不过是把params 变为 page
            wrapper.eq("catelog_id",catelogId);
            IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params),
                    wrapper);
            //把Ipage对象通过PageUtils进行处理 处理成我们自定义的字段返回给前端
            return new PageUtils(page);
        }

    }

4 最终结果测试
在这里插入图片描述
sql语句
在这里插入图片描述
测试成功

2 前端代码改写

思路: 我们点三级分类的时候才获取分类属性分组
点一级二级不响应
默认传值0
如果点击三级分类再次调用方法查询

	  created () {
    this.catId = 0
    this.getDataList();
  },

    //感知树节点被点击
    treenodeclick(data, node, component) {
      console.log("子组件传来的数据是",data);
      if (node.level == 3) {
        this.catId = data.catId
        this.getDataList(); //重新查询
      }
    },
        // 获取数据列表
    getDataList () {
      this.dataListLoading = true
      this.$http({
        url: this.$http.adornUrl(`/product/attrgroup/list/${this.catId}`),
        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
      })
    },

26 属性分组 分组新增&级联选择器

1 思路
我们在添加属性分组上
在这里插入图片描述
https://element.eleme.cn/#/zh-CN/component/cascader

    <el-form-item label="所属分类id" prop="catelogId">
<!--      <el-input v-model="dataForm.catelogId" placeholder="所属分类id"></el-input>-->
       <el-cascader v-model="dataForm.catelogIds" :options="categorys"  :props="props"></el-cascader>
    </el-form-item>

:options=“categorys”
代表选择器的数据来源
我们访问catagoty controller来获取并对其赋值

    created () {
      this.getMenus()
    },
    methods: {
      getMenus() {
        this.$http({
          url: this.$http.adornUrl("/product/category/list/tree"),
          method: 'get'
        }).then(({ data }) => {
          this.categorys = data.data;
        });
      },

:props="props"绑定属性设置
value是什么
显示的name是什么
子节点是什么

data () {
      return {
        props:{
          value:"catId",
          label:"name",
          children:"children"
        },

由于我们的三级目录仍然包含children[]空集合
因此我们要在后端进行设置
否则级联选择器在三级目录后会跟上一个空目录
在这里插入图片描述
最终页面效果
在这里插入图片描述
但我们选择三级目录级联选择器的value值是一个数组
因此我们在添加时要选择最后一个值
在这里插入图片描述

在这里插入图片描述
测试添加效果成功
全部代码

<template>
  <el-dialog
    :title="!dataForm.attrGroupId ? '新增' : '修改'"
    :close-on-click-modal="false"
    :visible.sync="visible">
    <el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="80px">
    <el-form-item label="组名" prop="attrGroupName">
      <el-input v-model="dataForm.attrGroupName" placeholder="组名"></el-input>
    </el-form-item>
    <el-form-item label="排序" prop="sort">
      <el-input v-model="dataForm.sort" placeholder="排序"></el-input>
    </el-form-item>
    <el-form-item label="描述" prop="descript">
      <el-input v-model="dataForm.descript" placeholder="描述"></el-input>
    </el-form-item>
    <el-form-item label="组图标" prop="icon">
      <el-input v-model="dataForm.icon" placeholder="组图标"></el-input>
    </el-form-item>
    <el-form-item label="所属分类id" prop="catelogId">
<!--      <el-input v-model="dataForm.catelogId" placeholder="所属分类id"></el-input>-->
       <el-cascader v-model="dataForm.catelogIds" :options="categorys"  :props="props"></el-cascader>
    </el-form-item>
    </el-form>
    <span slot="footer" class="dialog-footer">
      <el-button @click="visible = false">取消</el-button>
      <el-button type="primary" @click="dataFormSubmit()">确定</el-button>
    </span>
  </el-dialog>
</template>

<script>
  export default {
    data () {
      return {
        props:{
          value:"catId",
          label:"name",
          children:"children"
        },
        categorys:[],
        visible: false,
        dataForm: {
          attrGroupId: 0,
          attrGroupName: '',
          sort: '',
          descript: '',
          icon: '',
          catelogIds: [],
          catelogId: 0
        },
        dataRule: {
          attrGroupName: [
            { required: true, message: '组名不能为空', trigger: 'blur' }
          ],
          sort: [
            { required: true, message: '排序不能为空', trigger: 'blur' }
          ],
          descript: [
            { required: true, message: '描述不能为空', trigger: 'blur' }
          ],
          icon: [
            { required: true, message: '组图标不能为空', trigger: 'blur' }
          ],
          catelogId: [
            { required: true, message: '所属分类id不能为空', trigger: 'blur' }
          ]
        }
      }
    },
    created () {
      this.getMenus()
    },
    methods: {
      getMenus() {
        this.$http({
          url: this.$http.adornUrl("/product/category/list/tree"),
          method: 'get'
        }).then(({ data }) => {
          this.categorys = data.data;
        });
      },
      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 = this.dataForm.catelogIds[2]
              }
            })
          }
        })
      },
      // 表单提交
      dataFormSubmit () {
        this.$refs['dataForm'].validate((valid) => {
          if (valid) {
            this.$http({
              url: this.$http.adornUrl(`/product/attrgroup/${!this.dataForm.attrGroupId ? 'save' : 'update'}`),
              method: 'post',
              data: this.$http.adornData({
                'attrGroupId': this.dataForm.attrGroupId || undefined,
                'attrGroupName': this.dataForm.attrGroupName,
                'sort': this.dataForm.sort,
                'descript': this.dataForm.descript,
                'icon': this.dataForm.icon,
                'catelogId': this.dataForm.catelogIds[2]
              })
            }).then(({data}) => {
              if (data && data.code === 0) {
                this.$message({
                  message: '操作成功',
                  type: 'success',
                  duration: 1500,
                  onClose: () => {
                    this.visible = false
                    this.$emit('refreshDataList')
                  }
                })
              } else {
                this.$message.error(data.msg)
              }
            })
          }
        })
      }
    }
  }
</script>

27 属性分组 分组修改-级联选择器回显

我们在点击修改时要设置出回显
在这里插入图片描述
我们可以让后端返回我们需要的这个数据
1 实体类修改(AttrGroupEntity)
新增一个字段 这个字段就是我们回显需要的数据

	/**
	 * 修改组时候的回显需要的数据
	 */
	@TableField(exist = false)
	private Long[] catelogPath;

2 Controller层修改

/**
     * 信息
     */
    @RequestMapping("/info/{attrGroupId}")
    //@RequiresPermissions("product:attrgroup:info")
    public R info(@PathVariable("attrGroupId") Long attrGroupId){
        //根据attrGroupId查出对应的实体类
		AttrGroupEntity attrGroup = attrGroupService.getById(attrGroupId);

        //从实体类中获取catelogId
        Long catelogId = attrGroup.getCatelogId();
        //调用categoryService categoryService 查出 路径
        Long[] path = categoryService.findCatelogPath(catelogId);
        //把路径设置到其中 返回给前端
        attrGroup.setCatelogPath(path);


        return R.ok().put("attrGroup", attrGroup);
    }

3 categroyServiceImpl修改

/**
     *     找到一个三级目录对应的完整路径 [2,25,225]  /电器/好电器/手机
     */

    @Override
    public Long[] findCatelogPath(Long catelogId) {
        //新建一个ArrayList集合 接受Long
        List<Long> paths = new ArrayList<>();

        //调用方法找到完整路径[225,22,2]
        List<Long> parentPath = findParentPath(catelogId, paths);

        //调用逆序方法获得正确顺序[2,25,225]
        Collections.reverse(parentPath);


        return parentPath.toArray(new Long[parentPath.size()]);
    }

    /**
     *     找到一个三级目录对应的完整路径 [2,25,225]  /电器/好电器/手机
     */
    private List<Long> findParentPath(Long catelogId,List<Long> paths){
        //1、收集当前节点id
        paths.add(catelogId);
        //2 根据当前节点ID拿到其对应的实体类
        CategoryEntity byId = this.getById(catelogId);
        //3 当实体类父id不为0继续调用这个方法
        if(byId.getParentCid()!=0){
            findParentPath(byId.getParentCid(),paths);
        }
        //4 跳出递归循环返回数据
        return paths;

    }

在这里插入图片描述

4 修改前端代码
在这里插入图片描述
回显效果设置完成

5 绑定diglog事件

关闭dialog时清空Path
不影响我们新增时的操作
在这里插入图片描述

    methods: {
      dialogClose(){
        this.dataForm.catelogPath = []
      },

28 品牌管理 品牌分类关联与级联更新

1 我们要使用mybatis-plus进行分页

要配置分页插件很简单
在这里插入图片描述
接着配置一下分页插件

package com.atguigu.gulimall.product.config;


import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement //开启事务
@MapperScan("com.atguigu.gulimall.product.dao")
public class MybatisPlusConfig {
    //引入分页插件
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求  默认false
        paginationInterceptor.setOverflow(true);
        // 设置最大单页限制数量,默认 500 条,-1 不受限制
        paginationInterceptor.setLimit(1000);
        return paginationInterceptor;
    }
}

发现分页已经出效果了
在这里插入图片描述

2 我们来让品牌管理中的搜索生效

在这里插入图片描述
来到list的serviceimpl层加一些代码
在这里插入图片描述

@Service("brandService")
public class BrandServiceImpl extends ServiceImpl<BrandDao, BrandEntity> implements BrandService {

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        //参数列表中拿关键字
        String key = (String) params.get("key");
        QueryWrapper<BrandEntity> brandEntityQueryWrapper = new QueryWrapper<>();
        if (StringUtils.isNotEmpty(key)){
            brandEntityQueryWrapper.eq("brand_id",key).or().like("name",key);
        }


        IPage<BrandEntity> page = this.page(
                new Query<BrandEntity>().getPage(params),
                brandEntityQueryWrapper
        );

        return new PageUtils(page);
    }

}
3 我们让品牌管理的关联分类生效

在这里插入图片描述
需要编写两个controller

首先来编写查询品牌关联分类的controller
在这里插入图片描述

    /**
     * 获取当前品牌关联的所有分类列表
     */
    @GetMapping("/catelog/list")
    //@RequiresPermissions("product:categorybrandrelation: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);
    }

接着来编写新增品牌关联分类的controller


    /**
     * 保存
     */
    @RequestMapping("/save")
    //@RequiresPermissions("product:categorybrandrelation:save")
    public R save(@RequestBody CategoryBrandRelationEntity categoryBrandRelation){


        categoryBrandRelationService.saveDetail(categoryBrandRelation);

        return R.ok();
    }
 

service

public interface CategoryBrandRelationService extends IService<CategoryBrandRelationEntity> {

    PageUtils queryPage(Map<String, Object> params);

    void saveDetail(CategoryBrandRelationEntity categoryBrandRelation);
}

serviceimpl(这里面注入了两个DAO)

    @Autowired
    BrandDao brandDao;

    @Autowired
    CategoryDao categoryDao;
    
@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);

    }

测试新增关联关系 显示关联关系没有问题
在这里插入图片描述

4 连带更新(避免数据不一致)

我们因为这个数据表中有冗余字段
在这里插入图片描述

当我们更新brand表时候这里并不会更新
因此我们在更新brand时需要连带更新这个表中的数据
否则会出现字段属性不一致的问题
在这里插入图片描述
1 来更改品牌名称时关系表中的冗余字段同步更新

1 brandcontroller

/**
     * 修改
     */
    @RequestMapping("/update")
    //@RequiresPermissions("product:brand:update")
    public R update(@Validated(UpdateGroup.class) @RequestBody BrandEntity brand){
//		brandService.updateById(brand);
        brandService.updateDetail(brand);
        return R.ok();
    }

2 BrandService

public interface BrandService extends IService<BrandEntity> {

    PageUtils queryPage(Map<String, Object> params);

    void updateDetail(BrandEntity brand);
}

3 BrandServiceImpl

@Service("brandService")
public class BrandServiceImpl extends ServiceImpl<BrandDao, BrandEntity> implements BrandService {

    @Autowired
    CategoryBrandRelationService categoryBrandRelationService;

    @Transactional
    @Override
    public void updateDetail(BrandEntity brand) {
        //保证冗余字段的数据一致
        this.updateById(brand);
        //如果brand中的name更改的话
        if(!org.springframework.util.StringUtils.isEmpty(brand.getName())){
            //同步更新其他关联表中的数据
            categoryBrandRelationService.updateBrand(brand.getBrandId(),brand.getName());

            //TODO 更新其他关联
        }
    }

4 CategoryBrandRelationService

public interface CategoryBrandRelationService extends IService<CategoryBrandRelationEntity> {

    PageUtils queryPage(Map<String, Object> params);

    void saveDetail(CategoryBrandRelationEntity categoryBrandRelation);

    void updateBrand(Long brandId, String name);
}

5 CategoryBrandRelationServiceImpl

    @Override
    public void updateBrand(Long brandId, String name) {
        //创建一个新的关系实体类
        CategoryBrandRelationEntity relationEntity = new CategoryBrandRelationEntity();
        //设置ID和name为我们传来的
        relationEntity.setBrandId(brandId);
        relationEntity.setBrandName(name);
        //更新ID为xxx的name字段完成级联更新
        this.update(relationEntity,new UpdateWrapper<CategoryBrandRelationEntity>().eq("brand_id",brandId));
    }

2 来更改目录名称时关系表中的冗余字段同步更新

具体操作和1一样
在categorycontroller的update调用relation中的update
进行级联更新 具体就不再多赘述

下一个基础篇笔记链接

总结

123456789
987654321
(bornig)

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qwecxzz

鸡腿

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值