一、问题背景
模板文件存放在 resource 目录下受控,前端点击下载模板按钮时需要获取并下载对应的模板文件。
二、问题描述
通过 ClassPathResource 获取对应的模板文件资源后,写入到返回流中下载,下载完后点击图片显示文件已损坏无法打开。
三、相关代码
public void downloadTemplate(@RequestBody JSONObject jsonObject, HttpServletResponse response) {
try {
String fileName = jsonObject.get("fileName").toString();
String businessType = jsonObject.get("businessType").toString();
String fileType = jsonObject.get("fileType").toString();
fileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
response.addHeader(HttpHeaders.CONTENT_DISPOSITION, ("attachment;filename=" + fileName));
response.setHeader(HttpHeaders.CONTENT_TYPE, "application/octet-stream");
byte[] fileByte = batchImportService.downLoadTemplate(businessType, fileType);
OutputStream os = response.getOutputStream();
os.write(fileByte);
}
catch (UnsupportedEncodingException e) {
LOGGER.warn("Invalid file name");
}
catch (BaseAppException e) {
LOGGER.warn("Download file error");
}
catch (IOException e) {
LOGGER.warn("Create file error");
}
}
public byte[] downLoadTemplate(String businessType, String fileType) throws BaseAppException {
String innerFileName = businessType + FilenameUtils.EXTENSION_SEPARATOR_STR + fileType;
String resourceFullName = "/excel/templates/" + innerFileName;
byte[] bytes = new byte[0];
try {
ClassPathResource resource = new ClassPathResource(resourceFullName);
InputStream inputStream = resource.getInputStream();
AssertUtil.notNull(inputStream, "Unable to get resource file!");
bytes = IOUtils.toByteArray(inputStream);
}
catch (IOException e) {
logger.error(e);
ExceptionHandler.publish("41700056", ExceptionHandler.BUSS_ERROR);
}
return bytes;
}
四、排查思路
1. 文件本身损
点击原模板文件可以正常打开,新建 Excel 文件替换资源文件路径下的模板文件,仍然下载失败。
2. Content-type 不一致
response.addHeader(HttpHeaders.CONTENT_TYPE, "application/x-download");
response.setHeader(HttpHeaders.CONTENT_TYPE, "application/octet-stream");
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
以上方式均仍有问题
3. InputStream 转换 byte 时未指定长度
使用 IOUtils.toByteArray(InputStream input, int size) 仍有问题
4. 工具类问题
使用 StreamUtils.copy(inputStream, outputStream) 仍有问题
5. 文件存放路径问题
当前文件存在在 resource 资源文件目录中,编译后会被压缩到 jar 包中,将模板文件存在在本机目录下,通过 FileInputStream 获取本机目录下的模板文件,可以成功下载并打开
五、问题原因
(一) 资源文件存放路径
Spring Boot 编译后的 JAR 包中的资源文件通常是被压缩的。Spring Boot 使用了内嵌的 Tomcat、Jetty 或 Undertow 等 Servlet 容器,这意味着整个应用程序(包括静态资源文件)都被打包到一个可执行的 JAR 文件中。
Spring Boot 提供了一些默认的静态资源文件路径(如 /static、/public、/resources 等),可以将静态资源文件放置在这些路径下,它们会被打包到 JAR 文件中并在应用程序运行时被正确加载
在 JAR 文件中,资源文件通常是被压缩存储的,这样可以减小 JAR 文件的体积,同时也方便了部署和分发。
(二) Maven编译过程中的资源过滤
Maven 构建过程中的资源过滤是指在构建项目时,Maven 会根据配置的规则,对项目中的资源文件进行文本替换操作,以便将资源文件中的占位符替换为实际的值。这个过程通常用于将一些动态的值(比如版本号、环境变量等)注入到资源文件中,以便在构建后得到正确的资源文件。
xslx 文件是一种二进制文件格式,如果在 Maven 构建过程中对其进行了错误的文本替换,可能会导致文件损坏。
六、问题修复
通过 Maven 配置过滤资源文件中的 xslx 文件
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
<configuration>
<encoding>UTF-8</encoding>
<nonFilteredFileExtensions>
<nonFilteredFileExtension>xlsx</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>
当使用 Maven Resources Plugin 进行资源过滤时,通常会对所有资源文件进行过滤处理。但有些文件,例如二进制文件或者已经经过其他处理的文件,不适合进行过滤。这时就可以使用 nonFilteredFileExtension 标签指定这些文件的扩展名,以告诉 Maven Resources Plugin 不要对这些文件进行过滤处理。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。