谷粒商城基础篇------商品服务 - 品牌管理(gulimall-product:pms_brand)

5.1 逆向生成的前端代码

① 在renren-fast-vue项目的菜单管理里中新增一个菜单:和之前的分类维护一样,再次创建一个品牌管理。
在这里插入图片描述
在这里插入图片描述
② 将之前逆向工程生成的product微服务的代码(renren-generator代码生成器生成各个微服务的前后端代码

F:\JAVA\谷粒商城电商项目-2020-尚硅谷-雷丰阳\renren\ren_gulimall_product\main\resources\src\views\modules\product项目下的brand-add-or-update.vuebrand.vue文件拷贝到前端项目renren-fast-vue/src/views/modules/product中,然后在终端重新执行npm run dev重新运行项目,打开菜单栏的品牌管理,发现没有新增和删除功能:
在这里插入图片描述
在这里插入图片描述
③ 这是因为权限控制的原因,将renren-fast-vue项目的src\utils\index.js中的isAuth()方法永远返回true即可

/**
 * 是否有权限
 */
export function isAuth (key) {
  return true;
}

在这里插入图片描述
④测试新增和删除一个品牌,均正确
在这里插入图片描述

5.2 显示状态按钮

①前端代码

brand.vue

<template slot-scope="scope">
  <el-switch
    v-model="scope.row.showStatus"
    active-color="#13ce66"
    inactive-color="#ff4949"
    @change="updateBrandStatus(scope.row)"
    :active-value = "1"
    :inactive-value	= "0"
  ></el-switch>
</template>

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

brand-add-or-update.vue

 <el-form-item label="显示状态" prop="showStatus">
    <el-switch v-model="dataForm.showStatus" active-color="#13ce66" inactive-color="#ff4949"></el-switch>
 </el-form-item>

在这里插入图片描述
② 点击新增时也能够将显示状态设置为开关:

可以看到在brand.vue文件中引入了一个组件brand-add-or-update.vue,注意addOrUpdateHandle (id) 方法:

<script>
//从外部导入的功能    
import AddOrUpdate from './brand-add-or-update'
export default {
  data () {
    return {
      addOrUpdateVisible: false
    }
  },
  components: {
    AddOrUpdate
  },
  methods: {
    // 新增 / 修改
    addOrUpdateHandle (id) {
      this.addOrUpdateVisible = true
      this.$nextTick(() => {
        this.$refs.addOrUpdate.init(id)
      })
    },
  }
}
</script>

点击新增就会触发addOrUpdateHandle()方法:

<el-button v-if="isAuth('product:brand:save')" type="primary"
           @click="addOrUpdateHandle()">新增</el-button>

addOrUpdateHandle()方法中会将this.addOrUpdateVisible = true,会显示add-or-update组件:

<!-- 弹窗, 新增 / 修改 -->
<add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" 	                                            @refreshDataList="getDataList"></add-or-update>

所以我们要到brand-add-or-update.vue中修改显示状态将它设置为开关:

<el-form-item label="显示状态" prop="showStatus">
    <template slot-scope="scope">
        <el-switch v-model="dataForm.showStatus" active-color="#13ce66" inactive-color="#ff4949"></el-switch>
    </template>
</el-form-item>

在这里插入图片描述
③ 点击开关修改显示状态,并改变数据库中show_status的值(数据库中显示为1,不显示为0),而ElementUI的开关组件默认是显示为true,不显示为false,因此需要给开关组件的active-value和inactive-value属性绑定值,让其显示为1,不显示为0,同时还需要编写一个@change="updateBrandStatus(scope.row)"方法,将数据库中的show_status的值做相应更改:

<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>
updateBrandStatus (data) {
    console.log("最新信息", data)
    let { brandId, showStatus } = data
    //发送请求修改数据库中显示状态
    this.$http({
        url: this.$http.adornUrl("/product/brand/update"),
        method: "post",
        data: this.$http.adornData({ brandId, showStatus }, false)
    }).then(({ data }) => {
        this.$message({
            type: "success",
            message: "状态更新成功"
        })
    })
},

5.3 云存储的开通与使用(文件上传功能)

1、OSS开通

需求:点击新增后显示新增品牌,我们希望将品牌logo进行文件上传,和传统的单体应用不同,这里我们选择将数据上传到分布式文件服务器上。

① 进入https://www.aliyun.com/,选择产品,存储,对象存储OSS,立即开通
在这里插入图片描述
② 使用支付宝登录后完成实名认证,然后再开通对象存储OSS,管理控制台创建Bucket:
在这里插入图片描述
③ 找到创建的bucket,点击文件上传,上传一张图片,然后点击详情,复制url即可在浏览器访问呢图片:
在这里插入图片描述
④ 这种方式是手动上传图片,实际上我们可以在程序中设置自动上传图片到阿里云对象存储, 文件上传方式:
在这里插入图片描述

2、文件上传操作

参考:https://help.aliyun.com/document_detail/32009.html?spm=a2c4g.11186623.6.920.15b85cb1mYuz5t

方式1:原生方式整合OSS:

① 在Maven工程中使用OSS Java SDK,只需在pom.xml中加入相应依赖即可,在gulimall-product服务中:

<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.10.2</version>
</dependency>

② 在gulimall-product服务中编写一个测试类GulimallProductApplicationTests,上传文件流:

endpoint的取值和accessKeyId和accessKeySecret的取值需要从阿里云的对象存储OSS中获取

@Test
    public void test() throws FileNotFoundException {
        // Endpoint以杭州为例,https://oss.console.aliyun.com/bucket/oss-cn-hangzhou/gulimall-hengheng/overview
        String endpoint = "oss-cn-beijing.aliyuncs.com";
        // 云账号AccessKey有所有API访问权限,建议遵循阿里云安全最佳实践,创建并使用RAM子账号进行API访问或日常运维,https://ram.console.aliyun.com/users/gulimall/
        String accessKeyId = "LTAI4GAJzjPD4YcS4pEGae7b";
        String accessKeySecret = "sVID87zolwivkbSbG3ioXsYScXs0Py";
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        // 上传文件流。
        InputStream inputStream = new FileInputStream("F:\\JAVA\\谷粒商城电商项目-2020-尚硅谷-雷丰阳\\谷粒商城图片\\product55.png");
        ossClient.putObject("gulimall-ghh", "product55.png", inputStream);
        // 关闭OSSClient。
        ossClient.shutdown();
        System.out.println("上传成功");
    }

③endpoint的取值:
在这里插入图片描述
④accessKeyId和accessKeySecret需要创建一个RAM账号:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
注册成功的RAM账户:accessKeyId和accessKeySecret如下复制即可
在这里插入图片描述
⑤要想后端代码上传成功,还要添加权限
在这里插入图片描述
⑥启动测试
在这里插入图片描述
方式2:SpringCloud Alibaba-OSS

https://github.com/alibaba/aliyun-spring-boot/tree/master/aliyun-spring-boot-samples/aliyun-oss-spring-boot-sample

① 在gulimall-common服务的pom文件中导入依赖(记住要加入版本号,因为这个最高才2.2.0,与父项目的版本不匹配):

<dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alicloud-oss</artifactId>
            <version>2.2.0.RELEASE</version>
        </dependency>

② 在gulimall-product服务的application.yml文件中配置access-key和secret-key

spring:
  cloud:
    alicloud:
      access-key: LTAI4GAJzjPD4YcS4pEGae7b
      secret-key: sVID87zolwivkbSbG3ioXsYScXs0Py
      oss:
        endpoint: oss-cn-beijing.aliyuncs.com

③ 测试:GulimallProductApplicationTests.java

因为导入了yml配置文件,所以可以注释掉相关代码

@Resource
    OSSClient ossClient;
@Test
    public void test() throws FileNotFoundException {
        /*// Endpoint以杭州为例,https://oss.console.aliyun.com/bucket/oss-cn-hangzhou/gulimall-hengheng/overview
        String endpoint = "oss-cn-beijing.aliyuncs.com";
        // 云账号AccessKey有所有API访问权限,建议遵循阿里云安全最佳实践,创建并使用RAM子账号进行API访问或日常运维,https://ram.console.aliyun.com/users/gulimall/
        String accessKeyId = "LTAI4GAJzjPD4YcS4pEGae7b";
        String accessKeySecret = "sVID87zolwivkbSbG3ioXsYScXs0Py";
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);*/
        // 上传文件流。
        InputStream inputStream = new FileInputStream("F:\\JAVA\\谷粒商城电商项目-2020-尚硅谷-雷丰阳\\谷粒商城图片\\product9.png");
        ossClient.putObject("gulimall-ghh", "product9.png", inputStream);
        // 关闭OSSClient。
        ossClient.shutdown();
        System.out.println("上传成功");
    }

在这里插入图片描述

3、创建gulimall-third-party服务集成OSS服务

① 创建gulimall-third-party服务专门用来集成第三方服务,修改pom文件,将OSS这个第三方SDK不再放入gulimall-common中而是放入gulimall-third-party服务中:

将gulimall-common中的spring-cloud-starter-alicloud-oss的依赖剪切到gulimall-third-party的pom.xml里,因为其他微服务继承common,这样每一个微服务yml文件都需要配置OSS,如果不配置就报错。所以采用下面方式:

    <dependencies>
        <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>
        <!--OSS文件上传-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alicloud-oss</artifactId>
            <version>2.2.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

② 编写bootstrap.properties文件,配置nacos的配置中心地址,同时加载oss.yml中配置:

spring.cloud.nacos.config.server-addr=localhost:8848
spring.application.name=gulimall-third-party
spring.cloud.nacos.config.namespace=b4d070ce-6025-4963-b873-f16c542c9128

spring.cloud.nacos.config.ext-config[0].data-id=oss.yml
spring.cloud.nacos.config.ext-config[0].group=DEFAULT_GROUP
spring.cloud.nacos.config.ext-config[0].refresh=true

在这里插入图片描述
在这里插入图片描述
③编写thirdparty微服务的application.yml。

将product的oss配置剪切到thirdparty里,因为oss作为第三方以后应该放在第三方微服务里

spring:
  application:
    name: gulimall-third-party
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    alicloud:
      access-key: LTAI4GAJzjPD4YcS4pEGae7b
      secret-key: sVID87zolwivkbSbG3ioXsYScXs0Py
      oss:
        endpoint: oss-cn-beijing.aliyuncs.com
        bucket: gulimall-ghh
server:
  port: 30000

④将原先的product微服务的测试也剪切到thirdparty微服务的测试类;然后主启动添加@EnableDiscoveryClient

测试类:

@Resource
    private OSSClient ossClient;
@Test
    public void test() throws FileNotFoundException {
        InputStream inputStream = new FileInputStream("F:\\JAVA\\谷粒商城电商项目-2020-尚硅谷-雷丰阳\\谷粒商城图片\\product60.png");
        ossClient.putObject("gulimall-ghh", "product60.png", inputStream);
        // 关闭OSSClient。
        ossClient.shutdown();
        System.out.println("上传成功");
    }
4、OSS服务端签名后直传

原因:为什么采用OSS服务端签名后直传?

采用JavaScript客户端直接签名(参见JavaScript客户端签名直传)时,AccessKeyID和AcessKeySecret会暴露在前端页面,因此存在严重的安全隐患。因此,OSS提供了服务端签名后直传的方案。

原理介绍:

在这里插入图片描述
服务端签名后直传的原理如下:

  1. 用户发送上传Policy请求到应用服务器。
  2. 应用服务器返回上传Policy和签名给用户。
  3. 用户直接上传数据到OSS。

① 在gulimall-third-party服务中编写com.bigdata.gulimall.thirdparty.controller.OssController类:

@RestController
public class OssController {
    @Autowired
    private OSS ossClient;
    //从配置文件中获取值
    @Value("${spring.cloud.alicloud.oss.endpoint}")
    private String endpoint;
    @Value("${spring.cloud.alicloud.oss.bucket}")
    private String bucket;
    @Value("${spring.cloud.alicloud.access-key}")
    private String accessId;

    @RequestMapping("/oss/policy")
    public Map<String,String> policy() {
        // host的格式为 bucketname.endpoint
        String host = "https://" + bucket + "." + endpoint; 
        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 = ossClient.generatePostPolicy(expiration, policyConds);
            byte[] binaryData = postPolicy.getBytes("utf-8");
            String encodedPolicy = BinaryUtil.toBase64String(binaryData);
            String postSignature = ossClient.calculatePostSignature(postPolicy);

            respMap = new LinkedHashMap<String, String>();
            respMap.put("accessid", accessId);
            respMap.put("policy", encodedPolicy);
            respMap.put("signature", postSignature);
            respMap.put("dir", dir);
            respMap.put("host", host);
            respMap.put("expire", String.valueOf(expireEndTime / 1000));
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        return respMap;
    }
}

② 访问http://localhost:30000/oss/policy,可以得到签名数据:

{"accessid":"LTAI4GAJzjPD4YcS4pEGae7b","policy":"eyJleHBpcmF0aW9uIjoiMjAyMS0wMS0wNVQwNToyMzo1MS4zOTNaIiwiY29uZGl0aW9ucyI6W1siY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMTA0ODU3NjAwMF0sWyJzdGFydHMtd2l0aCIsIiRrZXkiLCIyMDIxLTAxLTA1LyJdXX0=","signature":"DXOPaKpr2Trug1md6SfJmuynolo=","dir":"2021-01-05/","host":"https://gulimall-ghh.oss-cn-beijing.aliyuncs.com","expire":"1609824231"}

③ 在gulimall-gateway中application.yml配置网关路由, 上传文件路径为:http://localhost:88/api/thirdparty/oss/policy

这个精确的要放在模糊的前面,防止顺序出错,带来结果出错

- id: third_party_route
  uri: lb://gulimall-third-party
  predicates:
    - Path=/api/thirdparty/**
  filters:
    - RewritePath=/api/thirdparty/(?<segment>.*),/$\{segment}
5、OSS前后端联调测试上传

① 将项目提供的upload文件放在renren-fast-vue\src\components目录下,然后将两个文件的文件上传地址,改为阿里云提供的 Bucket 域名:http://gulimall-ghh.oss-cn-beijing.aliyuncs.com
在这里插入图片描述
② 在brand-add-or-update.vue文件中使用singleUpload.vue这个单文件上传组件,如何使用?

首先,需要在brand-add-or-update.vue文件中导入外部组件singleUpload.vueL:

//导入外部组件
import SingleUpload from "@/components/upload/singleUpload";

其次,在components中指明这个vue组件中需要用到哪些组件,指明后就可以在vue中使用了:

export default {
    components: {SingleUpload},
}

最后,在vue组件中使用这个组件:

<el-form-item label="品牌logo地址" prop="logo">
    <!-- 属性名要和components指明的组件名称相同,SingleUpload或single-upload -->
    <single-upload v-model="dataForm.logo"></single-upload>
</el-form-item>

③ 修改后端com.bigdata.gulimall.thirdparty.controller.OssController类的返回值:

@RestController
public class OssController {
    @Autowired
    private OSS ossClient;
    //从配置文件中获取值
    @Value("${spring.cloud.alicloud.oss.endpoint}")
    private String endpoint;
    @Value("${spring.cloud.alicloud.oss.bucket}")
    private String bucket;
    @Value("${spring.cloud.alicloud.access-key}")
    private String accessId;

    @RequestMapping("/oss/policy")
    public R policy() {
        String host = "https://" + bucket + "." + endpoint;  
        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 = ossClient.generatePostPolicy(expiration, policyConds);
            byte[] binaryData = postPolicy.getBytes("utf-8");
            String encodedPolicy = BinaryUtil.toBase64String(binaryData);
            String postSignature = ossClient.calculatePostSignature(postPolicy);

            respMap = new LinkedHashMap<String, String>();
            respMap.put("accessid", accessId);
            respMap.put("policy", encodedPolicy);
            respMap.put("signature", postSignature);
            respMap.put("dir", dir);
            respMap.put("host", host);
            respMap.put("expire", String.valueOf(expireEndTime / 1000));
        } catch (Exception e) {
        System.out.println(e.getMessage());
    }
//        return respMap;
        return R.ok().put("data",respMap);
    }
}

④ 在页面点击新增,然后文件上传品牌logo,但是出现了如下的问题:
在这里插入图片描述
这又是一个跨域问题,在阿里云上开启跨域权限:
在这里插入图片描述
在这里插入图片描述
⑤ 解决完成后,重新进行文件上传,即可:
在这里插入图片描述

5.4 表单校验

1、前端表单校验

① 显示图片:

点击新增,新增一个品牌:
在这里插入图片描述
在这里插入图片描述
在brand.vue中动态绑定图片地址:

<el-table-column prop="logo" header-align="center" align="center" label="品牌logo地址">
    <template slot-scope="scope">
        <img :src="scope.row.logo" style="width: 100px; height: 80px" />
    </template>
</el-table-column>

在这里插入图片描述
② 前端表单校验:
在这里插入图片描述
参考:https://element.eleme.cn/#/zh-CN/component/form,ElementUI的form表单组件:

在brand-add-or-update.vue中添加:

<el-form-item label="排序" prop="sort">
    <!--将sort字段绑定一个数字-->
    <el-input v-model.number="dataForm.sort" placeholder="排序"></el-input>
</el-form-item>

dataRule: {
        name: [{ required: true, message: "品牌名不能为空", trigger: "blur" }],
        logo: [{ required: true, message: "品牌logo地址不能为空", trigger: "blur" }],
        descript: [{ required: true, message: "介绍不能为空", trigger: "blur" }],
        showStatus: [
          {
            required: true,
            message: "显示状态",
            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 (!Number.isInteger(value) || value < 0) {
                callback(new Error("排序必须是一个大于等于0的整数"))
              } else {
                callback()
              }
            },
            trigger: "blur"
          }
 },

在这里插入图片描述

2、JSR303后端数据校验

①添加后端JSR303时,需要添加以下依赖

<dependency>
   <groupId>jakarta.validation</groupId>
            <artifactId>jakarta.validation-api</artifactId>
            <version>2.0.2</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.0.20.Final</version>
        </dependency>         

②BrandEntity实体类添加校验注解,并添加自己的message提示:

//品牌
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
	private static final long serialVersionUID = 1L;

	@NotNull(message = "修改必须指定品牌id")
	@Null(message = "新增不能指定id")
	@TableId
	private Long brandId;

	@NotBlank(message = "品牌名必须提交")
	private String name;

	//该注解不能为null,并且至少包含一个非空字符。
	@NotBlank
	@URL(message = "logo必须是一个合法的url地址")
	private String logo;

	private String descript;
	private Integer showStatus;

	@NotEmpty
	@Pattern(regexp="^[a-zA-Z]$",message = "检索首字母必须是一个字母")
	private String firstLetter;

	@NotNull
	@Min(value = 0,message = "排序必须大于等于0")
	private Integer sort;
}

BrandController

开启校验功能@Valid,接收校验出错的结果BingResult:

/**
     * 保存
     */
    @RequestMapping("/save")
    public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){
        if(result.hasErrors()){
            Map<String,String> map = new HashMap<>();
            //1、获取校验的错误结果
            result.getFieldErrors().forEach((item)->{
                //获取错误的属性的名字
                String field = item.getField();
                //FieldError 获取到错误提示
                String message = item.getDefaultMessage();
                map.put(field,message);
            });
            return R.error(400,"提交的数据不合法").put("data",map);
        }else {
            brandService.save(brand);
            return R.ok();
        }
    }

