preface

Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”. File uploading is a very common function in daily work. How does the back end gracefully and beautifully implement this functionality?

This article will start from the upper exception class design to the upload tool class code implementation, and then to the control layer exposed interface, and finally use Postman test, running smoothly, dry goods full, MM no longer need to worry about not writing a file upload!!

encapsulation

Exception class

Relationship between the overview

BaseException

BaseException is the BaseException class. Other exception classes inherit this class to describe the module to which the exception belongs, the error code, the parameters corresponding to the error code, and the corresponding error message. The five overloaded parameter constructors greatly improve the universality and extensibility.

In addition, rewrite the getMessage () method, which makes ExceptionHandlerExceptionResolver exceptions resolution, can see clearly in the console exception class belongs to module and the corresponding error message under the class, late for maintenance and debugging.

public class BaseException extends RuntimeException {
    private static final long serialVersionUID = 1L;

    // Owning module
    private String module;

    / / error code
    private String code;

    // The parameter corresponding to the error code
    private Object[] args;

    // Error message
    private String defaultMessage;

    public BaseException(String module, String code, Object[] args, String defaultMessage) {
        this.module = module;
        this.code = code;
        this.args = args;
        this.defaultMessage = defaultMessage;
    }

    public BaseException(String module, String code, Object[] args) {
        this(module, code, args, null);
    }

    public BaseException(String module, String defaultMessage) {
        this(module.null.null, defaultMessage);
    }

    public BaseException(String code, Object[] args) {
        this(null, code, args, null);
    }

    public BaseException(String defaultMessage) {
        this(null.null.null, defaultMessage);
    }
    
    @Override
    public String getMessage(a) {
        String message = null;
        if(! StringUtils.isEmpty(code)) { message = code; }if (message == null) {
            message = defaultMessage;
        }
        return message;
    }
    // getter
}
Copy the code

FileException

FileException is an exception class that inherits the BaseException base class. The only constructor with parameters is initialized using the constructor of the parent class. At the same time, it indicates that the exception class belongs to the File module, which is convenient for the later joint debugging to quickly locate the thrown exception.

public class FileException extends BaseException {
    private static final long serialVersionUID = 1L;

    public FileException(String code, Object[] args) {
        super("file", code, args, null); }}Copy the code

FileNameLengthLimitExceededException

FileNameLengthLimitExceededException file name long limited exception class, inherited the exception class FileException file, belong to file a subclass of exception classes, used to determine whether the file name exceed the specified length, have and use the constructor of the parent class constructor initializes, The error code is passed to describe the error message, and the default file name length is taken as the parameter corresponding to the error message code to convey the maximum file name length.

public class FileNameLengthLimitExceededException extends FileException {
    private static final long serialVersionUID = 1L;

    public FileNameLengthLimitExceededException(int defaultFileNameLength) {
        super("upload.filename.exceed.limit.length".newObject[]{defaultFileNameLength}); }}Copy the code

FileSizeLimitExceededException

FileSizeLimitExceededException file size limit exception class, similarly is not here!

public class FileSizeLimitExceededException extends FileException {
    private static final long serialVersionUID = 1L;

    public FileSizeLimitExceededException(long defaultMaxSize) {
        super("upload.exceed.max.size".newObject[]{defaultMaxSize}); }}Copy the code

InvalidExtensionException

InvalidExtensionException file extension check exception class, inheritance FileUploadException file upload exception class, through its own constructor to allow to upload the file extensions array, upload the file extensions and filename, splicing error reminders, This tells the extension that it is outside the allowed range and assigns a value to the member variable.

In addition, four static inner classes are written in this class, which uniformly inherits the outer class to distinguish between different media type extension checking exceptions. The validation includes IMAGE_EXTENSION, FLASH_EXTENSION, MEDIA_EXTENSION, and VIDEO_EXTENSION, which will be covered later.

public class InvalidExtensionException extends FileUploadException {
    private static final long serialVersionUID = 1L;

    // Array of file extensions that are allowed to be uploaded
    private String[] allowedExtension;
    // The extension of the uploaded file
    private String extension;
    // Upload the file name
    private String filename;

