前言
在日常的开发中参数校验是非常重要的一个环节,严格参数校验会减少很多出bug的概率,增加接口的安全性。也会减少对接时不必要的沟通。比如说:在对接的时候前端动不动就甩个截图过来说接口有问题,你检查了半天发现前端传递的参数有问题。针对以上:今天给大家分享一下SpringBoot如何实现统一参数校验。
实现方式
使用 @Validated
注解配合参数校验注解, 比如:@NotEmpty
对参数进行校验。然后对抛出的异常ControllerAdvice
进行捕获然后调整输出数据。
controller
@PostMapping
@Log("新增资产管理分类")
@ApiOperation("新增资产管理分类")
@PreAuthorize("@el.check('tAtomBusiCategory:add')")
public ResponseEntity<Object> createTAtomBusiCategory(@Validated @RequestBody TAtomBusiCategory resources){
if (resources.getId() != null) {
throw new BadRequestException("A new "+ ENTITY_NAME +" cannot already have an ID");
}
tAtomBusiCategoryService.create(resources);
return new ResponseEntity<>(HttpStatus.CREATED);
}
接收的入参TAtomBusiCategory
package com.njry.modules.system.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.njry.base.BaseEntity;
import lombok.Data;
import cn.hutool.core.bean.BeanUtil;
import io.swagger.annotations.ApiModelProperty;
import cn.hutool.core.bean.copier.CopyOptions;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/**
* @description /
* @author WJ
* @date 2024-04-19
**/
@Data
@TableName("T_ATOM_BUSI_CATEGORY")
public class TAtomBusiCategory extends BaseEntity implements Serializable {
@NotNull(groups = BaseEntity.Update.class)
@TableId(value="category_id", type = IdType.INPUT)
@ApiModelProperty(value = "ID", hidden = true)
private Long id;
@ApiModelProperty(value = "业务类别ID")
private Long categoryId;
@NotEmpty(message = "业务类别名称不能为空")
@Length(min = 1 , max = 40 , message = "名字长度1~40")
@ApiModelProperty(value = "业务类别名称")
private String categoryName;
@ApiModelProperty(value = "上级ID")
private Long superiorId;
@ApiModelProperty(value = "层级")
private Integer categoryLev;
@ApiModelProperty(value = "排序")
private Integer orderBy;
@TableField(exist = false)
private List<TAtomBusiCategory> children;
@ApiModelProperty(value = "子业务分类数目", hidden = true)
private Integer subCount = 0;
@ApiModelProperty(value = "创建人ID")
private String createId;
@ApiModelProperty(value = "创建时间")
private Date createDate;
@Length(min = 1 , max = 10 , message = "业务类别英文名称长度1~10")
@ApiModelProperty(value = "业务类别英文名称")
private String categoryNameEn;
//更新的时候用
public void copy(TAtomBusiCategory source){
BeanUtil.copyProperties(source,this, CopyOptions.create().setIgnoreNullValue(true));
}
//多给前端返回的参数
public Boolean getHasChildren() {
return subCount > 0;
}
public Boolean getLeaf() {
return subCount <= 0;
}
public String getLabel() {
return categoryName;
}
}
不加校验@Length会一堆报错(如下图)
例如:@Length(min = 1 , max = 40 , message = “名字字节长度1~40”)
是因为我们Orcale表里这个字段是VARCHAR2(40),要根据数据库NLS_LENGTH_SEMANTICS和数据库字符集确定(如果是40个汉字,我肯定报错,暂时找不到解决办法)
了解oracle的字段
这个时候前端在对接的时候看到这样的错误信息,反手就是给你截个图告诉你接口有问题。所以这个时候就该使用 ControllerAdvice规范异常返回信息了。
我们项目处理
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理所有接口数据验证异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiError> handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
// 打印堆栈信息
log.error(ThrowableUtil.getStackTrace(e));
ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
String message = objectError.getDefaultMessage();
if (objectError instanceof FieldError) {
message = ((FieldError) objectError).getField() + ": " + message;
}
return buildResponseEntity(ApiError.error(message));
}
/**
* 统一返回
*/
private ResponseEntity<ApiError> buildResponseEntity(ApiError apiError) {
return new ResponseEntity<>(apiError, HttpStatus.valueOf(apiError.getStatus()));
}
}
我们定义一个ERROR类返回400
框架统一返回用org.springframework.http public class ResponseEntity
extends org.springframework.http.HttpEntity
@Data
class ApiError {
private Integer status = 400;
private Long timestamp;
private String message;
private ApiError() {
timestamp = System.currentTimeMillis();
}
public static ApiError error(String message){
ApiError apiError = new ApiError();
apiError.setMessage(message);
return apiError;
}
public static ApiError error(Integer status, String message){
ApiError apiError = new ApiError();
apiError.setStatus(status);
apiError.setMessage(message);
return apiError;
}
}
常用注解参照原文
案例
@Data
public class ExampleForm {
@NotEmpty(message = "姓名不能为空")
@Length(min = 1 , max = 10 , message = "名字长度1~10")
private String name;
@Range(min = 1 , max = 99 , message = "年龄范围在1~99岁")
private Integer age;
@Pattern(regexp = "^[1][3,4,5,7,8][0-9]{9}$" , message = "电话号码有误")
private String phone;
@Email(message = "邮箱格式有误")
private String email;
@Valid
@Size(min = 1 ,max = 10 , message = "列表中的元素数量为1~10")
private List<RequestForm> requestFormList;
@Future(message = "开始时间必须大于当前时间")
private Date beginTime;
}
实现嵌套验证
在实际的开发中,前台会后台传递一个list,我们不仅要限制每次请求list内的个数,同时还要对list内基本元素的属性值进行校验。这个时候就需要进行嵌套验证了,实现的方式很简单。在list上添加@Vaild就可以实现了。
@Data
public class JsonRequestForm {
@Vaild
@Size(min = 1 ,max = 10 , message = "列表中的元素数量为1~10")
private List<ExampleForm> requestFormList;
}
注解不是来自同一个依赖包(自己遇到麻烦@Length爆红)
@Size、@Max、@Min等注解是下面这个依赖包的
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.2</version>
</dependency>
@Length、这个注解是下面这个依赖的
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.18.Final</version>
</dependency>