Spring Boot自定义文件校验:如何捕获非CSV文件上传的NotValidFormatException
问题描述
我希望在上传非CSV格式文件时抛出NotValidFormatException异常,目前已实现自定义校验注解、校验器、异常响应类和全局异常处理器,但上传PDF等无效类型文件时,实际抛出的是CSV解析异常:Failed to parse csv file: (line 322) invalid char between encapsulated token and delimiter,无法被自定义的onNotValidFormatException方法捕获。相关实现代码如下:
自定义校验注解 ValidFile
@Documented @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) @Retention(RUNTIME) @Constraint(validatedBy = {FileValidator.class}) public @interface ValidFile { //... }
校验器 FileValidator
public class FileValidator implements ConstraintValidator<ValidFile, MultipartFile> { @Override public boolean isValid(MultipartFile multipartFile, ConstraintValidatorContext context) { // ... 此处校验逻辑未完善 } }
异常响应类 ValidationErrorResponse
public class ValidationErrorResponse { // 包含fieldName和response字段 }
全局异常处理器 ErrorHandlingControllerAdvice
@ControllerAdvice class ErrorHandlingControllerAdvice { @ExceptionHandler(NotValidFormatException.class) @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) @ResponseBody ValidationErrorResponse onNotValidFormatException(NotValidFormatException e) { //... } }
Controller层代码
public ResponseEntity<ResponseMessage> uploadFile( @Valid @ValidFile @RequestParam("file") MultipartFile file) throws Exception { employeeService.create(file); return ResponseEntity.ok(new ResponseMessage("File uploaded.")); }
尝试过使用@Validated、@RequestPart("file"),均无效果。
解决方案
1. 完善FileValidator的前置校验逻辑
当前校验器未真正拦截非CSV文件,导致无效文件进入Service层触发解析异常。需在isValid方法中提前校验文件格式,不通过时抛出自定义异常:
import org.apache.commons.io.FilenameUtils; import java.util.Arrays; import java.util.List; public class FileValidator implements ConstraintValidator<ValidFile, MultipartFile> { private static final List<String> ALLOWED_EXTENSIONS = Arrays.asList("csv"); private static final List<String> ALLOWED_MIME_TYPES = Arrays.asList( "text/csv", "application/vnd.ms-excel", "application/csv" ); @Override public boolean isValid(MultipartFile file, ConstraintValidatorContext context) { if (file.isEmpty()) { throw new NotValidFormatException("文件不能为空"); } // 校验文件扩展名 String originalFilename = file.getOriginalFilename(); String extension = FilenameUtils.getExtension(originalFilename).toLowerCase(); if (!ALLOWED_EXTENSIONS.contains(extension)) { throw new NotValidFormatException("仅支持CSV格式文件"); } // 校验MIME类型 String contentType = file.getContentType(); if (!ALLOWED_MIME_TYPES.contains(contentType)) { throw new NotValidFormatException("文件MIME类型无效,仅支持CSV"); } return true; } }
注:可引入
org.apache.commons.io.FilenameUtils处理扩展名,或自行实现字符串截取逻辑。
2. 处理Service层的CSV解析异常
若前置校验仍有遗漏(比如扩展名正确但内容不是CSV的文件),需在Service层捕获解析异常并转抛NotValidFormatException:
public void create(MultipartFile file) { try { // 原CSV解析逻辑 CSVParser parser = new CSVParser(...); // ... } catch (CSVParseException e) { throw new NotValidFormatException("文件内容不是有效的CSV格式", e); } }
3. 补充全局异常处理器(可选)
如果不想修改Service层代码,也可以在ErrorHandlingControllerAdvice中直接捕获CSV解析异常,转成自定义响应:
@ControllerAdvice class ErrorHandlingControllerAdvice { @ExceptionHandler(NotValidFormatException.class) @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) @ResponseBody ValidationErrorResponse onNotValidFormatException(NotValidFormatException e) { ValidationErrorResponse response = new ValidationErrorResponse(); response.setFieldName("file"); response.setResponse(e.getMessage()); return response; } // 捕获CSV解析异常,转成自定义响应 @ExceptionHandler(CSVParseException.class) @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) @ResponseBody ValidationErrorResponse onCSVParseException(CSVParseException e) { ValidationErrorResponse response = new ValidationErrorResponse(); response.setFieldName("file"); response.setResponse("文件内容不是有效的CSV格式"); return response; } }
4. 确保Controller启用校验
在Controller类上添加@Validated注解,确保方法参数的自定义校验生效:
@RestController @Validated // 添加此注解 public class FileUploadController { public ResponseEntity<ResponseMessage> uploadFile( @ValidFile @RequestParam("file") MultipartFile file) throws Exception { employeeService.create(file); return ResponseEntity.ok(new ResponseMessage("File uploaded.")); } }
注:使用
@Validated后,@Valid注解可省略,因为@Validated会触发参数校验。
内容的提问来源于stack exchange,提问作者user19601508
相关产品推荐
相关产品推荐

