为了确保前端传来的数据是合法的,后端需要对客户端传来的数据进行再次校验。使用JSR303对数据进行校验步骤如下:
1. 导入相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2. 在实体类中需要校验的字段上添加注解,以用户名和密码为例
/**
* 用户名
*/
@NotBlank(message = "用户名不能为空",groups = {AddGroup.class, UpdateGroup.class})
private String name;
/**
* 密码,注意:regexp = ""中的正则表达式不需要加//
*/
@NotNull(message = "密码不能为空",groups = {AddGroup.class})
@Pattern(regexp = "^[a-zA-Z][a-zA-Z0-9_]{4,15}$",message = "必须以字母开头,长度在5-16,允许字母数字下划线")
@NotEmpty(message = "密码不能为空",groups = {AddGroup.class})
private String password;
3. 在Controller中对于需要校验的参数添加@Validated注解
public R addUser(@Validated @RequestBody UserInfo userInfo, BindingResult result){
if (result.hasErrors()) {
Map<String,String> map = new HashMap<>();
//获取校验的错误结果
result.getFieldErrors().forEach((item->{
String message = item.getDefaultMessage();
String field = item.getField();
map.put(field,message);
}));
return R.error(400,"提交的信息不合法").put("data",map);
}
int res = userInfoMapper.insert(userInfo);
if(res > 0){
return R.ok("新增用户成功");
}
return R.error();
}
常用注解:
| 注解 | 功能 |
|---|---|
| 校验是否符合Email格式 | |
| @NotBlank | 不为null并且包含至少一个非空白字符 |
| @NotEmpty | 不为null并且不为空 |
| @NotNull | 不为null |
| @Null | 为null |
| @Pattern | 必须满足正则表达式 |
4. 对第三步进行优化,增加全局异常处理
/**
* 集中处理所有异常
*/
@Slf4j
@RestControllerAdvice(basePackages = "com.tomla.controller")
public class GlobalExceptionControllerAdvice {
@ExceptionHandler(value = BindException.class)
public R HandleValidException(BindException e){
log.error("数据校验出现问题:{},异常类型:{}",e.getMessage(),e.getClass());
BindingResult result = e.getBindingResult();
Map<String,String> errMap = new HashMap<>();
//获取校验的错误结果
result.getFieldErrors().forEach((item->{
String message = item.getDefaultMessage();
String field = item.getField();
errMap.put(field,message);
}));
return R.error(CodeEnums.VALID_EXCEPTION.getCode(), CodeEnums.VALID_EXCEPTION.getMessage()).put("data",errMap);
}
@ExceptionHandler(value = RuntimeException.class)
public Result HandleRuntimeException(RuntimeException e){
log.error("系统异常:{},异常类型:{}",e.getMessage(),e.getClass());
return Result.failed(e.getMessage());
}
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable throwable){
log.error("错误",throwable);
return R.error(CodeEnums.UNKNOWN_EXCEPTION.getCode(), CodeEnums.UNKNOWN_EXCEPTION.getMessage());
}
}
其中错误代码和消息提示定义为枚举类型
public enum CodeEnums {
UNKNOWN_EXCEPTION(10000,"系统未知异常"),
VALID_EXCEPTION(10001,"参数格式校验失败");
private int code;
private String message;
CodeEnums(int code,String message){
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
5. 简化Controller中的代码
@PostMapping("add")
public R addUser(@Validated({AddGroup.class}) @RequestBody UserInfo userInfo){
int res = userInfoMapper.insert(userInfo);
if(res > 0){
return R.ok("新增用户成功");
}else{
//将异常抛出,由全局异常处理器处理
throw new RuntimeException();
}
}
6. 第二步中的分组校验实现
- 定义空接口,作为group
//定义空接口,仅仅作为标识使用
public interface AddGroup {
}
- controller中使用:
@Validated({xxx.class}),表示开启分组校验 - 实体类字段中指定所属group
@NotBlank(message = "用户名不能为空",groups = {AddGroup.class, UpdateGroup.class})
private String name;
- 如果一个校验注解没有指定分组,那么在第2步开启分组校验的情况下,这个校验注解不生效
7. 除了使用javax.validation.constraints包下提供的各种常用注解之外,我们可以自定义校验注解,步骤如下:
- 编写一个自定义的校验注解
@Documented
@Constraint(validatedBy = {ListValueConstraintValidator.class}) //指定用什么来校验,可以指定多个校验器
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ListValue {
String message() default "{com.tomla.common.valid.ListValue.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int[] vals() default {};
}
- 框架自带的配置文件如下
- message中的消息需要写在ValidationMessages.properties配置文件中,新建同名配置文件并配置自定义的message
com.tomla.common.valid.ListValue.message=必须为指定的值
- 编写一个自定义的校验器,用来校验那些被自定义校验注解标注的字段
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
private Set<Integer> set = new HashSet<>();
@Override
public void initialize(ListValue constraintAnnotation) {
//获取@ListValue注解中的详细信息
int[] vals = constraintAnnotation.vals();
for (int val : vals) {
set.add(val);
}
}
/**
* 判断是否校验成功
* @param value 被注解的值(需要校验的值)
* @param context 上下文
* @return
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}
- 关联自定义的校验器和自定义的校验注解(第1步中已有完整代码 )
@Constraint(validatedBy = {ListValueConstraintValidator.class}) //可以指定多个校验器
- 使用自定义注解
/**
* 是否可用:0:可用,1:不可用
*/
@TableLogic(value = "0",delval = "1")
@ListValue(vals={0,1})
private Integer status;
- 测试自定义注解是否生效
补充:统一返回结果R类的代码如下
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.apache.http.HttpStatus;
import java.util.HashMap;
import java.util.Map;
public class R extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;
public R setData(Object data){
put("data",data);
return this;
}
public <T> T getData(String name,TypeReference<T> typeReference){
Object data = get(name);//map 类型
String jsonString = JSON.toJSONString(data);
T t = JSON.parseObject(jsonString, typeReference);
return t;
}
public <T> T getData(TypeReference<T> typeReference){
Object data = get("data");//map 类型
String jsonString = JSON.toJSONString(data);
T t = JSON.parseObject(jsonString, typeReference);
return t;
}
public R() {
put("code", 0);
put("msg", "success");
}
public static R error() {
return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "未知异常,请联系管理员");
}
public static R error(String msg) {
return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, msg);
}
public static R error(int code, String msg) {
R r = new R();
r.put("code", code);
r.put("msg", msg);
return r;
}
public static R ok(String msg) {
R r = new R();
r.put("msg", msg);
return r;
}
public static R ok(Map<String, Object> map) {
R r = new R();
r.putAll(map);
return r;
}
public static R ok() {
return new R();
}
public R put(String key, Object value) {
super.put(key, value);
return this;
}
public Integer getCode(){
return (Integer)this.get("code");
}
}