快速自定义一个带进度监控的文件资源类

快速自定义一个带进度监控的文件资源类

在平时开发中,我们常常会遇到一些需要调用接口,使用form-data方式向接口上传本地文件的request操作。今天记录一个好用的带进度监控的文件资源管理类的开发,实现代码如下:

复制代码
import org.springframework.core.io.AbstractResource;
import org.springframework.core.io.InputStreamSource;
import java.io.InputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class ProgressMonitorFileResource extends AbstractResource implements InputStreamSource {
    private final File file;
    private final ProgressCallback callback;

    // 进度回调接口
    public interface ProgressCallback {
        void update(long bytesRead, long contentLength);
    }

    public ProgressMonitorFileResource(File file, ProgressCallback callback) {
        this.file = file;
        this.callback = callback;
    }

    @Override
    public String getDescription() {
        return "File: " + file.getAbsolutePath();
    }

    @Override
    public InputStream getInputStream() throws IOException {
        return new ProgressMonitoringInputStream(
            new FileInputStream(file),
            file.length(),
            callback
        );
    }

    @Override
    public long contentLength() throws IOException {
        return file.length();
    }

    @Override
    public String getFilename() {
        return file.getName();
    }

    // 带进度监控的输入流
    private static class ProgressMonitoringInputStream extends InputStream {
        private final InputStream delegate;
        private final long totalBytes;
        private long bytesRead = 0;
        private final ProgressCallback callback;

        public ProgressMonitoringInputStream(InputStream delegate, 
                                            long totalBytes,
                                            ProgressCallback callback) {
            this.delegate = delegate;
            this.totalBytes = totalBytes;
            this.callback = callback;
        }

        @Override
        public int read() throws IOException {
            int data = delegate.read();
            if (data != -1) {
                bytesRead++;
                notifyProgress();
            }
            return data;
        }

        @Override
        public int read(byte[] b, int off, int len) throws IOException {
            int bytes = delegate.read(b, off, len);
            if (bytes != -1) {
                bytesRead += bytes;
                notifyProgress();
            }
            return bytes;
        }

        private void notifyProgress() {
            if (callback != null) {
                callback.update(bytesRead, totalBytes);
            }
        }

        @Override
        public void close() throws IOException {
            delegate.close();
        }
    }
}

在现有代码中的集成方式

可以很方便地在我们已有的代码里面调用它:

复制代码
private HttpEntity<MultiValueMap<String, Object>> buildMultipartRequest(File pdfFile) {
    MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
    
    // 使用带进度监控的资源
    body.add("file", new ProgressMonitorFileResource(pdfFile, (read, total) -> {
        double progress = (double) read / total * 100;
        System.out.printf("上传进度: %.2f%% (%d/%d bytes)%n", 
                        progress, read, total);
    }));

    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.MULTIPART_FORM_DATA);
    
    return new HttpEntity<>(body, headers);
}

实现效果示例

当上传一个100MB的PDF文件时,控制台会输出:

复制代码
上传进度: 0.24% (262144/104857600 bytes)
上传进度: 0.49% (524288/104857600 bytes)
上传进度: 0.73% (786432/104857600 bytes)
...
上传进度: 99.99% (104857600/104857600 bytes)

实现原理剖析

  1. 自定义Resource类

    • 继承AbstractResource实现Spring的资源抽象
    • 实现InputStreamSource接口提供输入流
    • 重写getInputStream()返回带监控的输入流
  2. 进度监控流

    • 包装原始FileInputStream
    • 重写read()方法统计读取字节数
    • 每次读取后通过回调接口通知进度
  3. 回调机制

    • 使用函数式接口ProgressCallback
    • 支持Lambda表达式实现自定义处理逻辑

高级配置选项

