SpringBoot下载文件文件损坏等问题解决方案

调用接口下载spring boot工程的resources目录下的excel模板文件,非常常见的一个文件下载功能,但是却容易遇到很多坑,下面总结记录下。

问题一:下载的文件名称出现中文乱码的问题

解决方案:

java 复制代码
response.setHeader("Content-Disposition",
                   "attachment;filename=" + new String("下载模板".getBytes("UTF-8"), "ISO8859-1"));
问题二:在swagger中测试下载接口,点击下载的文件,发现文件名是乱码的问题

解决方案:

java 复制代码
response.setHeader("Content-Disposition", "attachment;fileName=" + 
                   URLEncoder.encode("线索导入模板.xlsx","utf8"));

说明:

通过URLEncoder.encode函数对文件名称处理后,无论是在浏览器调用GET请求下载文件,还是Swagger中调用下载接口,都不会出现文件名乱码问题。

问题三:下载的excel文件打开时总是提示部分内容有问题,尝试恢复。

问题原因:

一般有2种情况:

1、由于没有找到文件,下载的文件字节大小为0,这种情况文件完全打不开

2、读取的文件大小和元素文件的大小不一致,这种情况会提升自动修复。

解决办法:

网上最多的解决方案是主动在response的Header中设置Content-Length大小。但这种方式其实是错误的。文件的Content-Length其实可以从返回流中直接获取,并不需要用户主动去设置。这里的问题核心应该是思考:为什么下载的文件和元素文件的大小会不一致?

下面的2个获取inputStream的长度的API,只有在读取磁盘上具体文件中才比较适用。如果是jar包中的文件,是获取不到大小。

加上设置大小:

java 复制代码
response.addHeader("Content-Length",String.valueOf(file.length()));
 //response.addHeader("Content-Length",String.valueOf(inputStream.available()));
问题四:采用BufferedInputStream缓冲流读写文件导致输出文件和原始文件体积差异的问题

由于下载的文件体积总是比元素文件体积大一点点,导致文件打开提示异常修复。

java 复制代码
    outputStream = response.getOutputStream();
    bis = new BufferedInputStream(inputStream);
    //缓冲数组,每次读取1024
    byte[] buff = new byte[1024];
    while (bis.read(buff)!= -1) {
        //异常代码行
        outputStream.write(buff, 0, buff.length);
    }
    outputStream.flush(); 

原因分析:

出现问题的原因就是buff.length,数组声明后长度就是固定的,而不是获取里面读取的内容的字节长度,所以导致这里的buff.length的值始终是1024。

解决方案:

java 复制代码
   outputStream = response.getOutputStream();
    bis = new BufferedInputStream(inputStream);
     //缓冲流,每次读取1024
    byte[] buff = new byte[1024];
    int readLength = 0;
     while (( readLength = bis.read(buff)) != -1) {
         //每次写入缓冲流buff读到的字节长度,而不是buff.length
         outputStream.write(buff, 0, readLength);
     }
    outputStream.flush();
问题五:开发环境下载成功,打成jar包发布到服务器上部署就出现下载失败问题

原因:

Resource下的文件是存在于jar这个文件里面,在磁盘上是没有真实路径存在的,它其实是位于jar内部的一个路径。所以通过ResourceUtils.getFile或者this.getClass().getResource("")方法无法正确获取文件。

解决:

通过ClassPathResource读取文件流

java 复制代码
InputStream inputStream = getClass().getClassLoader().getResourceAsStream("template/template.xlsx");
下载方式

方式一:经典的缓冲流BufferedInputStream读取法,一般读取比较大的文件,优先考虑缓冲流读取方式

方式二:利用spring的FileCopyUtils工具类,小文件优先考虑此方式,代码更简单不易出错。

实例
java 复制代码
1、控制层代码
@Operation(summary = "下载模版",description = "下载模版")
@GetMapping("/download")
public void download(HttpServletResponse response){
    templateService.download(response);
}
java 复制代码
/**
 * 下载线索模板
 * @param response
 */
public void download(HttpServletResponse response) {
    InputStream inputStream = null;
    BufferedInputStream bis = null;
    OutputStream outputStream = null;
    try {     
        inputStream=getClass().getClassLoader().getResourceAsStream("template/template.xlsx");
 
        response.setContentType("application/octet-stream");
        response.setHeader("content-type", "application/octet-stream");
        //待下载文件名
        String fileName = URLEncoder.encode("模板.xlsx","utf8");
        response.setHeader("Content-Disposition", "attachment;fileName=" + fileName);
        outputStream = response.getOutputStream();
        //加上设置大小 下载下来的excel文件才不会在打开前提示修复
        //这里流的长度很难在开始读取前获取,特别是打成jar包后,读取inputStream长度经常失败
        //response.addHeader("Content-Length",String.valueOf(classPathResource.getFile().length()));
       //response.addHeader("Content-Length",String.valueOf(inputStream.available()));
         //方式一:经典的缓冲流BufferedInputStream读取法
         //   bis = new BufferedInputStream(inputStream);
             //缓冲流,每次读取1024
        //    byte[] buff = new byte[1024];
        //    int readLength = 0;
        //     while (( readLength = bis.read(buff)) != -1) {
        //         outputStream.write(buff, 0, readLength);
        //     }
        //     outputStream.flush();
 
         //方式二:利用spring的FileCopyUtils工具类
         if(inputStream!=null){
            byte[] results = FileCopyUtils.copyToByteArray(inputStream);
            outputStream.write(results);
            outputStream.flush();
        }
    } catch ( IOException e ) {
        log.error("文件下载失败,e");
    } finally {
        IOUtils.closeQuietly(outputStream);
        IOUtils.closeQuietly(inputStream);
        IOUtils.closeQuietly(bis);
    }
}
相关推荐
爱勇宝7 小时前
第 1 章:别把“需求文档”当成真正的需求
前端·后端·程序员
IT_陈寒12 小时前
闭包陷阱让我加了两天班,JavaScript你真行
前端·人工智能·后端
易协同低代码13 小时前
通达OA核心类库TD类深度解析
后端
Gopher_HBo13 小时前
Go语言学习笔记(十八)Gin处理Session
后端
谭光志14 小时前
工具塞满上下文窗口怎么办?深度拆解 AI Agent Tool Search 按需加载实现原理
前端·后端·ai编程
她说..14 小时前
Java 默认值设置方式
java·开发语言·后端·springboot
foggyprojects14 小时前
从0开始,一句话启动AI DataAgent
后端·数据分析·ai编程
郡杰14 小时前
一些基础和问题解决
后端
陈随易14 小时前
前端项目部署只要30秒
前端·后端·程序员
YIAN15 小时前
从零手写文件读取 MCP 服务:一文吃透 Model Context Protocol 全链路通信原理
前端·后端·mcp