SpringBoot和Axios异步网络请求库实现文件下载(太他妈的坑了)

准备工作:导入maven jar包

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

第一步:编写前端页面

<!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>下载中心</title>

    <style>
        .txt {
            color: green;
        }
    </style>
</head>
<body>
    <div id="app">
        <h1>下载中心</h1>
        <ol>
            <li><span class="txt">微信小程序项目</span> <a href="javascript:void(0)" onclick="sendDownloadAsync('微信小程序项目.zip','d5cbcd2a-7e08-11ee-bf9c-54e1ad104b87')">下载</a></li>
            <li><span class="txt">Java工程师简历模板</span> <a href="javascript:;" onclick="sendDownloadAsync('Java工程师简历模板.docx','cbae974f-7e08-11ee-bf9c-54e1ad104b87')">下载</a></li>
        </ol>
    </div>

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

    <script>

        // 功能:下载文件
        function fileDownload(res) {
            //headers:"attachment;filename=%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F%E9%A1%B9%E7%9B%AE.zip"
            //表示从响应头中获取文件名
            const fileName = decodeURI(res.headers['content-disposition']).replace(/\w+;filename=(.*)/, '$1')

            console.log("解析的文件名称是",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') //创建一个a标签元素
            link.href = fileUrl //设置a标签的href属性
            link.download = decodeURI(fileName) //设置下载的文件名
            link.style.display = 'none'  //设置为不显示
            document.body.appendChild(link) //将a标签元素加入到页面中
            link.click() //自动触发单击事件

            document.body.removeChild(link);// 下载完成移除元素
            window.URL.revokeObjectURL(fileUrl); // 释放掉blob对象
        }


        // 功能:发起异步文件下载请求
        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();

                // 进行对象装换,避免将后端接口返回的错误信息也当成文件下载
                fileReader.onload = (event) => {
                    try {
                        const data = JSON.parse(event.target.result);
                        if (data.code !== 0) {
                            // 解析成对象失败,说明是普通对象数据
                            console.log(`下载提示: ${data.msg}`);
                            alert(`下载提示: ${data.msg}`)
                        }
                    } catch (err) {
                        // 解析成对象失败,说明是正常的文件流,可进行下载
                        // 正常下载文件
                        fileDownload(res)
                    }
                };
                fileReader.readAsText(data);
            })

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

第二步:编写下载控制器

@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"
            )
    );

    /**
     * 下载文件
     *
     * @param fileName 下载文件名
     * @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, "下载文件不能为空!");
        }
        if (StringUtils.isBlank(apiKey)) {
            throw new BizException(400, "apiKey不能为空!");
        }
        if (!APIKEY_LIST.contains(apiKey)) {
            throw new BizException(400, "apiKey无效,禁止下载文件!");
        }
        File downloadFile = new File(savePath, fileName);
        if (!downloadFile.exists()) {
            throw new BizException(400, "未找到需要下载的文件!");
        }
        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() + "");
        // 禁止浏览器缓存
        /*
        Cache-Control: no-cache, no-store, must-revalidate
        Pragma: no-cache
        Expires: 0
        其中,Cache-Control用于HTTP1.1(包括1.1)以上;Pragma用于HTTP1.0;Expires用于代理服务器缓存。
         */
        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, "文件下载失败");
        }
    }
}

第三步:统一JSON结果

@ApiModel("统一响应结果")
public class CommonResult<T> {

    @ApiModelProperty("响应状态码")
    private int code;

    @ApiModelProperty("响应提示信息")
    private String msg;

    @ApiModelProperty("响应的数据")
    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;
    }
}

 第三步:统一JSON结果  : 编写枚举类 ,进行code 和 msg 的返回

public enum ResultCode {
    SUCCESS(0, "操作成功"),
    FAILURE(1,"操作失败"),
    PARAM_VALID_ERROR(400, "参数检验错误"),
    UNAUTHORIZED(401, "未认证"),
    FORBIDDEN(403, "无权限"),
    SYSTEM_ERROR(500, "系统繁忙,请稍后再试!")

    ;

    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;
    }
}

第四步:全局异常处理器

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;
    }
}
/**
 * 全局异常处理器
 */
@RestControllerAdvice
//@ControllerAdvice
//@ResponseBody
@Slf4j
public class GlobalExceptionHandler {

    /**
     * 处理缺少请求参数异常
     * @param ex HttpMessageNotReadableException
     * @return
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public CommonResult handleMissingException(
            MissingServletRequestParameterException ex) {
        log.error("缺少请求参数,{}", ex.getMessage());
        return new CommonResult(400, "缺少必要的请求参数");
    }


    /**
     * 处理缺少请求参数异常
     * @param ex HttpMessageNotReadableException
     * @return
     */
    @ExceptionHandler(MaxUploadSizeExceededException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public CommonResult handleUploadSizeException(
            MaxUploadSizeExceededException ex) {
        log.error("文件上传最大允许大小超过{}字节。,", ex.getMaxUploadSize());
        return new CommonResult(400, "上传文件大小超过"+ex.getMaxUploadSize()+"B");
    }


    /**
     * 处理请求参数校验异常
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public CommonResult handleBindException(MethodArgumentNotValidException ex){
         log.error("请求参数校验出现问题:{},异常类型:{}", ex.getMessage(), ex.getClass()); // for debug
         String message = ex.getBindingResult().getFieldError().getDefaultMessage();
         return CommonResult.error(400,message);
    }


    /**
     * 处理自定义的业务异常
     */
    @ExceptionHandler(value = BizException.class)
    public CommonResult handleBizException(BizException ex){
        log.error("业务操作问题:{},异常类型:{}", ex.getMessage(), ex.getClass()); // for debug
        return CommonResult.error(ex.getCode(),ex.getMessage());
    }


    /**
     * 处理系统异常
     */
    @ExceptionHandler(value = Exception.class)
    public CommonResult handleException(Exception ex){
        log.error("系统出现问题:{},异常类型:{}", ex.getMessage(), ex.getClass()); // for debug
        return CommonResult.error();
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值