有时我们后台管理项目需要部署多个节点,文件也要存到一个统一的地方,这就需要对远程文件仓库进行上传、下载、文件拷贝、执行shell命令等操作。
接下来笔者将使用JSch来实现对远程文件仓库的操作
源码地址:https://gitcode.net/lu993356091/ftptest
pom.xml文件引入以下依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- sftp -->
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.55</version>
</dependency>
<!-- 连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.11.1</version>
</dependency>
application.yml
server:
port: 80
sftp:
#服务器ip
host: 192.168.xx.xx
#ssh端口
port: 22
#用户名
username: USER001
#密码
password: 123456
#连接池参数
pool:
#对象池中管理的最多对象个数。默认值是8
max-total: 10
#对象池中最大的空闲对象个数。默认值是8
max-idle: 10
#对象池中最小的空闲对象个数。默认值是0
min-idle: 5
创建配置类
SftpConfig.java
package org.example.config;
import com.jcraft.jsch.ChannelSftp;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.example.ftp.SftpUtil;
import org.example.pool.SftpFactory;
import org.example.pool.SftpGenericObjectPool;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SftpConfig {
/**
* 工厂
*
* @author fengfan
* @date 2022/1/14 14:37
* @return
*/
@Bean
public SftpFactory sftpFactory() {
return new SftpFactory();
}
@Bean
@ConfigurationProperties(prefix = "sftp.pool")
public GenericObjectPoolConfig<ChannelSftp> sftpPoolConfig(){
return new GenericObjectPoolConfig<>();
}
@Bean
public SftpGenericObjectPool sftpPool(SftpFactory sftpFactory, GenericObjectPoolConfig<ChannelSftp> sftpPoolConfig){
return new SftpGenericObjectPool(sftpFactory, sftpPoolConfig);
}
@Bean
public SftpUtil sftpUtil(SftpGenericObjectPool sftpPool){
return new SftpUtil(sftpPool);
}
}
创建连接工厂
SftpFactory.java
package org.example.pool;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.springframework.beans.factory.annotation.Value;
import java.util.Properties;
public class SftpFactory extends BasePooledObjectFactory<ChannelSftp> {
@Value("${sftp.host}")
private String host;
@Value("${sftp.port}")
private int port;
@Value("${sftp.username}")
private String username;
@Value("${sftp.password}")
private String password;
@Override
public ChannelSftp create() throws JSchException {
JSch jsch = new JSch();
Session sshSession = jsch.getSession(username, host, port);
sshSession.setPassword(password);
Properties sshConfig = new Properties();
sshConfig.put("StrictHostKeyChecking", "no");
sshSession.setConfig(sshConfig);
sshSession.connect();
ChannelSftp channel = (ChannelSftp) sshSession.openChannel("sftp");
channel.connect();
return channel;
}
@Override
public PooledObject<ChannelSftp> wrap(ChannelSftp channelSftp) {
return new DefaultPooledObject<>(channelSftp);
}
/**
* 销毁对象
*
* @param p
* @return
* @author fengfan
* @date 2022/1/14 15:26
*/
@Override
public void destroyObject(PooledObject<ChannelSftp> p) {
ChannelSftp channelSftp = p.getObject();
channelSftp.disconnect();
}
/**
* 激活连接池里面的sftp连接
*
* @param p
* @throws Exception
*/
@Override
public void activateObject(PooledObject<ChannelSftp> p) throws Exception {
ChannelSftp channelSftp = p.getObject();
if(!channelSftp.isConnected()){
channelSftp.connect();
}
}
}
创建对象池
SftpGenericObjectPool.java
package org.example.pool;
import com.jcraft.jsch.ChannelSftp;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
public class SftpGenericObjectPool{
private final GenericObjectPool<ChannelSftp> genericObjectPool;
public SftpGenericObjectPool(SftpFactory sftpFactory, GenericObjectPoolConfig<ChannelSftp> sftpPoolConfig) {
this.genericObjectPool = new GenericObjectPool<>(sftpFactory, sftpPoolConfig);
}
public ChannelSftp borrowObject() throws Exception {
return genericObjectPool.borrowObject();
}
public void returnObject(ChannelSftp obj) {
genericObjectPool.returnObject(obj);
}
}
创建文件操作工具类
SftpUtil.java
package org.example.ftp;
import com.jcraft.jsch.*;
import org.example.pool.SftpGenericObjectPool;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.StringUtils;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
public class SftpUtil {
//连接池
private SftpGenericObjectPool pool;
@Value("${sftp.host}")
private String host;
@Value("${sftp.port}")
private int port;
@Value("${sftp.username}")
private String username;
@Value("${sftp.password}")
private String password;
public SftpUtil(SftpGenericObjectPool pool) {
this.pool = pool;
}
public boolean isExist(String filePath) throws Exception {
ChannelSftp sftp = pool.borrowObject();
try {
sftp.lstat(filePath);
} catch (SftpException se) {
if (se.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
return false;
}
} finally {
pool.returnObject(sftp);
}
return true;
}
/**
* 下载文件
*
* @param dir 远程目录
* @param name 远程文件名
* @return 文件字节数组
*/
public InputStream download(String dir, String name) throws Exception {
ChannelSftp sftp = pool.borrowObject();
try {
sftp.cd(dir);
return sftp.get(name);
} finally {
pool.returnObject(sftp);
}
}
/**
* 根据全路径下载文件
* @param filePath 远程文件全路径
* @return 文件流
* @throws Exception
*/
public InputStream download(String filePath) throws Exception {
ChannelSftp sftp = pool.borrowObject();
try {
return sftp.get(filePath);
} finally {
pool.returnObject(sftp);
}
}
/**
* 通过http请求下载文件
* @param response HttpServletResponse
* @param filePath 文件全路径
* @param realFileName 下载出来的文件名
* @throws Exception
*/
public void download(HttpServletResponse response, String filePath, String realFileName) throws Exception {
response.reset();
response.setContentType("application/octet-stream");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Disposition","attachment;filename="+ URLEncoder.encode(realFileName,"UTF-8"));
ServletOutputStream os = response.getOutputStream();
BufferedInputStream bis = new BufferedInputStream(download(filePath));
byte[] buff = new byte[1024];
int i = 0;
while ((i = bis.read(buff)) != -1) {
os.write(buff, 0, i);
os.flush();
}
bis.close();
os.close();
}
/**
* 拿到了File,下载到浏览器
* @param response HttpServletResponse
* @param file 文件对象
* @param realFileName 下载的文件名
* @throws Exception
*/
public void download(HttpServletResponse response, File file, String realFileName) throws Exception {
response.reset();
response.setContentType("application/octet-stream");
response.setCharacterEncoding("utf-8");
response.setContentLength((int) file.length());
response.setHeader("Content-Disposition","attachment;filename="+ URLEncoder.encode(realFileName == null ? file.getName() : realFileName,"UTF-8"));
ServletOutputStream os = response.getOutputStream();
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
byte[] buff = new byte[1024];
int i = 0;
while ((i = bis.read(buff)) != -1) {
os.write(buff, 0, i);
os.flush();
}
bis.close();
os.close();
}
/**
* 下载远程文件并读取每一行数据
* @param filePath 远程文件全路径
* @return 文件内容
*/
public List<String> downloadAndReadAllLines(String filePath) {
ChannelSftp sftp = null;
try {
sftp = pool.borrowObject();
InputStream inputStream = sftp.get(filePath);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
ArrayList<String> result = new ArrayList<>();
for (; ; ) {
String line = bufferedReader.readLine();
if (line == null) {
break;
}
result.add(line);
}
return result;
} catch (Exception e) {
return new ArrayList<>();
} finally {
if (sftp != null) {
pool.returnObject(sftp);
}
}
}
/**
* 上传文件
*
* @param dir 远程目录
* @param name 远程文件名
* @param in 输入流
*/
public void upload(String dir, String name, InputStream in) throws Exception {
ChannelSftp sftp = pool.borrowObject();
try {
mkdirs(sftp, dir);
sftp.cd(dir);
sftp.put(in, name);
} finally {
pool.returnObject(sftp);
}
}
/**
* 上传文件
* @param filePath 远程文件全路径
* @param in 文件流
* @throws Exception
*/
public void upload(String filePath, InputStream in) throws Exception {
ChannelSftp sftp = pool.borrowObject();
try {
mkdirs(sftp, filePath);
sftp.put(in, filePath);
} finally {
pool.returnObject(sftp);
}
}
/**
* 直接上传内容数据
* @param filePath 远程文件全路径
* @param data 数据内容
* @throws Exception
*/
public void upload(String filePath, String data) throws Exception {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
this.upload(filePath, byteArrayInputStream);
}
/**
* 删除文件
*
* @param dir 远程目录
* @param name 远程文件名
*/
public void delete(String dir, String name) throws Exception {
ChannelSftp sftp = pool.borrowObject();
try {
sftp.cd(dir);
sftp.rm(name);
} finally {
pool.returnObject(sftp);
}
}
public void delete(String filePath) throws Exception {
ChannelSftp sftp = pool.borrowObject();
try {
sftp.rm(filePath);
} finally {
pool.returnObject(sftp);
}
}
/**
* 递归创建多级目录
*
* @param dir 多级目录
*/
private void mkdirs(ChannelSftp sftp, String dir) throws SftpException {
String[] folders = dir.split("/");
sftp.cd("/");
for (String folder : folders) {
if (folder.length() > 0) {
try {
sftp.cd(folder);
} catch (Exception e) {
sftp.mkdir(folder);
sftp.cd(folder);
}
}
}
}
/**
* 执行shell命令
* @param command shell命令
* 注意:执行shell命令不能使用pool来实现,否则只会第一次成功,后面执行会不起作用(也有可能是我代码的问题)
*/
public void execShell(String command) {
if (StringUtils.isEmpty(command)) {
return;
}
command = command + " \n";
JSch jsch = new JSch();
Session sshSession = null;
ChannelExec sftp = null;
try {
jsch.getSession(username,host,port);
sshSession.setPassword(password);
Properties sshConfig = new Properties();
sshConfig.put("StrictHostKeyChecking", "no");
sshSession.setConfig(sshConfig);
sshSession.connect();
sftp = (ChannelExec) sshSession.openChannel("exec");
sftp.setCommand(command);
OutputStream outputStream = sftp.getOutputStream();
sftp.connect();
outputStream.write(command.getBytes());
outputStream.flush();
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (sftp != null) {
sftp.disconnect();
}
if (sshSession != null) {
sshSession.disconnect();
}
}
}
public void copy(String srcPath, String dstPath) {
String command = "cp " + srcPath + " " + dstPath + ";";
this.execShell(command);
}
public SftpGenericObjectPool getPool() {
return pool;
}
public void setPool(SftpGenericObjectPool pool) {
this.pool = pool;
}
}
创建启动类
App.java
package org.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class App
{
public static void main( String[] args )
{
System.out.println( "Hello World!" );
SpringApplication.run(App.class);
}
}
创建测试Controller
AAController.java
package org.example.controller;
import org.example.ftp.SftpUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
@RestController
public class AAController {
@Autowired
SftpUtil sftpUtil;
@RequestMapping("/aaa")
public String test(){
return "aaa";
}
@GetMapping("/upload")
public String upload() throws Exception {
String dir = "/tmp/";
String name = "test.txt";
String uploadFilePath = "";
InputStream in = new FileInputStream(new File(uploadFilePath));
sftpUtil.upload(dir,name,in);
return "上传成功";
}
}
源码地址:https://gitcode.net/lu993356091/ftptest