使用 springboot + vue3 + jsch 实现远程sftp文件管理 示例

连接

@PostMapping("/connect")  
public RestBean<Void> connect(@RequestBody SftpConnectionVO vo) {  
    String connected = sftpService.connect(vo);  
    return connected == null ? RestBean.success() : RestBean.failure(400, "连接失败,请检查服务器是否开放端口");  
}

// SftpUtils.java
public static Map<String, SftpInfo> sftpMap = new ConcurrentHashMap<>();  
  
    public String getSftpConnection(SftpConnectionVO vo) {  
        // 定义一个 JSch 对象,用于执行 SFTP 操作  
        JSch jsch = new JSch();  
        Session session = null;  
        Channel channel = null;  
        ChannelSftp sftp = null;  
        try {  
            log.info("准备连接 {},用户名 {},密码 {},端口 {}", vo.getHost(), vo.getUsername(), vo.getPassword(), vo.getPort());  
            // 通过 JSch 对象,根据用户名,地址,端口,获取一个 SFTP 会话对象  
            session = jsch.getSession(vo.getUsername(), vo.getHost(), vo.getPort());  
            // 设置 SFTP 会话的密码  
            session.setPassword(vo.getPassword());  
            // 设置 SFTP 会话的配置,关闭严格的主机密钥检查  
            session.setConfig("StrictHostKeyChecking", "no");  
            session.setTimeout(5000);  
            // 连接 SFTP 会话  
            session.connect();  
            // 通过 SFTP 会话,打开一个 SFTP 通道  
            channel = session.openChannel("sftp");  
            // 连接 SFTP 通道  
            channel.connect();  
            // 把 SFTP 通道转换成 ChannelSftp 对象  
            sftp = (ChannelSftp) channel;  
            sftpMap.put(vo.getHost(), new SftpInfo(session, channel, sftp));  
            log.info("{} 已经连接", vo.getHost());  
            return null;  
        } catch (JSchException e) { 
            String message = e.getMessage();  
            if (message.equals("Auth fail")) { 
                log.error("连接Sftp失败,用户名或密码错误,登录失败");  
                return "登录Sftp失败,用户名或密码错误";  
            } else if (message.contains("Connection refused")) {  
                log.error("连接Sftp失败,连接被拒绝,可能是没有启动Sftp服务或是放开端口");  
                return "连接被拒绝,可能是没有启动Sftp服务或是放开端口";  
            } else { // 如果异常信息是其他情况,表示 Sftp 连接出现其他错误  
                log.error("连接Sftp时出现错误", e);  
                return "连接Sftp时出现错误";  
            }  
        }  
    }

@Data  
public class SftpInfo {  
    private final Session session;  
    private final Channel channel;  
    private final ChannelSftp sftp;  
  
    public SftpInfo(Session session, Channel channel, ChannelSftp sftp) {  
        this.session = session;  
        this.channel = channel;  
        this.sftp = sftp;  
    }  
}

上传 下载功能实现

// SftpController.java
@PostMapping("/upload")  
public RestBean<Void> handleFileUpload(@RequestParam("file") MultipartFile file, @RequestParam String host) {  
    sftpUtils.handleFileUpload(file,host);  
    return RestBean.success();  
}

@GetMapping("/downloadFile")  
public ResponseEntity<InputStreamResource> downloadFile(@RequestParam String host, @RequestParam String file) {  
    return sftpUtils.downloadFile(host, java.net.URLDecoder.decode(file, StandardCharsets.UTF_8));  
}

// SftpUtils.java
private static ChannelSftp getChannelSftp(String host) {  
    SftpInfo sftpInfo = sftpMap.get(host);  
    if (sftpInfo == null) {  
        throw new com.example.controller.exception.SftpException(400, "Sftp连接不存在");  
    }  
    ChannelSftp sftp = sftpInfo.getSftp();  
    return sftp;  
}

public void handleFileUpload(MultipartFile file, String host) {  
    ChannelSftp sftp = getChannelSftp(host);  
    try {  
        sftp.put(file.getInputStream(), file.getOriginalFilename());  
    } catch (SftpException | IOException e) {  
        throw new com.example.controller.exception.SftpException(400, "上传失败:文件已存在或权限不足");  
    }  
}

public ResponseEntity<InputStreamResource> downloadFile(String host, String file) {  
    try {  
        ChannelSftp sftp = getChannelSftp(host);  
        InputStream inputStream = sftp.get(file);  
        byte[] fileBytes = IOUtils.toByteArray(inputStream);  
  
        HttpHeaders headers = new HttpHeaders();  
        String encodedFilename = URLEncoder.encode(file, StandardCharsets.UTF_8.name());//用utf8编码 否则中文文件名无法传输  
        headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encodedFilename);  
  
        return ResponseEntity.ok()  
                .headers(headers)  
                .contentLength(fileBytes.length)  
                .contentType(MediaType.APPLICATION_OCTET_STREAM)  
                .body(new InputStreamResource(new ByteArrayInputStream(fileBytes)));  
    } catch (IOException | SftpException e) {  
        e.printStackTrace();  
        return ResponseEntity.badRequest().body(null);  
    }  
}


