对象数据校验
对象数据校验
当进行对象修改、对象保存等操作时,前端往往返回一个 JSON对象或者是 表单对象,通过 SpringMVC后一般都会封装为一个 Java对象;
我们针对这个 Java对象进行操作前,通常都要进行校验,可以使用 JSR303中定义的校验注解来简化
一、原始的写法
在每次请求中都写上校验,或者把校验抽取成一个方法,每次请求都执行;
/**
* 添加重审专家
*
* @param sid
* @param expId
* @return
*/
@PreAuthorize("hasAnyRole('ROLE_ADMIN')")
@ResponseBody
@PostMapping("/arrange/review")
public Object arrangeReviewExp(Integer sid, Integer expId) {
Sub sub = subService.getSubById(sid);
if (sub == null) {
return ResponseData.fail(WebConstant.RESPONSE_FAIL_SERVER_ERROR_500, SubConstant.SUB_MISS);
}
if (!(sub.getStatus() == 6 || sub.getStatus() == 7)) {
return ResponseData.fail(WebConstant.RESPONSE_FAIL_SERVER_ERROR_500, SubConstant.SUB_NOT_AT_REARRANGEABLE);
}
User user = userService.getExpById(expId);
if (user == null) {
return ResponseData.fail(WebConstant.RESPONSE_FAIL_SERVER_ERROR_500, "该专家不存在");
}
if (!userService.checkExpAbility(user)) {
return ResponseData
.fail(WebConstant.RESPONSE_FAIL_SERVER_ERROR_500, "重审专家 " + user.getUsername() + " 没有审批资格");
}
if (subExpService.checkReviewSubExpExistence(sid)) {
return ResponseData.fail(WebConstant.RESPONSE_FAIL_SERVER_ERROR_500, "重审专家已被分配,请勿重复提交");
}
// 真正的业务逻辑,之后才开始
}
二、简单校验
JSR303 是一套JavaBean参数校验的标准,它定义了很多常用的校验注解,我们可以直接将这些注解加在我们JavaBean的属性上面,就可以在需要校验的时候进行校验了。
2.1、依赖
<!--jsr 303-->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2.2、常用注解
声明校验:
- @Valid:常见用在方法,类中字段上进行校验,算是一种规范
- @Validated:是spring提供的对@Valid的封装,常见用在方法上进行校验,算是对 @Valid的一种实现,更牛逼
其他常见注解如下:
- @Null 限制只能为null
- @AssertFalse 限制必须为false
- @AssertTrue 限制必须为true
- @DecimalMax(value) 限制必须为一个不大于指定值的数字
- @DecimalMin(value) 限制必须为一个不小于指定值的数字
- @Digits(integer,fraction) 限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction
- @Future 限制必须是一个将来的日期
- @Max(value) 限制必须为一个不大于指定值的数字
- @Min(value) 限制必须为一个不小于指定值的数字
- @Past 限制必须是一个过去的日期
- @Pattern(value) 限制必须符合指定的正则表达式,如
^[a-zA-Z]$
- @Size(max,min) 限制字符长度必须在min到max之间
- @Past 验证注解的元素值(日期类型)比当前时间早
- @Email 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式
- @Length(min=, max=) 被注释的字符串的大小必须在指定的范围内
- @NotEmpty 被注释的字符串的必须非空
- @Range(min=, max=) 被注释的元素必须在合适的范围内
- @URL(protocol=,host=, port=,regexp=, flags=) 被注释的字符串必须是一个有效的url
- @NotNull 任何对象的 value不能为null
- @NotEmpty 集合对象的元素不为0,即集合不为空,也可以用于字符串不为null
- @NotBlank 只能用于字符串不为null,并且字符串trim()以后length要大于0
2.3、实体类
package com.zwb.gulimall.product.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import org.hibernate.validator.constraints.URL;
import javax.validation.constraints.*;
import java.io.Serializable;
/**
* 品牌
*
* @author OliQ
* @email yuanchuziwen@qq.com
* @date 2022-01-16 13:49:02
*/
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 品牌id
*/
@TableId
private Long brandId;
/**
* 品牌名
*
* @NotBlank 定义了使用 Validation包的注解校验方法
* 会在使用 @Valid注解标注的 Controller方法中进行校验,
* 可使用 message属性进行自定义反馈信息
*/
@NotBlank(message = "品牌名不能为空")
private String name;
/**
* 品牌logo地址
*/
@NotEmpty
@URL(message = "logo必须是一个合法的 URL地址")
private String logo;
/**
* 介绍
*/
private String descript;
/**
* 显示状态[0-不显示;1-显示]
*/
private Integer showStatus;
/**
* 检索首字母
*/
@NotEmpty
@Pattern(regexp = "^[a-zA-Z]$", message = "检索首字母必须是一个字母")
private String firstLetter;
/**
* 排序
*/
@NotNull
@Min(value = 0, message = "排序必须是一个大于 0的整数")
private Integer sort;
}
2.4、Controller
需要在被校验的对象前面加 @Valid
注解,它后面可以紧随着 BindingResult
对象,表示对该对象校验后的结果
/**
* 保存
*/
@RequestMapping("/save")
// @RequiresPermissions("product:brand:save")
public R save(@Valid @RequestBody BrandEntity brand, BindingResult result) {
if (result.hasErrors()) {
Map<String, String> errors = new HashMap<>(result.getFieldErrors().size());
result.getFieldErrors().forEach(item -> {
// 获取错误提示
String message = item.getDefaultMessage();
// 获取错误的属性字段
String field = item.getField();
errors.put(field, message);
});
return R.error().put("data", errors);
}
brandService.save(brand);
return R.ok();
}
2.5、异常统一处理
如果有多个方法进行 Validation校验,那么就得写很多遍代码,可以统一抽取出来;
package com.zwb.gulimall.product.exception;
import com.zwb.common.exception.BizCodeEnum;
import com.zwb.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
/**
* @author:OliQ
* @date: Created on 2022-01-20 21:00
* @description:
*/
@Slf4j
@RestControllerAdvice(basePackages = {"com.zwb.gulimall.product.controller"}) // 可以设置处理的异常来源
public class GulimallExceptionHandler {
@ExceptionHandler(value = MethodArgumentNotValidException.class) // 异常匹配是 小异常优先的
public R handleValidException(MethodArgumentNotValidException e) {
log.error("数据校验出现问题 {}, 异常类型 {}", e.getMessage(), e.getClass());
BindingResult result = e.getBindingResult();
Map<String, String> errors = new HashMap<>(result.getFieldErrors().size());
result.getFieldErrors().forEach(fieldError -> {
errors.put(fieldError.getField(), fieldError.getDefaultMessage());
});
return R.error(BizCodeEnum.VALID_EXCEPTION).put("data", errors);
}
}
三、分组校验
同一个实体类,其中的诸多属性在不同的请求中,可能需要不同的校验方式
因此,可以构造分组校验,以满足上述需求
3.1、分组声明
用一个空接口即可
package com.zwb.common.valid;
/**
* @author :OliQ
* @date :Created on 2022-01-20 22:30
*/
public interface AddGroup {
}
package com.zwb.common.valid;
/**
* @author :OliQ
* @date :Created on 2022-01-20 22:30
*/
public interface UpdateGroup {
}
3.2、实体类
在每一个约束注解中添加 groups属性,表示该注解在哪一种情况下生效;
如果 groups属性没有声明,就表示在 Default.class分组下
package com.zwb.gulimall.product.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.zwb.common.valid.AddGroup;
import com.zwb.common.valid.UpdateGroup;
import lombok.Data;
import org.hibernate.validator.constraints.URL;
import javax.validation.constraints.*;
import java.io.Serializable;
/**
* 品牌
*
* @author OliQ
* @email yuanchuziwen@qq.com
* @date 2022-01-16 13:49:02
*/
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 品牌id
*/
@TableId
@NotNull(message = "修改必须指定 ID", groups = {UpdateGroup.class})
@Null(message = "新增不能指定 ID", groups = {AddGroup.class})
private Long brandId;
/**
* 品牌名
*
* @NotBlank 定义了使用 Validation包的注解校验方法
* 会在使用 @Valid注解标注的 Controller方法中进行校验,
* 可使用 message属性进行自定义反馈信息
*/
@NotBlank(message = "品牌名不能为空", groups = {AddGroup.class})
private String name;
/**
* 品牌logo地址
*/
@NotEmpty
@URL(message = "logo必须是一个合法的 URL地址")
private String logo;
/**
* 介绍
*/
private String descript;
/**
* 显示状态[0-不显示;1-显示]
*/
private Integer showStatus;
/**
* 检索首字母
*/
@NotEmpty
@Pattern(regexp = "^[a-zA-Z]$", message = "检索首字母必须是一个字母")
private String firstLetter;
/**
* 排序
*/
@NotNull
@Min(value = 0, message = "排序必须是一个大于 0的整数")
private Integer sort;
}
3.3、Controller类
需要使用 @Validated
注解,代替 @Valid
注解
并且声明 value
属性,指明当前校验是在哪些分组情况下进行的
注意:如果在 value
属性中声明了具体的接口,那么 default.class
接口,也必须显示声明,就像类的空参和有参构造一样,如果不声明,那么处在 default.class
组中的约束(就是没刻意分组的那些约束)都不会再生效
package com.zwb.gulimall.product.controller;import com.zwb.common.utils.PageUtils;import com.zwb.common.utils.R;import com.zwb.common.valid.AddGroup;import com.zwb.common.valid.UpdateGroup;import com.zwb.gulimall.product.entity.BrandEntity;import com.zwb.gulimall.product.service.BrandService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.validation.BindingResult;import org.springframework.validation.annotation.Validated;import org.springframework.web.bind.annotation.*;import javax.validation.Valid;import javax.validation.Validator;import javax.validation.groups.Default;import java.util.Arrays;import java.util.HashMap;import java.util.Map;/** * 品牌 * * @author OliQ * @email yuanchuziwen@qq.com * @date 2022-01-16 13:49:02 */@RestController@RequestMapping("product/brand")public class BrandController { @Autowired private BrandService brandService; /** * 列表 */ @RequestMapping("/list") // @RequiresPermissions("product:brand:list") public R list(@RequestParam Map<String, Object> params) { PageUtils page = brandService.queryPage(params); return R.ok().put("page", page); } /** * 信息 */ @RequestMapping("/info/{brandId}") // @RequiresPermissions("product:brand:info") public R info(@PathVariable("brandId") Long brandId) { BrandEntity brand = brandService.getById(brandId); return R.ok().put("brand", brand); } /** * 保存 */ @RequestMapping("/save") // @RequiresPermissions("product:brand:save") public R save(@Validated({AddGroup.class, Default.class}) @RequestBody BrandEntity brand) { brandService.save(brand); return R.ok(); } /** * 修改 */ @RequestMapping("/update") // @RequiresPermissions("product:brand:update") public R update(@Validated({UpdateGroup.class, Default.class}) @RequestBody BrandEntity brand) { brandService.updateById(brand); return R.ok(); } /** * 删除 */ @RequestMapping("/delete") // @RequiresPermissions("product:brand:delete") public R delete(@RequestBody Long[] brandIds) { brandService.removeByIds(Arrays.asList(brandIds)); return R.ok(); }}
四、自定义校验
4.1、编写自定义校验注解
仿造其他的那些约束注解,自己写一个
必须有 groups
, payload
, message
属性
message
表示默认提示从哪里获取,需要自己在 resource
路径下,新建 ValidationMessages.properties
文件
package com.zwb.common.valid;import javax.validation.Constraint;import javax.validation.Payload;import java.lang.annotation.Documented;import java.lang.annotation.Retention;import java.lang.annotation.Target;import static java.lang.annotation.ElementType.*;import static java.lang.annotation.RetentionPolicy.RUNTIME;/** * @author :OliQ * @date :Created on 2022-01-20 22:57 */@Documented@Constraint(validatedBy = {ListValueConstraintValidator.class})@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})@Retention(RUNTIME)public @interface ListValue { String message() default "{com.zwb.common.ListValue.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; int[] vals() default {};}
ValidationMessages.properties
注意中文乱码问题,修改后如果还有问题,就把这个配置文件删掉重写一遍
com.zwb.common.ListValue.message=必须提交指定的值
实体类中的使用
package com.zwb.gulimall.product.entity;import com.baomidou.mybatisplus.annotation.TableId;import com.baomidou.mybatisplus.annotation.TableName;import com.zwb.common.valid.AddGroup;import com.zwb.common.valid.ListValue;import com.zwb.common.valid.UpdateGroup;import lombok.Data;import org.hibernate.validator.constraints.URL;import javax.validation.constraints.*;import java.io.Serializable;/** * 品牌 * * @author OliQ * @email yuanchuziwen@qq.com * @date 2022-01-16 13:49:02 */@Data@TableName("pms_brand")public class BrandEntity implements Serializable { /** * 显示状态[0-不显示;1-显示] */ @ListValue(vals={0, 1}, message = "必须提交指定的值") private Integer showStatus;}
4.2、编写自定义的校验器
自己写一个类,实现 ConstraintValidator<Anno, Type>
接口
- Anno: 为注解名称
- Type:为数据的类型
package com.zwb.common.valid;import javax.validation.ConstraintValidator;import javax.validation.ConstraintValidatorContext;import java.util.HashSet;import java.util.Set;/** * @author:OliQ * @date: Created on 2022-01-21 12:44 * @description: */public class ListValueConstraintValidator implements ConstraintValidator<ListValue, Integer> { private Set<Integer> set = new HashSet<>(); /** * 初始化方法 * * @param constraintAnnotation */ @Override public void initialize(ListValue constraintAnnotation) { 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); }}
4.3、将二者关联
在自定义注解上加上元注解 @Constrains(validatedBy={xxx.class})
package com.zwb.common.valid;@Documented@Constraint(validatedBy = {ListValueConstraintValidator.class})@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})@Retention(RUNTIME)public @interface ListValue { String message() default "{com.zwb.common.ListValue.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; int[] vals() default {};}
五、其他
5.1、异常状态的声明
针对多种可能的异常,使用枚举会比常量类更好
package com.zwb.common.exception;
/**
* @author:OliQ
* @date: Created on 2022-01-20 21:42
* @description:
*/
public enum BizCodeEnum {
VALID_EXCEPTION(10001,"参数格式校验失败"),
UNKNOWN_EXCEPTION(10000,"系统未知异常");
private Integer code;
private String msg;
BizCodeEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
5.2、用于返回的结果对象
/**
* Copyright (c) 2016-2019 人人开源 All rights reserved.
* <p>
* https://www.renren.io
* <p>
* 版权所有,侵权必究!
*/
package com.zwb.common.utils;
import com.zwb.common.exception.BizCodeEnum;
import org.apache.http.HttpStatus;
import java.util.HashMap;
import java.util.Map;
/**
* 返回数据
*
* @author Mark sunlightcs@gmail.com
*/
public class R extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;
public R() {
put("code", 0);
put("msg", "success");
}
public static R error() {
return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "未知异常,请联系管理员");
}
/*public static R error(BizCodeEnum bizCodeEnum) {
return error(bizCodeEnum.getCode(), bizCodeEnum.getMessage());
}*/
public static R error(String msg) {
return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, msg);
}
public static R error(BizCodeEnum bizCodeEnum) {
return error(bizCodeEnum.getCode(), bizCodeEnum.getMsg());
}
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();
}
@Override
public R put(String key, Object value) {
super.put(key, value);
return this;
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!