Java11+SpringBoot上传OTC后Zip文件锁定无法删除目录
问题概述
- 开发环境:
Java 11、Spring Boot v2.3.4 - 业务流程:
- 从S3桶下载文件到本地临时目录
- 压缩为Zip文件
- 将Zip文件上传至OTC云
- 删除步骤1创建的目录(问题出在此处)
核心问题
执行上传操作后,临时目录下的Zip文件被JVM进程锁定,导致无法删除目录。省略uploadObjectFromLocal()方法时可正常删除,说明锁由该上传方法导致。项目运行时手动删除也会提示文件被占用,仅能通过停服释放锁后删除。需修复上传方法的文件锁定问题,实现无需停服即可删除目录。
临时目录结构
-> Folder1 -> subfolder -> one.csv -> two.csv -> subfolder.zip -> Folder2 -> subfolder -> one.csv -> two.csv -> subfolder.zip
业务代码逻辑
compressToZip.zipFiles("subfolder path"); // 例如Folder1下的子文件夹 log.info("successfully zipped"); otcOperations.uploadObjectFromLocal("Key", "subfolder.zip"); // 上传对应Zip文件 log.info("successfully uploaded zip files"); // 将主文件夹路径加入列表待删除,第二次循环加入Folder2路径 deleteDirectoryList.add("Folder1 path");
OTC上传方法代码(otcOperations.uploadObjectFromLocal())
// Necessary imports.. import com.amazonaws.services.s3.model.S3ObjectInputStream; import lombok.extern.slf4j.Slf4j; import okhttp3.*; import org.apache.logging.log4j.util.Strings; import org.apache.tika.Tika; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; // Method public void uploadObjectFromLocal(String key,String filePath) throws Exception { File file = new File(filePath); Tika tika = new Tika(); String mimeType = tika.detect(file); Map<String, String[]> headers = new HashMap<String, String[]>(); Map<String, String> queries = new HashMap<String, String>(); queries.put("acl", null); String authSignature = headerSignature("POST", headers, queries, bucketName, key, filePath,null); //for policy String policy = "{ \"expiration\": \"2024-12-31T12:00:00.000Z\",\n" + "\"conditions\": [\n" + "{\"bucket\": \""+bucketName+"\"},\n" + "{\"key\": \""+key+"\"},\n" + "{\"content-type\": \""+mimeType+"\"},\n" + "{\"success_action_status\":\"201\"}\n" + "]\n" + "}"; String base64EncodedPolicy =Base64.getEncoder().encodeToString(policy.getBytes("utf-8")); String policySignature = calculateHMACSHA1(otcSK, base64EncodedPolicy); OkHttpClient client = new OkHttpClient().newBuilder() .build(); RequestBody body = new MultipartBody.Builder().setType(MultipartBody.FORM) .addFormDataPart("key",key) .addFormDataPart("AccessKeyId",otcAK) .addFormDataPart("Signature",policySignature) .addFormDataPart("content-type", mimeType) .addFormDataPart("policy",base64EncodedPolicy) .addFormDataPart("success_action_status","201") .addFormDataPart("bucket",bucketName) .addFormDataPart("file",key, RequestBody.create(MediaType.parse("application/octet-stream"), new File(filePath))) .build(); Request request = new Request.Builder() .url("URL") .method("POST", body) .addHeader("x-obs-acl", "private") .addHeader("x-obs-storage-class", "STANDARD") .addHeader("Authorization", authSignature) .build(); try (Response response = client.newCall(request).execute()){ if(response.code() != 201){ throw new Exception("Error creating object. Error: "+response.body().string()); } } }
异常信息
2024-02-02 18:43:43.626 INFO 11636 --- [ scheduling-1] awsotcservice.AwsOtcServiceApplication : successfully zipped 2024-02-02 18:43:56.861 INFO 11636 --- [ scheduling-1] awsotcservice.AwsOtcServiceApplication : successfully uploaded zip files 2024-02-02 18:44:03.229 ERROR 11636 --- [ scheduling-1] o.s.s.s.TaskUtils$LoggingErrorHandler : Unexpected error occurred in scheduled task java.io.IOException: Unable to delete file: C:\Users\Rj\AppData\Local\Temp\FOLDER1\subfolder.zip at org.apache.commons.io.FileUtils.forceDelete(FileUtils.java:2381) ~[commons-io-2.5.jar:2.5] at org.apache.commons.io.FileUtils.cleanDirectory(FileUtils.java:1679) ~[commons-io-2.5.jar:2.5] at org.apache.commons.io.FileUtils.deleteDirectory(FileUtils.java:1575) ~[commons-io-2.5.jar:2.5] at awsotcservice.AwsOtcServiceApplication.scheduledOperation(AwsOtcServiceApplication.java:111) ~[classes/:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na] at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:84) ~[spring-context-5.2.9.RELEASE.jar:5.2.9.RELEASE] at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54) ~[spring-context-5.2.9.RELEASE.jar:5.2.9.RELEASE] at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:93) ~[spring-context-5.2.9.RELEASE.jar:5.2.9.RELEASE] at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) ~[na:na] at java.base/java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:264) ~[na:na] at java.base/java.util.concurrent.FutureTask.run(FutureTask.java) ~[na:na] at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304) ~[na:na] at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[na:na] at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[na:na] at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]
手动删除现象
项目运行时手动删除目录,系统提示文件被占用无法删除。
解决方案
问题根源是OkHttp的RequestBody.create(File)未正确释放文件流,导致文件句柄被持有。可通过以下方式修复:
手动控制文件流关闭
改用InputStream构建请求体,通过try-with-resources确保流被关闭:// 替换原请求体中file部分的代码 try (InputStream fileInputStream = new FileInputStream(file)) { byte[] fileBytes = new byte[(int) file.length()]; fileInputStream.read(fileBytes); RequestBody fileBody = RequestBody.create(MediaType.parse("application/octet-stream"), fileBytes); RequestBody body = new MultipartBody.Builder().setType(MultipartBody.FORM) .addFormDataPart("key",key) .addFormDataPart("AccessKeyId",otcAK) .addFormDataPart("Signature",policySignature) .addFormDataPart("content-type", mimeType) .addFormDataPart("policy",base64EncodedPolicy) .addFormDataPart("success_action_status","201") .addFormDataPart("bucket",bucketName) .addFormDataPart("file", key, fileBody) .build(); // 后续请求逻辑不变 }确保响应体资源释放
在处理响应时显式关闭响应体,避免资源泄漏:try (Response response = client.newCall(request).execute()){ if(response.code() != 201){ String errorMsg = response.body().string(); response.body().close(); throw new Exception("Error creating object. Error: "+errorMsg); } response.body().close(); }升级OkHttp并复用客户端
若使用旧版OkHttp,存在文件句柄泄漏风险,建议升级至v4.x版本,同时将OkHttpClient配置为单例复用,避免频繁创建客户端导致资源无法及时回收。
内容的提问来源于stack exchange,提问作者Harsh Shiyani
相关产品推荐
相关产品推荐

