- 引言 -
如果你是一个摸爬滚打几年的开发者,那么这个阶段,对系统设计的合理性绝对是衡量一个人水平的重要标准。
一个好的设计不光能让你工作中避免很多麻烦,还能为你面试的时候增加很多谈资
而且,不同设计之间理念都是有借鉴性参考性的,你见过的设计多了,思考的多了,再次面临一个问题的时候,就会有很多点子不由自主的冒出来。
希望这个系列的文章,能够和大家互相借鉴参考,共同进步。
- 初级参数校验 -
很简单的场景
我要写一个接口,需要校验接口参数,比如编辑一条数据的时候id不能为空
很直白的一个做法就是写一个参数校验方法
if(id == null){
// 抛出异常
}
不用怀疑,这是可行的,也没什么性能问题,就是不够优雅而已,所以我们为了更优雅一些,少些点业务代码,可以进行一定程度的优化
- 中级参数校验 -
类上加@Validated注解
@Validated
@RestController
public class PeopleListController {
}
接口参数中加@Valid注解
@ApiOperation("编辑人物")
@PutMapping
public ApiResponse<JSONArray> edit(@RequestBody @Valid @ApiParam("人物") PeopleListEditParam peopleListEditParam) {
PeopleListEntity edit = peopleListService.edit(peopleListEditParam);
return ApiResponse.success(edit);
}
可以支持单个对象也可以支持数组
入参对象中写上校验注解
@NotNull(message = "id不能为空!")
private Long id;
类似的还有@NotBlank等等,这是spring自带的一套校验注解,使用起来比较简单。
校验结果如下:
什么都不管的话会把这一坨直接抛出去,不太友好,虽然硬看也能看懂。
有一个方法就是,在对应api中,有个BindResult对象可以接收到这一坨,然后把我们需要的信息抛出即可。
但是我觉得这样太冗余太麻烦了。
可以加一个全局异常处理类,这个类很有用,不光是体现在参数校验的时候
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 参数校验异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@SuppressWarnings("unchecked")
public ApiResponse<Object> handlerMethodArgumentNotValidException(MethodArgumentNotValidException e) {
BindingResult bindingResult = e.getBindingResult();
List<ObjectError> allErrors = bindingResult.getAllErrors();
List<String> lists = new ArrayList<>();
for (ObjectError objectError : allErrors) {
lists.add(objectError.getDefaultMessage());
}
return ApiResponse.fail(Constants.CODE_ERROR_PARAM, lists.toString());
}
}
增加如上代码后,校验结果就变成了
也可以多个参数同时校验
- 进阶参数校验 -
id不能为空,姓名不能为空之类的判断很好做,也可以用spring自带的@NotNull、@NotEmpty之类的来做。
但是如上,性别不合法这种比较自定义的校验怎么做呢?
使用注解+枚举来实现
首先定义一个注解
/**
* description 验证字段的枚举值
* @author LuHui
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
@Constraint(validatedBy = EnumValidatorClass.class)
public @interface EnumValidator {
Class<?> value();
String message() default "当前枚举值不合法";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
然后给我们的注解增加对应的校验逻辑处理类
public class EnumValidatorClass implements ConstraintValidator<EnumValidator, Object>, Annotation {
private final List<Object> values = new ArrayList<>();
@Override
public Class<? extends Annotation> annotationType() {
return null;
}
/**
* description: 初始化枚举值
*
* @param enumValidator 注解对象
* @author luhui
* @date 2022/10/26 20:53
*/
@Override
public void initialize(EnumValidator enumValidator) {
Class<?> clazz = enumValidator.value();
Object[] objects = clazz.getEnumConstants();
values.addAll(Arrays.asList(objects));
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
// 校验值
for (Object o : values) {
boolean equals = o.toString().equals(value.toString());
if (equals) {
return true;
}
}
return false;
}
}
然后我们以性别为例,增加一个性别枚举类
/**
* description 性别枚举类
*
* @author LuHui
*/
public enum SexEnum {
// 男性
MAN(1, "男"),
// 女性
WOMAN(2, "女"),
// 未知(比如一些不具备性别的人物)
UN_KNOWN(3, "无");
private static final HashMap<Integer, SexEnum> SEX_MAP = new HashMap<>();
static {
for (SexEnum e : SexEnum.values()) {
SEX_MAP.put(e.getValue(), e);
}
}
@Getter
private final Integer value;
@Getter
private final String desc;
SexEnum(Integer value, String desc) {
this.value = value;
this.desc = desc;
}
@Override
public String toString() {
// 为校验,枚举类需重写toString
if (value == null) {
return "";
}
return this.getValue().toString();
}
public static SexEnum find(Integer value) {
return SEX_MAP.get(value);
}
}
最后一步,在接收参数的实体中,增加校验注解,也就是我们自定义的注解
@EnumValidator(value = SexEnum.class, message = "性别不合法")
@ApiModelProperty("性别")
private Integer sex;
然后就大功告成了,快去看看你的接口用不用得到吧。
如果觉得我写的东西对您有一点帮助,可以关注下,会持续给大家更新一些小知识!感谢!
- 结束语 -
优雅的接口设计虽然不一定能带来多少立竿见影的性能上的提升,但是对我们的系统的影响是长远的,对开发者来说也是很有成就感的事情,毕竟大家都不想写一坨屎给别人,虽然这是所有系统的归宿。