1. 程式人生 > >使用sftp操作文件並添加事務管理

使用sftp操作文件並添加事務管理

adb factory 而不是 auto cto ear create nts ftp連接

  本文主要針對文件操作的事務管理,即寫文件和刪除文件並且能保證事務的一致性,可與數據庫聯合使用,比如需要在服務器存文件,相應的記錄存放在數據庫,那麽數據庫的記錄和服務器的文件數一定是要一一對應的,該部分代碼可以保證大多數情況下的文件部分的事務要求(特殊情況下面會說),和數據庫保持一致的話需要自行添加數據庫部分,比較簡單。

  基本原理就是,添加文件時先在目錄裏添加一個臨時的文件,如果失敗或者數據庫插入部分失敗直接回滾,即刪除該文件,如果成功則提交事務,即將該文件重命名為你需要的正式文件名字(重命名基本不會失敗,如果失敗了比如斷電,那就是特殊情況了)。同理刪除文件是先將文件重命名做一個臨時文件而不是直接刪除,然後數據庫部分刪除失敗的話回滾事務,即將該文件重命名成原來的,如果成功則提交事務,即刪除臨時文件。

  和數據庫搭配使用異常的邏輯判斷需要謹慎,比如刪除文件應先對數據庫操作進行判斷,如果先對文件操作進行判斷,加入成功了直接提交事務即刪除了臨時文件,數據庫部分失敗了文件是沒辦法回滾的。

我這裏用的是spriingBoot,如果用的別的看情況做修改即可,這裏需要四個類:

SftpProperties這個是sftp連接文件服務器的各項屬性,各屬性需要配置到springBoot配置文件中,也可以換種方法獲取到即可。

 1 import org.springframework.beans.factory.annotation.Value;
 2 import org.springframework.stereotype.Component;
3 4 @Component 5 public class SftpProperties { 6 @Value("${spring.sftp.ip}") 7 private String ip; 8 @Value("${spring.sftp.port}") 9 private int port; 10 @Value("${spring.sftp.username}") 11 private String username; 12 @Value("${spring.sftp.password}") 13 private
String password; 14 15 public String getIp() { 16 return ip; 17 } 18 19 public void setIp(String ip) { 20 this.ip = ip; 21 } 22 23 public int getPort() { 24 return port; 25 } 26 27 public void setPort(int port) { 28 this.port = port; 29 } 30 31 public String getUsername() { 32 return username; 33 } 34 35 public void setUsername(String username) { 36 this.username = username; 37 } 38 39 public String getPassword() { 40 return password; 41 } 42 43 public void setPassword(String password) { 44 this.password = password; 45 } 46 47 @Override 48 public String toString() { 49 return "SftpConfig{" + 50 "ip=‘" + ip + ‘\‘‘ + 51 ", port=" + port + 52 ", username=‘" + username + ‘\‘‘ + 53 ", password=‘******‘}"; 54 } 55 }

SftpClient:這個主要通過sftp連接文件服務器並讀取數據。

 1 import com.jcraft.jsch.*;
 2 import org.slf4j.Logger;
 3 import org.slf4j.LoggerFactory;
 4 import org.springframework.stereotype.Component;
 5 
 6 import java.io.*;
 7 
 8 @Component
 9 public class SftpClient implements AutoCloseable {
10     private static final Logger logger = LoggerFactory.getLogger(SftpClient.class);
11     private Session session;
12 
13     //通過sftp連接服務器
14     public SftpClient(SftpProperties config) throws JSchException {
15         JSch.setConfig("StrictHostKeyChecking", "no");
16         session = new JSch().getSession(config.getUsername(), config.getIp(), config.getPort());
17         session.setPassword(config.getPassword());
18         session.connect();
19     }
20 
21     public Session getSession() {
22         return session;
23     }
24 
25     public ChannelSftp getSftpChannel() throws JSchException {
26         ChannelSftp channel = (ChannelSftp) session.openChannel("sftp");
27         channel.connect();
28         return channel;
29     }
30 
31     /**
32      * 讀取文件內容
33      * @param destFm 文件絕對路徑
34      * @return
35      * @throws JSchException
36      * @throws IOException
37      * @throws SftpException
38      */
39     public byte[] readBin(String destFm) throws JSchException, IOException, SftpException {
40         ChannelSftp channel = (ChannelSftp) session.openChannel("sftp");
41         channel.connect();
42         try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
43             channel.get(destFm, outputStream);
44             return outputStream.toByteArray();
45         } finally {
46             channel.disconnect();
47         }
48     }
49 
50     /**
51      * 退出登錄
52      */
53     @Override
54     public void close() throws Exception {
55         try {
56             this.session.disconnect();
57         } catch (Exception e) {
58             //ignore
59         }
60     }
61 }

