返回顶部
首页 > 资讯 > 后端开发 > Python >手写一个@Valid字段校验器的示例代码
  • 691
分享到

手写一个@Valid字段校验器的示例代码

2024-04-02 19:04:59 691人浏览 安东尼

Python 官方文档:入门教程 => 点击学习

摘要

上次给大家讲述了 SpringBoot 中的 @Valid 注解 和 @Validated 注解的详细用法: 详解spring中@Valid和@Validated注解用法 当我们用上

上次给大家讲述了 SpringBoot 中的 @Valid 注解 和 @Validated 注解的详细用法:

详解spring中@Valid和@Validated注解用法

当我们用上面这两个注解的时候,需要首先在对应的字段上打上规则注解,类似如下。

@Data
public class Employee {
 
    
    @NotBlank(message = "请输入名称")
    @Length(message = "名称不能超过个 {max} 字符", max = 10)
    public String name;
 
    
    @NotNull(message = "请输入年龄")
    @Range(message = "年龄范围为 {min} 到 {max} 之间", min = 1, max = 100)
    public Integer age;
 
}

其实,在使用这些规则注解时,我觉得不够好用,比如我列举几个点:

(1)针对每个字段时,如果有多个校验规则,需要打多个对应的规则注解,这时看上去,就会显得较为臃肿。

(2)某些字段的类型根本不能校验,比如在校验 Double 类型的字段规则时,打上任何校验注解,都会提示报错,说不支持 Double 类型的数据;

(3)每打一个规则注解时,都需要写上对应的 message 提示信息,这不但使得写起来麻烦,而且代码看起来又不雅观,按理说,我们的一类规则提示应该都是相同的,比如 "xxx不能为空",所以,按理来说,我只要配置一次提示格式,就可以不用再写了,只需要配置每个字段的名称xxx即可。

(4)一般来说,我们通常进行字段校验时,可能还需要一些额外的数据处理,比如去掉字符串前后的空格,某些数据可以为空的时候,我们还可以设置默认值这些等。

(5)不能进行扩展,如果时自己写的校验器,还可以进行需求扩展。

(6)他们再进行校验的时候,都需要再方法参数上打上一个 @Valid 注解或者 @Validate 注解,如果我们采用 aop 去切所有 controller 中的方法的话,那么我们写的自定义规则校验器,甚至连方法参数注解都可以不用打,是不是又更加简洁了呢。

于是,介于上述点,写了一个自定义注解校验器,包括下面几个文件:

Valid

这个注解作用于字段上,用于规则校验。

package com.zyq.utils.valid;
 
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Valid {
 
    
    String name() default "";
 
    
    boolean required() default true;
 
    
    String defaultValue() default "";
 
    
    boolean trim() default true;
 
    
    int minLength() default 0;
 
    
    int maxLength() default 255;
 
    
    String regex() default "";
 
    
    String min() default "";
 
    
    String max() default "";
 
}

ValidUtils

自定义规则校验工具

package com.zyq.utils.valid;
 
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.regex.Pattern;
 
 

