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

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

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

我这里用的是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操作文件并添加事务管理的更多相关文章

  1. 在Controller中添加事务管理

    文章参考了此博客: https://blog.csdn.net/qq_40594137/article/details/82772545 写这篇文章之前先说明一下: 1. Controller中添加事 ...

  2. spring将service添加事务管理,在applicationContext.xml文件中的设置

    在applicationContext.xml文件中的设置为: <beans> <bean id="sessionFactory" class="org ...

  3. spring对数据库的操作、spring中事务管理的介绍与操作

    jdbcTemplate的入门 创建maven工程 此处省略 导入依赖 <!-- https://mvnrepository.com/artifact/org.springframework/s ...

  4. [译]:Orchard入门——媒体文件的添加与管理

    原文链接:Adding and Managing Media Content 注:此文内容相对较老,实际操作指导性不强,仅适合做研究 当你利用富文本编辑器上传图片时(或者使用XML-RPC客户端,例如 ...

  5. Spring Boot学习——数据库操作及事务管理

    本文讲解使用Spring-Data-Jpa操作数据库. JPA定义了一系列对象持久化的标准. 一.在项目中使用Spring-Data-Jpa 1. 配置文件application.properties ...

  6. Spring注入JPA+JPA事务管理

    本例实现的是Spring注入JPA 和 使用JPA事务管理.JPA是sun公司开发的一项新的规范标准.在本质上来说,JPA可以看作是Hibernate的一个子集:然而从功能上来说,Hibernate是 ...

  7. spring3-spring的事务管理机制

    1. Spring的事务管理机制 Spring事务管理高层抽象主要包括3个接口,Spring的事务主要是由他们共同完成的: PlatformTransactionManager:事务管理器—主要用于平 ...

  8. 12 Spring框架 SpringDAO的事务管理

    上一节我们说过Spring对DAO的两个支持分为两个知识点,一个是jdbc模板,另一个是事务管理. 事务是数据库中的概念,但是在一般情况下我们需要将事务提到业务层次,这样能够使得业务具有事务的特性,来 ...

  9. Spring - 事务管理概述

      什么是事务管理? 第一个问题:什么是事务? 事务一般是相对数据库而言的,对于数据库一次操作就属于一个事务, 一次操作可以是几句 SQL 语句,也可以是若干行 JDBC 的 Java 语句.事务既然 ...

随机推荐

  1. 004. 前端跨域资源请求: JSONP/CORS/反向代理

    1.什么是跨域资源请求? https://www.cnblogs.com/niuli1987/p/10252214.html 同源: 如果两个页面的协议,端口(如果有指定)和域名都相同,则两个页面具有 ...

  2. linux、shell一些操作指令

    1.cd $(dirname $0)   shell脚本里面添加这个命令就可以进入此sh目录下,不用写绝对路径 2.strMac=`ifconfig eth0 | grep "HWaddr& ...

  3. 章节十、2-用Linktext和PartialLinkText、ClassName、TagName定位元素

    一.通过内容的方式定位元素 使用Linktext和PartialLinkText定位元素的前提需要"文本"在“a”标签内,selenium才可以找到链接文本或者部分链接文本的元素. ...

  4. ceph 高级运维

    追查系统故障,需要找到问题的根源安置组和相关的OSD. 一般来说,归置组卡住时 ceph 的自修复功能往往无能为力,卡住的状态细分为: 1. unclean 不干净:归置组里有些对象的复制数未达到期望 ...

  5. 在 ASP.NET Core 中集成 Skywalking APM

    前言 大家好,今天给大家介绍一下如何在 ASP.NET Core 项目中集成 Skywalking,Skywalking 是 Apache 基金会下面的一个开源 APM 项目,有些同学可能会 APM ...

  6. 以Windows服务方式运行ASP.NET Core程序

    我们对ASP.NET Core的使用已经进行了相当一段时间了,大多数时候,我们的Web程序都是发布到Linux主机上的,当然了,偶尔也有需求要发布到Windows主机上,这样问题就来了,难道直接以控制 ...

  7. Linux 桌面玩家指南:11. 在同一个硬盘上安装多个 Linux 发行版以及为 Linux 安装 Nvidia 显卡驱动

    特别说明:要在我的随笔后写评论的小伙伴们请注意了,我的博客开启了 MathJax 数学公式支持,MathJax 使用$标记数学公式的开始和结束.如果某条评论中出现了两个$,MathJax 会将两个$之 ...

  8. nginx部署静态网站

    实验环境 服务器:centos7.5 1核1G Nginx版本:nginx-1.14.2 主题 部署静态文件 根据不同url请求路径,定向到不同的系统文件夹 部署静态文件 假设nginx安装在“/us ...

  9. Scanner对象及其获取数据出现小问题和解决方案

    Scanner类简介: Java 5添加了java.util.Scanner类,我们可以通过Scanner类来获取用户输入.它是以前的StringTokenizer和Matcher类之间的某种结合.由 ...

  10. Spring 数据库读写分离

    读写分离常见有俩种方式 1 第一种方式比较常用就是定义2个数据库连接,一个是Master,另一个是Slave.更新数据时我们取Master,查询数据时取Slave.太过简单不做介绍. 2 第二种方数据 ...