请求中参数值,?号后参数值获取
当前请求为http://localhost:8080/v1/banner/test/1?name=tom。如何在控制器中获取路径的参数值1,以及参数name的值
使用@PathVariable注解来获取请求地址中参数值,使用@RequestParam来获请求地址取携带的参数值。当携带的参数名与方法参数名一致时,可以省略
package com.zhqx.missyou.api.v1;
import com.zhqx.missyou.exception.http.ForbiddenException;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/banner")
public class BannerController {
@GetMapping("/test/{id}")
public String test(@PathVariable Integer id,@RequestParam String name) throws Exception {
throw new ForbiddenException(10001);
}
}
如果时post请求,且前端提交的参数是json类型,后端如何接收。前端请求参数如下。
{"name":"tome","age":17}
如果没有特别的实体来封装对应的参数。我们可以使用Map<String, Object>来接收参数
@PostMapping("/test/{id}")
public String test1(@PathVariable Integer id, @RequestBody Map<String, Object> person) throws Exception {
throw new ForbiddenException(10000);
}
我们也可以定义数据传输对象PersonDTO,用它来封装参数值
package com.zhqx.missyou.dto;
public class PersonDTO {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
//控制器代码
@PostMapping("/test/{id}")
public String test1(@PathVariable Integer id, @RequestBody PersonDTO personDTO) throws Exception {
throw new ForbiddenException(10000);
}
使用@Validated注解进行基础参数校验
Springboot2.3版本后,web模块下面没有依赖 validation。需要在pom文件中引入校验包。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
假设前端请求为:http://localhost:8080/v1/banner/test/1
后端的控制器逻辑为:
@PostMapping("/test/{id}")
public String test1(@PathVariable Integer id, @RequestBody PersonDTO personDTO) throws Exception {
return "ok";
}
我们可以通过简单的校验注解来控制id的范围。使用@Range注解
@PostMapping("/test/{id}")
public String test1(@PathVariable @Range(min = 1, max = 10, message = "id范围为1-10") Integer id, @RequestBody PersonDTO personDTO) throws Exception {
return "ok";
}
此时我们前端请求为:http://localhost:8080/v1/banner/test/11,这个时候我们会发现请求正常,并没有出错。
当我们在使用校验注解时,我们需要在控制器上添加@Validated注解,来开启对校验注解的支持。
@Validated
public class BannerController {
//...
}
添加后,我们访问后,发现服务器返回如下错误提示,控制台打印出异常信息
{
"code": 9999,
"message": "服务器异常",
"request": "POST /v1/banner/test/11"
}
控制台打印内容:javax.validation.ConstraintViolationException: test1.id: id范围为1-10
这是因为我们对异常信息进行了统一处理,控制台打印了捕获的当前异常
@ExceptionHandler(value = Exception.class)
@ResponseBody
@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
public UnifyResponse handleException(HttpServletRequest request, Exception e) {
String requestUrl = request.getRequestURI();
String method = request.getMethod();
//打印异常信息
System.out.println(e);
UnifyResponse message = new UnifyResponse(9999, "服务器异常", method + " " +requestUrl);
return message;
}
使用@Validated注解进行对象参数校验
当我们对PersonDTO的name属性进行参数验证
public class PersonDTO {
@Length(min = 1, max = 4, message = "名字为1-4个字符")
private String name;
//...省略其他代码
}
前端请求为:http://localhost:8080/v1/banner/test/10,[post方式]。携带参数如下:
{"name":"tomcat","age":17}
后端控制器为:
@PostMapping("/test/{id}")
public String test1(@PathVariable @Range(min = 1, max = 10, message = "id范围为1-10") Integer id,
@RequestBody PersonDTO personDTO) throws Exception {
//throw new ForbiddenException(10000);
return "ok";
}
我们会发现控制台并没有出错。这是因为实体参数的校验,需要在实体参数上使用@Validated注解,修改控制器如下:
@PostMapping("/test/{id}")
public String test1(@PathVariable @Range(min = 1, max = 10, message = "id范围为1-10") Integer id,
@RequestBody @Validated PersonDTO personDTO) throws Exception {
//throw new ForbiddenException(10000);
return "ok";
}
再次访问请求时,我们会发现控制台会打印出错误信息:
DefaultMessageSourceResolvable: codes [personDTO.name,name]; arguments []; default message [name],4,1]; default message [名字为1-4个字符]] ]
级联对象参数校验
修改PersonDTO,追加SchoolDTO对象,假设我们需要校验SchoolDTO对象的schoolName属性。
package com.zhqx.missyou.dto;
public class SchoolDTO {
@Length(min = 2, message = "学校名称最少2个字符")
private String schoolName;
//...省略其他代码
}
public class PersonDTO {
@Length(min = 1, max = 4, message = "名字为1-4个字符")
private String name;
private Integer age;
private SchoolDTO schoolDTO;
//...省略其他代码
}
前端请求为:http://localhost:8080/v1/banner/test/10,[post方式]。携带参数如下:
{"name":"tom","age":17,"schoolDTO":{"schoolName":"q"}}
此时访问控制器,发现并没有校验schoolDTO的schoolName属性。
当我们需要校验级联对象的属性时,我们需要在校验对象上加上@Valid注解
public class PersonDTO {
@Length(min = 1, max = 4, message = "名字为1-4个字符")
private String name;
private Integer age;
@Valid
private SchoolDTO schoolDTO;
//...省略其他代码
}
再次访问请求,这时候控制台即能打印出错误信息
default message [schoolDTO.schoolName],2147483647,2]; default message [学校名称最少2个字符]] ]
自定义校验
基础的校验,能够实现一些基础校验.实际开发中,我们需要自定义注解来帮助我们实现参数校验。修改PersonDTO,去除SchoolDTO
public class PersonDTO {
@Length(min = 1, max = 4, message = "名字为1-4个字符")
private String name;
private Integer age;
private String password1;
private String password2;
//...省略其他代码
}
假设我们需要校验password1和password2的值是一致的。这个时候我们就需要使用自定义注解。
创建自定义注解PasswordEqual
package com.zhqx.missyou.validators;
import javax.validation.Payload;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface PasswordEqual {
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payLoad() default {};
}
创建自定义注解PasswordEqual的逻辑处理类PasswordValidator
package com.zhqx.missyou.validators;
import com.zhqx.missyou.dto.PersonDTO;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class PasswordValidator implements ConstraintValidator<PasswordEqual, PersonDTO> {
@Override
public boolean isValid(PersonDTO personDTO, ConstraintValidatorContext constraintValidatorContext) {
String password1 = personDTO.getPassword1();
String password2 = personDTO.getPassword2();
boolean match = false;
if(password1 != null && password2 != null) {
match = password1.equals(password2);
}
return match;
}
}
将PasswordEqual和PasswordValidator进行关联
package com.zhqx.missyou.validators;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Constraint(validatedBy = PasswordValidator.class)
public @interface PasswordEqual {
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
使用自定义注解
@PasswordEqual
public class PersonDTO {
@Length(min = 1, max = 4, message = "名字为1-4个字符")
private String name;
private Integer age;
private String password1;
private String password2;
//...省略其他代码
}
前端访问:http://localhost:8080/v1/banner/test/10,post请求,携带参数如下:
{"name":"tom","age":17,"password1":"123456","password2":"12345"}
访问控制器为:
@PostMapping("/test/{id}")
public String test1(@PathVariable @Range(min = 1, max = 10, message = "id范围为1-10") Integer id,
@RequestBody @Validated PersonDTO personDTO) throws Exception {
//throw new ForbiddenException(10000);
return "ok";
}
控制台打印错误信息
default message [passwords are not equal]] ]
假设我们还需在注解中添加额外的参数,比如控制密码的最小长度为4和最大长度为6
package com.zhqx.missyou.validators;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Constraint(validatedBy = PasswordValidator.class)
public @interface PasswordEqual {
int min() default 4;
int max() default 6;
String message() default "passwords are not equal";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
修改默认的密码长度限制
//修改最小密码长度为1,覆盖原来的最小长度设置
@PasswordEqual(min = 1)
public class PersonDTO {
@Length(min = 1, max = 4, message = "名字为1-4个字符")
private String name;
private Integer age;
private String password1;
private String password2;、
//...省略其他代码
}
修改注解处理类PasswordValidator
package com.zhqx.missyou.validators;
import com.zhqx.missyou.dto.PersonDTO;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class PasswordValidator implements ConstraintValidator<PasswordEqual, PersonDTO> {
private int min;
private int max;
@Override
public void initialize(PasswordEqual constraintAnnotation) {
this.min = constraintAnnotation.min();
this.max = constraintAnnotation.max();
}
@Override
public boolean isValid(PersonDTO personDTO, ConstraintValidatorContext constraintValidatorContext) {
String password1 = personDTO.getPassword1();
String password2 = personDTO.getPassword2();
boolean match = false;
if(password1 != null && password2 != null) {
match = password1.equals(password2);
if(password1.length() < min) {
//省略逻辑代码
}
if(password1.length() > max) {
//省略逻辑代码
}
if(password2.length() < min) {
//省略逻辑代码
}
if(password2.length() > max) {
//省略逻辑代码
}
}
return match;
}
}
DTO参数校验异常属于MethodArgumentNotValidException,所以我们可以在GlobalExceptionAdvice新增一个统一异常处理
//DTO参数校验异常属于MethodArgumentNotValidException
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
@ResponseStatus(code = HttpStatus.BAD_REQUEST)//参数错误状态码
public UnifyResponse handleBeanValidation(HttpServletRequest request, MethodArgumentNotValidException e) {
String requestUrl = request.getRequestURI();
String method = request.getMethod();
List<ObjectError> errors = e.getBindingResult().getAllErrors();
String message = this.formatAllErrorMessages(errors);
return new UnifyResponse(10001, message, method + " " +requestUrl);
}
private String formatAllErrorMessages(List<ObjectError> errors) {
StringBuffer errorMsg = new StringBuffer();
errors.forEach(error -> errorMsg.append(error.getDefaultMessage()).append(";"));
return errorMsg.toString();
}
这样,当PersonDTO中的参数出现验证错误时,会执行此方法。
请求地址中的参数校验异常属于ConstraintViolationException,所以我们可以在GlobalExceptionAdvice新增一个统一异常处理
//请求地址中的参数校验异常属于ConstraintViolationException
@ExceptionHandler(ConstraintViolationException.class)
@ResponseBody
@ResponseStatus(code = HttpStatus.BAD_REQUEST)//参数错误状态码
public UnifyResponse handleConstraintValidation(HttpServletRequest request, ConstraintViolationException e) {
String requestUrl = request.getRequestURI();
String method = request.getMethod();
String message = e.getMessage();
return new UnifyResponse(10001, message, method + " " +requestUrl);
}
这样,当请求地址中的参数出现验证错误时,会执行此方法。
需要注意的是PersonDTO中的参数校验异常是高于请求地址中的参数校验异常的。当二者同时出现异常时,会优先处理PersonDTO中的参数校验异常。
原文链接: https://marshucheng1.github.io/2021/02/17/springboot-qiyue-3/