SpringBoot 整合sftp 实现文件的上传和下载

1.引入pom文件依赖

<!--sftp核心依赖包-->
<dependency>
    <groupId>com.jcraft</groupId>
    <artifactId>jsch</artifactId>
    <version>0.1.54</version>
</dependency>

1.1、pom文件详情

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.11</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.biao</groupId>
    <artifactId>super</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>super</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.26</version>
            <scope>provided</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3.1</version>
        </dependency>
        <!--数据库驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
        </dependency>
        <!--自动生成器-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.5.2</version>
        </dependency>
        <!--velocity模板引擎-->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.3</version>
        </dependency>
        <dependency>
            <groupId>org.jeecgframework.boot</groupId>
            <artifactId>codegenerate</artifactId>
            <version>1.4.3</version>
        </dependency>
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.22</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fhs-opensource</groupId>
            <artifactId>easy-trans-spring-boot-starter</artifactId>
            <version>2.0.12</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
        <dependency>
            <groupId>com.jcraft</groupId>
            <artifactId>jsch</artifactId>
            <version>0.1.54</version>
        </dependency>
        <dependency>
            <groupId>commons-net</groupId>
            <artifactId>commons-net</artifactId>
            <version>3.6</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.5</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2.项目目录结构预览

SpringBoot 整合sftp 实现文件的上传和下载_sftp

3.编写控制器

import com.biao.chaoji.service.ISFTPService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;

@Slf4j
@RestController
@RequestMapping("/super/sftp")
public class SFTPController {

    @Autowired
    private ISFTPService isftpService;

    @PostMapping("/tsetUp")
    public boolean tsetUpFile(){
        return isftpService.testUp();
    }

    /**
     * 文件上传
     * @param file 需要上传的文件
     * @return
     */
    @PostMapping("/up")
    public boolean upFile(@RequestPart("File")MultipartFile file) {
        return isftpService.upFile(file);
    }

    /**
     * 文件下载
     * @param directory     SFTP服务器的文件路径
     * @param downloadFile  SFTP服务器上的文件名
     * @param saveFile      保存到本地路径
     * @return
     */
    @GetMapping("/getFile")
    public boolean getFile(String directory, String downloadFile, String saveFile, String fileName) throws IOException {
        return isftpService.getFile(directory,downloadFile,saveFile,fileName);
    }

}

4.编写Service

import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;

public interface ISFTPService {

    //测试上传
    boolean testUp();

    boolean upFile(MultipartFile file);

    boolean getFile(String directory, String downloadFile, String saveFile, String fileName) throws IOException;
}

5.编写ServiceImpl

import com.biao.chaoji.entity.sftp.SftpAccountInfo;
import com.biao.chaoji.entity.sftp.SftpChannelInfo;
import com.biao.chaoji.service.ISFTPService;
import com.biao.chaoji.util.SftpUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;

@Service
public class ISFTPServiceImpl implements ISFTPService {



