Solon詳解系列文章:
Solon詳解(一)- 快速入門
Solon詳解(二)- Solon的核心
Solon詳解(三)- Solon的web開發
Solon詳解(四)- Solon的事務傳播機制
Solon詳解(五)- Solon擴充套件機制之Solon Plugin
Solon詳解(六)- Solon的校驗擴充套件框架使用與擴充套件
在業務的實現過程中,尤其是對外介面開發,我們需要對請求進行大量的驗證並返回錯誤狀態碼和描述。lombok 框架有很多很讚的註解,但是人家是throw一個異常,這與有些需求不一定能匹配。
該文將介紹Solon的擴充套件驗證框架:solon.extend.validation
的使用和擴充套件( org.noear:solon-web
已包含)。效果如下:
@XValid
@XController
public class UserController {
@NoRepeatSubmit //重複提交驗證
@Whitelist //白名單驗證
@NotNull({"name", "mobile", "icon", "code"}) //非NULL驗證
@Numeric({"code"})
@XMapping("/user/add")
public void addUser(String name, @Pattern("^http") String icon, int code, @Pattern("^13\\d{9}$") String mobile){
//...
}
}
相較於 Spring 的 Validator 是爭對 Bean,Solon 則是爭對 XContext(即http引數)。這點區別非常大,Solon 是在 XAction 執行之前對 http 引數進行校驗。
註解 | 作用範圍 | 說明 |
---|---|---|
Date | 引數 | 校驗註解的引數值為日期格式 |
DecimalMax(value) | 引數 | 校驗註解的引數值小於等於@ DecimalMax指定的value值 |
DecimalMin(value) | 引數 | 校驗註解的引數值大於等於@ DecimalMin指定的value值 |
引數 | 校驗註解的引數值為電子郵箱格式 | |
Length(min, max) | 引數 | 校驗註解的引數值長度在min和max區間內 |
Max(value) | 引數 | 校驗註解的引數值小於等於@Max指定的value值 |
Min(value) | 引數 | 校驗註解的引數值大於等於@Min指定的value值 |
NoRepeatSubmit | 控制器 或 動作 | 校驗本次請求沒有重複 |
NotBlank | 動作 或 引數 | 校驗註解的引數值不是空白 |
NotEmpty | 動作 或 引數 | 校驗註解的引數值不是空 |
NotNull | 動作 或 引數 | 校驗註解的引數值不是null |
NotZero | 動作 或 引數 | 校驗註解的引數值不是0 |
Null | 動作 或 引數 | 校驗註解的引數值是null |
Numeric | 動作 或 引數 | 校驗註解的引數值為數字格式 |
Pattern(value) | 引數 | 校驗註解的引數值與指定的正規表示式匹配 |
Whitelist | 控制器 或 動作 | 校驗本次請求在白名單範圍內 |
XValid | 控制器 或 動作 | 為控制器 或 動作啟用驗證能力 |
可作用在 [動作 或 引數] 上的註解,加在動作上時可支援多個引數的校驗。
一、定製使用
solon.extend.validation 通過 ValidatorManager,提供了一組定製和擴充套件介面。
1、@NoRepeatSubmit 改為分散式鎖
NoRepeatSubmit 預設使用了本地延時鎖。如果是分散式環境,需要定製為分散式鎖:
public class NoRepeatLockNew implements NoRepeatLock {
@Override
public boolean tryLock(String key, int seconds) {
//使用分散式鎖
//
return LockUtils.tryLock(XWaterAdapter.global().service_name(), key, seconds);
}
}
ValidatorManager.setNoRepeatLock(new NoRepeatLockNew());
2、@Whitelist 實現驗證
框架層面沒辦法為 Whitelist 提供一個名單庫,所以需要通過一個介面實現完成對接。
public class WhitelistCheckerNew implements WhitelistChecker {
@Override
public boolean check(Whitelist anno, XContext ctx) {
String ip = IPUtils.getIP(ctx);
return WaterClient.Whitelist.existsOfServerIp(ip);
}
}
ValidatorManager.setWhitelistChecker(new WhitelistCheckerNew());
3、改造校驗輸出
solon.extend.validation 預設輸出 http 400 狀態 + json;嘗試改改去掉 http 400 狀態。
@XConfiguration
public class Config {
@XBean //Solon 的 @XBean 也支援空函式,為其它提執行申明
public void adapter() {
ValidatorManager.globalSet(new ValidatorManager((ctx, ano, rst, message) -> {
ctx.setHandled(true);
if (XUtil.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}) //只讓它作用到引數,不管作用在哪,最終都是對XContext的校驗
@Retention(RetentionPolicy.RUNTIME)
public @interface Date {
@XNote("日期表示式, 預設為:ISO格式") //用XNote註解,是為了用時還能看到這個註釋
String value() default "";
String message() default "";
}
2、新增 @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 XResult validate(XContext ctx, Date anno, String name, StringBuilder tmp) {
String val = ctx.param(name);
if (val == null || tryParse(anno, val) == false) {
tmp.append(',').append(name);
}
if (tmp.length() > 1) {
return XResult.failure(tmp.substring(1));
} else {
return XResult.succeed();
}
}
private boolean tryParse(Date anno, String val) {
try {
if (XUtil.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、註冊到校驗管理器
@XConfiguration
public class Config {
@XBean
public void adapter() {
ValidatorManager.global().register(Date.class, DateValidator.instance);
}
}
4、使用一下
@XValid
@XController
public class UserController extends VerifyController{
@XMapping("/user/add")
public void addUser(String name, @Date("yyyy-MM-dd") String birthday){
//...
}
}