写点什么

校验参数的 6 大神功!

  • 2025-05-16
    福建
  • 本文字数:3984 字

    阅读完需:约 13 分钟

新手司机翻车实录


"哥,注册接口又被刷爆了!


"某一个周末下午,我接到电话,打开日志一看,NullPointerException堆栈里有 38 个不同位置的校验逻辑。


原来新人小王在 Controller 里写满了这样的代码:


// 典型错误示范(转载自某小厂祖传代码)public String register(UserDTO user) {    if (user.getName() == null) {        return "名字不能为空";    }    if (user.getAge() == null) {        return "年龄不能为空";    }    if (user.getAge() < 18) {        return "年龄不能小于18岁";    }    if (!user.getPhone().matches("^1[3-9]\\d{9}$")) {        return "手机号不合法";    }    // ...后续还有20个if...}
复制代码


这才是代码界的"九转大肠"——每个入口都让人窒息。


作为一位有很多开发经验的老司机,今天,老夫带你修炼参数校验的 6 大神功。



希望对你会有所帮助。


第一重:JSR 规范基础功


1.1 HibernateValidator 瞬炼大法


可以使用 Hibernate 中 Validator 框架做参数校验,具体代码如下:


public class UserDTO {    @NotBlank(message = "名称要填,皮这一下很开心?")    private String name;
@NotNull @Min(value = 18, message = "未成年禁止入内") @Max(60) private Integer age;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "这手机号是哪国来的?") private String phone;}
// Controller层启用校验(新手必知第一步)@PostMapping("/register")public Result register(@Valid @RequestBody UserDTO user) { // 业务代码...}
复制代码


技术要点

  • 引入spring-boot-starter-validation依赖(调料包记得加)

  • @Valid注解要放在入参侧(别贴在 DTO 类上)

  • 错误信息会进BindingResult(打扫战场需要手动处理)


第二重:全局异常擒龙手


2.1 统一异常拦截器


我们需要对异常进行统一拦截。


这样在出现参数校验异常,比如空指针时,不会把服务的内部错误信息直接输出给用户。


通过 @RestControllerAdvice 和 @ExceptionHandler 注解实现统一异常拦截器的功能。


具体代码如下:


@RestControllerAdvicepublic class GlobalExceptionHandler {        // 专治各种不服校验    @ExceptionHandler(MethodArgumentNotValidException.class)    public Result handleValidException(MethodArgumentNotValidException e) {        BindingResult result = e.getBindingResult();        return Result.fail(result.getFieldError().getDefaultMessage());    }}
// 返回格式规范(示例)public class Result<T> { private Integer code; private String msg; private T data; public static <T> Result<T> fail(String message) { return new Result<>(500, message, null); }}
复制代码


反爬虫机制

  • 禁止直接暴露字段名给前端(攻击者会利用字段名信息)

  • 错误信息字典化管理(后面会教国际化这招)


第三重:自定义校验屠龙技


3.1 手机/邮箱二元校验


有时候,Hibernate Validator 框架或者其他校验框架定义的校验不满足需求,我们需要自定义校验规则。


则可以自定义注解,实现 ConstraintValidator 接口,来实现具体的自定义的校验逻辑。


自定义注解 @Contact 在字段上使用。


具体代码如下:


@Target({FIELD, PARAMETER})@Retention(RUNTIME)@Constraint(validatedBy = ContactValidator.class)public @interface Contact {    String message() default "联系方式格式错误";    Class<?>[] groups() default {};    Class<? extends Payload>[] payload() default {};}
// 校验逻辑实现(不要相信前端的下拉框!)public class ContactValidator implements ConstraintValidator<Contact, String> { private static final Pattern PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$"); private static final Pattern EMAIL_PATTERN = Pattern.compile("^\\w+@\\w+\\.\\w+$");
@Override public boolean isValid(String value, ConstraintValidatorContext context) { return PHONE_PATTERN.matcher(value).matches() || EMAIL_PATTERN.matcher(value).matches(); }}
复制代码


六边形战士培养计划

  • 可通过context.buildConstraintViolationWithTemplate()动态修改错误信息

  • 支持 DI 注入 Spring 管理的 Bean(比如从数据库加载正则)


第四重:分组校验北冥功


4.1 增删改查不同校验规则


对于增删改查中,对于实体对象中的同一个参数,在不同的应用场景中需要做不同分组校验。


具体代码如下:


// 定义校验组别(划分阵营)public interface CreateGroup {}public interface UpdateGroup {}
// DTO根据场景应用分组public class ProductDTO { @Null(groups = UpdateGroup.class) @NotNull(groups = CreateGroup.class) private Long id;
@NotBlank(groups = {CreateGroup.class, UpdateGroup.class}) private String name;}
// 控制层按需激活校验组 @PostMapping("/create")public Result create(@Validated(CreateGroup.class) @RequestBody ProductDTO dto) { // 创建逻辑}
复制代码


多副本作战手册

  • Default 组始终生效(除非使用groups显式配置)

  • 妙用@ConvertGroup进行分组转换


第五重:跨界校验凌波微步


5.1 跨字段关系校验


如果存在跨字段关系校验的情况,即组合条件校验,比如:用户密码和确认密码,可以将自定义注解作用在类上。


具体代码如下:


@Target(TYPE)@Retention(RUNTIME)@Constraint(validatedBy = PasswordValidator.class)public @interface PasswordValid {    String message() default "两次密码不一致";    // ...}
public class PasswordValidator implements ConstraintValidator<PasswordValid, UserDTO> { @Override public boolean isValid(UserDTO user, ConstraintValidatorContext context) { return user.getPassword().equals(user.getConfirmPassword()); }}
// 应用到类级别@PasswordValidpublic class UserDTO { private String password; private String confirmPassword;}
复制代码


风控新法

  • 适用于订单金额与优惠券匹配等业务规则

  • DDD 值对象的天然场景


第六重:规则引擎之天机策


天机殿的自动化战场


新来的产品小妹指着参数校验文档:"每次改个手机号正则都要等发版?


"我默默掏出了祖传的规则引擎。


这种政商联动的需求,是时候施展大型工程的必杀技了!


6.1 规则引擎的三层境界


第一境:硬编码校验(青铜段位的 if-else)

第二境:配置化校验(黄金段位的数据库规则表)

第三境:热力场作战(王者段位的动态规则引擎)


6.2 Drools 天机大阵部署实录


战场场景:信贷额度动态校验(每小时调整风控模型) 。


天机规则文件如下:


// 天机规则文件(credit_rule.drl)rule "白领贷基础校验"    when        $req : LoanRequest(            occupation == "白领",             salary > 10000,             age >= 25 && age <= 45        )    then        $req.setRiskScore(-10); //加分项end
rule "高危行业拦截" when $req : LoanRequest( industry in ("赌博业", "传销"), location.contains("缅甸") ) then throw new ValidationException("阁下莫非是缅北战神?"); end
复制代码


布阵心法



阵法要诀

  1. 规则文件按业务线拆分(金融/电商/社交各立山头)

  2. 使用 kie-maven-plugin 自动编译规则文件

  3. KieScanner 监听规则变更(天机更新不重启服务)


6.3 SpringBoot 接引天机大阵


法咒集成


@Configurationpublic class DroolsConfig {        @Bean    public KieContainer kieContainer() {        KieServices ks = KieServices.Factory.get();        KieFileSystem kfs = ks.newKieFileSystem();                // 加载天机卷轴(规则文件)        Resource resource = new ClassPathResource("rules/credit_rule.drl");        kfs.write(ks.getResources().newInputStreamResource(resource.getInputStream())                    .setTargetPath("credit_rule.drl"));                KieBuilder kieBuilder = ks.newKieBuilder(kfs).buildAll();        return ks.newKieContainer(kieBuilder.getKieModule().getReleaseId());    }}
// Controller层调用天尊之力@PostMapping("/apply")public Result applyLoan(@RequestBody LoanRequest request) { kieSession.insert(request); kieSession.fireAllRules(); // 执行天机推演 return riskService.process(request);}
复制代码


天机沙箱防御

  1. 限制规则中 eval()的使用次数(防 CPU 过载)

  2. 为每个请求创建独立 KieSession(防线程污染)

  3. 设置规则执行超时熔断(天机殿也有算不动的时候)


6.4 天机策反制诀窍


某次上线后,规则引擎的神操作:


rule "特殊时段放水"    when        $req : LoanRequest(hour > 2 && hour < 5)    then        $req.setCreditLimit(50000); //给值夜班的兄弟开后门end
复制代码


反制方案

  1. 规则提交走审批流(太上长老团联署制)

  2. 生产环境禁用 update/modify 关键字(防自动夺舍)

  3. 规则版本回滚机制(祭出玄天宝镜倒转时空)


祖师爷级参数校验纲领



避坑法门


  1. 不过三:Controller 层校验不要超过三层(应该转给 Service)

  2. 见好就收:业务规则校验与基础格式校验分离

  3. 防君子更防小人:服务端校验必须存在(前端校验是防君子用的)

  4. 语义明确:错误提示避免暴露敏感信息(比如"用户不存在"改为"账号或密码错误")


最后提醒各位大侠:好的参数校验就像空气——你平时感受不到它的存在,但一旦失去它,整个系统瞬间崩塌!(代码 fields 正提刀赶来)


文章转载自:苏三说技术

原文链接:https://www.cnblogs.com/12lisu/p/18877432

体验地址:http://www.jnpfsoft.com/?from=001YH

用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
校验参数的6大神功!_参数_不在线第一只蜗牛_InfoQ写作社区