一.前言
在Spring Boot中连接多个SFTP服务器并动态切换需要的SFTP进行上传和下载是一个非常有用的功能。在本文中,我们将介绍如何使用Spring Boot连接多个SFTP服务器,并动态切换需要的SFTP进行上传和下载相关操作
首先,让我们来了解一下SFTP是什么。SFTP(Secure File Transfer Protocol)是一种安全的文件传输协议,它使用SSH(Secure Shell)协议进行加密和身份验证。SFTP比FTP更加安全和可靠,因为它使用加密技术来保护文件传输过程中的数据安全。
在Spring Boot中,我们可以使用JSch
来连接多个SFTP服务器,并实现上传和下载文件的功能。JSch
是一个用Java语言编写的SSH2协议的实现库,它提供了SSH2协议的完整实现,包括远程执行命令、传输文件等功能。
接下来,我们将介绍如何使用JSch
连接多个SFTP服务器,并动态切换需要的SFTP进行上传和下载相关操作。
二.具体实现
1.引入依赖
首先搭建一个基本的SpringBoot项目,这里我列出了主要用到的几个依赖,SpringBoot相关的我就不具体列了:
<properties>
<jsch-version>0.1.55</jsch-version>
<fastjson2-version>2.0.28</fastjson2-version>
<commons-lang3-version>3.12.0</commons-lang3-version>
<commons-io-version>2.11.0</commons-io-version>
</properties>
<dependencies>
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>${jsch-version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson2-version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3-version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io-version}</version>
</dependency>
</dependencies>
2.创建SFTP配置实体
引入依赖后,我们需要为SFTP相关的配置创建一个实体,用于保存SFTP相关的配置信息
public class SFtpConnectionConfig {
private String name;
private String host;
private int port;
private String username;
private String password;
private String remoteDirectory;
// getters and setters
}
其中name
属性用于标识某一个SFTP服务,对于多个SFTP服务来说,是唯一的,remoteDirectory
配置的是上传文件的远程根文件夹,所有上传的文件都放在此文件夹下,该实体存放的输入类似这样的:
{
"name": "default",
"host": "sftp.server1.com",
"port": 22,
"username": "user1",
"password": "password1",
"remoteDirectory": "/data/files"
}
3.定义接口并实现
我们定义一个接口,用于规范对SFTP可进行的操作,我们首先定义上传,下载和删除方法:
public interface FtpApi {
/**
* 默认SFTP服务的名称
*/
static final String DEFAULT_CHANNEL = "default";
/**
* @param inputStream 上传的文件流
* @param connectionName 上传SFTP服务的名称
* @param basePath 上传文件的文件路径,使用相对路径,格式exp: folder1/folder2
* @param fileName 上传文件的名称
* @return 上传文件的相对路径
* @throws Exception
*/
String upload(InputStream inputStream, String connectionName, String basePath, String fileName) throws Exception;
/**
* @param filename 下载文件的相对路径,注意要带着文件名
* @param connectionName 下载SFTP服务的名称
* @return 下载的文件
* @throws Exception
*/
Resource download(String filename, String connectionName) throws Exception;
/**
* @param filename 删除文件的相对路径,注意要带着文件名
* @param connectionName 删除SFTP服务的名称
* @throws Exception
*/
void delete(String filename, String connectionName) throws Exception;
}
有了接口之后,定义实现类用于实现 SFTP 文件的上传、下载和删除操作,并在实现类中动态加载 SFTP 连接信息:
@Service
public class SFtpApiImpl implements FtpApi {
private Map<String, SFtpConnectionConfig> sftpConnectionConfigMap;
@PostConstruct
public void init() {
//从配置文件中或者数据库中初始化SFTP配置列表,根据具体情况实现
}
@Override
public String upload(InputStream inputStream, String connectionName, String basePath, String fileName) throws Exception {
ChannelSftp channel = null;
try {
SFtpConnectionConfig config = getConnectionConfig(connectionName);
channel = getChannel(config);
if (StringUtils.isNotBlank(basePath)) {
createDir(config.getRemoteDirectory() + "/" + basePath, channel);
}
String filePath = StringUtils.isBlank(basePath) ? fileName : (basePath + "/" + fileName);
String fullFilePath = config.getRemoteDirectory() + "/" + filePath;
channel.put(inputStream, fullFilePath);
return filePath;
} catch (JSchException e) {
throw new RuntimeException(e);
} catch (Exception e) {
throw new Exception("Error uploading file: " + e.getMessage());
} finally {
Session session = null;
if (channel != null) {
session = channel.getSession();
channel.disconnect();
}
if (session != null) {
session.disconnect();
}
inputStream.close();
}
}
@Override
public Resource download(String filename, String connectionName) throws Exception {
ChannelSftp channel = null;
SFtpConnectionConfig config = null;
InputStream inputStream = null;
try {
config = getConnectionConfig(connectionName);
channel = getChannel(config);
inputStream = channel.get(config.getRemoteDirectory() + "/" + filename);
ByteArrayResource resource = new ByteArrayResource(IOUtils.toByteArray(inputStream));
return resource;
} catch (JSchException e) {
throw new RuntimeException(e);
} catch (Exception e) {
throw new Exception("Error downloading file: " + e.getMessage());
} finally {
Session session = null;
if (channel != null) {
session = channel.getSession();
channel.disconnect();
}
if (session != null) {
session.disconnect();
}
if (inputStream != null) {
inputStream.close();
}
}
}
@Override
public void delete(String filename, String connectionName) throws Exception {
ChannelSftp channel = null;
SFtpConnectionConfig config = null;
try {
config = getConnectionConfig(connectionName);
channel = getChannel(config);
channel.rm(config.getRemoteDirectory() + "/" + filename);
} catch (JSchException e) {
throw new RuntimeException(e);
} catch (Exception e) {
throw new Exception("Error deleting file: " + e.getMessage());
} finally {
Session session = null;
if (channel != null) {
session = channel.getSession();
channel.disconnect();
}
if (session != null) {
session.disconnect();
}
}
}
private void createDir(String path, ChannelSftp sftp) throws SftpException {
String[] folders = path.split("/");
sftp.cd("/");
for (String folder : folders) {
if (folder.length() > 0) {
try {
sftp.cd(folder);
} catch (SftpException e) {
sftp.mkdir(folder);
sftp.cd(folder);
}
}
}
}
private SFtpConnectionConfig getConnectionConfig(String connectionName) throws JSchException {
return Optional.ofNullable(sftpConnectionConfigMap.get(connectionName))
.orElseThrow(() -> new RuntimeException("SFTP connection not found: " + connectionName));
}
private ChannelSftp getChannel(SFtpConnectionConfig config) throws JSchException {
JSch jsch = new JSch();
Session sshSession = jsch.getSession(config.getUsername(), config.getHost(), config.getPort());
sshSession.setPassword(config.getPassword());
sshSession.setConfig("StrictHostKeyChecking", "no");
sshSession.connect();
Channel channel = sshSession.openChannel("sftp");
channel.connect();
return (ChannelSftp) channel;
}
}
最后编写控制类,可以通过传递 SFTP 连接名称参数,调用 SFtpApiImpl
的方法实现 SFTP 文件的上传、下载和删除操作:
@RestController
public class SFtpController {
@Autowired
private FtpApi ftpApi;
@PostMapping("/upload")
public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file,
@RequestParam("connectionName") String connectionName) {
try {
ftpApi.upload(file.getInputStream(), connectionName, "", file.getOriginalFilename());
return ResponseEntity.ok().body("File uploaded successfully");
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Failed to upload file: " + e.getMessage());
}
}
@GetMapping("/download")
public ResponseEntity<Resource> downloadFile(@RequestParam("filename") String filename,
@RequestParam("connectionName") String connectionName) {
try {
Resource resource = ftpApi.download(filename, connectionName);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"")
.body(resource);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(null);
}
}
@DeleteMapping("/delete")
public ResponseEntity<String> deleteFile(@RequestParam("filename") String filename,
@RequestParam("connectionName") String connectionName) {
try {
ftpApi.delete(filename, connectionName);
return ResponseEntity.ok().body("File deleted successfully");
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Failed to delete file: " + e.getMessage());
}
}
}
这里有一个地方需要注意,上传方法,如果上传相同路径下的同一个文件,新的文件会将旧的文件覆盖.
三.结束
综上所述,使用Spring Boot连接多个SFTP服务器并动态切换需要的SFTP进行上传和下载相关操作是一个非常实用和有用的功能。在本文中,我们介绍了如何使用JSch来实现这个功能,并提供了具体实现步骤,在具体的项目中,可以根据实际的项目需要以此进行相应的改造.
具体示例实现代码以上传,可以下载后查看或者使用