java通过sftp形式连接主机下载文件(附项目与主机编码不一致解决方法)

最近接了一个文件下载接口需求,需要采用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~,成功下载。

原文地址:https://www.cnblogs.com/keyforknowledge/p/13565756.html