SpringBoot and Axios asynchronous network request library implement file download (too damn pitfalls)

Preparation: Import the maven jar package

<dependency>
    <groupId>org.webjars.npm</groupId>
    <artifactId>axios</artifactId>
    <version>1.5.1</version>
</dependency>

Step 1: Write the front-end page

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Download Center</title>

    <style>
        .txt {
            color: green;
        }
    </style>
</head>
<body>
    <div id="app">
        <h1>Download Center</h1>
        <ol>
            <li><span class="txt">WeChat Mini Program Project</span> <a href="javascript:void(0)" onclick="sendDownloadAsync('WeChat Mini Program Project.zip','d5cbcd2a-7e08-11ee-bf9c -54e1ad104b87')">Download</a></li>
            <li><span class="txt">Java Engineer Resume Template</span> <a href="javascript:;" onclick="sendDownloadAsync('Java Engineer Resume Template.docx','cbae974f-7e08-11ee-bf9c-54e1ad104b87' )">Download</a></li>
        </ol>
    </div>

    <script src="/webjars/axios/1.5.1/dist/axios.js"></script>

    <script>

        // Function: Download file
        function fileDownload(res) {
            //headers:"attachment;filename=WeChat Mini Program Project.zip"
            //Indicates getting the file name from the response header
            const fileName = decodeURI(res.headers['content-disposition']).replace(/\w + ;filename=(.*)/, '$1')

            console.log("The parsed file name is ",fileName)

            //const blob = new Blob([data], {type: headers['content-type']})
            const blob = new Blob([res.data], { type: "application/octet-stream"});
            const fileUrl = window.URL.createObjectURL(blob)

            let link = document.createElement('a') //Create an a tag element
            link.href = fileUrl //Set the href attribute of the a tag
            link.download = decodeURI(fileName) //Set the downloaded file name
            link.style.display = 'none' //Set to not display
            document.body.appendChild(link) //Add the a tag element to the page
            link.click() //Automatically trigger click event

            document.body.removeChild(link);//Remove element after download is complete
            window.URL.revokeObjectURL(fileUrl); // Release the blob object
        }


        // Function: Initiate an asynchronous file download request
        function sendDownloadAsync(fileName,apiKey){
            axios({
                method: 'POST',
                url: '/api/download/file',
                data: {
                    fileName,
                    apiKey
                },
                headers: {
                    //"Authorization": localStorage.getItem("token")
                    "Content-Type": "application/x-www-form-urlencoded"
                },
                responseType: 'blob'
            }).then(res => {
                const data = res.data;
                const fileReader = new FileReader();

                // Carry out object replacement to avoid downloading the error message returned by the back-end interface as a file.
                fileReader.onload = (event) => {
                    try {
                        const data = JSON.parse(event.target.result);
                        if (data.code !== 0) {
                            // Failed to parse into an object, indicating that it is ordinary object data
                            console.log(`Download prompt: ${data.msg}`);
                            alert(`Download prompt: ${data.msg}`)
                        }
                    } catch (err) {
                        // Failed to parse into an object, indicating that it is a normal file stream and can be downloaded
                        //Download file normally
                        fileDownload(res)
                    }
                };
                fileReader.readAsText(data);
            })

        }
    </script>
</body>
</html>

Step 2: Write the download controller

@RestController
@RequestMapping("/api/download")
public class DownloadController {

    @Autowired
    private UploadProperties uploadProperties;

    // Redis
    private static final List<String> APIKEY_LIST = new ArrayList<>(
            Arrays.asList(
                    "d5cbcd2a-7e08-11ee-bf9c-54e1ad104b87",
                    "cbae974f-7e08-11ee-bf9c-54e1ad104b87"
            )
    );

    /**
     * download file
     *
     * @param fileName Download file name
     * @param apiKey apiKey
     */
    @PostMapping("/file")
    public void downloadFile(@RequestParam("fileName") String fileName,
                                     @RequestParam("apiKey") String apiKey,
                                     HttpServletResponse response) throws UnsupportedEncodingException, FileNotFoundException, BizException {
        final String savePath = uploadProperties.getSavePath();
        if (StringUtils.isBlank(fileName)) {
            throw new BizException(400, "The downloaded file cannot be empty!");
        }
        if (StringUtils.isBlank(apiKey)) {
            throw new BizException(400, "apiKey cannot be empty!");
        }
        if (!APIKEY_LIST.contains(apiKey)) {
            throw new BizException(400, "apiKey is invalid, file downloading is prohibited!");
        }
        File downloadFile = new File(savePath, fileName);
        if (!downloadFile.exists()) {
            throw new BizException(400, "The file to be downloaded was not found!");
        }
        File path = new File(ResourceUtils.getURL("classpath:").getPath());
        System.out.println("path?>?>>>>>>>>>>>>>>>>>>>>>>>>" + path);
        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));
        response.setContentType("application/octet-stream");
        response.addHeader("Content-Length", downloadFile.length() + "");
        // Disable browser caching
        /*
        Cache-Control: no-cache, no-store, must-revalidate
        Pragma: no-cache
        Expires: 0
        Among them, Cache-Control is used for HTTP1.1 (including 1.1) and above; Pragma is used for HTTP1.0; Expires is used for proxy server caching.
         */
        response.addHeader("Cache-Control", "no-cache, no-store, must-revalidate");
        response.addHeader("Pragma", "no-cache");
        response.addHeader("Expires", "0");

        byte[] buffer = new byte[8 * 1024]; // 8kb
        int readBytes = 0;
        try (
                BufferedInputStream bis = new BufferedInputStream(new FileInputStream(downloadFile));
                BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream());
        ) {
            while ((readBytes = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, readBytes);
                bos.flush();
            }
            response.flushBuffer();
            APIKEY_LIST.remove(apiKey);

        } catch (Exception ex) {
            throw new BizException(500, "File download failed");
        }
    }
}

Step 3: Unify JSON results

@ApiModel("Unified response result")
public class CommonResult<T> {

    @ApiModelProperty("Response status code")
    private int code;

    @ApiModelProperty("Response prompt information")
    private String msg;

    @ApiModelProperty("Response data")
    private T data;

    public CommonResult(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public CommonResult(ResultCode resultCode) {
        this.code = resultCode.getCode();
        this.msg = resultCode.getMsg();
    }

    public CommonResult(int code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public static <T> CommonResult<T> success(T data) {
        return new CommonResult<T>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMsg(), data);
    }

    public static <T> CommonResult<T> success(String msg) {
        return new CommonResult<T>(0,msg);
    }

    public static <T> CommonResult<T> fail(ResultCode resultCode) {
        return new CommonResult<T>(resultCode.getCode(), resultCode.getMsg());
    }


    public static <T> CommonResult<T> fail(int code, String msg) {
        return new CommonResult<T>(code,msg);
    }

    public static <T> CommonResult<T> error() {
        return new CommonResult<T>(ResultCode.SYSTEM_ERROR);
    }

    public static <T> CommonResult<T> error(int code,String msg) {
        return new CommonResult<T>(code,msg);
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

Step 3: Unify JSON results: write enumeration class to return code and msg

public enum ResultCode {
    SUCCESS(0, "Operation successful"),
    FAILURE(1,"Operation failed"),
    PARAM_VALID_ERROR(400, "Parameter verification error"),
    UNAUTHORIZED(401, "Uncertified"),
    FORBIDDEN(403, "No permission"),
    SYSTEM_ERROR(500, "The system is busy, please try again later!")

    ;

    private int code;
    private String msg;

    ResultCode(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}

Step 4: Global exception handler

public class BizException extends Exception{
    private int code;
    private String message;


    public BizException(ResultCode resultCode) {
        super(resultCode.getMsg());
        this.code = resultCode.getCode();
        this.message = resultCode.getMsg();
    }


    public BizException(int code,String message) {
        super(message);
        this.code = code;
        this.message = message;
    }

    public BizException(int code,String message, Throwable cause) {
        super(message, cause);
        this.code = code;
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    @Override
    public String getMessage() {
        return message;
    }
}
/**
 * Global exception handler
 */
@RestControllerAdvice
//@ControllerAdvice
//@ResponseBody
@Slf4j
public class GlobalExceptionHandler {

    /**
     * Handle missing request parameter exception
     * @param ex HttpMessageNotReadableException
     * @return
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public CommonResult handleMissingException(
            MissingServletRequestParameterException ex) {
        log.error("Missing request parameters, {}", ex.getMessage());
        return new CommonResult(400, "Missing necessary request parameters");
    }


    /**
     * Handle missing request parameter exception
     * @param ex HttpMessageNotReadableException
     * @return
     */
    @ExceptionHandler(MaxUploadSizeExceededException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public CommonResult handleUploadSizeException(
            MaxUploadSizeExceededException ex) {
        log.error("The maximum allowed file upload size exceeds {} bytes.,", ex.getMaxUploadSize());
        return new CommonResult(400, "The uploaded file size exceeds" + ex.getMaxUploadSize() + "B");
    }


    /**
     * Handle request parameter verification exceptions
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public CommonResult handleBindException(MethodArgumentNotValidException ex){
         log.error("There is a problem with request parameter verification: {}, exception type: {}", ex.getMessage(), ex.getClass()); // for debug
         String message = ex.getBindingResult().getFieldError().getDefaultMessage();
         return CommonResult.error(400,message);
    }


    /**
     * Handle customized business exceptions
     */
    @ExceptionHandler(value = BizException.class)
    public CommonResult handleBizException(BizException ex){
        log.error("Business operation problem: {}, exception type: {}", ex.getMessage(), ex.getClass()); // for debug
        return CommonResult.error(ex.getCode(),ex.getMessage());
    }


    /**
     * Handle system exceptions
     */
    @ExceptionHandler(value = Exception.class)
    public CommonResult handleException(Exception ex){
        log.error("System problem: {}, exception type: {}", ex.getMessage(), ex.getClass()); // for debug
        return CommonResult.error();
    }
}

The knowledge points of the article match the official knowledge files, and you can further learn relevant knowledge. Cloud native entry-level skills treeHomepageOverview 16993 people are learning the system