谷粒商城-全栈-26 商品服务-JSR303 统一异常处理

一、JSR3030基本用法学习

  • 1)、给Bean添加校验注解:javax.validation.constraints,并定义自己的message提示
  • 2)、开启校验功能@Valid
    效果:校验错误以后会有默认的响应;
  • 3)、给校验的bean后紧跟一个BindingResult,就可以获取到校验的结果

给bean定义自己的message

/**
     * 品牌名
     */
    @NotBlank(message = "品牌名不能为空")
    private String name;

还有其他的校验用法,可以看看

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.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;

/**
 * 品牌
 * 
 * @author kaiyi
 * @email corwienwong@gmail.com
 * @date 2020-08-10 15:45:20
 */
@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地址
     */
    @NotEmpty(message = "URL地址不能为空")
    @URL(message = "logo必须是一个合法的url地址")
    private String logo;
    /**
     * 介绍
     */
    private String descript;
    /**
     * 显示状态[0-不显示;1-显示]
     */
    private Integer showStatus;
    /**
     * 检索首字母
     */
    @NotEmpty
    @Pattern(regexp = "/^[a-zA-Z]$/", message = "检索首字母必须是一个字母")
    private String firstLetter;
    /**
     * 排序
     */
    @NotNull
    @Min(value = 0, message = "排序必须大于等于0")
    private Integer sort;

}

Controller 中需要校验的参数Bean前添加 @Valid 开启校验功能,紧跟在校验的Bean后添加一个BindingResult,BindingResult封装了前面Bean的校验结果。

@RestController
@RequestMapping("product/brand")
public class BrandController {

    @RequestMapping("/save")
    public R save (@Valid @RequestBody BrandEntity brand, BindingResult bindingResult)  {
        if (bindingResult.hasErrors()) {
            Map<String , String> map = new HashMap<>();
            bindingResult.getFieldErrors().forEach( (item) -> {
                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();
                }
    }

}

二、异常的统一处理

难道在每个方法都写这么一大堆的异常结果响应处理吗,需要这么麻烦了?NO,参数校验不通过时,会抛出 BindingException 异常,可以在统一异常处理中,做统一处理,这样就不用在每个需要参数校验的地方都用 BindingResult 获取校验结果了。

做统一异常处理,可以使用SpringMVC提供的 @ControllerAdvice

系统错误码

1、在 gulimall-product 服务创建异常类文件:
product/exception/GulimallExceptionControllerAdvice.java

package com.atguigu.gulimall.product.exception;

import com.atguigu.common.exception.BizCodeEnum;
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;

/**
 * 集中处理所有异常
 *
 * @author: kaiyi
 * @create: 2020-08-21 00:56
 */

@Slf4j
//@ResponseBody
//@ControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
// 处理product.controller包下边的异常
@RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
public class GulimallExceptionControllerAdvice {

  @ExceptionHandler(value = MethodArgumentNotValidException.class)
  public R handleValidException(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(BizCodeEnum.VALID_EXCEPTION.getCode(),
        BizCodeEnum.VALID_EXCEPTION.getMsg()).put("data", errorMap);

  }

  /**
   * 通用的异常处理方法
   * @description 如果找不到具体的异常处理方法,则由该方法处理
   *
   * @param throwable
   * @return
   */
  @ExceptionHandler(value = Throwable.class)
  public R handleException(Throwable throwable){
    log.error("错误:", throwable);
    return R.error(BizCodeEnum.UNKNOW_EXCEPTION.getCode(), BizCodeEnum.UNKNOW_EXCEPTION.getMsg()); 
  }
}

2、在gulimall-common 服务创建通用的异常代码枚举类
由于以后随着业务的不断扩展,需要定义不同的异常码和规范标准化,所以,这里直接定义通用的异常枚举类。
gulimall-common/src/main/java/com/atguigu/common/exception/BizCodeEnum.java

package com.atguigu.common.exception;

/***
 * 错误码和错误信息定义类
 * 1. 错误码定义规则为5为数字
 * 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知异常
 * 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式
 * 错误码列表:
 *  10: 通用
 *      001:参数格式校验
 *  11: 商品
 *  12: 订单
 *  13: 购物车
 *  14: 物流
 *
 *
 */
public enum BizCodeEnum {
  UNKNOW_EXCEPTION(10000, "系统未知异常"),
  VALID_EXCEPTION(10001, "参数格式校验失败");

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

  public int getCode(){
    return code;
  }

  public String getMsg(){
    return msg;
  }
}

3、修改原方法的异常处理
product/controller/BrandController.java

 /**
     * 保存
     */
    @RequestMapping("/save")
    //@RequiresPermissions("product:brand:save")
    public R save(@Validated @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();
    }

通过统一异常处理之后,原来需要在方法里边写异常处理的方式现在不需要了,代码又恢复到原来简洁的样子。

4、Postman测试

file

使用POSTman进行测试,可以看到返回的异常结果非常规范,业务代码code也是我们定义的 10001,至此,统一封装完成。

为者常成,行者常至