1. 进度采样(降低回调频率)
复制代码
private void notifyProgress() {
    if (callback != null && (bytesRead % 65536 == 0 || bytesRead == totalBytes)) {
        callback.update(bytesRead, totalBytes);
    }
}
2. 线程安全处理(适用于UI更新)
复制代码
// 在回调中使用Platform.runLater更新UI
body.add("file", new ProgressMonitorFileResource(pdfFile, (read, total) -> {
    Platform.runLater(() -> {
        progressBar.setProgress((double) read / total);
    });
}));
3. 上传速度计算
复制代码
// 在回调中添加时间统计
private long lastUpdateTime = System.currentTimeMillis();

private void notifyProgress() {
    long now = System.currentTimeMillis();
    long elapsed = now - lastUpdateTime;
    
    if (elapsed > 1000) { // 每秒更新一次
        double speed = (bytesRead - lastBytes) / (elapsed / 1000.0);
        System.out.printf("速度: %.2f KB/s%n", speed / 1024);
        
        lastUpdateTime = now;
        lastBytes = bytesRead;
    }
}

生产环境注意事项

  1. 性能优化

    • 避免在回调中执行耗时操作
    • 使用采样率控制回调频率
    • 对大量小文件上传禁用进度监控
  2. 异常处理

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
    try {
    // ...原有逻辑
    } catch (IOException e) {
    callback.onError(e);
    throw e;
    }
    }

  3. 资源释放

    @Override
    public void close() throws IOException {
    try {
    delegate.close();
    } finally {
    callback.onComplete();
    }
    }

  4. 大文件支持

  • 使用内存映射文件优化读取性能

    public InputStream getInputStream() throws IOException {
    return new ProgressMonitoringInputStream(
    Files.newInputStream(file.toPath()),
    file.length(),
    callback
    );
    }

完整调用示例

复制代码
public static void main(String[] args) {
    File pdfFile = new File("large-file.pdf");
    
    MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
    body.add("file", new ProgressMonitorFileResource(pdfFile, 
        (read, total) -> {
            System.out.println("已上传: " + humanReadableByteCount(read) 
                + "/" + humanReadableByteCount(total));
        }));
    
    // 发送请求(使用RestTemplate)
    // ...
}

// 辅助方法:格式化字节显示
private static String humanReadableByteCount(long bytes) {
    int unit = 1024;
    if (bytes < unit) return bytes + " B";
    int exp = (int) (Math.log(bytes) / Math.log(unit));
    String pre = "KMGTPE".charAt(exp-1) + "i";
    return String.format("%.1f %sB", 
        bytes / Math.pow(unit, exp), 
        pre);
}

常见问题解决方案

问题现象 可能原因 解决方案
进度回调不触发 文件太小(<1MB) 增加采样间隔
上传速度异常 网络波动 添加平滑处理(移动平均)
内存占用过高 未使用流式上传 确保使用InputStream方式
进度超过100% 文件大小计算不准确 使用File.length()获取准确值
相关推荐
wniuniu_13 小时前
运维运维运维
java·运维·dubbo
Sagittarius_A*13 小时前
边缘检测:基础算子到高级边缘提取【计算机视觉】
人工智能·python·opencv·计算机视觉
深蓝电商API14 小时前
Selenium 截图与元素高亮定位技巧
爬虫·python·selenium
鱼跃鹰飞20 小时前
设计模式系列:工厂模式
java·设计模式·系统架构
好好学习啊天天向上20 小时前
C盘容量不够,python , pip,安装包的位置
linux·python·pip
时见先生20 小时前
Python库和conda搭建虚拟环境
开发语言·人工智能·python·自然语言处理·conda
我是伪码农20 小时前
Vue 1.23
前端·javascript·vue.js
二十雨辰20 小时前
[python]-循环语句
服务器·python
a努力。20 小时前
国家电网Java面试被问:混沌工程在分布式系统中的应用
java·开发语言·数据库·git·mysql·面试·职场和发展
Yvonne爱编码20 小时前
Java 四大内部类全解析:从设计本质到实战应用
java·开发语言·python