使用sftp操作文件并添加事务管理
本文主要针对文件操作的事务管理,即写文件和删除文件并且能保证事务的一致性,可与数据库联合使用,比如需要在服务器存文件,相应的记录存放在数据库,那么数据库的记录和服务器的文件数一定是要一一对应的,该部分代码可以保证大多数情况下的文件部分的事务要求(特殊情况下面会说),和数据库保持一致的话需要自行添加数据库部分,比较简单。
基本原理就是,添加文件时先在目录里添加一个临时的文件,如果失败或者数据库插入部分失败直接回滚,即删除该文件,如果成功则提交事务,即将该文件重命名为你需要的正式文件名字(重命名基本不会失败,如果失败了比如断电,那就是特殊情况了)。同理删除文件是先将文件重命名做一个临时文件而不是直接删除,然后数据库部分删除失败的话回滚事务,即将该文件重命名成原来的,如果成功则提交事务,即删除临时文件。
和数据库搭配使用异常的逻辑判断需要谨慎,比如删除文件应先对数据库操作进行判断,如果先对文件操作进行判断,加入成功了直接提交事务即删除了临时文件,数据库部分失败了文件是没办法回滚的。
我这里用的是spriingBoot,如果用的别的看情况做修改即可,这里需要四个类:
SftpProperties:这个是sftp连接文件服务器的各项属性,各属性需要配置到springBoot配置文件中,也可以换种方法获取到即可。
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; @Component
public class SftpProperties {
@Value("${spring.sftp.ip}")
private String ip;
@Value("${spring.sftp.port}")
private int port;
@Value("${spring.sftp.username}")
private String username;
@Value("${spring.sftp.password}")
private String password; public String getIp() {
return ip;
} public void setIp(String ip) {
this.ip = ip;
} public int getPort() {
return port;
} public void setPort(int port) {
this.port = port;
} public String getUsername() {
return username;
} public void setUsername(String username) {
this.username = username;
} public String getPassword() {
return password;
} public void setPassword(String password) {
this.password = password;
} @Override
public String toString() {
return "SftpConfig{" +
"ip='" + ip + '\'' +
", port=" + port +
", username='" + username + '\'' +
", password='******'}";
}
}
SftpClient:这个主要通过sftp连接文件服务器并读取数据。
import com.jcraft.jsch.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component; import java.io.*; @Component
public class SftpClient implements AutoCloseable {
private static final Logger logger = LoggerFactory.getLogger(SftpClient.class);
private Session session; //通过sftp连接服务器
public SftpClient(SftpProperties config) throws JSchException {
JSch.setConfig("StrictHostKeyChecking", "no");
session = new JSch().getSession(config.getUsername(), config.getIp(), config.getPort());
session.setPassword(config.getPassword());
session.connect();
} public Session getSession() {
return session;
} public ChannelSftp getSftpChannel() throws JSchException {
ChannelSftp channel = (ChannelSftp) session.openChannel("sftp");
channel.connect();
return channel;
} /**
* 读取文件内容
* @param destFm 文件绝对路径
* @return
* @throws JSchException
* @throws IOException
* @throws SftpException
*/
public byte[] readBin(String destFm) throws JSchException, IOException, SftpException {
ChannelSftp channel = (ChannelSftp) session.openChannel("sftp");
channel.connect();
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
channel.get(destFm, outputStream);
return outputStream.toByteArray();
} finally {
channel.disconnect();
}
} /**
* 退出登录
*/
@Override
public void close() throws Exception {
try {
this.session.disconnect();
} catch (Exception e) {
//ignore
}
}
}
SftpTransaction:这个主要是对文件的操作
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSchException;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component; import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID; @Component
public class SftpTransaction {
private static final Logger LOGGER = LoggerFactory.getLogger(SftpTransaction.class);
private final String transactionId; // 事务唯一id
private final ChannelSftp channelSftp;
private int opType = -1; // 文件操作标识 1 添加文件 2 删除文件
private List<String> opFiles = new ArrayList<>(5); public SftpTransaction(SftpClient client) throws JSchException {
this.transactionId = StringUtils.replace(UUID.randomUUID().toString(), "-", "");
this.channelSftp = client.getSftpChannel();
} // 根据文件名和事务id创建临时文件
private String transactionFilename(String transactionId, String filename, String path) {
return String.format("%stransact-%s-%s", path, transactionId, filename);
} // 根据路径反推文件名
private String unTransactionFilename(String tfm, String path) {
return path + StringUtils.split(tfm, "-", 3)[2];
} /**
* 添加文件
* @param contents 存放文件内容
* @param path 文件绝对路径(不包含文件名)
* @throws Exception
*/
public void create(List<Pair<String, byte[]>> contents, String path) throws Exception {
if (this.opType == -1) {
this.opType = 1;
} else {
throw new IllegalStateException();
}
for (Pair<String, byte[]> content : contents) {
// 获取content里的数据
try (ByteArrayInputStream stream = new ByteArrayInputStream(content.getValue())) {
// 拼接一个文件名做临时文件
String destFm = this.transactionFilename(this.transactionId, content.getKey(), path);
this.channelSftp.put(stream, destFm);
this.opFiles.add(destFm);
}
}
} /**
* 删除文件
* @param contents 存放要删除的文件名
* @param path 文件的绝对路径(不包含文件名)
* @throws Exception
*/
public void delete(List<String> contents, String path) throws Exception {
if (this.opType == -1) {
this.opType = 2;
} else {
throw new IllegalStateException();
}
for (String name : contents) {
String destFm = this.transactionFilename(this.transactionId, name, path);
this.channelSftp.rename(path+name, destFm);
this.opFiles.add(destFm);
}
} /**
* 提交事务
* @param path 绝对路径(不包含文件名)
* @throws Exception
*/
public void commit(String path) throws Exception {
switch (this.opType) {
case 1:
for (String fm : this.opFiles) {
String destFm = this.unTransactionFilename(fm, path);
//将之前的临时文件命名为真正需要的文件名
this.channelSftp.rename(fm, destFm);
}
break;
case 2:
for (String fm : opFiles) {
//删除这个文件
this.channelSftp.rm(fm);
}
break;
default:
throw new IllegalStateException();
}
this.channelSftp.disconnect();
} /**
* 回滚事务
* @param path 绝对路径(不包含文件名)
* @throws Exception
*/
public void rollback(String path) throws Exception {
switch (this.opType) {
case 1:
for (String fm : opFiles) {
// 删除这个文件
this.channelSftp.rm(fm);
}
break;
case 2:
for (String fm : opFiles) {
String destFm = this.unTransactionFilename(fm, path);
// 将文件回滚
this.channelSftp.rename(fm, destFm);
}
break;
default:
throw new IllegalStateException();
}
this.channelSftp.disconnect();
}
}
SftpTransactionManager:这个是对事务的操作。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; @Component
public class SftpTransactionManager {
@Autowired
private SftpClient client; //开启事务
public SftpTransaction startTransaction() throws Exception {
return new SftpTransaction(client);
} /**
* 提交事务
* @param transaction
* @param path 绝对路径(不包含文件名)
* @throws Exception
*/
public void commitTransaction(SftpTransaction transaction, String path) throws Exception {
transaction.commit(path);
} /**
* 回滚事务
* @param transaction
* @param path 绝对路径(不包含文件名)
* @throws Exception
*/
public void rollbackTransaction(SftpTransaction transaction, String path) throws Exception {
transaction.rollback(path);
}
}
SftpTransactionTest:这是一个测试类,使用之前可以先行测试是否可行,有问题可以评论
import com.springcloud.utils.sftpUtil.SftpTransaction;
import com.springcloud.utils.sftpUtil.SftpTransactionManager;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.junit.Test; import java.util.ArrayList;
import java.util.List; /**
* 测试文件事务管理
*/
public class SftpTransactionTest { //创建文件
@Test
public static void createFile() throws Exception {
// 定义一个存放文件的绝对路径
String targetPath = "/data/file/";
//创建一个事务管理实例
SftpTransactionManager manager = new SftpTransactionManager();
SftpTransaction sftpTransaction = null;
try {
//开启事务并返回一个事务实例
sftpTransaction = manager.startTransaction();
//创建一个存放要操作文件的集合
List<Pair<String, byte[]>> contents = new ArrayList<>();
ImmutablePair aPair = new ImmutablePair<>("file_a", "data_a".getBytes()); //file_a是文件a的名字,data_a是文件a的内容
ImmutablePair bPair = new ImmutablePair<>("file_b", "data_b".getBytes());
ImmutablePair cPair = new ImmutablePair<>("file_c", "data_c".getBytes());
contents.add(aPair);
contents.add(bPair);
contents.add(cPair);
// 将内容进行事务管理
sftpTransaction.create(contents, targetPath);
// 事务提交
manager.commitTransaction(sftpTransaction, targetPath);
}catch (Exception e) {
if (sftpTransaction != null) {
// 发生异常事务回滚
manager.rollbackTransaction(sftpTransaction, targetPath);
}
throw e;
}
} //删除文件
@Test
public void deleteFile() throws Exception {
// 定义一个存放文件的绝对路径
String targetPath = "/data/file/";
//创建一个事务管理实例
SftpTransactionManager manager = new SftpTransactionManager();
SftpTransaction sftpTransaction = null;
try {
//开启事务并返回一个事务实例
sftpTransaction = manager.startTransaction();
List<String> contents = new ArrayList<>();
contents.add("file_a"); // file_a要删除的文件名
contents.add("file_b");
contents.add("file_c");
sftpTransaction.delete(contents, targetPath);
manager.commitTransaction(sftpTransaction, targetPath);
} catch (Exception e) {
//回滚事务
if (sftpTransaction != null) {
manager.rollbackTransaction(sftpTransaction, targetPath);
}
throw e;
}
}
}
这是对于sftp文件操作的依赖,其他的依赖应该都挺好。
<dependency> <groupId>com.jcraft</groupId> <artifactId>jsch</artifactId> </dependency>
ok,到这里已经完了,之前有需要写文件事务管理的时候只找到一个谷歌的包可以完成(包名一时半会忘记了),但是与实际功能还有些差别,所以就根据那个源码自己改了改,代码写的可能很一般,主要也是怕以后自己用忘记,就记下来,如果刚好能帮到有需要的人,那就更好。哪位大神如果有更好的方法也请不要吝啬,传授一下。(抱拳)
使用sftp操作文件并添加事务管理的更多相关文章
- 在Controller中添加事务管理
文章参考了此博客: https://blog.csdn.net/qq_40594137/article/details/82772545 写这篇文章之前先说明一下: 1. Controller中添加事 ...
- spring将service添加事务管理,在applicationContext.xml文件中的设置
在applicationContext.xml文件中的设置为: <beans> <bean id="sessionFactory" class="org ...
- spring对数据库的操作、spring中事务管理的介绍与操作
jdbcTemplate的入门 创建maven工程 此处省略 导入依赖 <!-- https://mvnrepository.com/artifact/org.springframework/s ...
- [译]:Orchard入门——媒体文件的添加与管理
原文链接:Adding and Managing Media Content 注:此文内容相对较老,实际操作指导性不强,仅适合做研究 当你利用富文本编辑器上传图片时(或者使用XML-RPC客户端,例如 ...
- Spring Boot学习——数据库操作及事务管理
本文讲解使用Spring-Data-Jpa操作数据库. JPA定义了一系列对象持久化的标准. 一.在项目中使用Spring-Data-Jpa 1. 配置文件application.properties ...
- Spring注入JPA+JPA事务管理
本例实现的是Spring注入JPA 和 使用JPA事务管理.JPA是sun公司开发的一项新的规范标准.在本质上来说,JPA可以看作是Hibernate的一个子集:然而从功能上来说,Hibernate是 ...
- spring3-spring的事务管理机制
1. Spring的事务管理机制 Spring事务管理高层抽象主要包括3个接口,Spring的事务主要是由他们共同完成的: PlatformTransactionManager:事务管理器—主要用于平 ...
- 12 Spring框架 SpringDAO的事务管理
上一节我们说过Spring对DAO的两个支持分为两个知识点,一个是jdbc模板,另一个是事务管理. 事务是数据库中的概念,但是在一般情况下我们需要将事务提到业务层次,这样能够使得业务具有事务的特性,来 ...
- Spring - 事务管理概述
什么是事务管理? 第一个问题:什么是事务? 事务一般是相对数据库而言的,对于数据库一次操作就属于一个事务, 一次操作可以是几句 SQL 语句,也可以是若干行 JDBC 的 Java 语句.事务既然 ...
随机推荐
- Linux遗忘root密码的其中两种解决方法
由于安全的需要,系统一般只有一个root用户,因此若遗忘root用户的登录密码,因此需要通过其他途径进行修改密码.1.通过单用户模式(1)重启系统,按任意键进入grub菜单.出现grub菜单时,按↑, ...
- BFPRT算法
解决的问题:在一个数组中找到最小的k个数 常规解法:1.排序,输出前k个数,时间复杂度O(n*log(n)). 2.利用一个大小为k的大根堆,遍历数组维持大根堆,最后返回大根堆就可以了,时间复杂度O( ...
- ArcGIS消除图斑重叠错误
在生产中,经常会遇见有图斑重叠这种拓扑错误的矢量,大部分情况下,需要人工比对影像处理.但是如果只需要用到这些矢量的形状.面积,可以在ArcMap中用以下方法,快速消除图斑重叠错误,不必手工处理. 如下 ...
- spring 【二】学习之spring EL
spring EL-spring 表达式语言,支持在xml和注解的形式,类似于JSP的el表达式的形式. 其主要使用@Value注解的结构形式 其主要功能 [1].注入普通字符串 [2].注入操作系统 ...
- ASP.Net Core on Linux (CentOS7) 共享第三方依赖库部署
背景: 这周,心情来潮,想把 Aries 开发框架 和 Taurus 开发框架 给部署到Linux上,于是开始折腾了. 经过重重非人的坑,终于完成了任务: Aries on CentOS7:mvc.a ...
- Could not load file or assembly……
今天在运行一个ASP.NET Core项目的时候发现这样的错误: 我一开始觉得这是个很简单的问题,很明显,出错的原因是项目中某些地方还保留了对Njt.MvcAuthLib这个库的引用,而现在我不需要了 ...
- WaitGroup
WaitGroup在go语言中,用于线程同步,单从字面意思理解,wait等待的意思,group组.团队的意思,WaitGroup就是指等待一组,等待一个系列执行完成后才会继续向下执行. 先说说Wait ...
- 使用nginx搭建高可用,高并发的wcf集群
很多情况下基于wcf的复杂均衡都首选zookeeper,这样可以拥有更好的控制粒度,但zk对C# 不大友好,实现起来相对来说比较麻烦,实际情况下,如果 你的负载机制粒度很粗糙的话,优先使用nginx就 ...
- 以太坊工作原理之txpool详解
txpool详解 交易池txpool作为区块链系统的重要组成部分,对系统的安全性和稳定性具有重要作用.功能可归纳为:交易缓存.交易验证和交易过滤. 基本介绍 交易分类和缓存 txpool主要包含两个重 ...
- Writing a Simple Publisher and Subscriber
用c++实现一个publisher/subscriber publisher #include "ros/ros.h" #include "std_msgs/String ...