最近的项目上需要访问客户的sftp服务器,开发文件的上传和下载的功能,上网查询了相关知识点,中间也踩过不少坑,学到蛮多东西。

参考链接:https://www.jianshu.com/p/05341634bae2

1. 使用ftpClient进行处理

刚开始在http的相关jar包中去找,发现android并没有官方提供的api方法调用,需要添加第三方jar包,这其中使用较多的就是 ftp4j 和common-net的包,后者属于java的基础包之一。这里我选择的是common-net的包,于是上网去查询,发现好多包的版本不一,而且都是需要积分的。这个时候突然想起,这种包应该在maven仓库中会有引入地址的。但是我突然忘记了maven官网的地址,这里安利一个网站叫 https://www.wanandroid.com/ 很适合学习安卓的小伙伴,我在这的入口找到了common-net的引用地址。

jar包地址

在gradle中添加远程依赖 implementation 'commons-net:commons-net:3.7.2',其中关键的一个类就是FtpClient,这个类中封装了请求ftp服务器的方法。具体使用过程可以参靠https://www.jianshu.com/p/05341634bae2,当我按照这个方法进行调试时,发现请求连接服务器失败,报错信息为 org.apache.commons.net.MalformedServerReplyException: Could not parse response code.Server Reply: SSH-2.0-OpenSSH_5.3 ,查询资料后得知,当使用org.apache.commons.net.ftp.FTPClient通过协议SSH2进行SFTP连接时报如上错误,原因是它不支持这种方式的连接(使用FTPSClient的SSL也是不行的)。

2. 使用jsch访问sftp服务器

common-net的包提供的方法不行,考虑是不是因为功能上有差异,于是转为使用ftp4j的jar包,引入方式也是采用gradle配置,引入之后发现核心处理类依旧是ftpclient,只是封装方式跟common-net有一些区别而已,既然是协议方面的问题,于是跟客户沟通发现其开通了sftp服务协议,那现在就应该是sftp的服务器了,那就不能用最基本的请求ftp的方法来处理 ,这里我使用的com.jcraft.jsch.JSch的类来进行处理。

首先依旧是引入远程仓库地址 implementation'com.jcraft:jsch:0.1.55'  ,接下来便是封装相关的工具类,这里我把代码放上来,很简单的代码。

