Solon 框架詳解(六)- Solon的校驗框架使用、定制與擴展


Solon 詳解系列文章:
Solon 框架詳解(一)- 快速入門
Solon 框架詳解(二)- Solon的核心
Solon 框架詳解(三)- Solon的web開發
Solon 框架詳解(四)- Solon的事務傳播機制
Solon 框架詳解(五)- Solon擴展機制之Solon Plugin
Solon 框架詳解(六)- Solon的校驗框架使用、定制與擴展
Solon 框架詳解(七)- Solon Ioc 的注解對比Spring及JSR330
Solon 框架詳解(八)- Solon的緩存框架使用和定制
Solon 框架詳解(九)- 渲染控制之定制統一的接口輸出
Solon 框架詳解(十)- Solon 的常用配置
Solon 框架詳解(十一)- Solon Cloud 的配置說明

在業務的實現過程中,尤其是對外接口開發,我們需要對請求進行大量的驗證並返回錯誤狀態碼和描述。lombok 框架有很多很贊的注解,但是人家是throw一個異常,這與有些需求不一定能匹配。

該文將介紹Solon的擴展驗證框架:solon.validation 的使用和擴展( org.noear:solon-web 已包含)。效果如下:

@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;
}

相較於 Spring 的 Validator 是爭對 Bean,Solon 則是爭對 Context(即http參數)。這點區別非常大,Solon 的設計是在 Action 執行之前對 http 參數進行校驗。

注解 作用范圍 說明
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) 參數 或 字段 校驗注解的集合長度在min和max區間內
Whitelist 控制器 或 動作 校驗本次請求在白名單范圍內
Validated 參數 或 字段 為實體參數 或 實體字段啟用驗證能力
Valid 控制器 或 動作 為控制器 或 動作啟用驗證能力

可作用在 [動作 或 參數] 上的注解,加在動作上時可支持多個參數的校驗。

一、定制使用

solon.extend.validation 通過 ValidatorManager,提供了一組定制和擴展接口。

1、@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,並進行重新注冊

2、@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,並進行重新注冊

3、改造校驗輸出

solon.extend.validation 默認輸出 http 400 狀態 + json;嘗試改改去掉 http 400 狀態。

@Configuration
public class Config {
    @Bean  //Solon 的 @Bean 也支持空函數,為其它提運行申明
    public void adapter() {
        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;
        });
    }
}

二、添一個擴展注解

1、先定義個校驗注解 @Date

偷懶一下,直接把自帶的扔出來了。只要看着能自己搞就行了:-P

@Target({ElementType.PARAMETER})   //只讓它作用到參數,不管作用在哪,最終都是對Context的校驗
@Retention(RetentionPolicy.RUNTIME)
public @interface Date {
    @Note("日期表達式, 默認為:ISO格式")  //用Note注解,是為了用時還能看到這個注釋
    String value() default  "";

    String message() default "";
}

2、添加 @Date 的校驗器實現類

public class DateValidator implements Validator<Date> {
    @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 instanceof String == false) {
            return Result.failure(clz.getSimpleName() + "." + name);
        }

        String val = (String) val0;

        if (val == null || 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 (val == null || verify(anno, val) == false) {
            return Result.failure(name);
        } else {
            return Result.succeed();
        }
    }

    private boolean verify(Date anno, String val) {
        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;
        }
    }
}

3、注冊到校驗管理器

@Configuration
public class Config {
    @Bean
    public void adapter() {
        //
        // 此處為注冊驗證器。如果有些驗證器重寫了,也是在此處注冊
        //
        ValidatorManager.register(Date.class, new DateValidator());
    }
}

4、使用一下

@Valid
@Controller
public class UserController extends VerifyController{
    @Mapping("/user/add")
    public void addUser(String name, @Date("yyyy-MM-dd") String birthday){
        //...
    }
}

附:Solon項目地址


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM