可以使用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
(打扫战场需要手动处理)
我们需要对异常进行统一拦截。
这样在出现参数校验异常,比如空指针时,不会把服务的内部错误信息直接输出给用户。
通过@RestControllerAdvice和@ExceptionHandler注解实现统一异常拦截器的功能。
具体代码如下:
@RestControllerAdvice
publicclass GlobalExceptionHandler {
// 专治各种不服校验
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result handleValidException(MethodArgumentNotValidException e) {
BindingResult result = e.getBindingResult();
return Result.fail(result.getFieldError().getDefaultMessage());
}
}
// 返回格式规范(示例)
publicclass Result {
private Integer code;
private String msg;
private T data;
publicstatic Result fail(String message) {
returnnew Result<>(500, message, null);
}
}
反爬虫机制:
禁止直接暴露字段名给前端(攻击者会利用字段名信息)
错误信息字典化管理(后面会教国际化这招)
有时候,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 {};
}
// 校验逻辑实现(不要相信前端的下拉框!)
publicclass ContactValidator implements ConstraintValidator {
privatestaticfinal Pattern PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");
privatestaticfinal 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(比如从数据库加载正则)
对于增删改查中,对于实体对象中的同一个参数,在不同的应用场景中需要做不同分组校验。
具体代码如下:
// 定义校验组别(划分阵营)
publicinterface CreateGroup {}
publicinterface UpdateGroup {}
// DTO根据场景应用分组
publicclass 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
进行分组转换
如果存在跨字段关系校验的情况,即组合条件校验,比如:用户密码和确认密码,可以将自定义注解作用在类上。
具体代码如下:
@Target(TYPE)
@Retention(RUNTIME)
@Constraint(validatedBy = PasswordValidator.class)
public @interface PasswordValid {
String message() default "两次密码不一致";
// ...
}
publicclass PasswordValidator implements ConstraintValidator {
@Override
public boolean isValid(UserDTO user, ConstraintValidatorContext context) {
return user.getPassword().equals(user.getConfirmPassword());
}
}
// 应用到类级别
@PasswordValid
publicclass UserDTO {
private String password;
private String confirmPassword;
}
风控新法:
适用于订单金额与优惠券匹配等业务规则
DDD值对象的天然场景
新来的产品小妹指着参数校验文档:"每次改个手机号正则都要等发版?
"我默默掏出了祖传的规则引擎。
这种政商联动的需求,是时候施展大型工程的必杀技了!
第一境:硬编码校验(青铜段位的if-else)
第二境:配置化校验(黄金段位的数据库规则表)
第三境:热力场作战(王者段位的动态规则引擎)
战场场景:信贷额度动态校验(每小时调整风控模型) 。
天机规则文件如下:
// 天机规则文件(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
布阵心法:
阵法要诀:
规则文件按业务线拆分(金融/电商/社交各立山头)
使用kie-maven-plugin自动编译规则文件
KieScanner监听规则变更(天机更新不重启服务)
法咒集成:
@Configuration
publicclass 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);
}
天机沙箱防御:
限制规则中eval()的使用次数(防CPU过载)
为每个请求创建独立KieSession(防线程污染)
设置规则执行超时熔断(天机殿也有算不动的时候)
某次上线后,规则引擎的神操作:
rule "特殊时段放水"
when
$req : LoanRequest(hour > 2 && hour < 5)
then
$req.setCreditLimit(50000); //给值夜班的兄弟开后门
end
反制方案:
规则提交走审批流(太上长老团联署制)
生产环境禁用update/modify关键字(防自动夺舍)
规则版本回滚机制(祭出玄天宝镜倒转时空)
段位 |
招式名称 |
修炼难度 |
适用场景 |
破坏力 |
---|---|---|---|---|
青铜 |
if-else硬编码 |
★☆☆ |
小型工具类 |
⚡⚡⚡ |
白银 |
JSR注解大法 |
★★☆ |
常规CRUD |
⚡⚡ |
黄金 |
全局异常拦截 |
★★★ |
RESTful API |
⚡ |
铂金 |
定制校验规则 |
★★★☆ |
复杂业务规则 |
⚡ |
钻石 |
组合条件校验 |
★★★★ |
跨字段业务约束 |
⚡ |
王者 |
规则引擎整合 |
★★★★★ |
动态风控场景 |
✨ |
不过三:Controller层校验不要超过三层(应该转给Service)
见好就收:业务规则校验与基础格式校验分离
防君子更防小人:服务端校验必须存在(前端校验是防君子用的)
语义明确:错误提示避免暴露敏感信息(比如"用户不存在"改为"账号或密码错误")