    public InvalidExtensionException(String[] allowedExtension, String extension, String filename) {
        super("filename : [" + filename + "], extension : [" + extension + "], allowed extension : [" + Arrays.toString(allowedExtension) + "]");
        this.allowedExtension = allowedExtension;
        this.extension = extension;
        this.filename = filename;
    }

    public static class InvalidImageExtensionException extends InvalidExtensionException {
        private static final long serialVersionUID = 1L;

        public InvalidImageExtensionException(String[] allowedExtension, String extension, String filename) {
            super(allowedExtension, extension, filename); }}public static class InvalidFlashExtensionException extends InvalidExtensionException {
        private static final long serialVersionUID = 1L;

        public InvalidFlashExtensionException(String[] allowedExtension, String extension, String filename) {
            super(allowedExtension, extension, filename); }}public static class InvalidMediaExtensionException extends InvalidExtensionException {
        private static final long serialVersionUID = 1L;

        public InvalidMediaExtensionException(String[] allowedExtension, String extension, String filename) {
            super(allowedExtension, extension, filename); }}public static class InvalidVideoExtensionException extends InvalidExtensionException {
        private static final long serialVersionUID = 1L;

        public InvalidVideoExtensionException(String[] allowedExtension, String extension, String filename) {
            super(allowedExtension, extension, filename); }}// getter
}
Copy the code

Utility class

File upload tool class

FileUploadUtils, this class defines the default maximum file size, the maximum length and upload file path, the most important way is to file upload, can specify the base path for file upload and upload to the default configuration, prior to uploading the file name is more than the default file name length as well as to the upload file size and largest extension check, If the verification fails, the corresponding exception message is thrown. See the comments for code details.

public class FileUploadUtils {
    // The default maximum file size is 50M
    public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024;

    // The maximum length of the file name is 100 by default
    public static final int DEFAULT_FILE_NAME_LENGTH = 100;

    // The default upload path (from the configuration file)
    private static String defaultBaseDir = ProjectConfig.getProfile();

    // Set the default upload base path
    public static void setDefaultBaseDir(String defaultBaseDir) {
        FileUploadUtils.defaultBaseDir = defaultBaseDir;
    }

    public static String getDefaultBaseDir(a) {
        return defaultBaseDir;
    }

    /** * Can specify the base path for file upload **@paramBaseDir Specifies the base path *@paramFile Uploaded file *@returnPath file name *@throwsIOException File read/write exception */
    public static final String upload(String baseDir, MultipartFile file)
            throws IOException, InvalidExtensionException {
        return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
    }

    /** * Upload files with default configuration **@paramFile Uploaded file *@returnPath file name *@throwsIOException Read/write file exception *@throwsInvalidExtensionException file extension validation exception * /
    public static final String upload(MultipartFile file)
            throws IOException, InvalidExtensionException {
        return upload(getDefaultBaseDir(), file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
    }

    /** * File upload **@paramBaseDir Indicates the base directory of the application@paramFile Uploaded file *@paramAllowedExtension Allows uploading file types *@returnPath file name *@throwsFileSizeLimitExceededException if exceed the maximum size *@throwsFileNameLengthLimitExceededException file name is too long@throwsIOException for example, when reading or writing a file fails *@throwsInvalidExtensionException file extension validation exception * /
    public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension)
            throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
            InvalidExtensionException {

        // Get the length of the upload file name
        int fileNameLength = file.getOriginalFilename().length();

        // Check whether the file name exceeds the default maximum length
        if (fileNameLength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) {
            throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
        }

        // Upload file size and extension check, fail to pass the check, throw an exception
        assertAllowed(file, allowedExtension);

        // Re-encode the file name to return the relative file path
        String fileName = extractFilename(file);

        // Concatenate the base path specified in the configuration file with the relative file path to obtain the abstract file on the absolute path
        File desc = getAbsoluteFile(baseDir, fileName);

        // Make the abstract files no longer abstract, memory files are written to disk
        file.transferTo(desc);

        // Returns the file name of the path after successful upload
        String pathFileName = getPathFileName(baseDir, fileName);
        return pathFileName;
    }

    /** * re-encodes the file name to return the relative file path **@paramFile Form file *@returnDate path + encoded file name. The extension is */
    public static final String extractFilename(MultipartFile file) {
        String fileName = file.getOriginalFilename();
        String extension = getExtension(file);
        fileName = DateUtils.datePath() + "/" + IdUtils.fastUUID() + "." + extension;
        return fileName;
    }

