依赖
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.55</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.5.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.5.6</version>
<scope>test</scope>
</dependency>
目录结构
+---java
| \---com
| \---example
| \---jsch
| | JschApplication.java
| |
| +---dto
| | DownloadDto.java
| | UploadDto.java
| |
| +---sftp
| | SftpConfig.java
| | SftpConfiguration.java
| | SftpUtil.java
| |
| \---util
| BeanFactory.java
|
\---resources
| application.yaml
配置类
@Data
@Configuration
@ConfigurationProperties(prefix = "sftp")
class SftpConfiguration {
private String host;
private String userName;
private String password;
private int port = 22;
private Integer timeout;
}
application.yaml
sftp:
userName: xxxx
password: xxxxxxxx
port: 22
timeout: 60000
host: xxxxxxxxx
工具类
BeanFactory
@Configuration
public class BeanFactory implements ApplicationContextAware {
private static ApplicationContext applicationContext;
public static <T> T getBean(Class<T> clazz){
return applicationContext.getBean(clazz);
}
public static <T> T getBean(String beanName){
return (T) applicationContext.getBean(beanName);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
SftpConfig
@Slf4j
class SftpConfig {
final String SFTP_CHANNEL_TYPE = "sftp";
final String CONNECT_FAIL_MESSAGE = "连接SFTP失败,host:【%s】,port:【%s】,userName:【%s】,请检查相关配置,网络,以及防火墙情况";
private Session session = null;
private Channel channel = null;
private ChannelSftp channelSftp = null;
public ChannelSftp getChannelSftp() {
return channelSftp;
}
public void disconnect() {
if (channelSftp != null) {
channelSftp.disconnect();
channelSftp.exit();
channelSftp = null;
}
if (channel != null) {
channel.disconnect();
channel = null;
}
if (session != null) {
session.disconnect();
session = null;
}
}
public static SftpConfig createSftpConfig() {
// 能拿到配置就行,不一定用Spring
SftpConfiguration sftpConfiguration = BeanFactory.getBean(SftpConfiguration.class);
SftpConfig sftpConfig = new SftpConfig(sftpConfiguration);
return sftpConfig;
}
private SftpConfig(SftpConfiguration sftpConfiguration) {
JSch jSch = new JSch();
connectSession(sftpConfiguration, jSch);
connectChannel(sftpConfiguration);
}
private void connectChannel(SftpConfiguration sftpConfiguration) {
try {
this.channel = session.openChannel(SFTP_CHANNEL_TYPE);
this.channel.connect();
} catch (JSchException e) {
log.error("channel连接失败", e);
if (this.channel != null) {
channel.disconnect();
}
throw buildConnectFailException(e, sftpConfiguration);
}
this.channelSftp = (ChannelSftp) channel;
}
private void connectSession(SftpConfiguration sftpConfiguration, JSch jSch) {
try {
this.session = jSch.getSession(sftpConfiguration.getUserName(), sftpConfiguration.getHost(), sftpConfiguration.getPort());
} catch (JSchException e) {
log.error("获取Session失败", e);
if (session != null) {
session.disconnect();
}
throw buildConnectFailException(e, sftpConfiguration);
}
this.session.setPassword(sftpConfiguration.getPassword());
Properties config = new Properties();
// 不验证 HostKey
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
try {
if (sftpConfiguration.getTimeout() == null) {
this.session.connect();
} else {
this.session.connect(sftpConfiguration.getTimeout());
}
} catch (JSchException e) {
log.error("session连接失败", e);
if (this.session != null) {
session.disconnect();
}
throw buildConnectFailException(e, sftpConfiguration);
}
}
public RuntimeException buildConnectFailException(JSchException e, SftpConfiguration sftpConfiguration) {
return new RuntimeException(String.format(CONNECT_FAIL_MESSAGE, sftpConfiguration.getHost(), sftpConfiguration.getPort(), sftpConfiguration.getUserName()), e);
}
}
SftpUtil
public static void upload(String uploadDir, String fileName, InputStream inputStream) {
UploadDto uploadDto = new UploadDto();
uploadDto.setUploadDir(uploadDir);
UploadDto.UploadInfo uploadInfo = new UploadDto.UploadInfo(inputStream, fileName);
uploadDto.setUploadInfoList(Collections.singletonList(uploadInfo));
upload(uploadDto);
}
public static void upload(UploadDto uploadDto) {
process(uploadDto, upload());
}
public static void download(String dir, String fileName, OutputStream outputStream) {
process(new DownloadDto(dir,fileName,outputStream), download());
}
public static void download(DownloadDto dto) {
process(dto, download());
}
private static <T> void process(T dto, BiConsumer<ChannelSftp, T> consumer) {
SftpConfig sftpConfig = SftpConfig.createSftpConfig();
ChannelSftp sftp = sftpConfig.getChannelSftp();
try {
consumer.accept(sftp, dto);
} finally {
sftpConfig.disconnect();
}
}
private static BiConsumer<ChannelSftp, UploadDto> upload() {
return (sftp, uploadDto) -> {
String uploadDir = uploadDto.getUploadDir();
cdToDir(sftp, uploadDir);
Collection<UploadDto.UploadInfo> uploadInfoList = uploadDto.getUploadInfoList();
for (UploadDto.UploadInfo uploadInfo : uploadInfoList) {
doUpload(sftp, uploadDir, uploadInfo);
}
};
}
private static BiConsumer<ChannelSftp, DownloadDto> download() {
return (sftp, dto) -> {
String dir = dto.getDir();
try {
sftp.cd(dir);
} catch (SftpException e) {
log.error("路径【{}】不存在", dir);
throw new RuntimeException("路径【" + dir + "】不存在", e);
}
String fileName = dto.getFileName();
try {
sftp.get(fileName, dto.getOutputStream());
} catch (SftpException e) {
log.error("下载路径【{}】的文件【{}】失败", dir, fileName);
throw new RuntimeException("下载路径【" + dir + "】的文件【" + fileName + "】失败", e);
}
};
}
private static void cdToDir(ChannelSftp sftp, String uploadDir) {
try {
//进入目录
sftp.cd(uploadDir);
} catch (SftpException exception) {
try {
//指定上传路径不存在
if (ChannelSftp.SSH_FX_NO_SUCH_FILE == exception.id) {
//创建目录
sftp.mkdir(uploadDir);
//进入目录
sftp.cd(uploadDir);
}
} catch (SftpException e) {
log.error("创建目录【" + uploadDir + "】失败", e);
throw new RuntimeException("创建目录【" + uploadDir + "】失败", e);
}
}
}
private static void doUpload(ChannelSftp sftp, String uploadDir, UploadDto.UploadInfo uploadInfo) {
String fileName = uploadInfo.getFileName();
try {
sftp.put(uploadInfo.getInputStream(), fileName);
log.info("上传文件【{}】至目录【{}】成功", fileName, uploadDir);
} catch (SftpException e) {
log.error("上传文件【{}】至目录【{}】失败", fileName, uploadDir);
throw new RuntimeException("上传文件【" + fileName + "】至目录【" + uploadDir + "】失败", e);
}
}
}
DTO
DownloadDto
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DownloadDto {
private String dir;
private String fileName;
private OutputStream outputStream;
}
UploadDto
@Data
public class UploadDto {
private String uploadDir;
private Collection<UploadInfo> uploadInfoList;
@AllArgsConstructor
@NoArgsConstructor
@Data
public static class UploadInfo{
private InputStream inputStream;
private String fileName;
}
}
测试
@SpringBootTest(classes = JschApplication.class)
class JschApplicationTests {
@Test
void upload() throws FileNotFoundException {
SftpUtil.upload("/app/testupload/","test.png",new FileInputStream("E:/young/spi-test.png"));
}
@Test
void download() throws FileNotFoundException {
SftpUtil.download("/app/testupload/","test.png",new FileOutputStream("E:/young/spi-test1.png"));
}
}