public class ValidUtils {
 
    
    public static <T> String getMsg(T obj) {
        List<String> msgList = getMsgList(obj);
        return msgList.isEmpty() ? null : msgList.get(0);
    }
 
    
    public static <T> List<String> getMsgList(T obj) {
        if (Objects.isNull(obj)) {
            return Collections.emptyList();
        }
        Field[] fields = obj.getClass().getDeclaredFields();
        if (fields.length == 0) {
            return Collections.emptyList();
        }
        List<String> msgList = new ArrayList<>();
        for (Field field : fields) {
            // 没有打校验注解的字段则不进行校验
            Valid valid = field.getAnnotation(Valid.class);
            if (Objects.isNull(valid)) {
                continue;
            }
            field.setAccessible(true);
            // String 类型字段校验
            if (field.getType().isAssignableFrom(String.class)) {
                String msg = validString(obj, field, valid);
                if (Objects.nonNull(msg)) {
                    msgList.add(msg);
                }
                continue;
            }
            // int / Integer 类型字符校验
            String typeName = field.getType().getTypeName();
            if (field.getType().isAssignableFrom(Integer.class) || "int".equals(typeName)) {
                String msg = validInteger(obj, field, valid);
                if (Objects.nonNull(msg)) {
                    msgList.add(msg);
                }
                continue;
            }
            // double/Double 类型字段校验
            if (field.getType().isAssignableFrom(Double.class) || "double".equals(typeName)) {
                String msg = validDouble(obj, field, valid);
                if (Objects.nonNull(msg)) {
                    msgList.add(msg);
                }
                continue;
            }
        }
        return msgList;
    }
 
    
    private static <T> String validString(T obj, Field field, Valid valid) {
        // 获取属性名称
        String name = getFieldName(field, valid);
        // 获取原值
        Object v = getValue(obj, field);
        String val = Objects.isNull(v) ? "" : v.toString();
        // 是否需要去掉前后空格
        boolean trim = valid.trim();
        if (trim) {
            val = val.trim();
        }
        // 是否必填
        boolean required = valid.required();
        if (required && val.isEmpty()) {
            return requiredMsg(name);
        }
        // 是否有默认值
        if (val.isEmpty()) {
            val = isDefaultNull(valid) ? null : valid.defaultValue();
        }
        // 最小长度校验
        int length = 0;
        if (Objects.nonNull(val)) {
            length = val.length();
        }
        if (length < valid.minLength()) {
            return minLengthMsg(name, valid);
        }
        // 最大长度校验
        if (length > valid.maxLength()) {
            return maxLengthMsg(name, valid);
        }
        // 正则判断
        if (!valid.regex().isEmpty()) {
            boolean isMatch = Pattern.matches(valid.regex(), val);
            if (!isMatch) {
                return regexMsg(name);
            }
        }
        // 将值重新写入原字段中
        setValue(obj, field, val);
        // 如果所有校验通过后,则返回null
        return null;
    }
 
    private static <T> String validInteger(T obj, Field field, Valid valid) {
        // 获取属性名称
        String name = getFieldName(field, valid);
        // 获取原值
        Object v = getValue(obj, field);
        Integer val = Objects.isNull(v) ? null : (Integer) v;
        // 是否必填
        boolean required = valid.required();
        if (required && Objects.isNull(val)) {
            return requiredMsg(name);
        }
        // 是否有默认值
        if (Objects.isNull(val)) {
            boolean defaultNull = isDefaultNull(valid);
            if (!defaultNull) {
                val = parseInt(valid.defaultValue());
            }
        }
        // 校验最小值
        if (!valid.min().isEmpty() && Objects.nonNull(val)) {
            int min = parseInt(valid.min());
            if (val < min) {
                return minMsg(name, valid);
            }
        }
        // 校验最大值
        if (!valid.max().isEmpty() && Objects.nonNull(val)) {
            int max = parseInt(valid.max());
            if (val > max) {
                return maxMsg(name, valid);
            }
        }
        // 将值重新写入原字段中
        setValue(obj, field, val);
        // 如果所有校验通过后,则返回null
        return null;
    }
 
