使用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 语句.事务既然 ...
随机推荐
- 简单几步用纯CSS3实现3D翻转效果
作为前端开发人员的必修课,CSS3翻转能带我们完成许多基本动效,本期我们将用CSS3实现hover翻转效果~ 第一步非常简单,我们简单画1个演示方块,为其 添加transition和transform ...
- openlayers4 入门开发系列之地图模态层篇(附源码下载)
前言 openlayers4 官网的 api 文档介绍地址 openlayers4 api,里面详细的介绍 openlayers4 各个类的介绍,还有就是在线例子:openlayers4 官网在线例子 ...
- oppo5.0以上系统怎么样不Root激活Xposed框架的经验
在非常多单位的引流或者业务操作中,基本上都需要使用安卓的黑高科技术Xposed框架,前几天我们单位购来了一批新的oppo5.0以上系统,基本上都都是基于7.0以上版本,基本上都不能够获取root超级权 ...
- VMWare安装Mac系统后无法全屏显示的问题
系统: VMTOOLs下载: 链接:https://pan.baidu.com/s/1KIzVWtPrb2vSrtokONToBw 提取码:zea3 1.虚拟机设置--显示器--监视器--指定监视器设 ...
- postgres的使用命令
1.更新源 yum install https://download.postgresql.org/pub/repos/yum/10/redhat/rhel-7-x86_64/pgdg-centos1 ...
- How to fix corrupt HDFS FIles
1 问题描述 HDFS在机器断电或意外崩溃的情况下,有可能出现正在写的数据(例如保存在DataNode内存的数据等)丢失的问题.再次重启HDFS后,发现hdfs无法启动,查看日志后发现,一直处于安全模 ...
- Linux内存管理 (5)slab分配器
专题:Linux内存管理专题 关键词:slab/slub/slob.slab描述符.kmalloc.本地/共享对象缓冲池.slabs_partial/slabs_full/slabs_free.ava ...
- 不能直接获取?聊聊如何在Shader Graph中获取深度图
0x00 前言 在这篇文章中,我们选择了过去几周Unity官方社区交流群以及UUG社区群中比较有代表性的几个问题,总结在这里和大家进行分享.主要涵盖了** StreamingAssets.Profil ...
- 【死磕 Spring】----- IOC 之解析 bean 标签:开启解析进程
原文出自:http://cmsblogs.com import 标签解析完毕了,再看 Spring 中最复杂也是最重要的标签 bean 标签的解析过程. 在方法 parseDefaultElement ...
- 模式识别笔记4-集成学习之AdaBoost
目前集成学习(Ensemble Learning) 分为两类: 个体学习器间存在强依赖关系.必须串行化生成的序列化方法:Boosting 个体学习器间不存在强依赖关系,可同时生成的并行化方法:Bagg ...