使用AWS Java SDK上传文件至S3时MD5计算失败,报FileNotFoundException
S3文件上传EC2生产环境异常排查与修复
问题概述
使用AWS SDK实现S3存储桶文件上传功能,本地环境运行正常,但部署到EC2生产实例时出现文件找不到的错误。
代码片段
上传逻辑
File fileObj = DocumentUtils.convertMultiPartFileToFile(file); User user = userService.getUserDetailsViaHttpHeader(httpHeaders); String filePath = user.getId().toString() + "/" + "KMLFiles" + "/" + "KML" + "_" + System.currentTimeMillis() + ".kml"; PutObjectResult putObjectRequest = s3Client.putObject(awsBucketName, filePath, fileObj);
MultipartFile转File方法
public static File convertMultiPartFileToFile(MultipartFile file) { File convertedFile = new File(file.getOriginalFilename()); try (FileOutputStream fos = new FileOutputStream(convertedFile)) { fos.write(file.getBytes()); } catch (IOException e) { } return convertedFile; }
依赖配置
<dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-java-sdk-s3</artifactId> <version>1.12.370</version> </dependency>
错误日志
May 13 19:26:52 ip-172-31-27-27 java[20208]: 2023-05-13 19:26:52.717 DEBUG 20208 --- [nio-9096-exec-4] o.s.web.servlet.DispatcherServlet : Failed to complete request: com.amazonaws.SdkClientException: Unable to calculate MD5 hash: Test.kml (No such file or directory) May 13 19:26:52 ip-172-31-27-27 java[20208]: 2023-05-13 19:26:52.721 ERROR 20208 --- [nio-9096-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is com.amazonaws.SdkClientException: Unable to calculate MD5 hash: Test.kml (No such file or directory)] with root cause May 13 19:26:52 ip-172-31-27-27 java[20208]: java.io.FileNotFoundException: Test.kml (No such file or directory) May 13 19:26:52 ip-172-31-27-27 java[20208]: #011at java.base/java.io.FileInputStream.open0(Native Method) ~[na:na] May 13 19:26:52 ip-172-31-27-27 java[20208]: #011at java.base/java.io.FileInputStream.open(FileInputStream.java:219) ~[na:na] May 13 19:26:52 ip-172-31-27-27 java[20208]: #011at java.base/java.io.FileInputStream.<init>(FileInputStream.java:157) ~[na:na] May 13 19:26:52 ip-172-31-27-27 java[20208]: #011at com.amazonaws.util.Md5Utils.computeMD5Hash(Md5Utils.java:97) ~[aws-java-sdk-core-1.12.370.jar!/:na] May 13 19:26:52 ip-172-31-27-27 java[20208]: #011at com.amazonaws.util.Md5Utils.md5AsBase64(Md5Utils.java:104) ~[aws-java-sdk-core-1.12.370.jar!/:na] May 13 19:26:52 ip-172-31-27-27 java[20208]: #011at com.amazonaws.services.s3.AmazonS3Client.getInputStream(AmazonS3Client.java:1920) ~[aws-java-sdk-s3-1.12.370.jar!/:na] May 13 19:26:52 ip-172-31-27-27 java[20208]: #011at com.amazonaws.services.s3.AmazonS3Client.uploadObject(AmazonS3Client.java:1842) ~[aws-java-sdk-s3-1.12.370.jar!/:na] May 13 19:26:52 ip-172-31-27-27 java[20208]: #011at com.amazonaws.services.s3.AmazonS3Client.putObject(AmazonS3Client.java:1821) ~[aws-java-sdk-s3-1.12.370.jar!/:na] May 13 19:26:52 ip-172-31-27-27 java[20208]: #011at com.amazonaws.services.s3.AmazonS3Client.putObject(AmazonS3Client.java:1746) ~[aws-java-sdk-s3-1.12.370.jar!/:na]
额外观察
- 本地环境上传功能运行完全正常
- 生产环境S3访问凭证配置正确
问题根源
- 路径与权限问题:
convertMultiPartFileToFile方法中直接用文件名创建文件,默认会在应用当前工作目录生成文件。EC2实例中应用进程可能没有该目录的写入权限,导致文件实际未创建成功。 - 异常被掩盖:方法中的空
catch块吞掉了文件写入时的IO异常,无法及时发现文件创建失败的问题,直到AWS SDK读取文件时才抛出找不到文件的错误。
修复方案
方案一:修复临时文件创建逻辑
使用系统临时目录创建文件,确保权限合法,同时添加异常处理:
import org.apache.commons.io.FilenameUtils; import java.io.IOException; import java.io.FileOutputStream; import java.io.File; public static File convertMultiPartFileToFile(MultipartFile file) throws IOException { // 基于原文件名后缀创建临时文件,避免文件名冲突 File convertedFile = File.createTempFile("kml-temp-", "." + FilenameUtils.getExtension(file.getOriginalFilename())); try (FileOutputStream fos = new FileOutputStream(convertedFile)) { fos.write(file.getBytes()); } // JVM退出时自动删除临时文件,避免磁盘占用 convertedFile.deleteOnExit(); return convertedFile; }
方案二:优化为直接流上传(推荐)
跳过本地文件创建步骤,直接使用MultipartFile的输入流上传到S3,彻底避免本地文件相关问题:
User user = userService.getUserDetailsViaHttpHeader(httpHeaders); String filePath = user.getId().toString() + "/KMLFiles/KML_" + System.currentTimeMillis() + ".kml"; // 构建文件元数据,设置ContentType和文件大小 ObjectMetadata metadata = new ObjectMetadata(); metadata.setContentType(file.getContentType()); metadata.setContentLength(file.getSize()); // 使用输入流直接上传 PutObjectResult putObjectRequest = s3Client.putObject( new PutObjectRequest(awsBucketName, filePath, file.getInputStream(), metadata) );
说明
- 方案一解决了权限和路径问题,同时保留了文件上传的原有逻辑;
- 方案二更高效,减少了本地IO操作,避免了所有本地文件相关的潜在问题,是推荐的优化方向;
- 无论采用哪种方案,都不要吞掉IO异常,需抛出或打印日志,方便后续排查问题。
内容的提问来源于stack exchange,提问作者Aditya Rewari
相关产品推荐
相关产品推荐