    @Override
    public boolean testUp() {
        //基础信息
        SftpChannelInfo channelInfo = getChannelInfo();

        String path = "E:\\tu\\1.jpg";
        int readData = 0;
        FileInputStream fileInputStream = null;

        try {
            //创建 FileInputStream 对象,用于读取文件
            fileInputStream = new FileInputStream(path);
            //如果返回-1,表示读取完毕
            while ((readData = fileInputStream.read()) != -1){
                //传输文件
                SftpUtils.uploadFile(channelInfo,fileInputStream,"wlb","1.jpg");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //关闭文件流,释放资源
            try {
                fileInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        SftpUtils.close(channelInfo);
        return true;
    }

    @Override
    public boolean upFile(MultipartFile file){
        if (file==null){
            return false;
        }

        //基础信息
        SftpChannelInfo channelInfo = getChannelInfo();

        byte [] byteArr = new byte[]{};

        try{
            byteArr = file.getBytes();
        } catch (IOException e){
            e.printStackTrace();
        }
        InputStream inputStream = new ByteArrayInputStream(byteArr);
        SftpUtils.uploadFile(channelInfo,inputStream,"wlb",file.getOriginalFilename());

        //关闭SFTP链接
        SftpUtils.close(channelInfo);
        return true;
    }

    @Override
    public boolean getFile(String directory, String downloadFile, String saveFile, String fileName) throws IOException {
        //基础信息
        SftpChannelInfo channelInfo = getChannelInfo();
        InputStream inputStream = SftpUtils.downloadFile(channelInfo, directory, downloadFile);

        int index;
        byte[] bytes = new byte[1024];
        OutputStream os = null;
        try {
            os = new FileOutputStream(saveFile +"/"+ fileName);
            while ((index = inputStream.read(bytes)) != -1){
                os.write(bytes, 0 , index);
                os.flush();
            }
        } catch (IOException e){
            e.printStackTrace();
        } finally {
            os.close();
            inputStream.close();
            //关闭SFTP链接
            SftpUtils.close(channelInfo);
        }


        return true;
    }

    private SftpChannelInfo getChannelInfo(){
        //基础信息
        SftpAccountInfo accountInfo = new SftpAccountInfo();
        accountInfo.setProtocol("sftp");
        accountInfo.setIp("xxx.xxx.xxx.xxx");
        accountInfo.setAccount("sftpuser");
        accountInfo.setPassword("password");
        accountInfo.setPort(22);
        SftpChannelInfo channelInfo = SftpUtils.createSftpChannelInfo(accountInfo);
        return channelInfo;
    }

}

6.编写工具类

6.1、IoUtils

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import org.springframework.http.MediaType;

import java.io.*;

@Slf4j
public class IoUtils {

    /**
     * 获取封装得MultipartFile
     *
     * @param inputStream inputStream
     * @param fileName    fileName
     * @return MultipartFile
     */
    public static MultipartFile getMultipartFile(InputStream inputStream, String fileName) {
        FileItem fileItem = createFileItem(inputStream, fileName);
        //CommonsMultipartFile是feign对multipartFile的封装,但是要FileItem类对象
        return new CommonsMultipartFile(fileItem);
    }


    /**
     * FileItem类对象创建
     *
     * @param inputStream inputStream
     * @param fileName    fileName
     * @return FileItem
     */
    private static FileItem createFileItem(InputStream inputStream, String fileName) {
        FileItemFactory factory = new DiskFileItemFactory(16, null);
        String textFieldName = "file";
        FileItem item = factory.createItem(textFieldName, MediaType.MULTIPART_FORM_DATA_VALUE, true, fileName);
        int bytesRead = 0;
        byte[] buffer = new byte[8192];
        OutputStream os = null;
        //使用输出流输出输入流的字节
        try {
            os = item.getOutputStream();
            while ((bytesRead = inputStream.read(buffer, 0, 8192)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            inputStream.close();
        } catch (IOException e) {
            log.error("Stream copy exception", e);
            throw new IllegalArgumentException("文件上传失败");
        } finally {
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    log.error("Stream close exception", e);

                }
            }
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    log.error("Stream close exception", e);
                }
            }
        }

        return item;
    }

}

6.2、IoUtils

import com.biao.chaoji.entity.sftp.SftpAccountInfo;
import com.biao.chaoji.entity.sftp.SftpChannelInfo;
import com.biao.chaoji.exception.ServiceException;
import com.jcraft.jsch.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StopWatch;

import java.io.*;
import java.util.Properties;
import java.util.Vector;

@Slf4j
public class SftpUtils {

    /**
     * session连接的最大超时
     */
    private static final int SESSION_CONNECT_TIMEOUT = 30000;

    /**
     * 通道连接的最大超时
     */
    private static final int CHANNEL_CONNECT_TIMEOUT = 2000;

    /**
     * 创建sftpChannel对象
     * @param sftpInfo
     * @return
     */
    public static SftpChannelInfo createSftpChannelInfo(SftpAccountInfo sftpInfo) {
        SftpChannelInfo info = new SftpChannelInfo(sftpInfo);
        Session sshSession = getSshSession(sftpInfo);
        ChannelSftp channelSftp = getSftpChannel(sftpInfo, sshSession);
        info.setSshSession(sshSession);
        info.setChannelSftp(channelSftp);
        return info;
    }

    /**
     * 获取sftp通道
     * @param sftpInfo
     * @param sshSession
     * @return
     */
    private static ChannelSftp getSftpChannel(SftpAccountInfo sftpInfo, Session sshSession) {
        ChannelSftp channelSftp = null;
        StopWatch watch = new StopWatch();
        watch.start();
        try {
            Channel channel = sshSession.openChannel("sftp");
            channel.connect(CHANNEL_CONNECT_TIMEOUT);
            channelSftp = (ChannelSftp) channel;
            channelSftp.setFilenameEncoding(sftpInfo.getEncoding());
            watch.stop();
            log.info("打开sftp channel时间:{}毫秒, num:{}, channel:{}", watch.getTotalTimeMillis(), watch.getTaskCount(), channel.toString());
            return channelSftp;
        } catch (Exception e) {
            exceptionClose(sshSession, channelSftp);
            log.error("连接sftp通道失败", e);
            throw new ServiceException("连接sftp通道失败", e.getMessage());
        }
    }