    /** * create file directory ** on absolute path@paramUploadDir Upload file base path *@paramFileName fileName *@returnAbstract file */ on absolute path
    public static final File getAbsoluteFile(String uploadDir, String fileName) {
        File desc = new File(uploadDir + File.separator + fileName);
        // Create a file directory
        if(! desc.exists()) {if (!desc.getParentFile().exists()) {
                desc.getParentFile().mkdirs();
            }
        }
        return desc;
    }

    /** * specifies the return path file name **@paramUploadDir Upload file base path *@paramFileName fileName *@returnResource prefix (/profile) /upload/ date path + encoded file name. The extension is */
    public static final String getPathFileName(String uploadDir, String fileName) {
        int dirLastIndex = ProjectConfig.getProfile().length() + 1;
        String currentDir = StringUtils.substring(uploadDir, dirLastIndex);
        String pathFileName = Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName;
        return pathFileName;
    }

    /** * File size and extension verification **@paramFile Uploaded file *@throwsFileSizeLimitExceededException if exceed the maximum size *@throwsInvalidExtensionException legal inspection file extensions * /
    public static final void assertAllowed(MultipartFile file, String[] allowedExtension)
            throws FileSizeLimitExceededException, InvalidExtensionException {
        // File size
        long size = file.getSize();
        if(DEFAULT_MAX_SIZE ! = -1 && size > DEFAULT_MAX_SIZE) {
            throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024);
        }
        
        / / file name
        String fileName = file.getOriginalFilename();
        // File name extension
        String extension = getExtension(file);
        if(allowedExtension ! =null && !isAllowedExtension(extension, allowedExtension)) {
            if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION) {
                throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension,
                        fileName);
            } else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION) {
                throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension,
                        fileName);
            } else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION) {
                throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension,
                        fileName);
            } else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION) {
                throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension,
                        fileName);
            } else {
                throw newInvalidExtensionException(allowedExtension, extension, fileName); }}}/** * Checks whether the current file extension is allowed **@paramExtension Specifies the current file extension *@paramAllowedExtension Allows uploading file types *@returnWhether */ is allowed
    public static final boolean isAllowedExtension(String extension, String[] allowedExtension) {
        for (String str : allowedExtension) {
            if (str.equalsIgnoreCase(extension)) {
                return true; }}return false;
    }

