使用JSCH通过SFTP上传文件至远程服务器时如何展示上传进度
Web服务器通过JSch上传文件到远程SFTP阶段进度条实现方案
你卡壳的核心原因是两个上传阶段的进度采集逻辑完全不同:前端到Web服务器的进度由浏览器侧监听HTTP上传事件采集,Web服务器到远程SFTP的进度需要在服务端采集,再通过通信通道同步给前端,具体落地步骤如下:
一、JSch侧进度采集实现
JSch原生提供了进度监听接口,直接实现SftpProgressMonitor即可拿到实时传输字节数,不需要自己包装流做计数:
- 自定义进度监听器,每批次数据传输完成后计算进度,存入可跨请求访问的缓存
import com.jcraft.jsch.SftpProgressMonitor; import java.util.UUID; import java.util.concurrent.atomic.AtomicLong; public class SftpUploadProgressMonitor implements SftpProgressMonitor { private final String taskId; private long totalFileSize = 0; private final AtomicLong transferredBytes = new AtomicLong(0); // 每个上传任务生成唯一taskId,用于和前端请求做匹配 public SftpUploadProgressMonitor(String taskId, long localFileSize) { this.taskId = taskId; this.totalFileSize = localFileSize; } @Override public void init(int op, String src, String dest, long max) { // 部分场景下JSch会自动识别文件总大小,覆盖传入的初始值 this.totalFileSize = max > 0 ? max : this.totalFileSize; transferredBytes.set(0); // 初始化时写入进度0 ProgressCache.set(taskId, 0); } @Override public boolean count(long batchBytes) { long currentTransferred = transferredBytes.addAndGet(batchBytes); int progress = (int) Math.min(100, (currentTransferred * 100) / totalFileSize); // 将当前进度写入缓存,注意缓存需设置过期时间(建议30~60分钟),避免内存泄漏 ProgressCache.set(taskId, progress); // 返回false可主动中断传输 return true; } @Override public void end() { // 传输完成强制写入100%进度 ProgressCache.set(taskId, 100); } // 对外暴露生成taskId的工具方法 public static String generateTaskId() { return UUID.randomUUID().toString().replace("-", ""); } }
- 上传SFTP时注入自定义监听器,不要调用无监听器的重载方法
// 前端文件上传到Web服务器本地临时目录完成后,先生成唯一任务ID String sftpTaskId = SftpUploadProgressMonitor.generateTaskId(); // 将taskId先返回给前端,前端拿到后启动进度查询/监听 try { channelSftp.put( new FileInputStream(localTempFile), remoteTargetPath, new SftpUploadProgressMonitor(sftpTaskId, localTempFile.length()), ChannelSftp.OVERWRITE ); } catch (Exception e) { // 传输异常时写入失败状态,供前端识别 ProgressCache.set(sftpTaskId, -1); throw e; }
注意:如果你的服务是多实例部署,不要用本地内存做进度缓存,替换为Redis这类集中式缓存,避免前端进度查询请求打到其他实例时拿不到数据
二、前后端进度同步方案
根据你的项目并发量和技术栈二选一即可:
方案1:短轮询(实现成本最低,适合低并发场景)
- 前端完成第一阶段(前端→Web服务器)上传,拿到返回的
sftpTaskId后,启动定时器,每300ms发一次GET请求,携带taskId查询当前SFTP传输进度 - 后端提供进度查询接口,根据taskId从缓存中读取进度值返回,包含三种状态:传输中(0~99的数值)、完成(100)、失败(-1)
- 前端拿到进度值后更新进度条,当进度为100或-1时,清除定时器,展示对应结果
轮询间隔不要小于100ms,否则会给服务端增加不必要的请求压力
方案2:SSE推送(实时性好,适合中高并发场景)
SSE是HTTP协议自带的单向服务端推送能力,实现成本比WebSocket低,完全适配进度上报场景:
- 前端拿到
sftpTaskId后,调用SSE连接接口,和服务端建立长连接,绑定当前taskId - 后端监听器的
count方法更新进度时,直接将进度值通过SSE连接推送给对应前端,不需要前端反复发请求 - 传输完成/失败/连接超时后,主动关闭SSE连接即可
三、常见踩坑
- 不要直接调用
channelSftp.put(fileInputStream, remotePath)这类不带进度监听器的重载方法,这类方法不会触发进度回调 - 进度计算时要做边界判断,避免总文件大小为0时触发除零异常
- 大文件分片上传到SFTP的场景,需要累加所有分片的传输字节数计算总进度,不要只统计单个分片的进度
- 缓存必须设置过期时间,避免传输中断的任务残留数据占用内存
内容的提问来源于stack exchange,提问作者user19398829
相关产品推荐
相关产品推荐

