• Solon Web 开发,八、校验、及定制与扩展


    在业务的实现过程中,尤其是对外接口开发,我们需要对请求进行大量的验证并返回错误状态码和描述。在前面的内容中也已经使用过验证机制。

    该文将介绍 solon.validation 框架的使用和扩展。效果如下:

    //这个注解一定要加类上(或者基类上)
    @Valid
    @Controller
    public class UserController {
        @NoRepeatSubmit  //重复提交验证
        @Whitelist     //白名单验证
        @NotNull({"name", "mobile", "icon", "code"})  //非NULL验证
        @Numeric({"code"})
        @Mapping("/user/add")
        public void addUser(String name, @Pattern("^http") String icon, @Validated User user){
            //...
        }
    }
    
    @Data
    public class User {
        @NotNull
        private String nickname;
        
        @Email
        private String email;
    }
    
    

    Solon 的校验框架,可支持Context的参数较验(即请求传入的参数),也可支持实体字段较验。

    注解 作用范围 说明
    Date 参数 或 字段 校验注解的值为日期格式
    DecimalMax(value) 参数 或 字段 校验注解的值小于等于@ DecimalMax指定的value值
    DecimalMin(value) 参数 或 字段 校验注解的值大于等于@ DecimalMin指定的value值
    Email 参数 或 字段 校验注解的值为电子邮箱格式
    Length(min, max) 参数 或 字段 校验注解的值长度在min和max区间内(对字符串有效)
    Logined 控制器 或 动作 校验本次请求主体已登录
    Max(value) 参数 或 字段 校验注解的值小于等于@Max指定的value值
    Min(value) 参数 或 字段 校验注解的值大于等于@Min指定的value值
    NoRepeatSubmit 控制器 或 动作 校验本次请求没有重复提交
    NotBlacklist 控制器 或 动作 校验本次请求主体不在黑名单
    NotBlank 动作 或 参数 或 字段 校验注解的值不是空白
    NotEmpty 动作 或 参数 或 字段 校验注解的值不是空
    NotNull 动作 或 参数 或 字段 校验注解的值不是null
    NotZero 动作 或 参数 或 字段 校验注解的值不是0
    Null 动作 或 参数 或 字段 校验注解的值是null
    Numeric 动作 或 参数 或 字段 校验注解的值为数字格式
    Pattern(value) 参数 或 字段 校验注解的值与指定的正则表达式匹配
    Size 参数 或 字段 校验注解的集合大小在min和max区间内(对集合有效)
    Whitelist 控制器 或 动作 校验本次请求主体在白名单范围内

    可作用在 [动作 或 参数] 上的注解,加在动作上时可支持多个参数的校验。

    1、开始定制使用

    solon.validation 通过 ValidatorManager,提供了一组定制和扩展接口。

    @NoRepeatSubmit 改为分布式锁验证

    NoRepeatSubmit 默认使用了本地延时锁。如果是分布式环境,需要定制为分布式锁:

    public class NoRepeatSubmitCheckerNew implements NoRepeatSubmitChecker {
        @Override
        public boolean check(String key, int seconds) {
            //使用分布式锁
            //
            return LockUtils.tryLock(XWaterAdapter.global().service_name(), key, seconds);
        }
    }
    
    ValidatorManager.setNoRepeatSubmitChecker(new NoRepeatSubmitCheckerNew());
    

    或者 完全重写 NoRepeatSubmitValidator,并进行重新注册

    @Whitelist 实现验证

    框架层面没办法为 Whitelist 提供一个名单库,所以需要通过一个接口实现完成对接。

    public class WhitelistCheckerNew implements WhitelistChecker {
        @Override
        public boolean check(Whitelist anno, Context ctx) {
            String ip = IPUtils.getIP(ctx);
    
            return WaterClient.Whitelist.existsOfServerIp(ip);
        }
    }
    
    ValidatorManager.setWhitelistChecker(new WhitelistCheckerNew());
    

    或者 完全重写 WhitelistValidator,并进行重新注册

    改造校验输出

    solon.validation 默认输出 http 400 状态 + json;尝试改改去掉 http 400 状态。

    @Configuration
    public class Config {
        @Bean  //Solon 的 @Bean 也支持空函数,用于一些初始化动作
        public void validInit() {
            ValidatorManager.setFailureHandler((ctx, ano, rst, message) -> {
                ctx.setHandled(true);
                ctx.setRendered(true);
            
                if (Utils.isEmpty(message)) {
                    message = new StringBuilder(100)
                            .append("@")
                            .append(ano.annotationType().getSimpleName())
                            .append(" verification failed")
                            .toString();
                }
            
                ctx.output(message);
            
                return true;
            });
        }
    }
    

    2、尝试添一个扩展注解

    先定义个校验注解 @Date

    偷懒一下,直接把自带的扔出来了。只要看这过程后,能自己搞就行了:-P

    @Target({ElementType.PARAMETER})   //只让它作用到参数,不管作用在哪,最终都是对Context的校验
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Date {
        @Note("日期表达式, 默认为:ISO格式")  //用Note注解,是为了用时还能看到这个注释
        String value() default  "";
    
        String message() default "";
    }
    

    添加 @Date 的校验器实现类

    public class DateValidator implements Validator<Date> {
        public static final DateValidator instance = new DateValidator();
    
        @Override
        public String message(Date anno) {
            return anno.message();
        }
    
        /**
         * 校验实体的字段
         * */
        @Override
        public Result validateOfEntity(Class<?> clz, Date anno, String name, Object val0, StringBuilder tmp) {
            if (val0 != null && val0 instanceof String == false) {
                return Result.failure(clz.getSimpleName() + "." + name);
            }
    
            String val = (String) val0;
    
            if (verify(anno, val) == false) {
                return Result.failure(clz.getSimpleName() + "." + name);
            } else {
                return Result.succeed();
            }
        }
    
        /**
         * 校验上下文的参数
         * */
        @Override
        public Result validateOfContext(Context ctx, Date anno, String name, StringBuilder tmp) {
            String val = ctx.param(name);
    
            if (verify(anno, val) == false) {
                return Result.failure(name);
            } else {
                return Result.succeed();
            }
        }
    
        private boolean verify(Date anno, String val) {
            //如果为空,算通过(交由@NotEmpty之类,进一步控制)
            if (Utils.isEmpty(val)) {
                return true;
            }
    
            try {
                if (Utils.isEmpty(anno.value())) {
                    DateTimeFormatter.ISO_LOCAL_DATE_TIME.parse(val);
                } else {
                    DateTimeFormatter.ofPattern(anno.value()).parse(val);
                }
    
                return true;
            } catch (Exception ex) {
                return false;
            }
        }
    }
    

    注册到校验管理器

    @Configuration
    public class Config {
        @Bean
        public void adapter() {
            //
            // 此处为注册验证器。如果有些验证器重写了,也是在此处注册
            //
            ValidatorManager.register(Date.class, new DateValidator());
        }
    }
    

    可以使用它了

    @Valid
    @Controller
    public class UserController extends VerifyController{
        @Mapping("/user/add")
        public void addUser(String name, @Date("yyyy-MM-dd") String birthday){
            //...
        }
    }
    
  • 相关阅读:
    javascript HTML DOM
    js 同步异步阻塞非阻塞非原创
    端口规范
    SASS用法指南
    整合js,css文件
    HTTP状态码大全
    控制移动端页面的缩放(meta)
    移动端最小以及最大的宽度
    H5手机端关注的问题
    javascript高级编程3第三章:基本概念 本章内容 语法 数据类型 流控制语句 函数
  • 原文地址:https://www.cnblogs.com/noear/p/15820806.html
Copyright © 2020-2023  润新知