基于jsr303 通过自定义注解实现,实现思路:
存在一些瑕疵,后续补充完善。
加入依赖
部分版本已不默认自动引入该依赖,选择手动引入
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
创建自定义注解以及实现类
目录结构:
- FileNotEmpty 自定义注解
- FileNotEmptyValidator 单文件校验
- FilesNotEmptyValidator 多文件校验
/*** jsr303 文件格式校验注解** @author maofs* @version 1.0* @date 2021 -11-29 10:16:03*/
@Documented
@Constraint(validatedBy = {FileNotEmptyValidator.class, FilesNotEmptyValidator.class}
)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface FileNotEmpty {/*** Message string.** @return the string*/String message() default "文件格式不正确";/*** 校验组** @return the class [ ]*/Class<?>[] groups() default {};/*** Payload class [ ].** @return the class [ ]*/Class<? extends Payload>[] payload() default {};/*** 需要校验的格式数组** @return the string [ ]*/String[] format() default {};/*** 是否必填 为false时文件为空则不校验格式,不为空则校验格式* 为true时文件不能为空且需要验证格式** @return the boolean*/boolean required() default true;
/*** 单文件校验** @author maofs* @version 1.0* @date 2021 -11-29 10:16:03*/
public class FileNotEmptyValidator implements ConstraintValidator<FileNotEmpty, MultipartFile> {private Set<String> formatSet = new HashSet<>();private boolean required;@Overridepublic void initialize(FileNotEmpty constraintAnnotation) {String[] format = constraintAnnotation.format();this.formatSet = new HashSet<>(Arrays.asList(format));this.required = constraintAnnotation.required();}@Overridepublic boolean isValid(MultipartFile multipartFile, ConstraintValidatorContext constraintValidatorContext) {if (multipartFile == null || multipartFile.isEmpty()) {return !required;}String originalFilename = multipartFile.getOriginalFilename();assert originalFilename != null;String type = originalFilename.substring(originalFilename.lastIndexOf('.') + 1).toLowerCase();if (!formatSet.isEmpty()) {return formatSet.contains(type);}return true;}
}
/*** 多文件校验** @author maofs* @version 1.0* @date 2021 -11-29 10:16:03*/
public class FilesNotEmptyValidator implements ConstraintValidator<FileNotEmpty, MultipartFile[]> {private Set<String> formatSet = new HashSet<>();private boolean required;@Overridepublic void initialize(FileNotEmpty constraintAnnotation) {String[] format = constraintAnnotation.format();this.formatSet = new HashSet<>(Arrays.asList(format));this.required = constraintAnnotation.required();}@Overridepublic boolean isValid(MultipartFile[] multipartFiles, ConstraintValidatorContext constraintValidatorContext) {if (multipartFiles == null || multipartFiles.length == 0) {return !required;}for (MultipartFile file : multipartFiles) {String originalFilename = file.getOriginalFilename();assert originalFilename != null;String type = originalFilename.substring(originalFilename.lastIndexOf('.') + 1).toLowerCase();if (formatSet.isEmpty() || !formatSet.contains(type)) {return false;}}return true;}
}
全局异常处理
/*** 统一异常处理** @author maofs* @version 1.0* @date 2021 -11-29 10:16:03*/
@ControllerAdvice
public class ExceptionHandle {private final static Logger logger = LoggerFactory.getLogger(ExceptionHandle.class);@ExceptionHandler(value = Exception.class)@ResponseBodypublic Result<String> handle(Exception e) {logger.error(e.getMessage());StringBuilder stringBuilder = new StringBuilder(); //jsr303异常if (e instanceof ConstraintViolationException) {ConstraintViolationException ex = (ConstraintViolationException)e;Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();for (ConstraintViolation<?> constraintViolation : constraintViolations) {stringBuilder.append(constraintViolation.getMessageTemplate());}} else if (e instanceof BindException) {BindException bindException = (BindException)e;stringBuilder.append(bindException.getFieldErrors().stream().map(FieldError::getDefaultMessage).collect(Collectors.joining(",")));} else {stringBuilder.append("未知错误:").append("请联系后台运维人员检查处理!"); }return ResultUtil.fail(stringBuilder.toString());}}
使用示例
/*** 文件上传示例接口** @author maofs* @version 1.0* @date 2021 -11-19 16:08:26*/
@RestController
@Validated
@RequestMapping("/annex")
public class AnnexController {@Resourceprivate IAnnexService annexService;/*** 文件上传示例1** @param uploadDTO the upload dto* @return the result*/@PostMapping(value = "/upload1")public Result<String> upload(@Valid AnnexUploadDTO uploadDTO) {return Boolean.TRUE.equals(annexService.upload(uploadDTO)) ? ResultUtil.success() : ResultUtil.fail();}/*** 文件上传示例2** @param number 项目编号* @param pictureFile 图片文件* @param annexFile 附件文件* @return result result*/@PostMapping(value = "/upload2")public Result<String> upload(@NotBlank(@FileNotEmpty(format = {"png", "jpg"}, message = "图片为png/jpg格式", required = false)MultipartFile pictureFile, @FileNotEmpty(format = {"doc", "docx", "xls", "xlsx"}, message = "附件为doc/docx/xls/xlsx格式", required = false)MultipartFile annexFile) { return Boolean.TRUE.equals(annexService.upload( pictureFile, annexFile)) ? ResultUtil.success() : ResultUtil.fail();}@Datastatic class AnnexUploadDTO{ @FileNotEmpty(format = {"pdf","doc","zip"}, message = "文件为pdf/doc/zip格式")private MultipartFile[] file;}
}
结果展示