private JSchjSch;
private Sessionsession =null;
private ChannelSftpsftp =null;
private SessionsshSession =null;
private static SftpUtilssftpUtils =null;
public SftpUtils() {
jSch =new JSch();
}
public static SftpUtilsgetInstance(){
if (sftpUtils==null){
sftpUtils =new SftpUtils();
}
return  sftpUtils;
}
/*
* 启动连接
* */
public void connect(){
try {
jSch.getSession(Constant.FTP_USERNAME,Constant.FTP_URL,22);
sshSession =jSch.getSession(Constant.FTP_USERNAME,Constant.FTP_URL,22);
sshSession.setPassword(Constant.FTP_PASSWORD);
Properties sshConfig =new Properties();
sshConfig.put("StrictHostKeyChecking", "no");
sshSession.setConfig(sshConfig);
sshSession.connect();
Channel channel =sshSession.openChannel("sftp");
channel.connect();
sftp = (ChannelSftp) channel;
}catch (JSchException e) {
e.printStackTrace();
}
}
/*
* 关闭连接
* */
public void disConnect(){
if (this.sftp!=null){
if (this.sftp.isConnected()){
this.sftp.disconnect();
}
}
if (this.sshSession !=null) {
if (this.sshSession.isConnected()) {
this.sshSession.disconnect();
}
}
}
/*
* 批量下载文件
* */
public ListbatchDownFile(){
List filenames =new ArrayList();//本地已更新文件的路径集合
String saveStoreFileName = SharedHelp.getString("saveStoreFileName","");
String saveProductFileName = SharedHelp.getString("saveProductFileName","");
boolean _flag =false;
if (!isConnect()){
connect();
_flag =true;
}
try {
Vector v = listFiles("inbound");
if (v.size()>0){
Iterator iterator = v.iterator();
while (iterator.hasNext()){
ChannelSftp.LsEntry entry = (ChannelSftp.LsEntry) iterator.next();
SftpATTRS attrs = entry.getAttrs();
if (!attrs.isDir()){
boolean flag =false;
String filename = entry.getFilename();
if (filename.contains("store_fixture") && !filename.equals(saveStoreFileName) ){
//ftp服务器上的文件跟本地文件不一致,启动更新
String localFilePath = CommonUtils.getLocalFilePath(Constant.LOCAL_FILE_INBODUN)
+ File.separator + filename;
flag = downloadFile(Constant.FTP_FILE_DOWNLOAD_PATH,filename,localFilePath);
if (flag){
SharedHelp.saveString("saveStoreFileName",filename);
filenames.add(localFilePath);
}
}else if(filename.contains("stock_sku_loc") && !filename.equals(saveProductFileName)){
String localFilePath = CommonUtils.getLocalFilePath(Constant.LOCAL_FILE_INBODUN)
+ File.separator + filename;
flag = downloadFile(Constant.FTP_FILE_DOWNLOAD_PATH,filename, localFilePath);
if (flag){
SharedHelp.saveString("saveProductFileName",filename);
filenames.add(localFilePath);
}
}
}
}
}
}catch (SftpException e) {
e.printStackTrace();
}finally {
if (_flag)
disConnect();
}
return filenames;
}
/*
* 下载单个文件
* */
public BooleandownloadFile(String remotePath,String remoteFileName,String localFilePath){
FileOutputStream fieloutput =null;
boolean _flag =false;
try {
if (!isConnect()){
connect();
_flag =true;
}
File file =new File(localFilePath);
if (!file.exists()) {
File dir =new File(file.getParent());
dir.mkdirs();
file.createNewFile();
}
fieloutput =new FileOutputStream(file);
sftp.get(remotePath + File.separator + remoteFileName, fieloutput);
return true;
}catch (Exception e) {
e.printStackTrace();
}finally {
if (null != fieloutput) {
try {
fieloutput.close();
}catch (IOException e) {
e.printStackTrace();
}
}
if (_flag)
disConnect();
}
return false;
}
/**
* 上传单个文件
*
* @param remotePath:远程保存目录
* @param remoteFileName:保存文件名
* @return
*/
public boolean uploadFile(FileInputStream fip, String remoteFileName, String remotePath) {
boolean _flag =false;
try {
if (!isConnect()){
connect();
_flag =true;
}
createDir(remotePath);
sftp.put(fip, remoteFileName);
return true;
}catch (SftpException e) {
e.printStackTrace();
}finally {
if (fip !=null) {
try {
fip.close();
}catch (IOException e) {
e.printStackTrace();
}
}
if (_flag)
disConnect();
}
return false;
}
/**
* 创建目录
*
* @param createpath
* @return
*/
public boolean createDir(String createpath) {
boolean _flag =false;
try {
if (!isConnect()){
connect();
_flag =true;
}
if (isDirExist(createpath)) {
this.sftp.cd(createpath);
return true;
}
String pathArry[] = createpath.split("/");
StringBuffer filePath =new StringBuffer("/");
for (String path : pathArry) {
if (path.equals("")) {
continue;
}
filePath.append(path +"/");
if (isDirExist(filePath.toString())) {
sftp.cd(filePath.toString());
}else {
// 建立目录
sftp.mkdir(filePath.toString());
// 进入并设置为当前目录
sftp.cd(filePath.toString());
}
}
this.sftp.cd(createpath);
return true;
}catch (SftpException e) {
e.printStackTrace();
}finally {
if (_flag)
disConnect();
}
return false;
}
/**
* 判断目录是否存在
*
* @param directory
* @return
*/
public boolean isDirExist(String directory) {
boolean isDirExistFlag =false;
boolean _flag =false;
try {
if (!isConnect()){
connect();
_flag =true;
}
SftpATTRS sftpATTRS =sftp.lstat(directory);
isDirExistFlag =true;
return sftpATTRS.isDir();
}catch (Exception e) {
if (e.getMessage().toLowerCase().equals("no such file")) {
isDirExistFlag =false;
}
}finally {
if (_flag)
disConnect();
}
return isDirExistFlag;
}
/**
* 列出目录下的文件
*
* @param directory:要列出的目录
* @return
* @throws SftpException
*/
public VectorlistFiles(String directory)throws SftpException {
boolean _flag =false;
if (!isConnect()){
connect();
_flag =true;
}
Vector ls =sftp.ls("/home/sftp/stockcount/" + directory);
if (_flag)
disConnect();
return ls;
}
public boolean isConnect() {
return (this.sftp !=null &&
this.sftp.isConnected() &&
this.sshSession !=null &&
this.sshSession.isConnected());
}

其中Constant类引用的静态对象是一些服务器地址  用户名  密码等配置。建议开发此功能之前先在电脑上下载查看ftp服务器的客户端应用。这样调试数据比较方便,也方便验证功能是否完善。网上很多工具,我这里使用的是xftp7,使用起来很方便。

xftp