目录
一、实现目的
在编写接口的时候,通常会先对参数进行一次校验,这样业务逻辑代码就略显冗杂,如果可以把校验参数的代码进行统一管理,在方法或者属性上直接添加注解就可以实现参数的校验,就可以提升代码编写的效率。
二、实现原理
通过自定义注解给定参数需要满足的条件,然后通过面向切面编程的相关知识进行解析,对比参数和条件,抛出异常统一处理返回。
三、代码详情
1.自定义注解
(1)先定义一个注解用于aop识别切点,参数根据实际情况自定义
@Target({ElementType.PARAMETER,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PCheck {
boolean open() default true;
}
(2)然后定义一个注解用于写入对应条件(这里是对字符串的解析)
min() : 参数最小长度
max():参数最大长度
regex():正则表达式
info(): 参数名称
ifNull():是否允许为空
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface StrVal {
int min() default 0;
int max() default 25;
String regex() default "";
String info() default "参数";
//默认是不允许为null的
boolean ifNull() default false;
}
2.切面类
@Aspect
@Component
public class ParamsCheckAspect {
/***
* 定义Pcheck使用的方法为切入点
* @param: []
* @return: void
* @author: kevin
* @date: 2023/5/10 9:56
*/
@Pointcut("@annotation(com.xxx.PCheck)") // 定义切点
public void pointcut() {}
/***
* 生命周期 为方法执行前
* @param: [joinPoint]
* @return: void
* @author: kevin
* @date: 2023/5/10 9:56
*/
@Before("pointcut()")
public void before(JoinPoint joinPoint) throws Exception {
// 获取方法参数
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
// 对参数进行校验
PcheckUtil.validate(arg);
}
}
}
3.工具类(判断逻辑)
@Component
public class PcheckUtil {
/***
* 校验传过来的参数
* @param: [arg]
* @return: void
* @author: kevin
* @date: 2023/5/10 9:58
*/
public static void validate(Object arg) throws IllegalAccessException {
// 获取对象中所有的字段和方法
Field[] fields = arg.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(StrVal.class)){
//存在某个注解就进行对应的处理
StrVal annotation = field.getAnnotation(StrVal.class);
String regex = annotation.regex();
int max = annotation.max();
int min = annotation.min();
String info = annotation.info();
boolean ifNull = annotation.ifNull();
//设置属性可见性
field.setAccessible(true);
String value = (String) field.get(arg);
//先判断空
//如果可以为空 值也为空就不进行处理
if (ifNull && StringUtils.isBlank(value)){
continue;
}
if (!ifNull && StringUtils.isBlank(value)){
throw new ParamsException(info+":不能为空!");
}
//如果正则不匹配就直接返回
if (StringUtils.isNotBlank(regex)){
if (!value.matches(regex)){
throw new ParamsException(info+":格式不正确!");
}
}
//最后判断长度
if (value.length()<min || value.length()>max){
throw new ParamsException(info+":请将字符长度控制在"+min+"~"+max+"之间");
}
}
}
}
}
4.全局异常拦截
【1】自定义异常
@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
@Data
public class ParamsException extends RuntimeException {
private static final long serialVersionUID = 7060237606941777850L;
private String message; // 异常信息
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
【2】全局异常拦截
注:Result类是我自己封装的,可以自己写Map或者实体类返回
@ControllerAdvice
public class GlobalExceptionHandler {
/***
* 拦截参数错误返回
* @param: [e]
* @return: com.atomee.rbacdemo.common.Result
* @author: kevin
* @date: 2023/5/10 10:42
*/
@ExceptionHandler(ParamsException.class)
@ResponseBody
public Result handleMyException(ParamsException e) {
return Result.getResult(411,e.getMessage(),null);
}
}
5.注解使用
【1】controller
@PCheck
@PostMapping("/xxxx/cooks/add")
public Result addCooks(@RequestBody AddCooksParams addCooksParams){
return cooksService.addCooks(addCooksParams);
}
【2】实体类
注:我使用了lambok
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AddCooksParams implements Serializable {
private static final long serialVersionUID = 2145635852726787978L;
@StrVal(info = "菜品名称",max = 26)
private String name;
private String src;
@StrVal(info = "菜品详情",max = 250)
private String detail;
}
6.返回效果