赞
踩
项目中有一个下载docx模板文件的功能。开发同学反馈:本地测试可以正常下载;部署到测试服务器后,下载的文件为空。
开发环境和测试环境有哪些差异呢?
下载文件为空的代码
/** * 从resources目录中下载docx文件(空文件) * * @return */ @GetMapping("/downloadEmpty") public void downloadEmpty(HttpServletResponse res) { String path = "templates/demo.docx"; ClassLoader classLoader = Demo3Application.class.getClassLoader(); try (InputStream inputStream = classLoader.getResourceAsStream(path); OutputStream outputStream = res.getOutputStream()) { res.addHeader("Content-Disposition", "attachment;filename=empty.docx"); res.addHeader("Content-Length", String.valueOf(inputStream.available())); res.setContentType("application/octet-stream"); byte[] bys = new byte[1024]; int len; while ((len = inputStream.read(bys)) != -1) outputStream.write(bys, 0, len); outputStream.flush(); } catch (IOException e) { e.printStackTrace(); } }
以下代码输出docx文件的一些信息,经分析可知:读取到的inputStream正常,只是inputStream.available() == 0
/** * 输出从resources目录下读取docx文件的信息 * * @return */ @GetMapping("/docx") public String doc() { StringBuilder sb = new StringBuilder(); String path = "templates/demo.docx"; ClassLoader classLoader = Demo3Application.class.getClassLoader(); try (InputStream inputStream = classLoader.getResourceAsStream(path)) { sb.append("path: ") .append(path) .append("<br/>ClassLoader: ") .append(classLoader.getClass()) .append("<br/>InputStream: ") .append(inputStream.getClass()) .append("<br/>inputStream.available: ") .append(inputStream.available()); byte[] bys = new byte[1024]; int len = 0, total = 0; while ((len = inputStream.read(bys)) != -1) total += len; sb.append("<br/>length: " + total); } catch (IOException e) { e.printStackTrace(); } return sb.toString(); /** * 输出示例: * path: templates/demo.docx ClassLoader: class * org.springframework.boot.loader.LaunchedURLClassLoader InputStream: class * org.springframework.boot.loader.data.RandomAccessDataFile$DataInputStream * inputStream.available: 0 * length: 16044 */ }
经测试及分析,注释掉res.addHeader(“Content-Length”, String.valueOf(inputStream.available()));即可;经验证,下载文件正常,代码如下:
/** * 从resources目录中下载docx文件(正常文件) * * @return */ @GetMapping("/download") public void download(HttpServletResponse res) { String path = "templates/demo.docx"; ClassLoader classLoader = Demo3Application.class.getClassLoader(); try (InputStream inputStream = classLoader.getResourceAsStream(path); OutputStream outputStream = res.getOutputStream()) { res.addHeader("Content-Disposition", "attachment;filename=demo.docx"); // res.addHeader("Content-Length", String.valueOf(inputStream.available())); res.setContentType("application/octet-stream"); byte[] bys = new byte[1024]; int len; while ((len = inputStream.read(bys)) != -1) outputStream.write(bys, 0, len); outputStream.flush(); } catch (IOException e) { e.printStackTrace(); } }
经测试及分析,看似下载文件为空的问题,实质是获取到输入流inputStream.available() == 0的问题。为什么会返回0呢?
我们观察到读取docx文件时候,返回的inputStream是org.springframework.boot.loader.data.RandomAccessDataFile$DataInputStream;翻开源码看一下,DataInputStream继承了InputStream,而InputStream的available()方法直接返回了0。
在实际验证问题的过程中并没有这么一帆风顺。起初我使用了一个txt文件来验证此问题,但是无法重现,也就是说,打包jar后也可以通过available()方法获取到文件的大小。测试代码如下:
/** * 输出从resources目录下读取txt文件的信息 * * @return */ @GetMapping("/txt") public String txt() { StringBuilder sb = new StringBuilder(); String path = "templates/demo.txt"; ClassLoader classLoader = Demo3Application.class.getClassLoader(); try (InputStream inputStream = classLoader.getResourceAsStream(path)) { sb.append("path: ") .append(path) .append("<br/>ClassLoader: ") .append(classLoader.getClass()) .append("<br/>InputStream: ") .append(inputStream.getClass()) .append("<br/>inputStream.available: ") .append(inputStream.available()); } catch (IOException e) { e.printStackTrace(); } return sb.toString(); /** * 输出示例: path: templates/demo.txt ClassLoader: class * org.springframework.boot.loader.LaunchedURLClassLoader InputStream: class * org.springframework.boot.loader.jar.ZipInflaterInputStream inputStream.available: 43 */ }
查看源码时候,也得到了验证。txt文件属于压缩文件(DEFLATED),返回inputStream是org.springframework.boot.loader.jar.ZipInflaterInputStream,
而返回inputStream是org.springframework.boot.loader.data.RandomAccessDataFile$DataInputStream的文件属于非压缩文件(STORED)
以上给出了解决方案及原理分析,但并不建议将下载的文件放到resources目录下;可以放到分布式存储或其他文件系统中。
完整的测试代码
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** 需要打成jar包执行 */ @RestController @RequestMapping(value = "/") public class DemoController { /** * 输出从resources目录下读取docx文件的信息 * * @return */ @GetMapping("/docx") public String doc() { StringBuilder sb = new StringBuilder(); String path = "templates/demo.docx"; ClassLoader classLoader = Demo3Application.class.getClassLoader(); try (InputStream inputStream = classLoader.getResourceAsStream(path)) { sb.append("path: ") .append(path) .append("<br/>ClassLoader: ") .append(classLoader.getClass()) .append("<br/>InputStream: ") .append(inputStream.getClass()) .append("<br/>inputStream.available: ") .append(inputStream.available()); byte[] bys = new byte[1024]; int len = 0, total = 0; while ((len = inputStream.read(bys)) != -1) total += len; sb.append("<br/>length: " + total); } catch (IOException e) { e.printStackTrace(); } return sb.toString(); /** * 输出示例: path: templates/demo.docx ClassLoader: class * org.springframework.boot.loader.LaunchedURLClassLoader InputStream: class * org.springframework.boot.loader.data.RandomAccessDataFile$DataInputStream * inputStream.available: 0 length: 16044 */ } /** * 输出从resources目录下读取txt文件的信息 * * @return */ @GetMapping("/txt") public String txt() { StringBuilder sb = new StringBuilder(); String path = "templates/demo.txt"; ClassLoader classLoader = Demo3Application.class.getClassLoader(); try (InputStream inputStream = classLoader.getResourceAsStream(path)) { sb.append("path: ") .append(path) .append("<br/>ClassLoader: ") .append(classLoader.getClass()) .append("<br/>InputStream: ") .append(inputStream.getClass()) .append("<br/>inputStream.available: ") .append(inputStream.available()); } catch (IOException e) { e.printStackTrace(); } return sb.toString(); /** * 输出示例: path: templates/demo.txt ClassLoader: class * org.springframework.boot.loader.LaunchedURLClassLoader InputStream: class * org.springframework.boot.loader.jar.ZipInflaterInputStream inputStream.available: 43 */ } /** * 从resources目录中下载docx文件(空文件) * * @return */ @GetMapping("/downloadEmpty") public void downloadEmpty(HttpServletResponse res) { String path = "templates/demo.docx"; ClassLoader classLoader = Demo3Application.class.getClassLoader(); try (InputStream inputStream = classLoader.getResourceAsStream(path); OutputStream outputStream = res.getOutputStream()) { res.addHeader("Content-Disposition", "attachment;filename=empty.docx"); res.addHeader("Content-Length", String.valueOf(inputStream.available())); res.setContentType("application/octet-stream"); byte[] bys = new byte[1024]; int len; while ((len = inputStream.read(bys)) != -1) outputStream.write(bys, 0, len); outputStream.flush(); } catch (IOException e) { e.printStackTrace(); } } /** * 从resources目录中下载docx文件(正常文件) * * @return */ @GetMapping("/download") public void download(HttpServletResponse res) { String path = "templates/demo.docx"; ClassLoader classLoader = Demo3Application.class.getClassLoader(); try (InputStream inputStream = classLoader.getResourceAsStream(path); OutputStream outputStream = res.getOutputStream()) { res.addHeader("Content-Disposition", "attachment;filename=demo.docx"); // res.addHeader("Content-Length", String.valueOf(inputStream.available())); res.setContentType("application/octet-stream"); byte[] bys = new byte[1024]; int len; while ((len = inputStream.read(bys)) != -1) outputStream.write(bys, 0, len); outputStream.flush(); } catch (IOException e) { e.printStackTrace(); } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。