④使用postman测试:http://localhost:88/api/product/brand/save
在这里插入图片描述

3、统一异常处理

上一节中对于参数校验发生的异常,我们使用了 BindingResult result这个变量来接收,但是这样做太复杂,因为参数校验的实体类很多,我们需要在每个Controller层的相应方法中加上参数校验并接收异常响应结果,因此只需要做统一异常处理即可,即将Controller层中所有的异常都抛出去,然后统一处理Controller层的异常。

① 在com.atguigu.gulimall**.product.e**xception包下新建一个统一异常处理类:

@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());
    }
}

②在package com.atguigu**.common**.exception包下封装一个枚举类,定义各种响应状态码和响应消息:

public enum BizCodeEnume {
    UNKNOW_EXCEPTION(10000,"系统未知异常"),
    VAILD_EXCEPTION(10001,"参数格式校验失败");

    private int code;
    private String msg;
    BizCodeEnume(int code,String msg){
        this.code = code;
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}

③将原先的BrandController的save方法修改,只保留成功的方法(因为异常方法已经在统一异常处理了GulimallExceptionControllerAdvice类)

@RequestMapping("/save")
    public R save(@Valid @RequestBody BrandEntity brand){
            brandService.save(brand);
            return R.ok();
    }

④postman测试:http://localhost:88/api/product/brand/save
在这里插入图片描述

4、JSR303分组数据校验

新增品牌和修改品牌的某些字段的注解校验规则不一样时,可以分组校验,校验注解只有在指定的分组下才生效,而且如果开启了分组校验注解功能,那些没有指定分组的校验注解就会不生效。

@Validated(UpdateGroup.class)分组校验

@Validated不分组校验

① 在com.atguigu.common.valid包下定义两个接口,不用写具体实现:

public interface UpdateGroup {
}
public interface AddGroup {
}

② 开启分组校验注解功能,在方法上使用@Validated({AddGroup.class})注解指明校验字段所属的分组类:

/**
   * 保存/新增
   */
@RequestMapping("/save")
public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){
    brandService.save(brand);
    return R.ok();
}

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

