您的位置 首页 java

SpringBoot + SFTP 实现文件上传与下载实战

SFTP介绍

  • SFTP是Secure File Transfer Protocol的缩写,安全文件传送协议。可以为传输文件提供一种安全的加密方法,语法几乎和FTP一致。
  • 相比于FTP,SFTP更安全,但更安全带来副作用就是的效率比FTP要低些。
  • SFTP是SSH的一部分,内部是采用SSH连接, 所以在以下代码中进行文件的操作都会先cd到SFTP存放文件的根路径下。
  • Reference:SFTP与FTP比较、浅谈SFTP与FTP。

实战

1. 相关依赖(基于SpringBoot)

<dependencies>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter</artifactId>
 </dependency>
 <dependency>
 <groupId>org.projectlombok</groupId>
 <artifactId>lombok</artifactId>
 <optional>true</optional>
 </dependency>
 <dependency>
 <groupId>com.jcraft</groupId>
 <artifactId>jsch</artifactId>
 <version>0.1.54</version>
 </dependency>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-test</artifactId>
 <scope>test</scope>
 </dependency>
 <dependency>
 <groupId>org.apache.commons</groupId>
 <artifactId>commons-lang3</artifactId>
 </dependency>
</dependencies>
 

2. 相关配置

#============================================================================
# SFTP Client Setting
#============================================================================
# 协议
sftp.client.protocol=sftp
# ip地址
sftp.client.host=127.0.0.1
# 端口
sftp.client.port=22
# 用户名
sftp.client. username =sftp
# 密码
sftp.client.password=sftp
# 根路径
sftp.client.root=/home/sftp/
# 密钥文件路径
sftp.client.privateKey=
# 密钥的密码
sftp.client.passphrase=
# 
sftp.client. session StrictHostKeyChecking=no
# session连接超时时间
sftp.client.session connect Timeout=15000
#  channel 连接超时时间
sftp.client.channelConnectedTimeout=15000
 
  • 这里暂时没有使用到使用加密密钥的方式登陆,所以暂不填写

3. 将application.properties中配置转为一个Bean

@Getter
@Setter
@Component
@ConfigurationProperties(ignoreUnknownFields = false, prefix = "sftp.client")
public class SftpProperties {
 private String host;
 private Integer port;
 private String protocol;
 private String username;
 private String password;
 private String root;
 private String privateKey;
 private String passphrase;
 private String sessionStrictHostKeyChecking;
 private Integer sessionConnectTimeout;
 private Integer channelConnectedTimeout;
}
 

4. 将上传下载文件封装成Service

  • FileSystemService
/**
 * @author jason.tang
 * @create 2019-03-07 13:33
 * @description
 */public interface FileSystemService {
 boolean uploadFile(String targetPath, InputStream  inputStream ) throws Exception;
 boolean uploadFile(String targetPath, File  file ) throws Exception;
 File downloadFile(String targetPath) throws Exception;
 boolean deleteFile(String targetPath) throws Exception;
}
 
  • 实现类:FileSystemServiceImpl(此处省略相关上传下载代码)
/**
 * @author jason.tang
 * @create 2019-03-07 13:33
 * @description
 */@Slf4j
@Service("fileSystemService")
public class FileSystemServiceImpl implements FileSystemService {
 @Autowired
 private SftpProperties config;
 // 设置第一次登陆的时候提示,可选值:(ask | yes | no)
 private static final String SESSION_CONFIG_STRICT_HOST_KEY_CHECKING = "StrictHostKeyChecking";
 /**
 * 创建SFTP连接
 * @return
 * @throws Exception
 */ private ChannelSftp createSftp() throws Exception {
 JSch jsch = new JSch();
 log.info("Try to connect sftp[" + config.getUsername() + "@" + config.getHost() + "], use password[" + config.getPassword() + "]");
 Session session = createSession(jsch, config.getHost(), config.getUsername(), config.getPort());
 session.setPassword(config.getPassword());
 session.connect(config.getSessionConnectTimeout());
 log.info("Session connected to {}.", config.getHost());
 Channel channel = session.openChannel(config.getProtocol());
 channel.connect(config.getChannelConnectedTimeout());
 log.info("Channel created to {}.", config.getHost());
 return (ChannelSftp) channel;
 }
 /**
 * 加密秘钥方式登陆
 * @return
 */ private ChannelSftp connectByKey() throws Exception {
 JSch jsch = new JSch();
 // 设置密钥和密码 ,支持密钥的方式登陆
 if (StringUtils.isNotBlank(config.getPrivateKey())) {
 if (StringUtils.isNotBlank(config.getPassphrase())) {
 // 设置带口令的密钥
 jsch.addIdentity(config.getPrivateKey(), config.getPassphrase());
 } else {
 // 设置不带口令的密钥
 jsch.addIdentity(config.getPrivateKey());
 }
 }
 log.info("Try to connect sftp[" + config.getUsername() + "@" + config.getHost() + "], use private key[" + config.getPrivateKey()
 + "] with passphrase[" + config.getPassphrase() + "]");
 Session session = createSession(jsch, config.getHost(), config.getUsername(), config.getPort());
 // 设置登陆超时时间
 session.connect(config.getSessionConnectTimeout());
 log.info("Session connected to " + config.getHost() + ".");
 // 创建sftp通信通道
 Channel channel = session.openChannel(config.getProtocol());
 channel.connect(config.getChannelConnectedTimeout());
 log.info("Channel created to " + config.getHost() + ".");
 return (ChannelSftp) channel;
 }
 /**
 * 创建session
 * @param jsch
 * @param host
 * @param username
 * @param port
 * @return
 * @throws Exception
 */ private Session createSession(JSch jsch, String host, String username, Integer port) throws Exception {
 Session session = null;
 if (port <= 0) {
 session = jsch.getSession(username, host);
 } else {
 session = jsch.getSession(username, host, port);
 }
 if (session == null) {
 throw new Exception(host + " session is null");
 }
 session.setConfig(SESSION_CONFIG_STRICT_HOST_KEY_CHECKING, config.getSessionStrictHostKeyChecking());
 return session;
 }
 /**
 * 关闭连接
 * @param sftp
 */ private void disconnect(ChannelSftp sftp) {
 try {
 if (sftp != null) {
 if (sftp.isConnected()) {
 sftp.disconnect();
 } else if (sftp.isClosed()) {
 log.info("sftp is closed already");
 }
 if (null != sftp.getSession()) {
 sftp.getSession().disconnect();
 }
 }
 } catch (JSchException e) {
 e.printStackTrace();
 }
 }
}
 

