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~,成功下載。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM