一:异常处理——JSR303
1.在product下新建exception包
2.在exception包下新建GulimailExceptionControllerAdvice类
3.在类上添加@ControllerAdvice注解
@ControllerAdvice(basePackages = "com.sysg.gulimail.product.controller")
- @ControllerAdvice:集中处理异常
- basePackages = “com.sysg.gulimail.product.controller”:表示在当前目录下的controller出现的异常都需要处理
4.编写handleValidException方法,用来接收所有数据校验异常
package com.sysg.gulimail.product.exception;
import com.sysg.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @author 77916
* @ControllerAdvice 集中处理异常
*/
@Slf4j
//@ControllerAdvice(basePackages = "com.sysg.gulimail.product.controller")
//@ResponseBody
@RestControllerAdvice(basePackages = "com.sysg.gulimail.product.controller")
public class GulimailExceptionControllerAdvice {
@ExceptionHandler(value = Exception.class)
public R handleValidException(Exception e){
log.error("数据校验出现问题{},异常类型:{}",e.getMessage(),e.getClass());
return R.error();
}
}
- @ExceptionHandler(value = Exception.class):用来标注所有异常都可以处理
- Exception e:用来接收从controller抛出的异常
- @Slf4j:此注解用来接收日志,记录日志
- @ResponseBody:将结果转化为字符串返回回去
- @RestControllerAdvice:就是@ResponseBody和@ControllerAdvice的合体
注:此时虽然获取到异常,但不知道具体是哪个字段的异常,所以我们需要完善方法
5.优化GulimailExceptionControllerAdvice类
1)完善数据异常处理方法handleValidException
@Slf4j
//@ControllerAdvice(basePackages = "com.sysg.gulimail.product.controller")
//@ResponseBody
@RestControllerAdvice(basePackages = "com.sysg.gulimail.product.controller")
public class GulimailExceptionControllerAdvice {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R handleValidException(MethodArgumentNotValidException e){
log.error("数据校验出现问题{},异常类型:{}",e.getMessage(),e.getClass());
//获取到具体的异常返回结果
BindingResult result = e.getBindingResult();
HashMap<String, String> errorMap = new HashMap<>(16);
//将错误信息遍历出来,获取错误字段和错误信息
result.getFieldErrors().forEach((fieldError)->{
//校验错误的字段
String field = fieldError.getField();
//校验错误的信息
String message = fieldError.getDefaultMessage();
errorMap.put(field, message);
});
return R.error(400,"数据校验出现问题").put("data",errorMap);
}
}
- MethodArgumentNotValidException:用来直接将具体那方面的异常获取到
- e.getBindingResult():将返回的错误结果获取到
- 当能精确匹配到异常类型的时候使用,MethodArgumentNotValidException
此时校验异常处理方法就可以正常返回错误信息了
2)新建其他异常处理方法handleException方法
6.在common项目下做状态码处理
1)在common下新建exception包
2)在exception下新建BizCodeEnume枚举,用来处理状态码
package com.sysg.common.exception;
/**
* 状态码枚举
* 错误码和错误信息定义类
* 1. 错误码定义规则为 5 为数字
* 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知 异常
* 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式
* 错误码列表:
* 10: 通用
* 001:参数格式校验
* 11: 商品
* 12: 订单
* 13: 购物车
* 14: 物流
* @author 77916
*/
public enum BizCodeEnume {
//参数校验
VALID_EXCEPTION(10001,"参数格式校验失败"),
//系统未知异常
UNKNOW_EXCEPTION(10000,"参数未知异常");
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;
}
}
1)修改GulimailExceptionControllerAdvice的handleValidException方法,其返回的状态码用枚举获取。
/**
* 用来处理数据校验异常
* 当能精确匹配到异常类型的时候使用
* @param e
* @return
*/
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R handleValidException(MethodArgumentNotValidException e){
log.error("数据校验出现问题{},异常类型:{}",e.getMessage(),e.getClass());
//获取到具体的异常返回结果
BindingResult result = e.getBindingResult();
HashMap<String, String> errorMap = new HashMap<>(16);
//将错误信息遍历出来,获取错误字段和错误信息
result.getFieldErrors().forEach((fieldError)->{
//校验错误的字段
String field = fieldError.getField();
//校验错误的信息
String message = fieldError.getDefaultMessage();
errorMap.put(field, message);
});
return R.error(BizCodeEnume.VALID_EXCEPTION.getCode(),BizCodeEnume.VALID_EXCEPTION.getMsg()).put("data",errorMap);
}
2)修改GulimailExceptionControllerAdvice的handleException方法,其返回的状态码用枚举获取。
/**
* 用来处理其他所有异常
* @param throwable
* @return
*/
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable throwable){
return R.error(BizCodeEnume.UNKNOW_EXCEPTION.getCode(),BizCodeEnume.UNKNOW_EXCEPTION.getMsg());
}
二:分组校验——多场景的复杂校验
使用场景:新增和修改的时候传递的参数不一样,校验的时候有所区别
1.在common下新建valid包
1)新建AddGroup接口,用来处理新增时候的业务字段校验
2)新建UpdateGroup接口,用来处理编辑时候的业务字段校验
2.groups——注解上添加什么时候需要什么注解校验
/**
* 品牌id
*/
@Null(message = "新增不能指定id",groups = {AddGroup.class})
@NotNull(message = "修改必须指定id",groups = {UpdateGroup.class})
@TableId
private Long brandId;
/**
* 品牌名
*/
@NotBlank(message="品牌名必须提交",groups = {AddGroup.class,UpdateGroup.class})
private String name;
- 使用groups用来标注哪些字段需要分组处理,给注解上添加什么时候需要什么注解校验
- 指定其在那种情况下触发
3.@Validated——指定我们要校验那一组
1)在controller的保存方法上添加**@Validated({AddGroup.class})**表示只有在保存方法的时候会触发校验
/**
* 保存
*/
@RequestMapping("/save")
//@RequiresPermissions("product:brand:save")
public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}
2)更新方法同样如此
/**
* 修改
*/
@RequestMapping("/update")
//@RequiresPermissions("product:brand:update")
public R update(@Validated({UpdateGroup.class}) @RequestBody BrandEntity brand){
brandService.updateById(brand);
return R.ok();
}
3)当没有添加分组校验的注解,默认是不起作用的
/**
* 品牌logo地址
* @URL 必须是一个url地址
*/
@NotBlank(groups = {AddGroup.class})
@URL(message="logo必须是一个合法的url地址",groups = {AddGroup.class,UpdateGroup.class})
private String logo;
- @NotBlank(groups = {AddGroup.class}):url新增的时候不能为空,且必须符合url的校验。
- @URL(message=“logo必须是一个合法的url地址”,groups = {AddGroup.class,UpdateGroup.class}):新增的时候不可以为空,且必须符合url校验。修改的时候可以为空,但不为空的时候一定要符合url校验。
4)其他字段处理方法类似
package com.sysg.gulimail.product.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import com.sysg.common.valid.AddGroup;
import com.sysg.common.valid.UpdateGroup;
import lombok.Data;
import org.hibernate.validator.constraints.URL;
import javax.validation.constraints.*;
/**
* 品牌
*
* @author sysg
* @email sunlightcs@gmail.com
* @date 2022-03-03 16:15:44
*/
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 品牌id
*/
@Null(message = "新增不能指定id",groups = {AddGroup.class})
@NotNull(message = "修改必须指定id",groups = {UpdateGroup.class})
@TableId
private Long brandId;
/**
* 品牌名
*/
@NotBlank(message="品牌名必须提交",groups = {AddGroup.class,UpdateGroup.class})
private String name;
/**
* 品牌logo地址
* @URL 必须是一个url地址
*/
@NotBlank(groups = {AddGroup.class})
@URL(message="logo必须是一个合法的url地址",groups = {AddGroup.class,UpdateGroup.class})
private String logo;
/**
* 介绍
*/
private String descript;
/**
* 显示状态[0-不显示;1-显示]
*/
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;
}
三:自定义校验注解
1.编写自定义校验注解
1)自定义注解JSR303必须拥有三个属性
- message,信息以及默认去哪里取,一般是校验注解的全类名
- groups,要支持分组校验功能
- payload,负载信息
2)标写源信息数据
- @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }):表明注解可以标注在什么位置,方法上,类上,属性,参数,构造器等
- @Retention(RUNTIME):校验注解在什么时候生效,在运行时生效
- @Constraint(validatedBy = { }):校验注解通过哪个校验器进行校验,在validatedBy里面指定校验器
3)在common的resource目录下新建ValidationMessages.properties配置文件
作用:用来取com.sysg.common.valid.ListValue.message的值,配置错误消息
com.sysg.common.valid.ListValue.message=必须提交指定的值
4)指定注解的values,可以是一个数组
int[] vals() default { };
2.编写自定义校验器——ConstraintValidator
1)在valid包下新建ListValueConstraintValidator接口
2)实现ConstraintValidator<ListValue,Integer>接口
- ListValue表示需要校验的注解
- Integer表示传入字段的类型
3)实现ConstraintValidator接口自带的方法
private Set<Integer> set = new HashSet<>();
/**
* 初始化方法
* @param constraintAnnotation
*/
@Override
public void initialize(ListValue constraintAnnotation) {
int[] vals = constraintAnnotation.vals();
for (int i = 0; i < vals.length; i++) {
set.add(vals[i]);
}
}
- initialize的参数ListValue ,可以获取到添加ListValue注解的详细信息
- 将获取到的值,遍历以后封装到set集合当中,这个值就是@ListValue(vals={0,1},groups = {AddGroup.class})的0和1
/**
* 判断是否校验成功
* @param value 需要校验的值
* @param context 校验的上下文环境信息
* @return
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
- value 需要校验的值
- context 校验的上下文环境信息
- 总结:将获取到的值和注上定义的值进行比较,然后返回
package com.sysg.common.valid;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;
/**
* @author 77916
*/
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
private Set<Integer> set = new HashSet<>();
/**
* 初始化方法
* @param constraintAnnotation
*/
@Override
public void initialize(ListValue constraintAnnotation) {
int[] vals = constraintAnnotation.vals();
for (int i = 0; i < vals.length; i++) {
set.add(vals[i]);
}
}
/**
* 判断是否校验成功
* @param value 需要校验的值
* @param context 校验的上下文环境信息
* @return
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}
3.关联自定义校验器和自定义校验注解
package com.sysg.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.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* @author 77916
*/
@Documented
@Constraint(validatedBy = {ListValueConstraintValidator.class})
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
String message() default "{com.sysg.common.valid.ListValue.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
int[] vals() default { };
}
1)在ListValue注解里面,通过@Constraint注解声明给什么东西进行校验
@Constraint(validatedBy = {ListValueConstraintValidator.class})
2)@Constraint也可以指定多个不同的校验器进行校验,用于针对传递的参数类型有变化时。