    /**
     * 获取sftp会话
     * @param sftpInfo
     * @return
     */
    private static Session getSshSession(SftpAccountInfo sftpInfo) {
        log.info("ip: {}, port: {}, account: {}", sftpInfo.getIp(), sftpInfo.getPort(), sftpInfo.getAccount());
        StopWatch watch = new StopWatch();
        watch.start();
        Session session = null;
        try {
            session = new JSch().getSession(sftpInfo.getAccount(), sftpInfo.getIp(), sftpInfo.getPort());
            if (null == session) {
                log.error("-1", "sshsession is null");
                throw new ServiceException("sshsession is null");
            }

            // 设置首次登录提示, 可选值: (ask | yes | no)
            Properties sshConfig = new Properties();
            sshConfig.put("StrictHostKeyChecking", "no");
            session.setConfig(sshConfig);
            session.setPassword(sftpInfo.getPassword());
            session.connect(SESSION_CONNECT_TIMEOUT);
            watch.stop();
            log.info("打开sftp session时间: {}毫秒。 num: {}, session: {}", watch.getTotalTimeMillis(), watch.getTaskCount(), session.toString());
            return session;
        } catch (Exception e) {
            exceptionClose(session, null);
            log.error("连接sftp会话失败", e);
            throw new ServiceException("连接sftp会话失败, ip:" + sftpInfo.getIp()+ ":" + sftpInfo.getPort(), e.getMessage());
        }
    }

    /**
     * 关闭session和channelSftp
     * @param session
     * @param channelSftp
     */
    private static void exceptionClose(Session session, ChannelSftp channelSftp) {
        try {
            if (null != channelSftp) {
                log.info("异常关闭sftp channel会话前, channel: {}, channel is open: {} and close: {}", channelSftp.toString(), channelSftp.isConnected(), channelSftp.isClosed());
                channelSftp.disconnect();
                log.info("异常关闭sftp channel会话后, channel: {}, channel is open: {} and close: {}", channelSftp.toString(), channelSftp.isConnected(), channelSftp.isClosed());
            }
        } catch (Exception e) {
            log.error("关闭SftpChannelInfo.channelSftp异常", e);
        }

        try {
            if (null != session) {
                log.info("异常关闭sftp session会话前, session: {}, session is open: {}", session.toString(), session.isConnected());
                session.disconnect();
                log.info("异常关闭sftp session会话后, session: {}, session is open: {}", session.toString(), session.isConnected());
            }
        } catch (Exception e) {
            log.error("关闭SftpChannelInfo.session异常", e);
        }
    }

    /**
     * 关闭ChannelExec
     * @param shell
     */
    private static void close(ChannelExec shell) {
         try {
             if (null != shell) {
                 log.info("关闭ChannelExec会话前, shell:{}, shell is open: {} and close:{}", shell.toString(), shell.isConnected(), shell.isClosed());
                 shell.disconnect();
                 log.info("关闭ChannelExec会话后, shell:{}, shell is open: {} and close:{}", shell.toString(), shell.isConnected(), shell.isClosed());
             }
         } catch (Exception e) {
             log.error("关闭ChannelExec异常", e);
         }
    }

    /**
     * 关闭输入流
     * @param inputStream
     */
    public static void close(InputStream inputStream) {
        try {
            if (null != inputStream) {
                log.info("关闭InputStream会话前, InputStream:{}", inputStream.toString());
                inputStream.close();
                log.info("关闭InputStream会话后, InputStream:{}", inputStream.toString());
            }
        } catch (Exception e) {
            log.error("关闭InputStream异常", e);
        }
    }

    /**
     * 关闭输出流
     * @param outputStream
     */
    public static void close(OutputStream outputStream) {
        try {
            if (null != outputStream) {
                log.info("关闭OutputStream会话前, outputStream:{}", outputStream.toString());
                outputStream.close();
                log.info("关闭OutputStream会话后, outputStream:{}", outputStream.toString());
            }
        } catch (Exception e) {
            log.error("关闭OutputStream异常", e);
        }
    }


