目录
4、定义sftp客户端:里面包含连接校验,文件夹创建,文件判断,文件上传下载等多种方法,根据实际需要使用;
直接上模块代码:
完整项目源码地址(参考ideal-sftp-1018模块):https://github.com/yangshilei/springCloud
1、项目结构
下面红圈中是主要的几个关于sftp客户端操作的对象方法。
2、添加sftp核心配置依赖
<!--sftp核心依赖包-->
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.54</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
3、定义sftpConfig对象
package com.demo.sftp.sftp;
import com.alibaba.fastjson.JSON;
import lombok.Data;
@Data
public class SftpConfig {
/**
* ftp连接名
*/
private String ftpName;
/**
* SFTP IP地址
*/
private String ip;
/**
* SFTP 端口
*/
private Integer port;
/**
* SFTP 用户名
*/
private String userName;
/**
* SFTP 密码
*/
private String password;
/**
* 连接失败次数
*/
private int clientFailNum;
@Override
public String toString() {
return JSON.toJSONString(this);
}
}
4、定义sftp客户端:里面包含连接校验,文件夹创建,文件判断,文件上传下载等多种方法,根据实际需要使用;
package com.demo.sftp.sftp;
import com.demo.sftp.utils.Base64Util;
import com.jcraft.jsch.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import javax.validation.constraints.NotNull;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
@Slf4j
@Data
public class SftpClient implements AutoCloseable {
/**
* 配置信息
*/
private SftpConfig conf;
/**
* Sftp客户端对象
*/
private ChannelSftp sftp = null;
/**
* 会话
*/
private Session sshSession = null;
/**
* 通道
*/
private Channel channel = null;
/**
* 最后一次使用的时间
* 为便于计算, 使用long型时间戳记录
*/
private long lastUseDate;
public SftpClient(SftpConfig conf) {
this(conf.getFtpName(), conf.getIp(), conf.getPort(), conf.getUserName(), conf.getPassword());
}
public SftpClient(String ip, int port, String username, String password) {
this(null, ip, port, username, password);
}
public SftpClient(String sftpName,String ip, int port, String username, String password) {
conf = new SftpConfig();
if(StringUtils.isEmpty(sftpName)){
conf.setFtpName(username + "@" + ip + ":" + port);
}
conf.setIp(ip);
conf.setPort(port);
conf.setUserName(username);
conf.setPassword(password);
this.connect();
}
public synchronized ChannelSftp connect(){
if(!this.checkConf()){
log.error("sftp连接失败,连接信息不全");
int clientFailNum = conf.getClientFailNum() + 1;
conf.setClientFailNum(clientFailNum);
throw new RuntimeException("sftp连接失败,连接信息不全");
}
return connect(conf.getIp(), conf.getPort(), conf.getUserName(), conf.getPassword());
}
/**
* 连接sftp服务器,连接失败就报异常;
* @param ip
* @param port
* @param username
* @param password
* @return
*/
private synchronized ChannelSftp connect(String ip, int port, String username, String password){
JSch jsch = new JSch();
Properties sshConfig = new Properties();
try {
sshSession = jsch.getSession(username, ip, port);
sshSession.setPassword(password);
sshConfig.put("StrictHostKeyChecking", "no");
sshSession.setConfig(sshConfig);
sshSession.connect();
channel = sshSession.openChannel("sftp");
channel.connect();
sftp = (ChannelSftp) channel;
log.info("sftp服务器连接成功!");
}catch (Exception e){
log.error("sftp 连接错误: {}@{}:{}; pwd:{}", username, ip, port, password);
int num = conf.getClientFailNum() + 1;
conf.setClientFailNum(num);
throw new RuntimeException("sftp连接错误");
}
return sftp;
}
/**
* 校验连接sftp的4个基本信息是否为空
* 齐全:true;否则:false;
*/
private boolean checkConf() {
if (null != conf) {
return (conf.getIp() != null &&
conf.getPort() != 0 &&
conf.getUserName() != null &&
conf.getPassword() != null);
}
return false;
}
@Override
public synchronized void close() {
if(null != channel){
channel.disconnect();
}
if(null != sftp){
sftp.disconnect();
}
if(null != sshSession){
sshSession.disconnect();
}
}
// *********************************下面是文件操作的方法******************************************************
/**
* 功能说明:打开指定目录
*/
public synchronized boolean openDir(String directory) {
checkClient();
log.info("打开指定目录 sftp cd {}", directory);
if (StringUtils.isEmpty(directory)) {
log.error("sftp 打开目录失败: 目录名不能为空!");
return false;
}
try {
if (!isDirExist(directory)) {
sftp.mkdir(directory);
}
sftp.cd(directory);
return true;
} catch (SftpException e) {
log.error("sftp 打开目录错误: {}", e.getMessage());
return false;
}
}
/**
* 检查连接状态, 连接断开则自动重连,重连失败抛出异常
*/
public synchronized void checkClient() {
if (!this.isConnected()) {
this.connect();
}
}
/**
* 检查连接状态 true: 连接正常 false: 连接断开
*/
public synchronized boolean isConnected() {
if (sftp != null && sftp.isConnected()) {
return true;
}
log.info("SFTP {}@{} 连接被断开", conf.getUserName(), conf.getIp());
return false;
}
/**
* 判断目录是否存在
*/
public synchronized boolean isDirExist(String directory) {
boolean isDirExistFlag = false;
try {
SftpATTRS sftpATTRS = sftp.lstat(directory);
isDirExistFlag = true;
return sftpATTRS.isDir();
} catch (Exception e) {
if ("no such file".equals(e.getMessage().toLowerCase())) {
isDirExistFlag = false;
} else {
this.close();
}
}
return isDirExistFlag;
}
/**
* 通过发送pwd命令模拟心跳, 用于维持长连接
* 先检查连接状态, 连接断开时进行一次重连
*
* @return 连接正常或重连成功返回true <br/>
* 连接错误且重连失败返回false
*/
public synchronized boolean heartbeat() {
checkClient();
return (null != pwd());
}
/**
* 查看当前所处目录
*/
public synchronized String pwd() {
String path = null;
try {
path = sftp.pwd();
} catch (SftpException e) {
log.error("sftp {} 密码错误:{}", conf.getFtpName(), e.getMessage());
}
return path;
}
/**
* 重命名文件或者目录 ,移动文件或者目录
*
* @param oldpath 旧文件或目录
* @param newpath 新文件或目录
*/
public synchronized boolean rename(String oldpath, String newpath) {
log.info("sftp 移动文件从旧目录 {}到新目录 {}", oldpath, newpath);
try {
sftp.rename(oldpath, newpath);
return true;
} catch (Exception e) {
log.error("SFTP移动文件错误: {}", e.getMessage());
return false;
}
}
/**
* 上传文件
* 上传本地文件
*
* @param directory 上传的目录
* @param fileName 要上传的文件名
*/
public synchronized boolean upload(String directory, String fileName, InputStream in) throws IOException {
log.info("sftp upload file {} to {}", fileName, directory);
if (null == in || null == fileName) {
log.error("上传文件失败, 缺少重要参数!");
return false;
}
boolean status = false;
try {
if (this.openDir(directory)) {
sftp.put(in, fileName);
status = true;
}
} catch (Exception e) {
log.error("上传文件错误! {}", e.getMessage());
this.close();
} finally {
in.close();
}
return status;
}
/**
* 上传图片
* 上传base64Str 格式的图片
*
* @param base64Str base64编码必填(去掉"data:image/jpeg;base64,"头)
* @param directory 目录,必填(device=设备图片目录,inspection=巡检图片目录)
* @param fileName 文件名可以为空(扩展名建议jpg)
*/
public synchronized String uploadFile(@NotNull String base64Str, String directory, String fileName) {
if (StringUtils.isBlank(base64Str)) {
throw new RuntimeException("base64Str不能为空");
}
try (InputStream inputStream = Base64Util.baseToInputStream(base64Str)) {
if (this.openDir(directory)) {
if (StringUtils.isEmpty(fileName)) {
fileName = this.getUUID() + ".jpg";
}
// 图片服务器目录:device=设备图片目录,inspection=巡检图片目录,文件名=UUID
// 目标文件名
String dst = FileUtil.unite(directory, fileName);
sftp.put(inputStream, dst, ChannelSftp.OVERWRITE);
}
} catch (Exception e) {
log.error("上传图片错误: {}", e);
this.close();
}
return fileName;
}
/**
* 获取宇宙唯一码.
*
* @version Revision 1.0.0
* @see:
* @功能说明:
*
* @return
*/
private String getUUID() {
UUID uuid = UUID.randomUUID();
String uuidStr = uuid.toString().toUpperCase(Locale.ENGLISH);
if (StringUtils.isEmpty(uuidStr)) {
return "";
}
return uuidStr;
}
/**
* 下载文件
*
* @param directory 下载文件所在路径目录
* @param downFileName 下载的文件名称
* @param savePath 保存到本地的路径目录
*/
public synchronized boolean download(String directory, String downFileName, String savePath) {
log.info("sftp 下载 {} to {}", FileUtil.unite(directory, downFileName), savePath);
boolean status = false;
File file = null;
FileOutputStream fileOutputStream = null;
try {
if (openDir(directory)) {
if (FileUtil.isExistDir(savePath)) {
String saveFile = FileUtil.unite(savePath, downFileName);
file = new File(saveFile);
fileOutputStream = new FileOutputStream(file);
sftp.get(downFileName, fileOutputStream);
status = true;
}
}
} catch (Exception e) {
log.error("sftp 下载 {} 失败: {}", downFileName, e);
this.close();
} finally {
try {
if (null != fileOutputStream) {
fileOutputStream.close();
}
} catch (Exception e) {
log.error("sftp 下载文件, 关闭输出流失败");
status = false;
}
}
return status;
}
/**
* 下载文件
*
* @param directory 下载目录
* @param downloadFile 下载的文件名称
* @param localFile 本地文件名称
* @param saveDir 存在本地的目录
*/
public synchronized boolean download(String directory, String downloadFile, String localFile, String saveDir)
throws IOException {
boolean status = false;
File file = null;
FileOutputStream fileOutputStream = null;
try {
if (openDir(directory)) {
File fileDir = new File(saveDir);
if (isExistsDir(fileDir)) {
file = new File(saveDir + localFile);
fileOutputStream = new FileOutputStream(file);
sftp.get(downloadFile, fileOutputStream);
status = true;
}
}
} catch (Exception e) {
log.error(e.getMessage());
this.close();
} finally {
if (null != fileOutputStream) {
fileOutputStream.close();
}
}
return status;
}
/**
* 下载文件,下载过程中采用重命名防止被其他程序误处理
*
* @param downloadPath 下载目录
* @param fileName 文件名
* @param savePath 保存目录
* @param suffixPattren 下载中文件后缀名
* @return boolean
*/
public synchronized boolean downloadAsnFile(String downloadPath, String fileName, String savePath, String suffixPattren) {
boolean status = true;
FileOutputStream fileOutputStream = null;
try {
File fileDir = new File(savePath);
if (!fileDir.exists()) {
fileDir.mkdirs();
}
File tempFile = new File(FileUtil.unite(savePath, fileName + suffixPattren));
fileOutputStream = new FileOutputStream(tempFile);
sftp.get(fileName, fileOutputStream);
File file = new File(FileUtil.unite(savePath, fileName));
tempFile.renameTo(file);
} catch (Exception e) {
status = false;
log.error("下载文件异常,原因:{}", e.getMessage());
} finally {
try {
if (null != fileOutputStream) {
fileOutputStream.close();
}
} catch (IOException e) {
log.error("关闭文件{}出错", fileName);
}
}
return status;
}
/**
* 判断指定文件夹是否存在, 不存在则创建
*
* @param file 指定文件夹
* @author RenZhengGuo 2016年8月13日 下午5:29:37
*/
private synchronized boolean isExistsDir(File file) {
boolean mkdir = false;
// 如果文件夹不存在则创建
if (!file.exists() && !file.isDirectory()) {
log.info("目录不存在,创建目录");
mkdir = file.mkdirs();
}
return mkdir;
}
/**
* @param file
* @author RenZhengGuo 2016年8月13日 下午5:29:37
*/
public synchronized void cd(String file) {
// 如果文件夹不存在则创建
try {
sftp.cd(file);
} catch (SftpException e) {
createDir(file);
// chmod(Integer.parseInt("777", 8), file);
}
}
/**
* 创建目录
*
* @param file
* @return
*/
public synchronized boolean mkdir(String file) {
if (isDirExist(file)) {
return true;
}
try {
sftp.mkdir(file);
return true;
} catch (SftpException e) {
log.error("创建文件夹出错!{}", e);
return false;
}
}
/**
* 给目录授权
*
* @param permsion
* @param file
*/
public synchronized void chmod(int permsion, String file) {
try {
sftp.chmod(permsion, file);
} catch (SftpException e) {
log.info("为文件:{}授权失败!", file);
}
}
/**
* 创建一个文件目录
*/
public synchronized void createDir(String createpath) {
try {
if (isDirExist(createpath)) {
this.sftp.cd(createpath);
return;
}
// mkdir 命令不能创建多级目录, 所以要先根据文件分隔符分割成单个目录组
String[] pathArry = createpath.trim().split(FileUtil.DEF_LINE_SEPARATOR);
StringBuilder filePath = null;
// 如果是绝对路径会以"/"开头, 此时分割出的数组首位是空串应替换成"/"
if (pathArry[0].isEmpty()) {
filePath = new StringBuilder(FileUtil.DEF_LINE_SEPARATOR);
} else {
filePath = new StringBuilder();
}
for (String pathNode : pathArry) {
if (StringUtils.isEmpty(pathNode)) {
continue;
}
filePath.append(pathNode);
String path = filePath.toString();
if (!isDirExist(path)) {
sftp.mkdir(path);
}
filePath.append(FileUtil.DEF_LINE_SEPARATOR);
}
this.sftp.cd(createpath);
} catch (SftpException e) {
log.info("创建路径错误:" + createpath);
}
}
/**
* 判断文件是否存在
*/
public synchronized boolean fileExist(String directory, String fileName) {
boolean isDirExistFlag = false;
try {
Vector<ChannelSftp.LsEntry> vector = sftp.ls(directory);
if (vector != null && !vector.isEmpty()) {
Iterator<ChannelSftp.LsEntry> iterator = vector.iterator();
while (iterator.hasNext()) {
ChannelSftp.LsEntry f = iterator.next();
if (f.getAttrs().isDir()) {
continue;
}
if (fileName.equals(f.getFilename())) {
return true;
}
}
}
return false;
} catch (Exception e) {
if ("no such file".equals(e.getMessage().toLowerCase())) {
isDirExistFlag = false;
} else {
this.close();
}
}
return isDirExistFlag;
}
/**
* 删除文件
* 不能使用全路径删除, 先CD到文件所在目录, 删除完毕后在CD回原目录
*
* @param directory 要删除文件所在目录
* @param deleteFile 要删除的文件
*/
public synchronized boolean delete(String directory, String deleteFile) {
checkClient();
log.info("sftp del {}/{}", directory, deleteFile);
String pwd = pwd();
boolean status = false;
try {
sftp.cd(directory);
sftp.rm(deleteFile);
status = true;
sftp.cd(pwd);
} catch (Exception e) {
log.error("sftp 删除文件错误: {}", e);
this.close();
}
return status;
}
/**
* 删除文件
* 删除当前目录下的文件
*
* @param deleteFile 要删除的文件
*/
public synchronized boolean delete(String deleteFile) {
checkClient();
log.info("sftp del {}", deleteFile);
boolean status = false;
try {
sftp.rm(deleteFile);
status = true;
} catch (Exception e) {
log.error("sftp 删除文件错误: {}", e);
this.close();
}
return status;
}
/**
* 列出指定目录下的文件
* 失败时返回空list
*
* @param directory 要列出的目录
* @return 文件名列表
*/
public synchronized List<String> listFiles(String directory) {
checkClient();
log.info("sftp ls {}", directory);
List<String> ftpFileNameList = new ArrayList<>();
if (!StringUtils.isEmpty(directory)) {
try {
Vector<ChannelSftp.LsEntry> sftpFile = sftp.ls(directory);
for (ChannelSftp.LsEntry item : sftpFile) {
ftpFileNameList.add(item.getFilename());
}
} catch (SftpException e) {
log.error("sftp 获取文件列表错误! {}", e.getMessage());
this.close();
}
}
return ftpFileNameList;
}
/**
* 改变目录用户组
*
* @param gid
* @param path
* @return boolean
*/
public synchronized boolean chgrp(Integer gid, String path) {
try {
sftp.chgrp(gid, path);
return true;
} catch (SftpException e) {
log.info("改变用户组失败:{}", e.getMessage());
return false;
}
}
/**
* 打开指定文件名的文件, 返回InputStream
* 失败返回 null
*
* @param filePath 要打开的文件名
* @return io流
*/
public synchronized InputStream openFile(String filePath) {
log.info("sftp open file {}", filePath);
InputStream inputStream = null;
try {
inputStream = sftp.get(filePath);
return inputStream;
} catch (SftpException e) {
log.error("打开文件错误: {}", e);
this.close();
return null;
}
}
}
5、定义sftp工厂类
package com.demo.sftp.sftp;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
/**
* Sftp连接创建工厂
*/
@Slf4j
public class SftpFactory {
private Map<String, SftpList> factory;
private final int DEF_MAP_NUM = 16;
/**
* 默认连接数
* 初始化连接池的时候默认创建的连接数
*/
private int defClientNum = 1;
/**
* 连接池自动增长的维护时间间隔
*/
private int growDistanceTime = 10;
/**
* 连接池自动清理空闲连接的维护时间间隔
*/
private int strictionDistanceTime = 2;
public SftpFactory() {
factory = new HashMap<>(DEF_MAP_NUM);
}
public SftpFactory(int defClientNum) {
if (defClientNum > 0){
this.defClientNum = defClientNum;
}
factory = new HashMap<>(DEF_MAP_NUM);
}
/**
* 通过工厂创建SFTP连接<br/>
* 如果连接池里已经有同样的连接则直接从连接池里获取<br/>
* 如果连接池里没有, 则创建新的
*
* @param config SFTP连接配置
* @return SFTP连接对象
*/
public SftpClient createSftp(SftpConfig config){
return createSftp(config.getIp(),config.getPort(),config.getUserName(),config.getPassword());
}
public SftpClient createSftp(String ip, int port, String username, String password){
String key = username + "@" + ip + ":" + port;
SftpConfig conf = new SftpConfig();
conf.setIp(ip);
conf.setPort(port);
conf.setUserName(username);
conf.setPassword(password);
conf.setFtpName(key);
SftpList sftpList = factory.get(key);
if (null == sftpList){
sftpList = new SftpList(conf, defClientNum);
factory.put(key, sftpList);
}
else if (sftpList.isEmpty()){
sftpList.add(conf);
}
return sftpList.next();
}
private class SftpList{
private List<SftpClient> sftpClients;
private SftpClient tempClient;
private SftpConfig conf;
/**
* 最大连接数
*/
private final int MAX_CLIENT_NUM = 10;
/**
* 最小连接数
*/
private final int MIN_CLIENT_NUM = 1;
/**
* 最大错误次数,
*/
private final int MAX_ERROR_NUM = 3;
private int index = 0;
private SftpList(SftpConfig config) {
this(config, defClientNum);
}
/**
* 创建一个新的连接池
* @param config sftp 连接配置
* @param clientNum 指定连接数
*/
private SftpList(SftpConfig config, int clientNum){
if (clientNum < MIN_CLIENT_NUM){
clientNum = MIN_CLIENT_NUM;
}else if (clientNum > MAX_CLIENT_NUM){
clientNum = MIN_CLIENT_NUM;
}
this.sftpClients = new ArrayList<>(clientNum);
this.conf = config;
init();
}
/**
* 在已有连接池增加一个 sftp 连接,
* 增加后的总连接数不能大于最大连接数
*
* @param config 连接配置
*/
public void add(SftpConfig config){
if (conf == null || conf.getFtpName() == null){
conf = config;
}
add();
}
private void add(){
if (size() < MAX_CLIENT_NUM){
SftpClient sftp = new SftpClient(conf);
sftpClients.add(sftp);
}
}
/**
* 初始化连接池
* 如果连接池是空的则自动按默认连接数增加连接
*/
public void init(){
int size = sftpClients.size();
if (size <= 0){
size = defClientNum;
}
for (int i = 0; i < size; i++) {
SftpClient sftp = new SftpClient(conf);
sftp.setLastUseDate(System.currentTimeMillis());
sftpClients.add(sftp);
}
this.heartbeat();
}
/**
* 取出一个sftp连接
* @return sftp连接
*/
private synchronized SftpClient next(){
if ((++index) >= size()){
index = 0;
}
SftpClient sftp = sftpClients.get(index);
if (sftp == null || !sftp.isConnected()){
log.warn("SFTP---{}({}) 空连接尝试重连: {}", index, size(), conf.getFtpName());
sftp = new SftpClient(conf);
sftpClients.set(index, sftp);
}
sftp.setLastUseDate(System.currentTimeMillis());
log.info("获取 SFTP-{}({}): {}", index, size(), conf.getFtpName());
return sftp;
}
/**
* 获取一条早期使用的 sftp 连接
* @return sftp 连接
*/
private SftpClient early(){
int i = index + 2;
if (i == sftpClients.size()){
i = 0;
}
return sftpClients.get(i);
}
private void updateConf(SftpConfig config){
if (null != config){
conf.setIp(config.getIp());
conf.setPort(config.getPort());
conf.setUserName(config.getUserName());
conf.setPassword(config.getPassword());
}
for (SftpClient ftp : sftpClients){
ftp.setConf(conf);
}
log.info("更新 SFTP 连接: {}", conf.getFtpName());
}
/**
* 删除最后一个连接
* 为避免关闭使用中的连接, 先将最后一个连接放到缓存中, 等下一次检查连接时再删除
*/
private void removeLast(){
int lastIndex = sftpClients.size() - 1;
if (this.tempClient != null){
this.tempClient.close();
}
tempClient = sftpClients.get(lastIndex);
sftpClients.remove(lastIndex);
log.info("删除最后一条 SFTP 连接: {}", conf.getFtpName());
}
/**
* 删除一个指定的连接, 删除的连接会直接关闭
* @param index 需要删除的连接下标
*/
private void removeSftp(int index){
if (sftpClients.size() > index){
SftpClient ftp = sftpClients.get(index);
if (ftp != null){
ftp.close();
}
sftpClients.remove(index);
log.info("删除 SFTP-{}({}) 连接: {}", index, size(), conf.getFtpName());
}
}
private void removeAll(){
for (SftpClient ftp : sftpClients){
ftp.close();
}
sftpClients.clear();
log.info("清除 SFTP 连接池: {}", conf.getFtpName());
}
private final long DELAY = 10L;
private final long LONG_DISTANCE = TimeUnit.MINUTES.toMillis(growDistanceTime);
private final long SHORT_DISTANCE = TimeUnit.MINUTES.toMillis(strictionDistanceTime);
/**
* 定时器, 定时检查连接状态;<be/>
* 超过15分钟没有调用SFTP连接, 则关闭最后一个连接;<be/>
* 总连接数等于最小连接数(MIN_CLIENT_NUM)时不再关闭连接;<be/>
*
* 所有SFTP连接的最后一次调用时间间隔小于2分钟时, 说明使用比较频繁;<be/>
* SFTP连接调用频繁时, 自动增加一个新连接;<be/>
* 总连接数等于最大连接数(MAX_CLIENT_NUM)时, 不再增加连接;<be/>
*/
public void heartbeat(){
ThreadFactory threadFactory = new BasicThreadFactory.Builder()
.namingPattern("sftp-heartbeat-%d").daemon(false).build();
ScheduledExecutorService executorService =
new ScheduledThreadPoolExecutor(1, threadFactory);
executorService.scheduleWithFixedDelay(()->{
// 关闭临时连接
if (tempClient != null){
tempClient.close();
tempClient = null;
}
// sftp 连接心跳
for (SftpClient f : sftpClients){
try{
f.heartbeat();
}catch(Exception e){
log.error("sftp心跳错误");
removeAll();
}
}
// 总连接数大于最小连接数时, 判断最后一次使用的连接距当前的使用间隔
// 大于10分钟未使用的移到临时连接, 等待下一次自动关闭
if (sftpClients.size() > MIN_CLIENT_NUM){
long nowDate = System.currentTimeMillis();
long da = nowDate - early().getLastUseDate();
if (da > LONG_DISTANCE){
removeLast();
log.info("清理空闲连接: {}", conf.getFtpName());
}
}
// 总连接数小于最大连接数时, 轮询所有连接
// 如果使用间隔小于2分钟, 自动增加一个连接
if (sftpClients.size() < MAX_CLIENT_NUM && checkClient()){
add();
}
}, DELAY, DELAY, TimeUnit.MINUTES);
}
private boolean checkClient(){
long nowDate = System.currentTimeMillis();
for (SftpClient ftp: sftpClients){
long da = nowDate - ftp.getLastUseDate();
if (da > SHORT_DISTANCE){
return false;
}
}
log.info("增加连接: {}", conf.getFtpName());
return true;
}
public boolean isEmpty(){
return sftpClients.isEmpty();
}
public int size(){
return sftpClients.size();
}
}
}
6、启动类中添加SftpFactory;
package com.demo.sftp;
import com.demo.sftp.sftp.SftpFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@SpringBootApplication
@EnableSwagger2
@EnableAsync // 开启spring异步注解
public class SftpApplication {
public static void main(String[] args) {
SpringApplication.run(SftpApplication.class,args);
}
@Bean
public SftpFactory sftpFactory(){
return new SftpFactory();
}
}
7、创建测试方法
因为太懒,本方法中关于sftp的连接信息是直接写死的,实际项目中建议将sftp连接信息配置到配置文件中或者数据库中进行动态获取,这样当sftp的连接信息有所变动时候,可以避免修改对应连接sftp客户端的代码。
package com.demo.sftp.controller;
import com.demo.sftp.dto.Result;
import com.demo.sftp.sftp.FileUtil;
import com.demo.sftp.sftp.SftpClient;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
@Slf4j
@Api("sftp学习接口")
@RestController
@RequestMapping(value = "/sftp")
public class TestController {
@ApiOperation(value = "测试接口",notes = "测试接口")
@PostMapping("/test")
Result test(){
log.info("请求正常");
this.uploadFile();
return Result.ok("success");
}
/**
* 模拟上传文件到sftp服务器:
* 代码生成数据,写入文件并上传到sftp服务器;
*/
private void uploadFile(){
// 因为该模块代码对外公开,所以此处使用代码的符合表示实际的连接信息。
String ip = "ip";
String port = "port";
String userName = "username";
String passWord = "passWord";
SftpClient sftp = new SftpClient(null, ip, Integer.parseInt(port), userName, passWord);
log.info("sftp客户端创建成功");
String content = this.getContent();
ByteArrayInputStream bakInputStream = new ByteArrayInputStream(content.getBytes());
ByteArrayInputStream inputStream = new ByteArrayInputStream(content.getBytes());
log.info("开始上传文件");
String bakPath = "/upload/bakPath";
String storePath = "/upload/storePath";
String fileName = "YSL_" + 8888 + "_test" + System.currentTimeMillis();
String tempFileName = "~" + fileName;
boolean b = this.uploadFile(sftp, bakPath, storePath,
fileName, tempFileName, bakInputStream, inputStream);
log.info("文件上传结束");
if(!b){
log.info("文件上传失败了");
return;
}
sftp.close();
}
/**
* 文件内容封装
*/
private String getContent(){
StringBuilder outStr = new StringBuilder("STA|1"+"\r\n");
outStr.append("数据编号,企业ID,企业名称,操作类型,账单类型,产品类型,产品唯一识别编码," +
"账目项1,账目项1产品单价,账目项1产品个数,账目项2,账目项2产品单价,账目项2产品个数"+"\r\n");
outStr.append(1+",")
.append(1112233+",")// 数据编号
.append(31803+",") // 企业ID
.append(3+",") // 操作类型
.append(2 + ",") // 账单类型
.append(40+",") // 产品类型编号
.append("SV3333333"+",") // 产品唯一识别编码
.append("zmx111222"+",") // 账目项1
.append(233+",") // 账目项1产品单价
.append(1+",") // 账目项1产品个数
.append("zmx111222"+",") // 账目项2
.append(33+",") // 账目项2产品单价
.append(1+",") // 账目项2产品个数
.append("\r\n")
.append("end");
return outStr.toString();
}
/**
* 上传文件
*/
public static boolean uploadFile(SftpClient sftp, String bakPath, String storePath,
String fileName, String tempFileName,
InputStream bakInput, InputStream input) {
try {
//上传至备份目录
log.info("开始上传文件fileName==={}",fileName);
sftp.upload(bakPath, fileName, bakInput); // 文件上传到备份文件夹
//上传临时文件至下发目录
boolean b = sftp.upload(storePath, tempFileName, input);
// 临时文件上传成功,修改临时文件名为正式名
if (b) {
sftp.rename(FileUtil.unite(storePath, tempFileName), FileUtil.unite(storePath, fileName));
}
return true;
} catch (IOException e) {
log.error("上传文件{}出错", fileName);
log.error(e.getLocalizedMessage());
return false;
}
}
}
8、swagger接口调用测试,看是否生成文件
查看对应sftp目录下是否确实创建目录成功:如果使用的账号在当前目录没有创建目录的权限,则需要手动将对应的文件目录创建好之后才能使用程序上传文件。
文件夹下文件也上传成功
至此sftp上传文件的演示结束,实际生产中,可能有很多的文件服务器,对应不同的账号,建议这些信息写入系统信息数据库表中,以动态读取的形式获取配置信息,毕竟将很多的sftp服务器信息都配置在配置文件中还是显得很冗余。