<!-- 引入依赖 -->
<dependency>
<groupId>com.github.mwiede</groupId>
<artifactId>jsch</artifactId>
<version>0.2.21</version>
</dependency>
SFTP工具类代码
/**
* SFTP 工具类,用于通过 SSH 协议进行文件传输
*/
@Slf4j
@Component
public class SftpUtils {
public static final String SFTP_KEY = "SFTP_KEY";
private Session session;
private ChannelSftp channel;
@Resource
private Dao dao;
/**
// * 连接 SFTP 服务器 这里使用的是调用数据库获取配置信息
// * @param host 主机名
// * @param port 端口
// * @param username 用户名
// * @param password 密码
* @throws JSchException 连接异常
*/
public String connect() {
log.info("SftpUtils connect start===========================");
String content = dao.getConfigInfoByCode(SFTP_KEY);
if (StrUtil.isBlank(content)) {
log.error("SftpUtils connect 获取SFTP_KEY配置失败!!!");
throw new Exception("获取SFTP_KEY配置失败!!!");
}
log.info("SftpUtils connect content : {}", content);
SftpConfigVO sftpConfigVO = JSONUtil.toBean(content, SftpConfigVO.class);
if(StrUtil.isNotBlank(sftpConfigVO.getPrivateKeyPath())){
//使用秘钥文件登录
connect2(sftpConfigVO);
}else{
//使用账号密码登录
connect1(sftpConfigVO);
}
//返回远程根目录路径
return sftpConfigVO.getRemoteDir();
}
public void connect1(SftpConfigVO sftpConfigVO) {
log.info("SftpUtils connect1 start - sftpConfigVO :{}===========================", JSONUtil.toJsonStr(sftpConfigVO));
try {
JSch jsch = new JSch();
log.info("================connect1 SFTP 获取sshSession开始================");
session = jsch.getSession(sftpConfigVO.getUsername(), sftpConfigVO.getHost(), sftpConfigVO.getPort());
log.info("================connect1 SFTP 获取sshSession成功================");
session.setPassword(sftpConfigVO.getPassword());
// 配置 SSH 会话属性
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no"); // 跳过主机密钥检查
session.setConfig(config);
session.setTimeout(30000); // 设置连接超时时间
log.info("================connect1 SFTP 开启sshSession链接================");
session.connect();
log.info("connect1 SFTP 开启sshSession链接成功! host : {} , port : {}", sftpConfigVO.getHost(), sftpConfigVO.getPort());
// 打开 SFTP 通道
channel = (ChannelSftp) session.openChannel("sftp");
channel.connect();
log.info("connect1 SFTP 通道连接成功");
} catch (JSchException e) {
disconnect();
e.printStackTrace();
log.error("connect1 SftpUtils connect exception !!! : {}", e.getMessage());
throw new Exception("connect1 SftpUtils connect error");
}
// finally {
// disconnect();
// }
log.info("SftpUtils connect1 end===========================");
}
public void connect2(SftpConfigVO sftpConfigVO) {
log.info("SftpUtils connect2 start - sftpConfigVO : {}===========================", JSONUtil.toJsonStr(sftpConfigVO));
try {
JSch jsch = new JSch();
// 设置私钥
if (StrUtil.isNotBlank(sftpConfigVO.getPrivateKeyPath())) {
// 从项目资源中加载私钥文件
ClassPathResource meiryo = new ClassPathResource(sftpConfigVO.getPrivateKeyPath());
String privateKeyPath = meiryo.getAbsolutePath();
if (StrUtil.isNotBlank(sftpConfigVO.getPassphrase())) {
jsch.addIdentity(privateKeyPath, sftpConfigVO.getPassphrase());
} else {
jsch.addIdentity(privateKeyPath);
}
log.info("connect2 已设置私钥文件: {}", privateKeyPath);
}
// 创建session并连接
log.info("================connect2 SFTP 获取sshSession开始================");
session = jsch.getSession(sftpConfigVO.getUsername(), sftpConfigVO.getHost(), sftpConfigVO.getPort());
log.info("================connect2 SFTP 获取sshSession成功================");
// 设置session参数
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no"); // 不检查主机密钥
session.setConfig(config);
// 设置超时时间
session.setTimeout(30000);
// 连接到服务器
log.info("================connect2 SFTP 开启sshSession链接================");
session.connect();
log.info("connect2 SFTP 开启sshSession链接成功! host : {} , port : {}", sftpConfigVO.getHost(), sftpConfigVO.getPort());
// 打开SFTP通道
channel = (ChannelSftp)session.openChannel("sftp");
channel.connect();
// sftp = (ChannelSftp) channel;
log.info("connect2 SFTP 通道连接成功");
} catch (Exception e) {
disconnect();
e.printStackTrace();
log.error("connect2 SftpUtils connect exception !!! : {}", e.getMessage());
throw new Exception("connect2 SftpUtils connect error");
}
log.info("SftpUtils connect2 end===========================");
}
/**
* 上传文件到 SFTP 服务器
* @param localFilePath 本地文件路径
// * @param remoteFileName 远程文件名
* @throws SftpException 上传异常
* @throws FileNotFoundException 文件未找到异常
*/
public void uploadFile(String localFilePath) {
log.info("SftpUtils uploadFile start - localFilePath : {} ", localFilePath);
String remoteDir = this.connect();
if (StrUtil.isBlank(remoteDir)) {
// 确保连接关闭
disconnect();
throw new Exception("SftpUtils uploadFile remoteDir is blank!!!!");
}
log.info("SftpUtils uploadFile start - remoteDir : {} ", remoteDir);
if (channel == null || !channel.isConnected()) {
// throw new IllegalStateException("SFTP连接未建立");
disconnect();
throw new Exception("SftpUtils uploadFile SFTP连接未建立");
}
File localFile = new File(localFilePath);
if (!localFile.exists() || !localFile.canRead()) {
log.error("SftpUtils uploadFile File Not Found !!!!");
return;
}
// 上传文件
String remoteFilePath = remoteDir + "/" + localFile.getName();
try {
// 创建远程目录(如果不存在)
createDirectoryIfNotExists(remoteDir);
// 上传文件
channel.put(localFilePath, remoteFilePath);
} catch (Exception e) {
e.printStackTrace();
log.error("SftpUtils uploadFile exception !!! : {}", e.getMessage());
throw new Exception("SftpUtils uploadFile error");
} finally {
// 确保连接关闭
disconnect();
}
log.info("SftpUtils uploadFile end : {} -> {}", localFilePath, remoteFilePath);
}
/**
* 下载指定目录下的所有文件(不递归)
* @param remoteDir 远程非根目录路径
* @param localDir 本地保存目录
* @return 下载成功的文件列表
*/
public List<String> downloadFiles(String remoteDir ,String localDir) {
log.info("SftpUtils downloadFile start - remoteDir : {} , localDir : {}", remoteDir, localDir);
String baseRemoteDir = this.connect();
log.info("SftpUtils downloadFile - baseRemoteDir : {} ", baseRemoteDir);
if (StrUtil.isBlank(baseRemoteDir)) {
// 确保连接关闭
disconnect();
throw new Exception("SftpUtils downloadFile SftpUtils baseRemoteDir is blank!!!!");
}
if (channel == null || !channel.isConnected()) {
disconnect();
throw new IllegalStateException("SftpUtils downloadFile SFTP连接未建立");
}
// 创建本地目录(如果不存在)
File localDirFile = new File(localDir);
if (!localDirFile.exists()) {
if (!localDirFile.mkdirs()) {
throw new Exception("SftpUtils downloadFile 无法创建本地目录: " + localDir);
}
}
if (StrUtil.isNotBlank(remoteDir)) {
if (remoteDir.startsWith("/")) {
remoteDir = "/" + remoteDir;
}
remoteDir = baseRemoteDir + remoteDir;
}
log.info("SftpUtils downloadFile - fullPath : {} ", remoteDir);
List<String> downloadedFiles = new ArrayList<>();
try {
// 切换到远程目录
channel.cd(remoteDir);
// 获取目录下的所有文件和文件夹
Vector<ChannelSftp.LsEntry> fileList = channel.ls(remoteDir);
for (ChannelSftp.LsEntry entry : fileList) {
String fileName = StringUtil.formatString(entry.getFilename());
// log.info("SftpUtils downloadFile 当前遍历文件: {}", StringUtil.escapeJava(fileName));
// 跳过 . 和 ..以及文件夹
if (".".equals(fileName)
|| "..".equals(fileName)
|| entry.getAttrs().isDir()) {
log.info("SftpUtils downloadFile 跳过排除的文件: {}", fileName);
continue;
}
// 只处理文件,不处理文件夹
String localFilePath = localDir + File.separator + fileName;
final String fullName = StringUtil.formatString(localFilePath);
// log.info("文件完整地址:{}", StringUtil.escapeJava(fullName));
try (FileOutputStream fos = new FileOutputStream(fullName)) {
// 下载文件
log.info("SftpUtils downloadFile 拉取远程ftp文件开始");
channel.get(fileName, fos);
log.info("SftpUtils downloadFile 拉取远程ftp文件{}成功", StringUtil.escapeJava(fileName));
downloadedFiles.add(fullName);
// log.info("SftpUtils downloadFile 成功下载文件: {}", fileName);
} catch (Exception e) {
e.printStackTrace();
log.error("SftpUtils downloadFile 下载文件失败!!! fileName : {} , exception : {} ", fileName, e.getMessage());
}
}
log.info("SftpUtils downloadFile end localDir : {}", localDir);
return downloadedFiles;
} catch (Exception e) {
e.printStackTrace();
log.info("SftpUtils downloadFile exception : {} ", e.getMessage());
throw new Exception("SftpUtils downloadFile error !!!");
} finally {
// 确保连接关闭
disconnect();
}
}
/**
* 删除 SFTP 服务器上的文件
* @param remoteFilePath 远程文件路径
* @throws SftpException 删除异常
*/
public void deleteFile(String remoteFilePath) throws SftpException {
channel.rm(remoteFilePath);
log.info("文件删除成功: {}", remoteFilePath);
}
/**
* 创建远程目录(递归创建)
* @param remoteDir 远程目录路径
* @throws SftpException 创建异常
*/
public void createDirectory(String remoteDir) throws SftpException {
try {
channel.cd(remoteDir);
} catch (SftpException e) {
// 目录不存在,递归创建
String[] dirs = remoteDir.split("/");
String tempPath = "";
for (String dir : dirs) {
if (dir.isEmpty()) continue;
tempPath += "/" + dir;
try {
channel.cd(tempPath);
} catch (SftpException ex) {
channel.mkdir(tempPath);
channel.cd(tempPath);
log.info("创建目录: {}", tempPath);
}
}
}
}
/**
* 检查远程目录是否存在,不存在则创建
* @param remoteDir 远程目录路径
* @throws SftpException 操作异常
*/
private void createDirectoryIfNotExists(String remoteDir) throws SftpException {
try {
channel.cd(remoteDir);
} catch (SftpException e) {
createDirectory(remoteDir);
}
}
/**
* 获取指定路径下的所有文件名
* @param extPath 远程子目录路径
* @return 文件名列表
* @throws SftpException 获取文件列表异常
*/
public List<String> listFileNames(String extPath) {
log.info("SftpUtils listFileNames satrt extPath : {}", extPath);
String remoteDir = this.connect();
log.info("SftpUtils downloadFile start - remoteFilePath1 : {} ", remoteDir);
if (StrUtil.isBlank(remoteDir)) {
// 确保连接关闭
disconnect();
throw new Exception("listFileNames SftpUtils remoteDir is blank!!!!");
}
// log.info("SftpUtils downloadFile start - remoteDir : {} ", remoteDir);
if (channel == null || !channel.isConnected()) {
disconnect();
throw new IllegalStateException("listFileNames SFTP连接未建立");
}
if(StrUtil.isNotBlank(extPath)) {
if (!extPath.startsWith("/")) {
extPath = "/" + extPath;
}
remoteDir = remoteDir + extPath;
}
log.info("SftpUtils downloadFile start - remoteFilePath2 : {} ", remoteDir);
List<String> fileNames = new ArrayList<>();
try {
// 切换到指定目录
// channel.cd(remoteDir);
// 获取目录下的所有文件
Vector<ChannelSftp.LsEntry> entries = channel.ls(remoteDir);
for (ChannelSftp.LsEntry entry : entries) {
String fileName = entry.getFilename();
// 排除当前目录和上级目录 // 不是目录
if (!".".equals(fileName)
&& !"..".equals(fileName)
&& !entry.getAttrs().isDir()
) {
fileNames.add(remoteDir + "/" + fileName);
}
}
} catch (SftpException e) {
e.printStackTrace();
log.info("SftpUtils listFileNames exception : {} ", e.getMessage());
throw new Exception("SftpUtils listFileNames error");
} finally {
// 确保连接关闭
disconnect();
}
log.info("SftpUtils listFileNames end");
return fileNames;
}
/**
* 关闭 SFTP 连接
*/
public void disconnect() {
if (channel != null && channel.isConnected()) {
channel.disconnect();
log.info("SFTP 通道已关闭");
}
if (session != null && session.isConnected()) {
session.disconnect();
log.info("SFTP 会话已关闭");
}
}
/**
* 根据操作系统类型设置文件权限
*/
/*private void setFilePermissions(File file) throws IOException {
// 检查操作系统类型
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
// Windows系统处理
log.info("在Windows系统上运行,跳过设置文件权限");
// Windows系统下JSch可能不需要严格的文件权限
// 但是为了安全起见,我们可以尝试使用Java NIO API设置文件属性
try {
// 设置文件为只读
file.setReadable(true, true); // 仅所有者可读
file.setWritable(true, true); // 仅所有者可写
file.setExecutable(false); // 不可执行
log.info("已设置Windows文件权限");
} catch (Exception e) {
log.warn("设置Windows文件权限失败,但将继续尝试连接", e);
}
} else {
// Linux/Unix/Mac系统处理
try {
// 尝试使用Java 7+的NIO API设置POSIX权限
Set<PosixFilePermission> permissions = new HashSet<>();
permissions.add(PosixFilePermission.OWNER_READ);
permissions.add(PosixFilePermission.OWNER_WRITE);
Files.setPosixFilePermissions(file.toPath(), permissions);
log.info("已设置Linux/Unix文件权限为600");
} catch (Exception e) {
// 如果NIO API失败,尝试使用Runtime.exec执行chmod命令
log.warn("使用NIO API设置文件权限失败,尝试使用chmod命令", e);
try {
Process process = Runtime.getRuntime().exec("chmod 600 " + file.getAbsolutePath());
int exitCode = process.waitFor();
if (exitCode == 0) {
log.info("已使用chmod命令设置文件权限为600");
} else {
log.warn("chmod命令执行失败,退出码: " + exitCode);
}
} catch (Exception ex) {
log.error("设置Linux/Unix文件权限失败", ex);
throw new IOException("无法设置私钥文件权限", ex);
}
}
}
}*/
}
配置的实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SftpConfigVO implements Serializable {
private static final long serialVersionUID = 1L;
// host SFTP服务器地址
private String host;
//port SFTP服务器端口
private int port;
// username 用户名
private String username;
private String password;
//privateKeyPath 私钥文件路径
private String privateKeyPath;
//passphrase 私钥密码(如果有)
private String passphrase;
// 远程目标目录
private String remoteDir;
}
另外说一下遇到的坑=================================================
原先使用的依赖是0.1.54版本的,结果在使用密钥文件进行远程登录发生报错
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.54</version>
</dependency>
以下是报错信息如下:
com.jcraft.jsch.JSchException: invalid privatekey: [B@17f1bc4
at com.jcraft.jsch.KeyPair.load(KeyPair.java:664)
at com.jcraft.jsch.KeyPair.load(KeyPair.java:561)
at com.jcraft.jsch.IdentityFile.newInstance(IdentityFile.java:40)
at com.jcraft.jsch.JSch.addIdentity(JSch.java:406)
at com.jcraft.jsch.JSch.addIdentity(JSch.java:366)
at com.szlanyou.cloud.util.SftpUtils.connect2(SftpUtils.java:148)
at com.szlanyou.cloud.util.SftpUtils.connect(SftpUtils.java:60)
at com.szlanyou.cloud.util.SftpUtils.downloadFiles(SftpUtils.java:242)
后来终于找到了原因,说是此版本不支持高版本的密钥文件,即以下开头的文件
-----BEGIN OPENSSH PRIVATE KEY-----
需要通过命令转成低版本的
ssh-keygen -p -m pem -f path
即以下密钥文件开头的
-----BEGIN RSA PRIVATE KEY-----
感谢大佬的文章救我【jsch链接报错invalid privatekey解决密钥格式openssh转rsa】-CSDN博客
=========================================================================
后来想了一下上面工具类是以依赖注入的方式使用的,也可改成以下方式直接调用
/**
* SFTP 工具类,用于通过 SSH 协议进行文件传输
*/
@Slf4j
public class SftpUtils {
public static final String SFTP_KEY = "SFTP_KEY";
private static Session session;
private static ChannelSftp channel;
/**
// * 连接 SFTP 服务器
// * @param host 主机名
// * @param port 端口
// * @param username 用户名
// * @param password 密码
*/
public static String connect() {
log.info("SftpUtils connect start===========================");
XxxDao xxxDao = SpringContextHolder.getBean(XxxDao.class);
String content = XxxDao.querySendIpInfoByCode(SFTP_KEY);
if (StrUtil.isBlank(content)) {
log.error("SftpUtils connect 获取SFTP_KEY配置失败!!!");
throw new Exception("IT00100001010011");
}
log.info("SftpUtils connect content : {}", content);
SftpConfigVO sftpConfigVO = JSONUtil.toBean(content, SftpConfigVO.class);
if(StrUtil.isBlank(sftpConfigVO.getRemoteDir())){
throw new Exception("SftpUtils connect baseRemoteDir is blank!!!!");
}
if(StrUtil.isNotBlank(sftpConfigVO.getPrivateKeyPath())){
connect2(sftpConfigVO);
}else{
connect1(sftpConfigVO);
}
if (channel == null || !channel.isConnected()) {
// throw new IllegalStateException("SFTP连接未建立");
disconnect();
throw new Exception("SftpUtils connect SFTP连接未建立");
}
return sftpConfigVO.getRemoteDir();
}
public static void connect1(SftpConfigVO sftpConfigVO) {
log.info("SftpUtils connect1 start - sftpConfigVO :{}===========================", JSONUtil.toJsonStr(sftpConfigVO));
try {
JSch jsch = new JSch();
log.info("================connect1 SFTP 获取sshSession开始================");
session = jsch.getSession(sftpConfigVO.getUsername(), sftpConfigVO.getHost(), sftpConfigVO.getPort());
log.info("================connect1 SFTP 获取sshSession成功================");
session.setPassword(sftpConfigVO.getPassword());
// 配置 SSH 会话属性
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no"); // 跳过主机密钥检查
session.setConfig(config);
session.setTimeout(30000); // 设置连接超时时间
log.info("================connect1 SFTP 开启sshSession链接================");
session.connect();
log.info("connect1 SFTP 开启sshSession链接成功! host : {} , port : {}", sftpConfigVO.getHost(), sftpConfigVO.getPort());
// 打开 SFTP 通道
channel = (ChannelSftp) session.openChannel("sftp");
channel.connect();
log.info("connect1 SFTP 通道连接成功");
} catch (JSchException e) {
log.error("connect1 SftpUtils connect exception !!! \n {} ", e.getMessage(), e);
disconnect();
throw new Exception("connect1 SftpUtils connect error");
}
log.info("SftpUtils connect1 end===========================");
}
public static void connect2(SftpConfigVO sftpConfigVO) {
log.info("SftpUtils connect2 start - sftpConfigVO : {}===========================", JSONUtil.toJsonStr(sftpConfigVO));
try {
JSch jsch = new JSch();
// 设置私钥
if (StrUtil.isNotBlank(sftpConfigVO.getPrivateKeyPath())) {
// 从项目资源中加载私钥文件
ClassPathResource keyResource = new ClassPathResource(sftpConfigVO.getPrivateKeyPath());
String privateKeyPath = keyResource.getAbsolutePath();
if (StrUtil.isNotBlank(sftpConfigVO.getPassphrase())) {
jsch.addIdentity(privateKeyPath, sftpConfigVO.getPassphrase());
} else {
jsch.addIdentity(privateKeyPath);
}
log.info("connect2 已设置私钥文件: {}", privateKeyPath);
}
// 创建session并连接
log.info("================connect2 SFTP 获取sshSession开始================");
session = jsch.getSession(sftpConfigVO.getUsername(), sftpConfigVO.getHost(), sftpConfigVO.getPort());
log.info("================connect2 SFTP 获取sshSession成功================");
// 设置session参数
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no"); // 不检查主机密钥
session.setConfig(config);
// 设置超时时间
session.setTimeout(30000);
// 连接到服务器
log.info("================connect2 SFTP 开启sshSession链接================");
session.connect();
log.info("connect2 SFTP 开启sshSession链接成功! host : {} , port : {}", sftpConfigVO.getHost(), sftpConfigVO.getPort());
// 打开SFTP通道
channel = (ChannelSftp)session.openChannel("sftp");
channel.connect();
// sftp = (ChannelSftp) channel;
log.info("connect2 SFTP 通道连接成功");
} catch (Exception e) {
log.error("connect2 SftpUtils connect exception !!! : {}", e.getMessage(),e);
disconnect();
throw new Exception("connect2 SftpUtils connect error");
}
log.info("SftpUtils connect2 end===========================");
}
/**
* 上传文件到 SFTP 服务器
* @param localFilePath 本地文件路径
* @param extRemotePath 远程子路径
*/
public static void uploadFile(String localFilePath,String extRemotePath) {
log.info("SftpUtils uploadFile start - localFilePath : {} ", localFilePath);
String remoteDir = connect();
log.info("SftpUtils uploadFile start - baseRemoteDir : {} ", remoteDir);
remoteDir = getDetailRemoteDir(extRemotePath, remoteDir);
log.info("SftpUtils uploadFile start - remoteDir : {} ", remoteDir);
File localFile = new File(localFilePath);
if (!localFile.exists() || !localFile.canRead()) {
log.error("SftpUtils uploadFile File Not Found !!!! PATH : {}", localFile.getAbsolutePath());
return;
}
// 上传文件
String remoteFilePath = remoteDir + "/" + localFile.getName();
try {
// 创建远程目录(如果不存在)
createDirectoryIfNotExists(remoteDir);
// 上传文件
channel.put(localFilePath, remoteFilePath);
} catch (Exception e) {
log.error("SftpUtils uploadFile exception !!! : {}",e.getMessage(), e);
throw new Exception("SftpUtils uploadFile error");
} finally {
// 确保连接关闭
disconnect();
}
log.info("SftpUtils uploadFile end : {} -> {}", localFilePath, remoteFilePath);
}
/**
* 下载指定目录下的所有文件(不递归),过滤掉excludeFiles列表中的文件
* @param extRemotePath 远程目录路径
* @param localDir 本地保存目录
* @return 下载成功的文件列表
*/
public static List<String> downloadFiles(String extRemotePath ,String localDir) {
log.info("SftpUtils downloadFile start - extRemotePath : {} , localDir : {}", extRemotePath, localDir);
String baseRemoteDir = connect();
log.info("SftpUtils downloadFile - baseRemoteDir : {} ", baseRemoteDir);
extRemotePath = getDetailRemoteDir(extRemotePath, baseRemoteDir);
log.info("SftpUtils downloadFile - remoteDir : {} ", extRemotePath);
// 创建本地目录(如果不存在)
File localDirFile = new File(localDir);
if (!localDirFile.exists()) {
if (!localDirFile.mkdirs()) {
throw new Exception("SftpUtils downloadFile 无法创建本地目录: " + localDir);
}
}
List<String> downloadedFiles = new ArrayList<>();
try {
// 切换到远程目录
channel.cd(extRemotePath);
// 获取目录下的所有文件和文件夹
Vector<ChannelSftp.LsEntry> fileList = channel.ls(extRemotePath);
for (ChannelSftp.LsEntry entry : fileList) {
String fileName = StringUtil.formatString(entry.getFilename());
// log.info("SftpUtils downloadFile 当前遍历文件: {}", StringUtil.escapeJava(fileName));
// 跳过 . 和 ..
if (".".equals(fileName)
|| "..".equals(fileName)
|| entry.getAttrs().isDir()) {
log.info("SftpUtils downloadFile 跳过排除的文件: {}", fileName);
continue;
}
// 只处理文件,不处理文件夹
String localFilePath = localDir + File.separator + fileName;
final String fullName = StringUtil.formatString(localFilePath);
// log.info("文件完整地址:{}", StringUtil.escapeJava(fullName));
try (FileOutputStream fos = new FileOutputStream(fullName)) {
// 下载文件
log.info("SftpUtils downloadFile 拉取远程ftp文件开始");
channel.get(fileName, fos);
log.info("SftpUtils downloadFile 拉取远程ftp文件{}成功", StringUtil.escapeJava(fileName));
downloadedFiles.add(fullName);
// log.info("SftpUtils downloadFile 成功下载文件: {}", fileName);
} catch (Exception e) {
log.error("SftpUtils downloadFile 下载文件失败!!! fileName : {} , exception : {} ", fileName,e.getMessage(), e);
}
}
log.info("SftpUtils downloadFile end localDir : {}", localDir);
return downloadedFiles;
} catch (Exception e) {
log.info("SftpUtils downloadFile exception : {} ", e.getMessage(), e);
throw new Exception("SftpUtils downloadFile error !!!");
} finally {
// 确保连接关闭
disconnect();
}
}
/**
* 获取指定路径下的所有文件名
* @param extRemotePath 目录子路径 可为null
* @return 文件名列表
*/
public static List<String> listFileNames(String extRemotePath) {
log.info("SftpUtils listFileNames start extPath : {}", extRemotePath);
String remoteDir = connect();
log.info("SftpUtils downloadFile start - remoteFilePath1 : {} ", remoteDir);
if (StrUtil.isBlank(remoteDir)) {
// 确保连接关闭
disconnect();
throw new Exception("listFileNames SftpUtils remoteDir is blank!!!!");
}
remoteDir = getDetailRemoteDir(extRemotePath, remoteDir);
log.info("SftpUtils downloadFile start - remoteFilePath2 : {} ", remoteDir);
List<String> fileNames = new ArrayList<>();
try {
// 切换到指定目录
// channel.cd(remoteDir);
// 获取目录下的所有文件
Vector<ChannelSftp.LsEntry> entries = channel.ls(remoteDir);
for (ChannelSftp.LsEntry entry : entries) {
String fileName = entry.getFilename();
// 排除当前目录和上级目录 // 不是目录
if (!".".equals(fileName)
&& !"..".equals(fileName)
&& !entry.getAttrs().isDir()
) {
fileNames.add(remoteDir + "/" + fileName);
}
}
} catch (SftpException e) {
log.info("SftpUtils listFileNames exception : {} ", e.getMessage(), e);
throw new Exception("SftpUtils listFileNames error");
} finally {
// 确保连接关闭
disconnect();
}
log.info("SftpUtils listFileNames end");
return fileNames;
}
/**
* 删除 SFTP 服务器上的文件
* @param remoteDir 远程文件路径
* @throws SftpException 删除异常
*/
public static void deleteFile(String remoteDir) {
log.info("SftpUtils deleteFile start - remoteDir : {}", remoteDir);
String baseRemoteDir = connect();
log.info("SftpUtils deleteFile - baseRemoteDir : {} ", baseRemoteDir);
remoteDir = getDetailRemoteDir(remoteDir, baseRemoteDir);
log.info("SftpUtils deleteFile - fullPath : {} ", remoteDir);
try {
channel.rm(remoteDir);
log.info("SftpUtils deleteFile success - remoteDir : {} ", remoteDir);
} catch (Exception e) {
log.error("SftpUtils deleteFile exception !!! : {}",e.getMessage(), e);
throw new Exception("SftpUtils deleteFile error");
} finally {
// 确保连接关闭
disconnect();
}
log.info("SftpUtils deleteFile end");
}
/**
* 安全删除远程目录(带保护机制)
* @param remoteDir 远程目录路径
* @param confirmToken 确认令牌,防止误删
*/
public static void safeDeleteDirectory(String remoteDir, String confirmToken) {
log.info("SftpUtils safeDeleteDirectory start remoteDir : {}", remoteDir);
// 安全检查:禁止删除根目录
if ("/".equals(remoteDir) || "".equals(remoteDir)) {
// throw new IllegalArgumentException("禁止删除根目录");
throw new Exception("SftpUtils safeDeleteDirectory 禁止删除根目录 !!");
}
String baseRemoteDir = connect();
log.info("SftpUtils safeDeleteDirectory - baseRemoteDir : {} ", baseRemoteDir);
remoteDir = getDetailRemoteDir(remoteDir, baseRemoteDir);
log.info("SftpUtils safeDeleteDirectory - fullPath : {} ", remoteDir);
// 安全检查:确认令牌验证
/*if (!"CONFIRM_DELETE".equals(confirmToken)) {
throw new SecurityException("删除操作需要确认令牌");
}*/
// 安全检查:限制最大递归深度(防止无限递归)
int maxDepth = 10;
if (remoteDir.split("/").length > maxDepth) {
throw new IllegalArgumentException("SftpUtils safeDeleteDirectory 目录路径过深,可能存在风险");
}
// 执行删除
log.info("SftpUtils safeDeleteDirectory deleteDirectory satrt remoteDirPath : {}", remoteDir);
try {
deleteDirectory(remoteDir);
}catch (SftpException e){
log.error("SftpUtils safeDeleteDirectory deleteDirectory exception1 !!! : {}", e.getMessage(), e);
throw new Exception("SftpUtils safeDeleteDirectory deleteDirectory error !!!");
}
log.info("SftpUtils safeDeleteDirectory deleteDirectory end remoteDirPath : {}", remoteDir);
}
/**
* 递归删除远程目录
* @param remoteDirPath 远程目录路径
*/
public static void deleteDirectory(String remoteDirPath) throws SftpException {
try {
// 检查目录是否存在
channel.lstat(remoteDirPath);
} catch (SftpException e) {
if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
// 目录不存在,直接返回
log.info("SftpUtils safeDeleteDirectory deleteDirectory NO SUCH FILE !!! remoteDirPath : {}", remoteDirPath);
return;
}
log.error("SftpUtils safeDeleteDirectory deleteDirectory exception2 !!! : {}", e.getMessage(), e);
throw new Exception("SftpUtils safeDeleteDirectory deleteDirectory error");
}
// 递归删除目录及其内容
recursiveDelete(remoteDirPath);
}
/**
* 递归删除目录及其内容
*/
private static void recursiveDelete(String path) throws SftpException {
log.info("SftpUtils safeDeleteDirectory recursiveDelete satrt path : {}", path);
Vector<ChannelSftp.LsEntry> entries = channel.ls(path);
for (ChannelSftp.LsEntry entry : entries) {
String fileName = entry.getFilename();
if (".".equals(fileName) || "..".equals(fileName)) {
continue;
}
String filePath = path + "/" + fileName;
if (entry.getAttrs().isDir()) {
// 递归删除子目录
recursiveDelete(filePath);
// 删除空目录
channel.rmdir(filePath);
log.info("SftpUtils safeDeleteDirectory recursiveDelete isDir success - filePath : {} ", filePath);
} else {
// 删除文件
channel.rm(filePath);
log.info("SftpUtils safeDeleteDirectory recursiveDelete isFile success - filePath : {} ", filePath);
}
}
// 删除当前空目录
channel.rmdir(path);
log.info("SftpUtils safeDeleteDirectory recursiveDelete rmdir success - path : {} ", path);
}
//========================================================================
private static String getDetailRemoteDir(String extRemotePath, String remoteDir) {
if (StrUtil.isNotBlank(extRemotePath)) {
if (extRemotePath.contains("\\")) {
extRemotePath = URLEncoder.encode(extRemotePath, StandardCharsets.UTF_8)
.replaceAll("\\+", "%20");
}
if (!extRemotePath.startsWith("/")) {
extRemotePath = "/" + extRemotePath;
}
remoteDir = remoteDir + extRemotePath;
}
return remoteDir;
}
/**
* 创建远程目录(递归创建)
* @param remoteDir 远程目录路径
* @throws SftpException 创建异常
*/
public static void createDirectory(String remoteDir) throws SftpException {
try {
channel.cd(remoteDir);
} catch (SftpException e) {
// 目录不存在,递归创建
String[] dirs = remoteDir.split("/");
String tempPath = "";
for (String dir : dirs) {
if (dir.isEmpty()) continue;
tempPath += "/" + dir;
try {
channel.cd(tempPath);
} catch (SftpException ex) {
channel.mkdir(tempPath);
channel.cd(tempPath);
log.info("创建目录: {}", tempPath);
}
}
}
}
/**
* 检查远程目录是否存在,不存在则创建
* @param remoteDir 远程目录路径
* @throws SftpException 操作异常
*/
private static void createDirectoryIfNotExists(String remoteDir) throws SftpException {
try {
channel.cd(remoteDir);
} catch (SftpException e) {
createDirectory(remoteDir);
}
}
/**
* 关闭 SFTP 连接
*/
public static void disconnect() {
if (channel != null && channel.isConnected()) {
channel.disconnect();
log.info("SFTP 通道已关闭");
}
if (session != null && session.isConnected()) {
session.disconnect();
log.info("SFTP 会话已关闭");
}
}
/**
* 根据操作系统类型设置文件权限
*/
/*private void setFilePermissions(File file) throws IOException {
// 检查操作系统类型
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
// Windows系统处理
log.info("在Windows系统上运行,跳过设置文件权限");
// Windows系统下JSch可能不需要严格的文件权限
// 但是为了安全起见,我们可以尝试使用Java NIO API设置文件属性
try {
// 设置文件为只读
file.setReadable(true, true); // 仅所有者可读
file.setWritable(true, true); // 仅所有者可写
file.setExecutable(false); // 不可执行
log.info("已设置Windows文件权限");
} catch (Exception e) {
log.warn("设置Windows文件权限失败,但将继续尝试连接", e);
}
} else {
// Linux/Unix/Mac系统处理
try {
// 尝试使用Java 7+的NIO API设置POSIX权限
Set<PosixFilePermission> permissions = new HashSet<>();
permissions.add(PosixFilePermission.OWNER_READ);
permissions.add(PosixFilePermission.OWNER_WRITE);
Files.setPosixFilePermissions(file.toPath(), permissions);
log.info("已设置Linux/Unix文件权限为600");
} catch (Exception e) {
// 如果NIO API失败,尝试使用Runtime.exec执行chmod命令
log.warn("使用NIO API设置文件权限失败,尝试使用chmod命令", e);
try {
Process process = Runtime.getRuntime().exec("chmod 600 " + file.getAbsolutePath());
int exitCode = process.waitFor();
if (exitCode == 0) {
log.info("已使用chmod命令设置文件权限为600");
} else {
log.warn("chmod命令执行失败,退出码: " + exitCode);
}
} catch (Exception ex) {
log.error("设置Linux/Unix文件权限失败", ex);
throw new IOException("无法设置私钥文件权限", ex);
}
}
}
}*/
}

1901

被折叠的 条评论
为什么被折叠?