SftpTransaction:這個主要是對文件的操作

  1 import com.jcraft.jsch.ChannelSftp;
  2 import com.jcraft.jsch.JSchException;
  3 import org.apache.commons.lang.StringUtils;
  4 import org.apache.commons.lang3.tuple.Pair;
  5 import org.slf4j.Logger;
  6 import org.slf4j.LoggerFactory;
  7 import org.springframework.stereotype.Component;
  8 
  9 import java.io.ByteArrayInputStream;
 10 import java.util.ArrayList;
 11 import java.util.List;
 12 import java.util.UUID;
 13 
 14 @Component
 15 public class SftpTransaction {
 16     private static final Logger LOGGER = LoggerFactory.getLogger(SftpTransaction.class);
 17     private final String transactionId;  // 事務唯一id
 18     private final ChannelSftp channelSftp;
 19     private int opType = -1;  // 文件操作標識 1 添加文件  2 刪除文件
 20     private List<String> opFiles = new ArrayList<>(5);
 21 
 22     public SftpTransaction(SftpClient client) throws JSchException {
 23         this.transactionId = StringUtils.replace(UUID.randomUUID().toString(), "-", "");
 24         this.channelSftp = client.getSftpChannel();
 25     }
 26 
 27     // 根據文件名和事務id創建臨時文件
 28     private String transactionFilename(String transactionId, String filename, String path) {
 29         return String.format("%stransact-%s-%s", path, transactionId, filename);
 30     }
 31 
 32     // 根據路徑反推文件名
 33     private String unTransactionFilename(String tfm, String path) {
 34         return path + StringUtils.split(tfm, "-", 3)[2];
 35     }
 36 
 37     /**
 38      * 添加文件
 39      * @param contents 存放文件內容
 40      * @param path 文件絕對路徑(不包含文件名)
 41      * @throws Exception
 42      */
 43     public void create(List<Pair<String, byte[]>> contents, String path) throws Exception {
 44         if (this.opType == -1) {
 45             this.opType = 1;
 46         } else {
 47             throw new IllegalStateException();
 48         }
 49         for (Pair<String, byte[]> content : contents) {
 50             // 獲取content裏的數據
 51             try (ByteArrayInputStream stream = new ByteArrayInputStream(content.getValue())) {
 52                 // 拼接一個文件名做臨時文件
 53                 String destFm = this.transactionFilename(this.transactionId, content.getKey(), path);
 54                 this.channelSftp.put(stream, destFm);
 55                 this.opFiles.add(destFm);
 56             }
 57         }
 58     }
 59 
 60     /**
 61      * 刪除文件
 62      * @param contents 存放要刪除的文件名
 63      * @param path 文件的絕對路徑(不包含文件名)
 64      * @throws Exception
 65      */
 66     public void delete(List<String> contents, String path) throws Exception {
 67         if (this.opType == -1) {
 68             this.opType = 2;
 69         } else {
 70             throw new IllegalStateException();
 71         }
 72         for (String name : contents) {
 73             String destFm = this.transactionFilename(this.transactionId, name, path);
 74             this.channelSftp.rename(path+name, destFm);
 75             this.opFiles.add(destFm);
 76         }
 77     }
 78 
 79     /**
 80      * 提交事務
 81      * @param path 絕對路徑(不包含文件名)
 82      * @throws Exception
 83      */
 84     public void commit(String path) throws Exception {
 85         switch (this.opType) {
 86             case 1:
 87                 for (String fm : this.opFiles) {
 88                     String destFm = this.unTransactionFilename(fm, path);
 89                     //將之前的臨時文件命名為真正需要的文件名
 90                     this.channelSftp.rename(fm, destFm);
 91                 }
 92                 break;
 93             case 2:
 94                 for (String fm : opFiles) {
 95                     //刪除這個文件
 96                     this.channelSftp.rm(fm);
 97                 }
 98                 break;
 99             default:
100                 throw new IllegalStateException();
101         }
102         this.channelSftp.disconnect();
103     }
104 
105     /**
106      * 回滾事務
107      * @param path 絕對路徑(不包含文件名)
108      * @throws Exception
109      */
110     public void rollback(String path) throws Exception {
111         switch (this.opType) {
112             case 1:
113                 for (String fm : opFiles) {
114                     // 刪除這個文件
115                     this.channelSftp.rm(fm);
116                 }
117                 break;
118             case 2:
119                 for (String fm : opFiles) {
120                     String destFm = this.unTransactionFilename(fm, path);
121                     // 將文件回滾
122                     this.channelSftp.rename(fm, destFm);
123                 }
124                 break;
125             default:
126                 throw new IllegalStateException();
127         }
128         this.channelSftp.disconnect();
129     }
130 }

