文件压缩及上传FTP服务器简单应用(实践篇)

前言

时隔这么久我又回来了,最近忙里偷闲写了一个FTP上传文件的demo,用于上传公司发布的USDK服务的log文件,之前发布的USDK在客户那儿出现了不少bug,而解决这些bug需要我们的辛勤测试人员根据客户描述的现象复现修正,效率极低;那干脆就把现场log直接上传就好了呗~(不禁吐槽公司这么久了连个文件上传的管理服务器都没有,唉~);咳咳,言归正传,这个demo主要包含了三点:生成log文件/log文件的压缩/上传服务器,请看下文!

正文

生成log文件

这一趴其实没什么好讲的,正常的IO文件操作,所以就直接上代码了:代码片

    /**
     * 保存log到文件
     *
     * @param tag      log的tag值
     * @param finalMsg log的内容
     */
    private static void writeToLog(String tag, String finalMsg) {
        File logFile = new File(LOG_FILE_PATH, LOG_FILE_NAME);
        File parentFile = logFile.getParentFile();
        if (!parentFile.exists()) {
            if (!parentFile.mkdirs()) {
                return;
            }
        }
        //delete file if over MAX_LOG_SIZE 
        if (logFile.exists() && logFile.length() > MAX_LOG_SIZE) {
            logFile.delete();
        }
        if (!logFile.exists()) {
            try {
                if (!logFile.createNewFile()) {
                    return;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        BufferedOutputStream outputStream = null;
         try {
             outputStream = new BufferedOutputStream(new FileOutputStream(logFile, true));
             String content = FtpCommonUtils.getTagTime(DATE_FORMAT) + " " + tag + " " + finalMsg + "\n";
             outputStream.write(content.getBytes());
             outputStream.flush();
         } catch (IOException e) {
            e.printStackTrace();
        } finally {
             if (outputStream != null) {
                 try {
                     outputStream.close();
                 } catch (IOException e) {
                     e.printStackTrace();
                 }
             }
         }
    }

我也没对文件进行时间的划分,单个log文件就够用,只是对log文件的大小进行了限制,这里打log的方法稍微进行了封装,代码片

    public static void d(String msg) {
        String tag = getLogTag();
        String finalMsg = buildMsg(msg);
        if (curLevel <= TAG_LEVEL_D) {
            Log.d(TAG + " " + tag, finalMsg);
        }
        if (isWriteLogFile) {
            writeToLog(tag, finalMsg);
        }
    }
    /**
     * 返回包含调用方法名的log信息
     *
     * @param msg 原始传入的信息
     * @return 包含调用方法名的log信息
     */
    private static String buildMsg(String msg) {
        StackTraceElement[] element = new Throwable().fillInStackTrace().getStackTrace();
        String methodName = element[2].getMethodName();
        return methodName + " " + msg;
    }
    /**
     * 获取当前打印的类名
     *
     * @return 当前调用的类名
     */
    private static String getLogTag() {
        StackTraceElement[] element = new Throwable().fillInStackTrace().getStackTrace();
        String fullName = element[2].getClassName();
        String simpleName = fullName;
        if (fullName != null && fullName.contains(".")) {
            String[] buffer = fullName.split("\\.");
            simpleName = buffer[buffer.length - 1];
        }
        return simpleName;
    }

这里直接通过方法栈找到调用封装打印接口的类和方法;使用了Throwable()类获取当前方法栈的内容,获取方法栈的方法有两种:

//method 1
Thread.currentThread().getStackTrace()
//method 2
new Throwable().getStackTrace()

不同之处在于方法一会先打印生成使用方法,具体见下(部分数据,使用方法getClassName和getMethodName打印):
--------------------------Throwable-------------------
com.topwise.compress.utils.LogUtil;getLogTag
com.topwise.compress.utils.LogUtil;d
com.topwise.compress.MainActivity;onClick
-------------------------currentThread---------------
dalvik.system.VMStack;getThreadStackTrace
java.lang.Thread;getStackTrace
com.topwise.compress.utils.LogUtil;getLogTag
com.topwise.compress.utils.LogUtil;d
com.topwise.compress.MainActivity;onClick

log文件的压缩

压缩文件使用压缩文件流,主要的类是ZipOutputStream ZipEntry,可实现压缩操作,代码如下代码片

    /**
     * 压缩文件方法
     *
     * @param srcFileName 待压缩文件(夹)路径
     * @return 返回压缩后的文件路径
     */
    String FileZip(String[] srcFileName) {
        LogUtil.d("src file name: " + srcFileName);
        //create zip file
        File zipFile = getZipFile();
        if (zipFile == null) {
            return null;
        }
        //find src files 
        File[] srcFiles = getSrcFiles(srcFileName);
        if (srcFiles == null) {
            return null;
        }
        ZipOutputStream zipOutputStream = null;
        FileInputStream fileInputStream = null;
        try {
            zipOutputStream = new ZipOutputStream(new FileOutputStream(zipFile));
            for (File srcFile : srcFiles) {
                // 将源文件数组中的当前文件读入 FileInputStream 流中
                fileInputStream = new FileInputStream(srcFile);
                // 实例化 ZipEntry 对象,源文件数组中的当前文件
                ZipEntry zipEntry = new ZipEntry(srcFile.getName());
                zipOutputStream.putNextEntry(zipEntry);
                // 该变量记录每次真正读的字节个数
                int len;
                // 定义每次读取的字节数组
                byte[] buffer = new byte[1024];
                while ((len = fileInputStream.read(buffer)) > 0) {
                    zipOutputStream.write(buffer, 0, len);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        } finally {
            try {
                if (zipOutputStream != null) {
                    zipOutputStream.closeEntry();
                    zipOutputStream.close();
                }
                if (fileInputStream != null) {
                    fileInputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return zipFile.getAbsolutePath();
    }

生成压缩文件使用putNextEntry方法,读取压缩文件使用getNextEntry,这个也是正常的IO流操作。
这里在查找log文件夹下我需要的log文件时,使用了文件过滤器:代码块

    private class ZipFileFileFilter implements FileFilter {

        @Override
        public boolean accept(File pathname) {
            return pathname.getName().endsWith(".log") || pathname.getName().endsWith(".jpg");
        }
    }

FileFilter这个接口的使用只要实现其accept方法,把你需要的过滤的文件限制写好,调用file.listFiles(new ZipFileFileFilter())即可过滤出你想要的文件;

上传文件到FTP服务器

使用FTP需引入lib包(commons-net-3.3.jar,下载链接见文章底部),主要使用FTPClient这个类进行文件的上传下载操作,上传需要连接登录/上传文件/断开链接三步;

连接登陆

主要代码如下代码块

    /**
     * 连接登录FTP服务器
     *
     * @return boolean 连接结果
     */
    boolean ftpConnect() {
        LogUtil.d("start ftpConnect");
        boolean result = false;
        try {
            ftpClient = new FTPClient();
            //set timeout
            ftpClient.setConnectTimeout(TIME_OUT / 2);
            ftpClient.setDataTimeout(TIME_OUT);
            //start connect
            ftpClient.connect(FTP_SERVER);
            //get connect result
            result = FTPReply.isPositiveCompletion(ftpClient.getReplyCode());
            if (result) {
                //set passive mode
                ftpClient.enterLocalPassiveMode();
                //log in
                ftpClient.login(FTP_USERNAME, FTP_PASSWORD);
                //set filetype, here set bytearray
                ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
            }
        } catch (Exception e) {
            e.printStackTrace();
            LogUtil.e("error: " + e);
        } finally {
            if (!result) {
                ftpDisConnect();
            }
        }
        return result;
    }

这里需解释的是

  1. ftpClient.enterLocalPassiveMode():FTP分主动模式和被动模式,关于主动模式和被动模式在补充中做了相关的简单说明;由于很多客户端在防火墙内,开放端口给服务器端用比较困难,所以用被动模式的时候比较多;设置模式需要在登录之前设置才会生效;
  2. ftpClient.setFileType(FTP.BINARY_FILE_TYPE):设置文件传输方式,传输方式主要有ASCII和二进制传输两种方式,我的理解是拷贝文本文件可用ASCII方式,一般都用二进制传输方式传输;

上传文件

上传文件其实很简单,只需要调用ftpClient.storeFile方法就可以了,部分代码如下代码块

    /**
     * 文件上传ftp
     *
     * @param srcFile        待上传文件
     * @param remoteFileName ftp文件名称
     * @return 上传结果
     */
    int uploadFile(String srcFile, String remoteFileName) {
        LogUtil.d("uploadFile: " + srcFile + ";remoteFileName: " + remoteFileName);
        if (srcFile == null || remoteFileName == null) {
            return FtpCommonUtils.INPUT_PARAM_ERROR;
        }
        if (!ftpClient.isConnected()) {
            LogUtil.d("ftpClient not connected!");
            return FtpCommonUtils.FTP_NOT_CONNECTED;
        }
        FileInputStream stream = null;
        boolean uploadRet = false;
        try {
            stream = new FileInputStream(srcFile);
            uploadRet = ftpClient.storeFile(remoteFileName, stream);
            stream.close();
        } catch (IOException e) {
            e.printStackTrace();
            LogUtil.e("error: " + e);
        } finally {
            try {
                if (stream != null) {
                    stream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return uploadRet ? FtpCommonUtils.RETURN_SUCCESS : FtpCommonUtils.FTP_UPLOAD_FAIL;
    }

ftpClient.storeFile方法的入参第一个是服务端的文件名称,第二个是本地文件的IO流;注意的是服务端的文件名不要带中文,也不要带:(中英文都不行)等特殊字符(我就是吃了:的亏,想在名称中附上上传时间,结果查了好些时间)。

断开FTP连接

    /**
     * 断开FTP链接
     */
    void ftpDisConnect() {
        LogUtil.d("ftpDisConnect");
        if (ftpClient != null && ftpClient.isConnected()) {
            try {
                ftpClient.logout();
                ftpClient.disconnect();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

此demo所需要的权限:读写sd卡 internet权限

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.INTERNET"/>

OK了,到这里基本上就结束了,是不是很简单啊~

补充

  1. FTP主动模式和被动模式解释:
    这两种模式发起连接的方向截然相反,主动模式是从服务器端向客户端发起连接;被动模式是客户端向服务器端发起连接。
      PORT(主动)方式的连接过程是:客户端向服务器的FTP端口(默认是21)发送连接请求,服务器接受连接,建立一条命令链路。当需要传送数据时,客户端在命令链路上用PORT命令告诉服务器:“我打开了***X端口,你过来连接我”。于是服务器从20端口向客户端的***X端口发送连接请求,建立一条数据链路来传送数据。
      PASV(被动)方式的连接过程是:客户端向服务器的FTP端口(默认是21)发送连接请求,服务器接受连接,建立一条命令链路。当需要传送数据时,服务器在命令链路上用PASV命令告诉客户端:“我打开了***X端口,你过来连接我”。于是客户端向服务器的***X端口发送连接请求,建立一条数据链路来传送数据。
      从上面可以看出,两种方式的命令链路连接方法是一样的,而数据链路的建立方法就完全不同。
  2. 关于FTP的断点续传,实际上是有相关接口支持的,使用断点续传需要注意的是:1)服务器支持断点续传2)客户端要知道使用REST等一系列指令来作断点续传;断点续传所使用的方法主要是setRestartOffset设置断点位置,具体实现就让小伙伴们自己尝试了。

好了,本文到这儿就基本结束了,有什么错误的地方劳烦各位大佬评论指正!

续集

  1. 最近(19-11-07)又做了一个过滤系统log的需求,使用的方法代码如下:
    public void saveSystemLog(File logFile, long fileLimit) {
        Log.d(TAG, "saveSystemLog logFile: " + logFile + ";fileLimit: " + fileLimit);
        ArrayList<String> list = new ArrayList<>();
        list.add("logcat");
        //logcat -v time -s *:I
        list.add("-v");
        list.add("time");
        list.add("-s");
        list.add("*:I");

        ArrayList<String> clearList = new ArrayList<>();
        clearList.add("logcat");
        clearList.add("-c");

        String[] order = list.toArray(new String[list.size()]);
        String[] clearOrder = clearList.toArray(new String[clearList.size()]);

        Process process = null;
        try {
            process = Runtime.getRuntime().exec(order);
            Runtime.getRuntime().exec(clearOrder);
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (process == null) {
            return;
        }

        FileOutputStream outputStream = null;
        InputStream inputStream = null;
        try {
            outputStream = new FileOutputStream(logFile, true);
            inputStream = process.getInputStream();
            int line;
            byte[] buffer = new byte[1024];
            while ((line = inputStream.read(buffer)) > 0 && !isStop) {
                if (fileLimit > 0 && logFile.length() >= fileLimit) {
                    break;
                }
                outputStream.write(buffer, 0, line);
                outputStream.flush();
            }
        } catch (IOException e) {
            Log.d(TAG, "error: " + e);
            e.printStackTrace();
        } finally {
            try {
                if (outputStream != null) {
                    outputStream.close();
                }
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
                Log.d(TAG, "error: " + e);
                e.printStackTrace();
            }
        }
    }

使用Runtime类的exec执行adb命令获取,你可以在电脑上执行adb logcat --help查看命令使用,便于你查找你需要使用的方法;

adb logcat -v time -s *:I
-v time:需要log自带时间打印 -s为过滤使用,后面跟过滤条件(Tag:TAG级别),*代表无过滤tag,I代表过滤info级别及以上的log;
adb logcat -c : 清除日志缓存

权限说明:获取系统log目前需要程序为系统权限,即进行系统签名才可以,并且需要声明权限

<uses-permission android:name="android.permission.READ_LOGS"/>

[参考博文链接]
https://blog.csdn.net/weixin_40759186/article/details/79423396
[下载FTP-lib包链接]
链接: https://pan.baidu.com/s/1t_HqSqnmTyVDMOk8Ko_voQ&shfl=shareset 提取码: e58b

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值