欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

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

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

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

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

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