    /** * gets the suffix ** of the file name@paramFile Form file *@returnSuffix * /
    public static final String getExtension(MultipartFile file) {
        String extension = FilenameUtils.getExtension(file.getOriginalFilename());
        // Failed to obtain the file name extension
        if (StringUtils.isEmpty(extension)) {
            extension = MimeTypeUtils.getExtension(file.getContentType());
        }
        returnextension; }}Copy the code

Note: For configuration file setup information, see my articleConfigure read/write separation – Digging gold (juejin. Cn)

Path to upload files

Return path file name

That is, after the upload is successful, the file name returned to the front-end path => Resource prefix (/profile) /upload/ date path + the encoded file name. Corresponding extension

Media type utility classes

MimeTypeUtils specifies some image format constants and defines a static method to get the corresponding extension by the file content type.

For example: MimeTypeUtils getExtension (file. GetContentType ())

public class MimeTypeUtils {
    
    public static final String IMAGE_JPG = "image/jpg";

    public static final String IMAGE_JPEG = "image/jpeg";

    public static final String IMAGE_BMP = "image/bmp";

    public static final String IMAGE_GIF = "image/gif";

    public static final String[] IMAGE_EXTENSION = {"bmp"."gif"."jpg"."jpeg"."png"};

    public static final String IMAGE_PNG = "image/png";

    public static final String[] FLASH_EXTENSION = {"swf"."flv"};

    public static final String[] MEDIA_EXTENSION = {"swf"."flv"."mp3"."wav"."wma"."wmv"."mid"."avi"."mpg"."asf"."rm"."rmvb"};

    public static final String[] VIDEO_EXTENSION = {"mp4"."avi"."rmvb"};

    public static final String[] DEFAULT_ALLOWED_EXTENSION = {
            / / picture
            "bmp"."gif"."jpg"."jpeg"."png".// word excel powerpoint
            "doc"."docx"."xls"."xlsx"."ppt"."pptx"."html"."htm"."txt".// Compress the file
            "rar"."zip"."gz"."bz2".// Video format
            "mp4"."avi"."rmvb".// pdf
            "pdf"};

    public static String getExtension(String prefix) {
        switch (prefix) {
            case IMAGE_PNG:
                return "png";
            case IMAGE_JPG:
                return "jpg";
            case IMAGE_JPEG:
                return "jpeg";
            case IMAGE_BMP:
                return "bmp";
            case IMAGE_GIF:
                return "gif";
            default:
                return ""; }}}Copy the code

In addition to the default extensions, this class defines common image, Flash, and video media extensions to meet different service requirements for file uploading.

Control layer

code

@RestController
@RequestMapping(value = "/file")
public class FileController {

    private static final Logger logger = LoggerFactory.getLogger(FileController.class);

    @PostMapping("/upload")
    public Result uploadFile(@RequestParam("file") MultipartFile file) throws IOException, InvalidExtensionException {
        if(! file.isEmpty()) { String path = FileUploadUtils.upload(ProjectConfig.getUploadPath(), file); Map<String, String> map =new HashMap<>(1);
            logger.info("Return path file name {}", path);
            map.put("path", path);
            return ResultGenerator.genSuccessResult(map);
        }
        return ResultGenerator.genFailResult("Upload failed, please select file"); }}Copy the code

instructions

The code is relatively simple. Directly call the upload method of file uploading tool class FileUploadUtils and pass in two parameters, one parameter is the base path of file uploading and the other parameter is the file to be uploaded. If the upload succeeds, the file name will be returned to the front-end path. Otherwise, the thrown exception will be handled by GlobeExceptionHandler.

Global exception handling

code

No complex logic, relatively simple ☟

@RestControllerAdvice
public class GlobeExceptionHandler {

    @ExceptionHandler(Exception.class)
    public Object handleException(Exception e, HttpServletRequest req) {
        Result result = new Result();
        result.setResultCode(HttpStatus.ERROR);

        // Distinguish whether it is a custom exception
        if (e instanceof CustomException) {
            result.setMessage(e.getMessage());
        } else if (e instanceof FileNameLengthLimitExceededException) {
            result.setMessage("Upload file name too long");
        } else if (e instanceof FileSizeLimitExceededException) {
            result.setMessage("File is too large");
        } else if (e instanceof InvalidExtensionException) {
            result.setMessage("Upload file extension not allowed");
        } else {
            result.setMessage("Unknown error");
        }
        returnresult; }}Copy the code

The interface test

Test the file upload function using Postman ☟

Normal condition:

Abnormal conditions:

  1. File name extensions are not allowed

Console:

Resolved [com.hualei.mybatis_plus_learning.exception.file.InvalidExtensionException: filename : [jfif], extension : [warm.jfif], allowed extension : [[bmp, gif, jpg, jpeg, png, doc, docx, xls, xlsx, ppt, pptx, html, htm, txt, rar, zip, gz, bz2, mp4, avi, rmvb, pdf]]]
Copy the code
  1. The file is too large

Console:

Resolved [com.hualei.mybatis_plus_learning.exception.file.FileSizeLimitExceededException: upload.exceed.maxSize]
Copy the code
  1. File name too long

Console:

Resolved [com.hualei.mybatis_plus_learning.exception.file.FileNameLengthLimitExceededException: upload.filename.exceed.length]
Copy the code

At the end

Writing is not easy, welcome everyone to like, comment, your attention, like is my unremitting power, thank you to see here! Peace and Love.