    /**
     * 关闭会话
     * @param sftpChannelInfo
     */
    public static void close(SftpChannelInfo sftpChannelInfo) {
        String sftpChannelInfoAddr = null;
        if (null != sftpChannelInfo) {
            sftpChannelInfoAddr = sftpChannelInfo.toString();
        }

        Session session = null;
        if (null != sftpChannelInfo && null != sftpChannelInfo.getSshSession()) {
            session = sftpChannelInfo.getSshSession();
        }

        ChannelSftp channelSftp = null;
        if (null != sftpChannelInfo && null != sftpChannelInfo.getChannelSftp()) {
            channelSftp = sftpChannelInfo.getChannelSftp();
        }

        try {
            log.info("关闭sftp session会话开始,sftpChannelInfo:{}", sftpChannelInfoAddr);
            if (null != session) {
                log.info("关闭sftp session会话前, sftpChannelInfo: {}, session:{}, session is open:{}",
                        sftpChannelInfoAddr, session.toString(), session.isConnected());
                session.disconnect();
                log.info("关闭sftp session会话前, sftpChannelInfo: {}, session:{}, session is open:{}",
                        sftpChannelInfoAddr, session.toString(), session.isConnected());
            }
        } catch (Exception e) {
            log.error("关闭SftpChannelInfo.SshSession异常", e);
        }

        try {
            log.info("关闭sftp channel会话开始,sftpChannelInfo:{}", sftpChannelInfoAddr);
            if (null != channelSftp) {
                log.info("关闭sftp channelSftp会话前, sftpChannelInfo: {}, channel:{}, channel is open:{} and close: {}",
                        sftpChannelInfoAddr, channelSftp.toString(), channelSftp.isConnected(), channelSftp.isClosed());
                channelSftp.disconnect();
                log.info("关闭sftp channelSftp会话前, sftpChannelInfo: {}, channel:{}, channel is open:{} and close: {}",
                        sftpChannelInfoAddr, channelSftp.toString(), channelSftp.isConnected(), channelSftp.isClosed());
            }
        } catch (Exception e) {
            log.error("关闭关闭SftpChannelInfo.channelSftp异常", e);
        }
    }

    public static void mkdirs(String dir, SftpChannelInfo sftpChannelInfo) {
        log.info("创建目录[{}]start", dir);
        try {
            ChannelSftp channelSftp = sftpChannelInfo.getChannelSftp();
            // 进入根目录, 否则进入的是sftp用户的home目录
            channelSftp.cd("/");
            String[] folders = dir.split("/");
            for (String folder : folders) {
                if (null == folder || folder.trim().length() == 0) {
                    log.error("创建folder[{}]失败", folder);
                    throw new ServiceException("创建目录失败");
                }

                if (!checkDirExist(folder, sftpChannelInfo)) {

                }
            }
        } catch (Exception e) {
            log.error("创建目录[{}]失败", dir, e);
            throw new ServiceException("创建目录失败", e.getMessage());
        }
    }

    /**
     * 判断目录是否重载
     * @param dir
     * @param sftpChannelInfo
     * @return
     */
    private static boolean checkDirExist(String dir, SftpChannelInfo sftpChannelInfo) {
        try {
            ChannelSftp channelSftp = sftpChannelInfo.getChannelSftp();
            Vector<?> vector = channelSftp.ls(dir);
            if (null == vector) {
                return false;
            }

            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 删除文件
     *
     * @param channelInfo SFTP会话对象
     * @param deleteFile 要删除的文件路径,如:/root/test/saveFile/mylog.log
     */
    public static void deleteFile(SftpChannelInfo channelInfo, String deleteFile) {
        try {
            channelInfo.getChannelSftp().rm(deleteFile);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 文件上传
     *
     * @param fileStram 文件输入流
     * @param upToPath  要上传到的文件夹路径
     * @param fileName  上传后的文件名
     */
    public static void uploadFile(SftpChannelInfo channelInfo, InputStream fileStram, String upToPath, String fileName) {
        upToPath = null != upToPath && upToPath.endsWith("/") ? upToPath : upToPath + "/";
        try {
            channelInfo.getChannelSftp().put(fileStram, upToPath + fileName);
        } catch (SftpException e) {
            e.printStackTrace();
        }
    }

    /**
     * 文件下载
     *
     * @param downlownPath 要下载的文件的所在文件夹路径
     * @param fileName     文件名
     * @return download  返回下载的文件流
     */
    public static InputStream downloadFile(SftpChannelInfo channelInfo, String downlownPath, String fileName) {
        downlownPath = null != downlownPath && downlownPath.endsWith("/") ? downlownPath : downlownPath + "/";
        InputStream download = null;
        try {
            download = channelInfo.getChannelSftp().get(downlownPath + fileName);
        } catch (SftpException e) {
            e.printStackTrace();
        }
        return download;
    }

}

7.启动测试

SpringBoot 整合sftp 实现文件的上传和下载_maven_02

8.打开filezilla校验

SpringBoot 整合sftp 实现文件的上传和下载_sftp_03