You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java11+SpringBoot上传OTC后Zip文件锁定无法删除目录

问题概述
  • 开发环境:Java 11、Spring Boot v2.3.4
  • 业务流程:
    1. 从S3桶下载文件到本地临时目录
    2. 压缩为Zip文件
    3. 将Zip文件上传至OTC云
    4. 删除步骤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)未正确释放文件流,导致文件句柄被持有。可通过以下方式修复:

  1. 手动控制文件流关闭
    改用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();
        
        // 后续请求逻辑不变
    }
    
  2. 确保响应体资源释放
    在处理响应时显式关闭响应体,避免资源泄漏:

    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();
    }
    
  3. 升级OkHttp并复用客户端
    若使用旧版OkHttp,存在文件句柄泄漏风险,建议升级至v4.x版本,同时将OkHttpClient配置为单例复用,避免频繁创建客户端导致资源无法及时回收。


内容的提问来源于stack exchange,提问作者Harsh Shiyani

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 02:45:56