SFTP介紹
SFTP是Secure File Transfer Protocol的縮寫,安全文件傳送協議。可以為傳輸文件提供一種安全的加密方法,語法幾乎和FTP一致。
相比於FTP,SFTP更安全,但更安全帶來副作用就是的效率比FTP要低些。
SFTP是SSH的一部分,內部是采用SSH連接,所以在以下代碼中進行文件的操作都會先cd到SFTP存放文件的根路徑下。
實戰
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.sessionStrictHostKeyChecking=no # session連接超時時間 sftp.client.sessionConnectTimeout=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相關配置,請自行更換。如有不對之處,請指出,感謝閱讀!