    private static <T> String validDouble(T obj, Field field, Valid valid) {
        return null;
    }
 
    
    private static <T> Object getValue(T obj, Field field) {
        try {
            return field.get(obj);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            return null;
        }
    }
 
    
    private static <T> void setValue(T obj, Field field, Object val) {
        try {
            field.set(obj, val);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
 
    
    private static String getFieldName(Field field, Valid valid) {
        return valid.name().isEmpty() ? field.getName() : valid.name();
    }
 
    
    private static boolean isDefaultNull(Valid valid) {
        return "null".equals(valid.defaultValue());
    }
 
    
    private static String msg(String name, String msg) {
        return "【" + name + "】" + msg;
    }
 
    
    private static String requiredMsg(String name) {
        return msg(name, "不能为空");
    }
 
    
    private static String minLengthMsg(String name, Valid valid) {
        return msg(name, "不能少于" + valid.minLength() + "个字符");
    }
 
    
    private static String maxLengthMsg(String name, Valid valid) {
        return msg(name, "不能超过" + valid.maxLength() + "个字符");
    }
 
    
    private static String regexMsg(String name) {
        return msg(name, "填写格式不正确");
    }
 
    
    private static String minMsg(String name, Valid valid) {
        return msg(name, "不能小于" + valid.min());
    }
 
    
    private static String maxMsg(String name, Valid valid) {
        return msg(name, "不能大于" + valid.max());
    }
 
    
    private static int parseInt(String intStr) {
        try {
            return Integer.valueOf(intStr);
        } catch (NumberFORMatException e) {
            return 0;
        }
    }
}

ValidAop

这是一个 controller 拦截切面,写了这个,就不用再 controller 方法参数上打上类似于原@Valid 和 @Validate 注解,还原的方法参数的原始整洁度。

但需要注意的是:类中 controller 的路径需要替换为你的包路径(我这里 controller 包路径为com.zyq.controller)。

package com.zyq.aop;
 
import com.alibaba.fastJSON.jsON;
import com.alibaba.fastjson.JSONObject;
import com.unisoc.outsource.config.global.ValidException;
import com.unisoc.outsource.utils.valid.ValidUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.WEB.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
 
import javax.servlet.Http.httpservletRequest;
import java.util.Map;
import java.util.Objects;
 

@Aspect
@Component
public class ValidAop {
 
    private static final String APPLICATION_JSON = "application/json";
 
    // 这里为你的 controller 包路径
    @Pointcut("execution(* com.zyqok.controller.*Controller.*(..))")
    public void pointCut() {
    }
 
    @Before("pointCut()")
    public void doBefore(JoinPoint jp) throws ValidException {
        // 获取所有请求对象
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 获取请求类型
        String contentType = request.getHeader("Content-Type");
        String json = null;
        if (contentType != null && contentType.startsWith(APPLICATION_JSON)) {
            // JSON请求体
            json = JSON.toJSONString(jp.getArgs()[0]);
        } else {
            // 键值对参数
            json = getParams(request);
        }
        // 获取请求类对象
        String validClassName = getParamClassName(jp);
        String msg = valid(json, validClassName);
        if (!isEmpty(msg)) {
            throw new ValidException(msg);
        }
    }
 
    
    private String getParamClassName(JoinPoint jp) {
        // 获取参数对象
        MethodSignature signature = (MethodSignature) jp.getSignature();
        Class<?>[] types = signature.getParameterTypes();
        // 没有参数则不进行校验
        if (types == null || types.length == 0) {
            return null;
        }
        // 返回项目中的对象类名
        for (Class<?> clazz : types) {
            if (clazz.getName().startsWith("com.unisoc.outsource")) {
                return clazz.getName();
            }
        }
        return null;
    }
 
    
    private String getParams(HttpServletRequest request) {
        Map<String, String[]> parameterMap = request.getParameterMap();
        if (Objects.isNull(parameterMap) || parameterMap.isEmpty()) {
            return "{}";
        }
        JSONObject obj = new JSONObject();
        parameterMap.forEach((k, v) -> {
            if (Objects.nonNull(v) && v.length == 1) {
                obj.put(k, v[0]);
            } else {
                obj.put(k, v);
            }
        });
        return obj.toString();
    }
 
    
    private String valid(String json, String className) {
        if (isEmpty(className)) {
            return null;
        }
        System.out.println("json : " + json);
        System.out.println("className : " + className);
        try {
            Class<?> clazz = Class.forName(className);
            Object o = JSON.parseObject(json, clazz);
            return ValidUtils.getMsg(o);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
 
    
    private boolean isEmpty(String s) {
        return Objects.isNull(s) || s.trim().isEmpty();
    }
}

ValidException

因为 AOP 切面里,不能在前置切面中直接返回校验规则的错误提示,所以我们可以采用抛异常的方式,最后对异常进行捕捉,再提示给用户(原 Springboot 的 @Validate 也是采用类似方式进行处理)。

package com.zyq.valid;
 

public class ValidException extends RuntimeException {
 
    private String msg;
 
    public String getMsg() {
        return msg;
    }
 
    public void setMsg(String msg) {
        this.msg = msg;
    }
 
    public ValidException(String msg) {
        this.msg = msg;
    }
}

ValidExceptionHandler

这个异常处理器就是用于捕捉上面的异常,最后提示给前端

@ControllerAdvice
@ResponseBody
public class ValidExceptionHandler {
 
    @ExceptionHandler(ValidException.class)
    public Map<String, String> validExceptionHandler(ValidException ex) {
        Map<String, String> map = new HashMap();
        map.put("code", 1);
        map.put("msg", ex.getMsg());
        return map;
    }
 
}

当把所有文件复制到文件中后,那么在使用的时候

只需要将方法中的参数打上我们定义的 @Valid 即可,其余不用做任何操作就OK


@Data
public class EntryApplyCancelReq {
 
    @Valid
    private Integer id;
 
    @Valid(name = "取消原因", maxLength = 50)
    private String reason;
 
}

到此这篇关于手写一个@Valid字段校验器的示例代码的文章就介绍到这了,更多相关@Valid字段校验器内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: 手写一个@Valid字段校验器的示例代码

本文链接: https://lsjlt.com/news/164906.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作