请求参数验证器的使用 2023-08-24 15:29 请求参数验证器的使用 本文内容:springboot中,使用注解验证请求参数。比如请求参数某个字段是否是email?是否是某些字符串中的一个?是否符合某种规则。 ### 1、依赖 ```xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.14</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>WebDemo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>WebDemo</name> <description>WebDemo</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-common</artifactId> <version>2.2.0</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project> ``` ### 2、注解及验证器 自定义注解: ```java package com.example.webdemo.common.annotation; import com.example.webdemo.validator.EmailValidator; import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.*; //作用在字段上 @Target({ElementType.FIELD}) //运行期间有效 @Retention(RetentionPolicy.RUNTIME) //指定验证器为EmailValidator @Constraint(validatedBy = EmailValidator.class) public @interface ValidEmail { String message() default "Invalid email address"; //所属验证组,必填,JSR-303规范.可用于分组验证 Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } ``` 编写验证器校验注解标注的字段内容: ```java package com.example.webdemo.validator; import com.example.webdemo.common.annotation.ValidEmail; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; /** * Email验证器 * <ValidEmail, String> :<作用的注解, 作用的数据类型> * String,表示将来要处理的字段类型是String类型的,所以此注解不能标注在其他类型的字段上做校验 */ public class EmailValidator implements ConstraintValidator<ValidEmail, String> { //初始化 @Override public void initialize(ValidEmail constraintAnnotation) { } /** * 验证 * @param email object to validate * @param context context in which the constraint is evaluated * * @return false标识不通过, true表示通过 */ @Override public boolean isValid(String email, ConstraintValidatorContext context) { if (email == null) { return false; } // 这里可以添加自定义的邮箱验证逻辑,这里只是一个简单的示例 return email.matches("[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}"); } } ``` ### 3、全局异常拦截及统一返回体 ```java package com.example.webdemo.exception; import com.alibaba.nacos.common.utils.CollectionUtils; import com.example.webdemo.common.response.CommonApiCode; import com.example.webdemo.common.response.ResponseVO; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; /** * 全局异常拦截 */ @RestControllerAdvice @Slf4j public class WebExceptionHandler { /** * 拦截MethodArgumentNotValidException异常,当参数校验失败时会抛出此异常 * @param e * @return */ @ExceptionHandler(value = MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.OK) @ResponseBody public ResponseVO<Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { ResponseVO<Object> vo = new ResponseVO<>(); for (FieldError item : e.getBindingResult().getFieldErrors()) { String itemMessage = item.getDefaultMessage(); vo.addFieldError(item.getField(), itemMessage); } vo.setCode(CommonApiCode.FIELD_ERRORS.getCode()); vo.setMessage(CommonApiCode.FIELD_ERRORS.getMessage()); if(CollectionUtils.isNotEmpty(vo.getFieldErrors())){ vo.setMessage(CommonApiCode.FIELD_ERRORS.getMessage() + "-" + vo.getFieldErrors().get(0).getMessage()); } return vo; } @ExceptionHandler(Exception.class) @ResponseStatus(HttpStatus.OK) @ResponseBody public ResponseVO<Object> handleException(Exception e) { log.error(e.getMessage(), e); ResponseVO<Object> vo = new ResponseVO<>(); vo.setCode(CommonApiCode.ERROR.getCode()); vo.setMessage(CommonApiCode.ERROR.getMessage()); return vo; } } ``` 返回码定义: ```java package com.example.webdemo.common.response; import lombok.Getter; /** * 接口返回码定义 */ @Getter public enum CommonApiCode implements IAPICode { /** 统一返回码 **/ SUCCESS(200, "成功"), /** * 安全 */ BAD_REQUEST(400, "请求错误"), NO_AUTH(401, "请登录"), NO_PERMISSIONS(403, "无权限"), DATA_NOT_FOUND(404, "资源不存在"), /** * 异常 */ ERROR(500, "系统异常!"), OPENFEIGN_ERROR(501, "服务远程调用错误"), PROPERTY_CONVERT_ERROR(502, "参数类型错误"), UNAVAILABLE(503, "服务不可用"), REQUEST_TIMEOUT(504, "请求超时"), REMOTE_TIMEOUT(505, "远程调用超时异常"), SQL_UNION_ERROR(506, "数据库唯一性校验异常"), RESPONSE_DATA_IS_NULL(507, "返回的数据为空"), DATABASE_OPERATION_EXCEPTION(508, "数据库操作异常"), FIELD_ERRORS(509, "请求参数错误"), REDIS_LOCK_ERROR(510, "操作太快了,请稍后再试!"), ; private final int code; private final String message; CommonApiCode(int code, String message) { this.code = code; this.message = message; } } ``` 异常码接口: ```java package com.example.webdemo.common.response; /** * 异常码接口,供不同服务使用 */ public interface IAPICode { /** * 响应信息编码 * @return 编码 */ int getCode(); /** * 响应信息描述 * @return 描述 */ String getMessage(); } ``` 字段校验信息: ```java package com.example.webdemo.common.response; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * 字段校验信息vo */ @Data @AllArgsConstructor @NoArgsConstructor public class FieldExceptionVO { private String fieldName; private String message; } ``` 统一返回对象: ```java package com.example.webdemo.common.response; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Data; import javax.validation.constraints.NotNull; import java.util.*; /** * 统一返回对象 */ @Data public class ResponseVO<V> { /** * 异常Code,200表示成功 */ @JsonInclude(JsonInclude.Include.NON_NULL) @NotNull(message = "返回编码不能为空") private Integer code = 200; /** * 分字段出错信息,例如表单中某一个字段 */ private List<FieldExceptionVO> fieldErrors; /** * 错误信息 */ private String message; /** * 返回数据 */ private V data; /** * 返回数据 */ private Object errorData; /** * 无参构造 */ public ResponseVO() { super(); } public ResponseVO(V data) { this.data = data; } /** * 有参构造 * * @param fieldErrors 错误字段列表 * @param message 错误信息 * @param data 数据 */ public ResponseVO(List<FieldExceptionVO> fieldErrors, String message, V data) { super(); this.fieldErrors = fieldErrors; this.data = data; } public ResponseVO(List<FieldExceptionVO> fieldErrors, String message) { super(); this.fieldErrors = fieldErrors; } public ResponseVO(Integer code, String message) { this.code = code; } public ResponseVO(IAPICode apiCode) { this.code = apiCode.getCode(); } public static <T> ResponseVO<T> successResponse(T data) { return response(Collections.emptyList(), "请求成功!", data); } public static <T> ResponseVO<T> successResponse() { return response(Collections.emptyList(), "请求成功!"); } public static <T> ResponseVO<T> response(List<FieldExceptionVO> fieldErrors, String msg, T data) { return new ResponseVO<>(fieldErrors, msg, data); } public static <T> ResponseVO<T> response(List<FieldExceptionVO> fieldErrors, String msg) { return new ResponseVO<>(fieldErrors, msg); } public static <T> ResponseVO<T> response(IAPICode apiCode) { return new ResponseVO<>(apiCode); } public static <T> ResponseVO<T> failResponse(String msg) { return new ResponseVO<>(Collections.emptyList(), msg); } public static <T> ResponseVO<T> failResponse(Integer code, String msg) { return new ResponseVO<>(code, msg); } public void addFieldError(String fieldName, String errorMsg) { if (this.getFieldErrors() == null) { this.fieldErrors = new ArrayList<>(); } this.fieldErrors.add(new FieldExceptionVO(fieldName, errorMsg)); } } ``` ### 4、示例 作用于字段上: ```java package com.example.webdemo.dto; import com.example.webdemo.common.annotation.ValidEmail; import lombok.Data; @Data public class UserDTO { //使用注解验证该字段 @ValidEmail private String email; // 其他字段和方法 } ``` 在请求中使用: ```java package com.example.webdemo.controller; import com.example.webdemo.common.response.ResponseVO; import com.example.webdemo.dto.UserDTO; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.validation.Valid; @RestController @RequestMapping("/users") public class UserController { @PostMapping("/register") public ResponseVO<String> registerUser(@Valid @RequestBody UserDTO userDTO) { // 如果验证失败,将抛出MethodArgumentNotValidException // 否则,继续处理用户注册逻辑 return ResponseVO.successResponse("User registered successfully"); } } ``` 请求:http://localhost:8080/users/register 请求体: ```json { "email": "123@" } ``` ![](https://minio.riun.xyz/riun1/2023-08-24_47617P49f7BUfo7kqZ.jpg) 修改请求体: ```json { "email": "123@qq.com" } ``` ![](https://minio.riun.xyz/riun1/2023-08-24_47622s5zOgNZKXztvl.jpg) ### 5、其他验证器的使用 如果想对字段进行非空验证,对象类型的字段可以使用@NotNull,验证其不为null。而String类型的字段我们一般希望他不仅不是null,也不要是空字符串,所以可以使用:@NotEmpty,而若我们也不希望该String字段不能为空格时(即" "),可以使用@NotBlank。这跟StringUtils.isNotEmpty(),StringUtils.isNotBlank()类似。 ```java @Data public class UserDTO { //使用注解验证该字段 @ValidEmail private String email; //username字段不能没有,不能为空字符串,也不能为全空格串 @NotBlank(message = "username不能为空") private String username; //uid字段不能没有,可以是123这种数字类型,也可以是"123"这种可以转为数字的字符串类型,接收到后会自动转为Long //但是不能为"1d"这种不能转为数字的字符串类型,否则会抛出HttpMessageNotReadableException异常 @NotNull(message = "uid不能为空") private Long uid; //buyList字段不能没有,允许接收元素内容为空串,但元素个数必须在2~10之间 @NotEmpty(message = "buyList不允许为空") @Size(min = 2, max = 10) private List<String> buyList; } ``` 例如,请求: ```json { "email": "123@qq.com", "username": "123", "uid": "4", "buyList": [""] } ``` ![](https://minio.riun.xyz/riun1/2023-08-24_476CUFUoa6UU6BqTWv.jpg) 请求: ```json { "email": "123@qq.com", "username": "123", "uid": "4", "buyList": ["", "324"] } ``` ![](https://minio.riun.xyz/riun1/2023-08-24_476DIdmXJ6wc004cMR.jpg) #### 捕获HttpMessageNotReadableException 在WebExceptionHandler中,@ExceptionHandler(Exception.class)上面,添加对HttpMessageNotReadableException异常的捕获 ```java @ExceptionHandler(HttpMessageNotReadableException.class) @ResponseStatus(HttpStatus.OK) @ResponseBody public ResponseVO<Object> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) { log.error(e.getMessage(), e); ResponseVO<Object> vo = new ResponseVO<>(); vo.setCode(CommonApiCode.BAD_REQUEST.getCode()); vo.setMessage(CommonApiCode.BAD_REQUEST.getMessage()); return vo; } ``` #### 复杂类型的校验 ```java package com.example.webdemo.dto; import com.example.webdemo.common.annotation.ValidEmail; import lombok.Data; import javax.validation.Valid; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import java.util.List; @Data public class UserDTO { //使用注解验证该字段 @ValidEmail private String email; @NotBlank(message = "username不能为空") private String username; @NotNull(message = "uid不能为空") private Long uid; @NotEmpty(message = "buyList不允许为空") @Size(min = 2, max = 10) private List<String> buyList; //必须写了@Valid,CityConfigForm自己的校验规则:cityCode和cityName不能为空 才生效 @NotEmpty(message = "城市配置不允许为空") @Valid private List<CityConfigForm> cityConfigList; } ``` CityConfigForm: ```java package com.example.webdemo.dto; import lombok.Data; import javax.validation.constraints.NotBlank; /** * @author zhang */ @Data public class CityConfigForm { /** * 城市code */ @NotBlank(message = "城市code不允许为空") private String cityCode; /** * 城市名称 */ @NotBlank(message = "城市名称不允许为空") private String cityName; } ``` --END--
发表评论