Solon

solon.validation

<dependency>
    <groupId>org.noear</groupId>
    <artifactId>solon.validation</artifactId>
</dependency>

1、描述

基础扩展插件,为 solon 提供完整的数据校验能力。支持:

  • 动作注解校验
    • 支持 Context::paramMap 数据校验
    • 支持行为验证,比如重复提交(需要对接)
    • 支持身份验证,比如白名单(需要对接)
  • 参数注解校验
  • 实体注解校验

2、应用示例

//这个注解一定要加类上(或者基类上)
@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, //注解在参数或字段上时,不需要加 value 属性
            @Validated User user) //实体校验,需要加 @Validated
    { 
        //...
    }
}

@Data
public class User {
    @NotNull
    private String nickname;
    
    @Email  //注解在参数或字段上时,不需要加 value 属性
    private String email;
}

3、需要业务对接的注解能力

  • @NoRepeatSubmit 不能重复提交注解
//示例:通过组件模式定义检测器(或通过配置器生产Bean)
@Component
public class NoRepeatSubmitCheckerImpl implements NoRepeatSubmitChecker {
    @Override
    public boolean check(NoRepeatSubmit anno, Context ctx, String submitHash, int limitSeconds) {
        //借用分布式锁,挡住 submitHash 在一定时间内多次进入
        return LockUtils.tryLock(Solon.cfg().appName(), submitHash, limitSeconds);
    }
}
  • @Whitelist 白名单注解
//示例:通过组件模式定义检测器(或通过配置器生产Bean)
@Component
public class WhitelistCheckerImpl implements WhitelistChecker {
    @Override
    public boolean check(Whitelist anno, Context ctx) {
        String ip = ctx.realIp();

        //借用业务系统的名单列表,进行检测
        return CloudClient.list().inListOfIp("whitelist",ip);
    }
}
  • @NotBlacklist 非默名单注解
//示例:通过组件模式定义检测器(或通过配置器生产Bean)
@Component
public class NotBlacklistCheckerImpl implements NotBlacklistChecker {
    @Override
    public boolean check(NotBlacklist anno, Context ctx) {
        String ip = ctx.realIp();

        //借用业务系统的名单列表,进行检测
        return CloudClient.list().inListOfIp("blacklist",ip) == false;
    }
}
  • @Logined 已登录注解
//示例:通过组件模式定义检测器(或通过配置器生产Bean)
@Component
public class SsoLoginedChecker implements LoginedChecker {

    @Inject
    SsoService ssoService;

    @Override
    public boolean check(Logined anno, Context ctx, String userKeyName) {
        if (ctx.header(Attrs.h_token) == null) {
            return false;
        }

        long user_id = ctx.sessionAsLong(Constants.SESSION_USER_ID);
        String sso_key = ctx.session(Constants.SESSION_SSO_KEY,"");

        if (user_id == 0){
            return false;
        }


        //为单测增加支持(不然没法跑)
        if(Solon.cfg().isDebugMode() || Config.is_test()){
            return true;
        }

        if (Utils.isEmpty(sso_key)) {
            return false;
        }

        String sso_key0 = ssoService.getUserSsoKey(user_id);
        return sso_key.equals(sso_key0);
    }
}

4、可以定制的校验结果

//示例:通过组件模式定义检测器(或通过配置器生产Bean)
@Component
public class ValidatorFailureHandlerImpl implements ValidatorFailureHandler {

    @Override
    public boolean onFailure(Context ctx, Annotation anno, Result rst, String msg) throws Throwable {
        ctx.setHandled(true);

        if (rst.getCode() > 400 && rst.getCode() < 500) {
            ctx.status(rst.getCode());
        } else {
            ctx.status(400);
        }

        if (ctx.getRendered() == false) {

            if (Utils.isEmpty(msg)) {
                if (Utils.isEmpty(rst.getDescription())) {
                    msg = new StringBuilder(100)
                            .append("@")
                            .append(anno.annotationType().getSimpleName())
                            .append(" verification failed")
                            .toString();
                } else {
                    msg = new StringBuilder(100)
                            .append("@")
                            .append(anno.annotationType().getSimpleName())
                            .append(" verification failed: ")
                            .append(rst.getDescription())
                            .toString();
                }
            }

            ctx.render(Result.failure(rst.getCode(), msg));

        }

        return true;
    }
}