//pwd cd 功能
public void cd(String host, String path) {  
    ChannelSftp sftp = getChannelSftp(host);  
    try {  
        sftp.cd(path);  
    } catch (SftpException e) {  
        throw new com.example.controller.exception.SftpException(400, "目录不存在");  
    }  
}  
  
public String pwd(String host) throws SftpException {  
    ChannelSftp sftp = getChannelSftp(host);  
    return sftp.pwd();  
}

// 关闭连接
public void closeConnection(String host) {  
    // 获取指定host的SFTP连接信息  
    SftpInfo sftpInfo = sftpMap.get(host);  
    if (sftpInfo != null) {  
        // 通过连接信息获取会话和通道  
        Session session = sftpInfo.getSession();  
        Channel channel = sftpInfo.getChannel();  
        try {  
            // 关闭SFTP通道  
            if (channel != null && channel.isConnected()) {  
                channel.disconnect();  
            }  
            // 关闭会话  
            if (session != null && session.isConnected()) {  
                session.disconnect();  
            }  
        } finally {  
            // 从缓存连接池中移除  
            sftpMap.remove(host);  
            log.info("SFTP connection to {} closed", host);  
        }  
    }  
}

与前端交互

// 前端使用elementui plus 绑定表格数据
<div>  
    <div class="navigation">  
      <el-breadcrumb :separator-icon="ArrowRight">  
        <el-breadcrumb-item v-for="(item, index) in path" :key="index">  
          <el-tag @click="cd(index)" effect="light">{{ item }}</el-tag>  
        </el-breadcrumb-item>      </el-breadcrumb>    </div>  
<!--    <div @click.right.native="showContextMenu($event)">-->  
    <div style="width: 500px">  
      <div class="op">  
        <el-button @click="getFiles">获取文件列表</el-button>  
        <el-button @click="pwd">pwd</el-button>  
        <el-button type="primary" plain @click="openNewFileModal" :icon="DocumentAdd">新建</el-button>  
        <el-button type="success" plain @click="handleDownload" :icon="Download">下载</el-button>  
        <el-popover placement="right" :width="400" trigger="click">  
          <template #reference>  
            <el-button type="success" plain :icon="Upload">上传</el-button>  
          </template>          <el-upload              class="upload-demo"  
              drag  
              :action="`${axios.defaults.baseURL}/api/sftp/upload?host=${props.info.host}`"  
              :on-success="getFiles"  
              multiple  
          >  
            <el-icon class="el-icon--upload"><upload-filled /></el-icon>  
            <div class="el-upload__text">  
              拖拽 <em>或点击上传</em>  
            </div>            <template #tip>  
              <div class="el-upload__tip">  
                在这里上传文件  
              </div>  
            </template>          </el-upload>        </el-popover>  
      </div>        <el-table class="mac-table" :data="files" max-height="650px"  
                  @row-click="handleItemClick" style="width: 100%"  
                  v-loading="fileLoading" element-loading-text="文件加载中..."  
                  @selection-change="handleSelectionChange"  
                  :default-sort="{prop: 'name', order: 'ascending'}">  
          <el-table-column type="selection" width="40"></el-table-column>  
          <el-table-column prop="name" label="名称" width="160" sortable>  
            <template #default="{ row }">  
              <div class="cell-content">  
                <el-icon v-if="row.directory"><Folder/></el-icon>  
                <el-icon v-else><Document/></el-icon>  
                {{ row.name }}  
              </div>  
            </template>          </el-table-column>          <el-table-column prop="size" width="100" label="大小"></el-table-column>  
          <el-table-column prop="modifyTime" width="180" label="修改时间" sortable></el-table-column>  
        </el-table>      </div>  
  </div>
// 发送下载请求
function handleDownload() {  
  if (select.value.length === 0) {  
    ElMessage.warning('请选择要下载的文件');  
    return;  
  }  
  const files = select.value.map(file => file.name);  
  files.forEach(file => {  
    axios.get('api/sftp/downloadFile?host=' + props.info.host + '&file=' + encodeURIComponent(file), { responseType: 'blob' })  
        .then(response => {  
          const url = window.URL.createObjectURL(new Blob([response.data]));  
          const link = document.createElement('a');  
          link.href = url;  
          link.setAttribute('download', file);  // 使用文件的原始名称作为下载的文件名  
          document.body.appendChild(link);  
          link.click();  
          document.body.removeChild(link);  
        })  
        .catch(error => {  
          ElMessage.error(`下载文件${file}时出错: ${error}`);  
        });  
  });  
}

效果

GIF 1.gif

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