一、问题背景
笔者在开发一个Spring Boot项目时,遇到了一个棘手的问题:通过后端接口下载位于src/main/resources目录下的Excel(.xlsx)文件,前端接收到的文件总是损坏的,无法正常打开。检查发现,下载后的文件大小与原始文件不一致,这表明文件内容在传输或处理过程中被修改了。
经过反复排查代码,从Controller层的文件读取和响应头设置,到Service层的业务逻辑,均未发现明显错误。然而,问题依旧存在,这让我不得不将目光投向项目构建工具——Maven。
二、问题根源分析
经过一番探索和查阅资料,笔者最终定位到问题根源在于Maven的资源过滤(resource filtering)机制。
(一)Maven资源过滤机制
Maven在构建项目时,默认会开启资源过滤功能。它会扫描src/main/resources目录下的所有文件,并尝试替换其中用${...}包裹的占位符。这个功能在处理application.properties或application.yml等文本配置文件时非常有用,可以方便地实现动态配置。
然而,当这个机制应用于二进制文件(如Excel、Word、PDF、图片、压缩包等)时,就会引发灾难。Maven会错误地将这些二进制文件当作文本文件处理,扫描其中的字节流。一旦文件中的某些字节序列恰好符合${...}的模式,Maven就会尝试进行替换,从而破坏文件的原始二进制结构,导致文件损坏。
这就是为什么下载后的文件大小会发生变化,并且无法正常打开的原因。
(二)问题复现
为了更直观地理解这个问题,我们可以设想一个场景:
一个.xlsx文件的二进制内容中,可能存在一个字节序列恰好是24 7b 61 70 70 2e 6e 61 6d 65 7d,这在ASCII编码中恰好对应字符串${app.name}。如果你的pom.xml或application.properties中定义了app.name属性,Maven就会用该属性的值去替换这个字节序列,从而导致Excel文件结构被破坏。
三、解决方案
既然问题出在Maven的资源过滤上,那么解决方案就是阻止Maven对特定的二进制文件进行过滤。
我们可以通过配置maven-resources-plugin插件来实现这一目标。在pom.xml文件的<build> -> <plugins>标签下,添加或修改以下配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| <build> <plugins> <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> <nonFilteredFileExtension>xls</nonFilteredFileExtension> <nonFilteredFileExtension>doc</nonFilteredFileExtension> <nonFilteredFileExtension>docx</nonFilteredFileExtension> <nonFilteredFileExtension>pdf</nonFilteredFileExtension> <nonFilteredFileExtension>png</nonFilteredFileExtension> <nonFilteredFileExtension>gif</nonFilteredFileExtension> <nonFilteredFileExtension>jpg</nonFilteredFileExtension> <nonFilteredFileExtension>jpeg</nonFilteredFileExtension> <nonFilteredFileExtension>zip</nonFilteredFileExtension> <nonFilteredFileExtension>rar</nonFilteredFileExtension> </nonFilteredFileExtensions> </configuration> </plugin> </plugins> </build>
|
配置解析
- **
<nonFilteredFileExtensions>**:这个标签是整个解决方案的核心。它告诉Maven,在进行资源过滤时,跳过所有列出的文件扩展名。
- **
<nonFilteredFileExtension>**:每个该标签定义一个不需要过滤的文件扩展名。
通过以上配置,Maven在打包时将不会处理.xlsx、.xls、.pdf等二进制文件,而是将它们原封不动地复制到目标目录(target/classes),从而保证了文件的完整性和正确性。
四、代码实现参考
虽然问题出在Maven配置,但一个健壮的文件下载接口代码同样重要。这里提供一个标准的Spring Boot文件下载接口实现,以供参考。
(一)目录结构
1 2 3 4
| src/main/resources/ └── templates/ └── excel/ └── user_template.xlsx
|
(二)Controller代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; 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; import java.net.URLEncoder;
@RestController @RequestMapping("/api/download") public class FileDownloadController {
@GetMapping("/excel-template") public void downloadExcelTemplate(HttpServletResponse response) { ClassPathResource classPathResource = new ClassPathResource("/template/template.xlsx");
if (!classPathResource.exists()) { response.sendError(HttpServletResponse.SC_NOT_FOUND, "Template file not found"); return; }
response.reset(); response.setContentType("application/octet-stream"); String filename = "template.xlsx"; response.addHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(filename, "UTF-8")); response.setContentLengthLong(classPathResource.contentLength());
InputStream is = null; BufferedInputStream inputStream = null; try { is = classPathResource.getInputStream(); inputStream = new BufferedInputStream(is); ServletOutputStream outputStream = response.getOutputStream(); byte[] b = new byte[1024]; int len; while ((len = inputStream.read(b)) > 0) { outputStream.write(b, 0, len); } outputStream.flush(); } catch (IOException e) { e.printStackTrace(); throw new IOException("模版下载失败"); } finally { if (inputStream != null) { inputStream.close(); } if (is != null) { is.close(); } } } }
|
五、总结
当遇到后端下载的二进制文件损坏时,应优先排查是否是Maven或Gradle等构建工具的资源过滤机制导致。在Spring Boot项目中,通过在pom.xml中配置maven-resources-plugin并指定<nonFilteredFileExtensions>,可以有效避免此类问题的发生。
这个小小的配置项,体现了项目构建过程中细节的重要性,也是每一位Java开发者都应该了解的知识点。