最近接了一个文件下载接口需求,需要采用sftp形式与对端主机连接,进行文件传输。 首先引入java操作sftp的工具类包: <dependency> <groupId>com.jcraft</groupId> <artifactId>jsch</artifactId> <version>0.1.53</version> </dependency>编写文件工具类(包含sftp连接、断开、下载文件等方法) /** * sftp形式下载文件 * * @Author wangshuai * */@Slf4jpublic 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")@Slf4jpublic 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~,成功下载。 有相同爱好的可以进来一起讨论哦:企鹅群号:1046795523
|