使用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,到这里已经完了,之前有需要写文件事务管理的时候只找到一个谷歌的包可以完成(包名一时半会忘记了),但是与实际功能还有些差别,所以就根据那个源码自己改了改,代码写的可能很一般,主要也是怕以后自己用忘记,就记下来,如果刚好能帮到有需要的人,那就更好。哪位大神如果有更好的方法也请不要吝啬,传授一下。(抱拳)
推荐阅读
-
使用sftp操作文件并添加事务管理
-
python文件读写并使用mysql批量插入示例分享(python操作mysql)
-
python文件读写并使用mysql批量插入示例分享(python操作mysql)
-
JAVA使用quartz添加定时任务,并依赖注入对象操作
-
vue项目中使用rem,在入口文件添加内容操作
-
python文件读写并使用mysql批量插入示例分享(python操作mysql)
-
python文件读写并使用mysql批量插入示例分享(python操作mysql)
-
使用sftp操作文件并添加事务管理
-
利用nodejs监控文件变化并使用sftp上传到服务器
-
JAVA使用quartz添加定时任务,并依赖注入对象操作