使用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 语句.事务既然 ...
随机推荐
- 设计模式 | 工厂方法模式(factory method)
定义: 定义一个用于创建对象的接口,让子类决定实例化哪一个类.工厂方法使一个类的实例化延迟到其子类. 结构:(书中图,侵删) 一个工厂的抽象接口 若干个具体的工厂类 一个需要创建对象的抽象接口 若干个 ...
- Exception: Exception caught in workbook destructor. Explicit close() may be required for workbook. 错误解决办法
# 写入表格 writer = pd.ExcelWriter('data.xlsx') new_df.to_excel(writer, sheet_name='sheet', index=True) ...
- 使用Update Strategy组件无法进行delete操作
问题: Update Strategy组件根据字段值对目标表进行DD_DELETE操作时失效 同时,session log中报错:Target table [XXXXXXXX] does not al ...
- select 如何在选中后获取选中的时是什么元素 ,(原生js)
在日常开发中,我们经常遇到选择框的业务处理:如何去获取我们所选中的数据呢? 很多小伙伴还不是很熟悉! <!DOCTYPE html> <html lang="en" ...
- Python基础(文件操作)
文件读取: #文件读取方式一 f=open("a.txt","r+",encoding="utf8") data=f.read() prin ...
- 如何在ASP.NET Core中使用JSON Patch
原文: JSON Patch With ASP.NET Core 作者:.NET Core Tutorials 译文:如何在ASP.NET Core中使用JSON Patch 地址:https://w ...
- 『集群』001 Slithice 服务器集群 概述
Slithice 服务器集群 概述 Slithice是做什么的 Slithice 是一个 跨平台 的 分布式架构 框架: 旨在简化 分布式开发 的开发难度,节省 开发成本 和 后期维护成本: 并提供 ...
- 前端笔记之服务器&Ajax(上)服务器&PHP&数据交互&HTTP
一.服务器 1.1 什么是服务器,做什么的? 服务器,就是放在机房中的电脑,和我们的电脑的区别在与服务器有固定的IP,服务器的安全性和稳定性相当的高;性能一般就可以了,但是CPU的性能要比普通的客户机 ...
- 前端笔记之移动端&响应式(下)默认样式&事件&惯性抛掷&swiper&loaction对象
一.移动端默认样式 ·IOS和Android下触摸元素时出现半透明灰色遮罩 a,input,button{ -webkit-tap-highlight-color: transparent; } ·I ...
- spring beans源码解读之--总结篇
spring beans下面有如下源文件包: org.springframework.beans, 包含了操作java bean的接口和类.org.springframework.beans.anno ...