SftpTransactionManager:這個是對事務的操作。

 1 import org.springframework.beans.factory.annotation.Autowired;
 2 import org.springframework.stereotype.Component;
 3 
 4 @Component
 5 public class SftpTransactionManager {
 6     @Autowired
 7     private SftpClient client;
 8 
 9     //開啟事務
10     public SftpTransaction startTransaction() throws Exception {
11         return new SftpTransaction(client);
12     }
13 
14     /**
15      * 提交事務
16      * @param transaction
17      * @param path 絕對路徑(不包含文件名)
18      * @throws Exception
19      */
20     public void commitTransaction(SftpTransaction transaction, String path) throws Exception {
21         transaction.commit(path);
22     }
23 
24     /**
25      * 回滾事務
26      * @param transaction
27      * @param path 絕對路徑(不包含文件名)
28      * @throws Exception
29      */
30     public void rollbackTransaction(SftpTransaction transaction, String path) throws Exception {
31         transaction.rollback(path);
32     }
33 }

SftpTransactionTest:這是一個測試類,使用之前可以先行測試是否可行,有問題可以評論

 1 import com.springcloud.utils.sftpUtil.SftpTransaction;
 2 import com.springcloud.utils.sftpUtil.SftpTransactionManager;
 3 import org.apache.commons.lang3.tuple.ImmutablePair;
 4 import org.apache.commons.lang3.tuple.Pair;
 5 import org.junit.Test;
 6 
 7 import java.util.ArrayList;
 8 import java.util.List;
 9 
10 /**
11  *  測試文件事務管理
12  */
13 public class SftpTransactionTest {
14 
15     //創建文件
16     @Test
17     public static void createFile() throws Exception {
18         // 定義一個存放文件的絕對路徑
19         String targetPath = "/data/file/";
20         //創建一個事務管理實例
21         SftpTransactionManager manager = new SftpTransactionManager();
22         SftpTransaction sftpTransaction = null;
23         try {
24             //開啟事務並返回一個事務實例
25             sftpTransaction = manager.startTransaction();
26             //創建一個存放要操作文件的集合
27             List<Pair<String, byte[]>> contents = new ArrayList<>();
28             ImmutablePair aPair = new ImmutablePair<>("file_a", "data_a".getBytes());  //file_a是文件a的名字,data_a是文件a的內容
29             ImmutablePair bPair = new ImmutablePair<>("file_b", "data_b".getBytes());
30             ImmutablePair cPair = new ImmutablePair<>("file_c", "data_c".getBytes());
31             contents.add(aPair);
32             contents.add(bPair);
33             contents.add(cPair);
34             // 將內容進行事務管理
35             sftpTransaction.create(contents, targetPath);
36             // 事務提交
37             manager.commitTransaction(sftpTransaction, targetPath);
38         }catch (Exception e) {
39             if (sftpTransaction != null) {
40                 // 發生異常事務回滾
41                 manager.rollbackTransaction(sftpTransaction, targetPath);
42             }
43             throw e;
44         }
45     }
46     
47     //刪除文件
48     @Test
49     public void deleteFile() throws Exception {
50         // 定義一個存放文件的絕對路徑
51         String targetPath = "/data/file/";
52         //創建一個事務管理實例
53         SftpTransactionManager manager = new SftpTransactionManager();
54         SftpTransaction sftpTransaction = null;
55         try {
56             //開啟事務並返回一個事務實例
57             sftpTransaction = manager.startTransaction();
58             List<String> contents = new ArrayList<>();
59             contents.add("file_a");  // file_a要刪除的文件名
60             contents.add("file_b");
61             contents.add("file_c");
62             sftpTransaction.delete(contents, targetPath);
63             manager.commitTransaction(sftpTransaction, targetPath);
64         } catch (Exception e) {
65             //回滾事務
66             if (sftpTransaction != null) {
67                 manager.rollbackTransaction(sftpTransaction, targetPath);
68             }
69             throw e;
70         }
71     }
72 }

這是對於sftp文件操作的依賴,其他的依賴應該都挺好。

 1 <dependency> 2 <groupId>com.jcraft</groupId> 3 <artifactId>jsch</artifactId> 4 </dependency> 

  ok,到這裏已經完了,之前有需要寫文件事務管理的時候只找到一個谷歌的包可以完成(包名一時半會忘記了),但是與實際功能還有些差別,所以就根據那個源碼自己改了改,代碼寫的可能很一般,主要也是怕以後自己用忘記,就記下來,如果剛好能幫到有需要的人,那就更好。哪位大神如果有更好的方法也請不要吝嗇,傳授一下。(抱拳)

使用sftp操作文件並添加事務管理