③ 给校验注解标注什么情况需要进行校验,默认没有指定分组的校验注解,在开启了分组校验的情况下不生效。

@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
   private static final long serialVersionUID = 1L;

   @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;

   @NotBlank(groups = {AddGroup.class})
   @URL(message = "logo必须是一个合法的url地址",groups={AddGroup.class,UpdateGroup.class})
   private String logo;

   private String descript;
   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;
}
5、JSR303自定义数据校验

gulimall-common服务中,com.atguigu.common.valid包下:

自定义一个校验注解:ListValue

@Documented
//关联自定义的校验器
@Constraint(validatedBy = { ListValueConstraintValidator.class })
//注解可以放在哪里
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
    //校验注解发生异常的时候,提示信息该配置文件中获取
    String message() default "{com.atguigu.common.valid.ListValue.message}";
    Class<?>[] groups() default { };
    Class<? extends Payload>[] payload() default { };
    int[] vals() default { };
}

ValidationMessages.properties文件:

com.atguigu.common.valid.ListValue.message=必须提交指定的值

② 编写一个自定义校验器ConstraintValidator:

public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {

    private Set<Integer> set = new HashSet<>();
    
    //初始化方法
    @Override
    public void initialize(ListValue constraintAnnotation) {
        int[] vals = constraintAnnotation.vals();
        for (int val : vals) {
            set.add(val);
        }
    }

    //判断是否校验成功
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        //判断set集合中是否包含这个值,如果不包含就报错
        return set.contains(value);
    }
}
public interface UpdateStatusGroup {
}

在这里插入图片描述
③④是gulimall-product的entity和controller

③ 给BrandEntity实体类的showStatus字段添加自定义校验注解@ListValue并指明分组:

@NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
@ListValue(vals={0,1},groups = {AddGroup.class, UpdateStatusGroup.class})
private Integer showStatus;

④ BrandController类 @Validated(UpdateStatusGroup.class)开启注解校验功能:

@RequestMapping("/update/status")
public R updateStatus(@Validated(UpdateStatusGroup.class) @RequestBody BrandEntity brand){
    brandService.updateById(brand);
    return R.ok();
}

⑤ 测试:

访问新增方法:http://localhost:88/api/product/brand/save
在这里插入图片描述
访问修改方法:http://localhost:88/api/product/brand/update/status
在这里插入图片描述
⑥ 测试前后端联调,前端点击新增和修改一个品牌:
在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值