5. 上传文件

  • 5.1 将inputStream上传到指定路径下(单级或多级目录)
@ Override 
public boolean uploadFile(String targetPath, InputStream inputStream) throws Exception {
 ChannelSftp sftp = this.createSftp();
 try {
 sftp.cd(config.getRoot());
 log.info("Change path to {}", config.getRoot());
 int index = targetPath.lastIndexOf("/");
 String fileDir = targetPath.substring(0, index);
 String fileName = targetPath.substring(index + 1);
 boolean dirs = this.createDirs(fileDir, sftp);
 if (!dirs) {
 log.error("Remote path error. path:{}", targetPath);
 throw new Exception("Upload File failure");
 }
 sftp.put(inputStream, fileName);
 return true;
 } catch (Exception e) {
 log.error("Upload file failure. TargetPath: {}", targetPath, e);
 throw new Exception("Upload File failure");
 } finally {
 this.disconnect(sftp);
 }
}
 
  • 5.2 创建多级目录
private boolean createDirs(String dirPath, ChannelSftp sftp) {
 if (dirPath != null && !dirPath.isEmpty()
 && sftp != null) {
 String[] dirs = Arrays.stream(dirPath.split("/"))
 .filter(StringUtils::isNotBlank)
 .toArray(String[]::new);
 for (String dir : dirs) {
 try {
 sftp.cd(dir);
 log.info("Change directory {}", dir);
 } catch (Exception e) {
 try {
 sftp.mkdir(dir);
 log.info("Create directory {}", dir);
 } catch (SftpException e1) {
 log.error("Create directory failure, directory:{}", dir, e1);
 e1.printStackTrace();
 }
 try {
 sftp.cd(dir);
 log.info("Change directory {}", dir);
 } catch (SftpException e1) {
 log.error("Change directory failure, directory:{}", dir, e1);
 e1.printStackTrace();
 }
 }
 }
 return true;
 }
 return false;
}
 
  • 5.3 将文件上传到指定目录
@Override
public boolean uploadFile(String targetPath, File file) throws Exception {
 return this.uploadFile(targetPath, new FileInputStream(file));
}
 

6. 下载文件

@Override
public File downloadFile(String targetPath) throws Exception {
 ChannelSftp sftp = this.createSftp();
 OutputStream outputStream = null;
 try {
 sftp.cd(config.getRoot());
 log.info("Change path to {}", config.getRoot());
 File file = new File(targetPath.substring(targetPath.lastIndexOf("/") + 1));
 outputStream = new FileOutputStream(file);
 sftp.get(targetPath, outputStream);
 log.info("Download file success. TargetPath: {}", targetPath);
 return file;
 } catch (Exception e) {
 log.error("Download file failure. TargetPath: {}", targetPath, e);
 throw new Exception("Download File failure");
 } finally {
 if (outputStream != null) {
 outputStream.close();
 }
 this.disconnect(sftp);
 }
}
 

7. 删除文件

/**
 * 删除文件
 * @param targetPath
 * @return
 * @throws Exception
 */@Override
public boolean deleteFile(String targetPath) throws Exception {
 ChannelSftp sftp = null;
 try {
 sftp = this.createSftp();
 sftp.cd(config.getRoot());
 sftp.rm(targetPath);
 return true;
 } catch (Exception e) {
 log.error("Delete file failure. TargetPath: {}", targetPath, e);
 throw new Exception("Delete File failure");
 } finally {
 this.disconnect(sftp);
 }
}
 

8. 最后

  • 涉及到对文件的操作,一定记得将流关闭。
  • 在使用中比如下载文件,请将生成的文件在使用后删除(file.delete()),避免在服务器中占据大量资源。
  • application.proerties中SFTP相关配置,请自行更换。如有不对之处,请指出,感谢阅读!

文章来源:智云一二三科技

文章标题:SpringBoot + SFTP 实现文件上传与下载实战

文章地址:https://www.zhihuclub.com/188683.shtml

关于作者: 智云科技

热门文章

网站地图