使用sftp操作文件并添加事务管理

  本文主要针对文件操作的事务管理,即写文件和删除文件并且能保证事务的一致性,可与数据库联合使用,比如需要在服务器存文件,相应的记录存放在数据库,那么数据库的记录和服务器的文件数一定是要一一对应的,该部分代码可以保证大多数情况下的文件部分的事务要求(特殊情况下面会说),和数据库保持一致的话需要自行添加数据库部分,比较简单。

  基本原理就是,添加文件时先在目录里添加一个临时的文件,如果失败或者数据库插入部分失败直接回滚,即删除该文件,如果成功则提交事务,即将该文件重命名为你需要的正式文件名字(重命名基本不会失败,如果失败了比如断电,那就是特殊情况了)。同理删除文件是先将文件重命名做一个临时文件而不是直接删除,然后数据库部分删除失败的话回滚事务,即将该文件重命名成原来的,如果成功则提交事务,即删除临时文件。

  和数据库搭配使用异常的逻辑判断需要谨慎,比如删除文件应先对数据库操作进行判断,如果先对文件操作进行判断,加入成功了直接提交事务即删除了临时文件,数据库部分失败了文件是没办法回滚的。

我这里用的是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,到这里已经完了,之前有需要写文件事务管理的时候只找到一个谷歌的包可以完成(包名一时半会忘记了),但是与实际功能还有些差别,所以就根据那个源码自己改了改,代码写的可能很一般,主要也是怕以后自己用忘记,就记下来,如果刚好能帮到有需要的人,那就更好。哪位大神如果有更好的方法也请不要吝啬,传授一下。(抱拳)

猜你喜欢

转载自www.cnblogs.com/Qsunshine/p/10497658.html
今日推荐