最近接了一個文件下載接口需求,需要采用sftp形式與對端主機連接,進行文件傳輸。
首先引入java操作sftp的工具類包:
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.53</version>
</dependency>
編寫文件工具類(包含sftp連接、斷開、下載文件等方法)
/**
* sftp形式下載文件
*
* @author wangshuai
*
*/
@Slf4j
public class SFTPUtil {
private ChannelSftp sftp = new ChannelSftp();
private Session session;
/**
* SFTP 登錄用戶名
*/
private String username;
/**
* SFTP 登錄密碼
*/
private String password;
/**
* 私鑰
*/
private String privateKey;
/**
* SFTP 服務器地址IP地址
*/
private String host;
/**
* SFTP 端口
*/
private int port;
/**
* 構造基於密碼認證的sftp對象
*/
public SFTPUtil(String username, String password, String host, int port) {
this.username = username;
this.password = password;
this.host = host;
this.port = port;
}
/**
* 構造基於秘鑰認證的sftp對象
*/
public SFTPUtil(String username, String host, int port, String privateKey) {
this.username = username;
this.host = host;
this.port = port;
this.privateKey = privateKey;
}
public SFTPUtil() {
}
/**
* 連接sftp服務器
*/
public void login() {
try {
JSch jsch = new JSch();
if (privateKey != null) {
jsch.addIdentity(privateKey);// 設置私鑰
}
session = jsch.getSession(username, host, port);
if (password != null) {
session.setPassword(password);
}
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.connect();
Channel channel = session.openChannel("sftp");
channel.connect();
sftp = (ChannelSftp) channel;
} catch (JSchException e) {
e.printStackTrace();
log.error("login.faild",e);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SftpException e) {
e.printStackTrace();
}
}
/**
* 關閉連接 server
*/
public void logout() {
if (sftp != null) {
if (sftp.isConnected()) {
sftp.disconnect();
}
}
if (session != null) {
if (session.isConnected()) {
session.disconnect();
}
}
}
public boolean isExist(String serverPath, String folderName) {
try {
sftp.cd(serverPath);
log.info("登錄到當前路徑:"+serverPath);
SftpATTRS attrs = null;
attrs = sftp.stat(folderName);
if (attrs != null) {
return true;
}
} catch (Exception e) {
e.getMessage();
return false;
}
return false;
}
public boolean isConnect() {
if (null != session) {
return session.isConnected();
}
return false;
}
public InputStream download(String directory) throws SftpException {
return sftp.get(directory);
}
}
編寫對應controller控制層代碼
@RestController
@RequestMapping(value = "XXXXXXX")
@Slf4j
public class StatementShowController {
@Value(value = "${statementshow.ftpHost}")
String ftpHost;
@Value(value = "${statementshow.ftpUserName}")
String ftpUserName;
@Value(value = "${statementshow.ftpPort}")
int ftpPort;
@Value(value = "${statementshow.ftpPassword}")
String ftpPassword;
@Value(value = "${statementshow.hostDirectory}")
String hostDirectory;
@GetMapping("/downloadfile")
public DataResult<Boolean> downloadFile (@RequestParam("fdate") String fdate, @RequestParam("fname") String fname,
HttpServletRequest request, HttpServletResponse response){
if (StringUtils.isBlank(fdate) || StringUtils.isBlank(fname)) {
return new DataResult<>(403, "fdate,fileName不能為空.", false);
}
log.info("downloadfile--sftpInfo:"+ftpHost+","+ftpUserName+","+ftpPort+","+ftpPassword+","+hostDirectory+","+fdate+","+fname);
String filePath =hostDirectory+"/"+fdate;
byte[] buffer = new byte[1024 * 10];
InputStream fis = null;
SFTPUtil sftp = null;
OutputStream out = null;
try {
//sftp登錄
log.info("sftplogin:begin");
sftp = new SFTPUtil(ftpUserName, ftpPassword, ftpHost,ftpPort);
sftp.login();
log.info("sftplogin:success");
//判斷有無對應文件
if (!sftp.isExist(filePath, fname)) {
log.info("sftp:文件或路徑未找到");
throw new ServiceException("文件未找到");
}
response.setContentType("application/force-download;charset=UTF-8");
final String userAgent = request.getHeader("USER-AGENT");
try {
String fileName = null;
if (org.apache.commons.lang3.StringUtils.contains(userAgent, "MSIE") || org.apache.commons.lang3.StringUtils.contains(userAgent, "Edge")) {
// IE瀏覽器
fileName = URLEncoder.encode(fname, "UTF8");
} else if (org.apache.commons.lang3.StringUtils.contains(userAgent, "Mozilla")) {
// google,火狐瀏覽器
fileName = new String(fname.getBytes(), "ISO8859-1");
} else {
// 其他瀏覽器
fileName = URLEncoder.encode(fname, "UTF8");
}
response.setHeader("Content-disposition", "attachment; filename=" + fileName);
} catch (UnsupportedEncodingException e) {
log.error(e.getMessage(), e);
return null;
}
fis = sftp.download(fname);
out = response.getOutputStream();
//讀取文件流
int len = 0;
while ((len = fis.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
return new DataResult<>(200, "文件成功下載.", true);
} catch (Exception e) {
e.printStackTrace();
log.error("文件下載失敗",e);
return new DataResult<>(403, "文件下載失敗.請檢查文件路徑", false);
} finally {
sftp.logout();
if (out != null) {
try {
out.close();
} catch (IOException e) {
out = null;
}
}
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
fis = null;
}
}
}
}
}
此時代碼已經初步完畢,只要你的項目編碼與sftp目標主機編碼一致就完全沒問題。
問題是我的項目編碼是utf-8,目標主機用的是gbk編碼。此時可以下載不帶中文的文件。帶中文名稱的報找不到對應文件的錯誤、。
此時可以設置ChannelSftp的編碼來解決問題,調用setFilenameEncoding("GBK")即可。
問題是調用這個方法以后項目運行到這一行報錯了,報錯信息是:The encoding can not be changed for this sftp server.
debug進這個方法:

發現他的version是3,而version在3與5之間是不可以設置編碼的。
一開始我嘗試換該jar包的版本,但是換了好幾個發現這個version依舊是3.
此時只能通過反射來修改這個屬性的值,進而使setFilenameEncoding("GBK")生效。
在login方法最后加以下代碼:
Class<?> cl = ChannelSftp.class;
Field f =cl.getDeclaredField("server_version");
f.setAccessible(true);
f.set(sftp, 2);
sftp.setFilenameEncoding("GBK");
OK~,成功下載。
