前言:开发中的需求要去对方的 ftp
服务器下载文件,这里下载文件采用 ftp
方式,下载之后程序再去解析文件的内容,然后再存数据库。下载过来的文件默认是 zip
格式,需要解压 unzip
一下,然后里面有一个 csv
文件,用 easyecel
读取 csv
中的数据,至此需求梳理完成。这里每次取下载或者操作 ftp
的时候都要新建一个连接,用完再主动关闭,这里并未复用此连接,相当于说每次都是新建一个连接,这样不好,因此对这里进行设计改造。
引入 pom 依赖
<!-- SFTP -->
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.55</version>
</dependency>
<!-- commons-pool2 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.1</version>
</dependency>
定义yml文件配置信息
sftp:
mft:
sftp-host: 101.233.389.58
sftp-port: 80219
sftp-username: user
sftp-password: 789678
sftp-path: /file/mft
save-path: /mft
pool:
max-total: 10
max-idle: 10
min-idle: 5
定义配置属性SftpProperties
信息
读取yml
中的属性信息,注意@ConfigurationProperties(prefix = "sftp.mft")
注解是定义属性,下面的
sftpHost
属性就读取到了yml
文件中配置的信息,但是类中的maxTotal
和yml文件中的max-total
是怎么映射的呢?注意看yml
中有一个pool
下面 类中有一个private Pool pool = new Pool();
把pool
改个名字就从yml
文件点不进来了,说明是根据此来映射的
package x.x.config.sftpPool;
import com.jcraft.jsch.ChannelSftp;
import lombok.Data;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "sftp.mft")
public class SftpProperties {
private String sftpHost;
private int sftpPort;
private String sftpUsername;
private String sftpPassword;
private String sftpPath;
private String savePath;
private Pool pool = new Pool();
public static class Pool extends GenericObjectPoolConfig<ChannelSftp> {
private int maxTotal = DEFAULT_MAX_TOTAL;
private int maxIdle = DEFAULT_MAX_IDLE;
private int minIdle = DEFAULT_MIN_IDLE;
public Pool() {
super();
}
@Override
public int getMaxTotal() {
return maxTotal;
}
@Override
public void setMaxTotal(int maxTotal) {
this.maxTotal = maxTotal;
}
@Override
public int getMaxIdle() {
return maxIdle;
}
@Override
public void setMaxIdle(int maxIdle) {
this.maxIdle = maxIdle;
}
@Override
public int getMinIdle() {
return minIdle;
}
@Override
public void setMinIdle(int minIdle) {
this.minIdle = minIdle;
}
}
}
定义工厂类
生产sftp
连接,获取上一步定义好的属性 SftpProperties
实现三个方法,create
方法创建连接
package x.x.config.sftpPool;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import java.util.Properties;
@Data
@Slf4j
public class SftpFactory extends BasePooledObjectFactory<ChannelSftp> {
private SftpProperties properties;
public SftpFactory(SftpProperties properties) {
this.properties = properties;
}
@Override
public ChannelSftp create() {
try {
JSch jsch = new JSch();
Session sshSession = jsch.getSession(properties.getSftpUsername(), properties.getSftpHost(), properties.getSftpPort());
sshSession.setPassword(properties.getSftpPassword());
Properties sshConfig = new Properties();
sshConfig.put("StrictHostKeyChecking", "no");
sshSession.setConfig(sshConfig);
sshSession.connect();
ChannelSftp channel = (ChannelSftp) sshSession.openChannel("sftp");
channel.connect();
return channel;
}catch (JSchException e){
throw new RuntimeException("连接sfpt失败", e);
}
}
@Override
public PooledObject<ChannelSftp> wrap(ChannelSftp channelSftp) {
return new DefaultPooledObject<>(channelSftp);
}
@Override
public void destroyObject(PooledObject<ChannelSftp> p) throws Exception {
ChannelSftp channelSftp = p.getObject();
channelSftp.disconnect();
}
}
定义池子
引入工厂制造,此处会根据yml
文件配置的属性生成连接
package x.x.config.sftpPool;
import com.jcraft.jsch.ChannelSftp;
import lombok.Data;
import org.apache.commons.pool2.impl.GenericObjectPool;
@Data
public class SftpPool {
private GenericObjectPool<ChannelSftp> pool;
public SftpPool(SftpFactory factory) {
this.pool = new GenericObjectPool<>(factory, factory.getProperties().getPool());
}
/**
* 获取一个sftp连接对象
* @return sftp连接对象
*/
public ChannelSftp borrowObject() {
try {
return pool.borrowObject();
} catch (Exception e) {
throw new RuntimeException("获取ftp连接失败", e);
}
}
/**
* 归还一个sftp连接对象
* @param channelSftp sftp连接对象
*/
public void returnObject(ChannelSftp channelSftp) {
if (channelSftp!=null) {
pool.returnObject(channelSftp);
}
}
}
定义help类操作
引入pool
直接操作获取,用完返回
package x.x.config.sftpPool;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.SftpATTRS;
import com.jcraft.jsch.SftpException;
import lombok.extern.slf4j.Slf4j;
import java.io.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
@Slf4j
public class SftpHelper {
private SftpPool pool;
public SftpHelper(SftpPool pool) {
this.pool = pool;
}
public void createDir(String createPath) throws Exception {
ChannelSftp channelSftp = pool.borrowObject();
try {
channelSftp.cd("/");
if (!isDirExist(createPath)) {
String[] pathArry = createPath.split("/");
for (String path : pathArry) {
if ("".equals(path)) {
continue;
}
if (isDirExist(path)) {
channelSftp.cd(path);
} else {
// 建立目录
channelSftp.mkdir(path);
// 进入并设置为当前目录
channelSftp.cd(path);
}
}
} else {
channelSftp.cd(createPath);
}
} catch (SftpException e) {
log.error(e.getMessage(), e);
throw new Exception("创建路径错误:" + createPath);
}finally {
pool.returnObject(channelSftp);
}
}
public boolean isDirExist(String directory) {
ChannelSftp channelSftp = pool.borrowObject();
boolean isDirExistFlag = false;
try {
channelSftp.cd("/");
SftpATTRS sftpATTRS = channelSftp.lstat(directory);
isDirExistFlag = true;
channelSftp.cd(directory);
return sftpATTRS.isDir();
} catch (Exception e) {
log.error(e.getMessage(), e);
if ("no such file".equals(e.getMessage().toLowerCase())) {
isDirExistFlag = false;
}
}finally {
pool.returnObject(channelSftp);
}
return isDirExistFlag;
}
/**
* 文件上传
*
* @param directory 目录
* @param fileName 要上传的文件名
*/
public void uploadFile(ChannelSftp uploadChannelSftp, String directory, String sourceFilePath, String fileName) {
ChannelSftp channelSftp = pool.borrowObject();
//获取输入流
InputStream inputStream = null;
try {
inputStream = uploadChannelSftp.get(sourceFilePath);
} catch (SftpException e) {
e.printStackTrace();
throw new RuntimeException(e);
}finally {
pool.returnObject(channelSftp);
}
try {
channelSftp.cd(directory);
//写入文件
channelSftp.put(inputStream, fileName);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("文件上传异常:" + e.getMessage());
} finally {
pool.returnObject(channelSftp);
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
throw new RuntimeException("Close stream error." + e.getMessage());
}
}
}
}
/**
* 列出目下下的所有文件
*
* @param sftpPath 文件路径
* @return 文件名
* @throws Exception 异常
*/
public List<String> listFiles(String sftpPath) {
ChannelSftp channelSftp = pool.borrowObject();
Vector fileList = null;
List<String> fileNameList = new ArrayList<>();
try {
fileList = channelSftp.ls(sftpPath);
} catch (SftpException e) {
throw new RuntimeException(e);
}
Iterator it = fileList.iterator();
while (it.hasNext()) {
String fileName = ((ChannelSftp.LsEntry) it.next()).getFilename();
if (".".equals(fileName) || "..".equals(fileName)) {
continue;
}
fileNameList.add(fileName);
}
pool.returnObject(channelSftp);
return fileNameList;
}
/**
* 下载文件。
*
* @param sourceDirectory 下载目录
* @param downloadFile 下载的文件
* @param targetSaveDirectory 存在本地的路径
*/
public void downloadFile(String sourceDirectory, String downloadFile, String targetSaveDirectory) throws RuntimeException {
ChannelSftp channelSftp = pool.borrowObject();
OutputStream targetFile = null;
try {
if (sourceDirectory != null && !"".equals(sourceDirectory)) {
channelSftp.cd(sourceDirectory);
}
File folder = new File(targetSaveDirectory);
// 确保文件夹存在
if (!folder.exists()) {
if (!folder.mkdirs()) {
log.error("无法创建文件夹");
return;
}
}
targetFile = new FileOutputStream(new File(targetSaveDirectory, downloadFile));
channelSftp.get(downloadFile, targetFile);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
pool.returnObject(channelSftp);
try {
targetFile.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
/**
* 删除文件
*
* @param directory 要删除文件所在目录
* @param deleteFile 要删除的文件
*/
public void deleteFile(String directory, String deleteFile) throws Exception {
ChannelSftp channelSftp = pool.borrowObject();
try {
channelSftp.cd(directory);
channelSftp.rm(deleteFile);
} catch (Exception e) {
throw new Exception("文件删除失败" + e.getMessage());
}finally {
pool.returnObject(channelSftp);
}
}
/**
* 检查文件路径是否存在
*
* @param path 文件路径
* @return true/false
*/
public boolean checkedFileDirectory(String path) {
ChannelSftp channelSftp = pool.borrowObject();
File file = new File(path);
if (!file.exists() && !file.isDirectory()) {
log.info("file or dir not exist, will create it : "+ path);
file.setWritable(true, false);
return file.mkdirs();
}
pool.returnObject(channelSftp);
return true;
}
}
配置bean信息
SftpFactory
工厂的建立依赖于SftpProperties
配置属性,SftpProperties
是通过注解@EnableConfigurationProperties
引入
SftpPool
池子依赖于工厂SftpFactory
创建连接
sftpHelper
操作类依赖于 池子SftpPool
中的连接操作具体的文件
package x.x.config.sftpPool;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(SftpProperties.class)
public class SftpConfig {
// 工厂
@Bean
public SftpFactory sftpFactory(SftpProperties properties) {
return new SftpFactory(properties);
}
// 连接池
@Bean
public SftpPool sftpPool(SftpFactory sftpFactory) {
return new SftpPool(sftpFactory);
}
// 辅助类
@Bean
public SftpHelper sftpHelper(SftpPool sftpPool) {
return new SftpHelper(sftpPool);
}
}
至此一个简单的池子就完成了