Jsch-java中控制ls返回,删除服务器上上百万的历史文件保留近期文件

一、Jsch简介


       Jsch 是SSH2的一个纯Java实现。它允许你连接到一个sshd 服务器,使用端口转发,X11转发,文件传输等等。你可以将它的功能集成到你自己的 程序中。

二、 实现原理

        1. 根据远程主机的IP地址,用户名和端口,建立会话(Session);

        2. 设置用户信息(包括密码和Userinfo),然后连接session;
            getSession()只是创建一个session,需要设置必要的认证信息之后,调用connect()才能建立连接。

        3. 在session上建立指定类型的通道(Channel);

        4. 设置channel上需要远程执行的Shell脚本,连接channel,就可以远程执行该Shell脚本;
            调用openChannel(String type) 可以在session上打开指定类型的channel。该channel只是被初始化,使用前需要先调用                connect()进行连接。

        5. 可以读取远程执行Shell脚本的输出,然后依次断开channel和session的连接;

三、Channel的类型可以为如下类型:

1)shell - ChannelShell 

2)exec - ChannelExec 

3)direct-tcpip - ChannelDirectTCPIP 

4)sftp - ChannelSftp 

5)subsystem - ChannelSubsystem

四、使用

公司有个需求需要删除服务器上历史不用的文件,第一次执行的时候会有上百万的数据需要删除。我们知道Jsch可以用来连接服务器实现sftp操作,当然用它来操作。不能直接执行rm -r *,因为只删除历史数据,保留近期的数据,所有要对文件的日期进行判断。

1:maven引入:

      <dependency>
			<groupId>com.jcraft</groupId>
			<artifactId>jsch</artifactId>
			<version>0.1.55</version>
		</dependency>

2:创建连接:

getCurrentSession方法是获取当前线程的session,封装了一个内部类LocalSession,Biz为项目中封装配置属性的。这个类主要操作

sftp协议的,创建的是ChannelSftp  类来操作服务器。

channel.cd(path);  //用来切换目录

 channel.put(String src, String dst, int mode) ;//方法用来操作上传数据。

 channel.rm(str)  //用来操作删除文件。

需求用到的是下面这个方法。

重点说下:Vector files= channel.ls(path);   //这个方法用来列出目录下所有的文件信息、包括文件夹。注意返回的是Vector

public class SftpNew {

    private final static Logger logger = Logger.getLogger(SftpNew.class);
    private JSch jsch = new JSch();
    private ThreadLocal<LocalSession> current = new ThreadLocal<SftpNew.LocalSession>();
    private class LocalSession {
        Session session;
        ChannelSftp channel;

        public void close() {
            if (this.channel != null) {
                this.channel.exit();
                this.channel.disconnect();
                this.channel = null;
            }
            if (this.session != null) {
                this.session.disconnect();
                this.session = null;
            }
        }
    }

   /**
     * 上传pdf
     *
     * @param src  本地pdf路径
     * @param dest sftp上传路径
     * @throws Exception
     */
    public boolean upload(String src, String dest) {
        LocalSession s = this.getCurrentSession();
        if (s == null) {
            return false;
        }

        ChannelSftp channel = s.channel;

        boolean b = true;
        try {
            logger.info("上传pdf:" + src);
            if (src == null) {
                b = false;
            }

            channel.put(src, dest, ChannelSftp.OVERWRITE);
        } catch (Exception e) {
            logger.error("sftp文件上传失败,错误信息是:" + Tools.getStackTrace(e));
            b = false;
            this.close();
        }
        return b;
    }
   
     /**
     * 删除sftp上的pdf文件
     *
     * @param path    存放目录
     * @param pdfPath pdf文件名
     * @return
     */
    public boolean delete(String path, String pdfPath) {
        LocalSession s = this.getCurrentSession();
        if (s == null) {
            return false;
        }

        ChannelSftp channel = s.channel;
        try {
            logger.info("删除pdf:" + path);
            channel.cd(pdfPath); // 打开pdf存放目录
            channel.rm(path); // 删除PDF
        } catch (Exception e) {
            if (!e.getMessage().toLowerCase().contains("no such file")) {
                logger.error("PDF删除失败,请检查");
                logger.error("错误信息是:" + e.getMessage());
                this.close();
            } else {
                logger.info("文件不存在,不用删除了");
                return true;
            }
            return false;
        }
        return true;
    }

    private LocalSession getCurrentSession() {
        LocalSession s = this.current.get();
        if (s == null) {
            s = new LocalSession();
            logger.info("create new sftp channel");
            try {
                s.session = this.jsch.getSession(Biz.getUser(), Biz.getIp(), 
                Integer.valueOf(Biz.getPort()));
                s.session.setPassword(Biz.getPwd());
                s.session.setConfig("StrictHostKeyChecking", "no");
                s.session.setTimeout(30000);
                s.session.connect();

                s.channel = (ChannelSftp) s.session.openChannel("sftp");
                s.channel.connect();
                this.current.set(s);
            } catch (JSchException e) {
                logger.error("sftp error: " + e.getMessage());
                return null;
            }
        } else {
            if (!s.channel.isConnected()) {
                logger.info("the sftp is not sonnected");
                // 如果已经失败,则重新连接一下
                s.close();
                this.current.remove();
                return this.getCurrentSession();
            }
        }
        return s;
    }
}

正常情况下我们要操作某个目录下文件,我们直接channel.ls(path)返回文件集合就可以了,但是现在的情况时,第一次去ls的时候会返回上百万的文件信息,如果直接用集合接收不是要炸了。


我们点进去看下它里面是如何操作的:

它直接创建了一个Vector集合和实现了一个内部接口LsEntrySelector对象,这个selector接口会在调用

ls(String path, LsEntrySelector selector)的时候传进去。这个接口里面只有一个select(LsEntry entry)方法,看方法描述为:

此方法会在ls方法遍历每个文件实例的时候调用,如果此方法返回BREAK,则ls方法会取消。

再看ls(path)方法里是怎么操作的,它直接重写了selector方法,把每个文件实例LsEntry不做处理直接添加进集合里面,然后返回CONTINUE。

ls(String path, LsEntrySelector selector)方法中会调用 cancel = selector.select(new LsEntry(f, l, attrs));  新建的文件实例直接传入,然后添加进Vector集合。返回的一直为CONTINUE.

 

因此默认情况下,我们调用ls(path) 方法,默认实现的LsEntrySelector 类中的select方法不会返回BREAK,Jsch会把当前目录下所有文件都遍历完添加进Vector中。



显然默认情况下的ls方法并不是我们需要的,我们需要自定义LsEntrySelector实现类,来控制ls什么时候结束,这才是我们需要的。

我采取的方法是,ls每次返回2048个文件实例,删除完这2048个文件之后再去ls,循环删除。

如果条件允许,可以把循环得到的文件实例集合交给多线程处理,主线程只关注读取文件实例,不做删除,我这里生产环境不方便操作多线程。

方法如下:

/**
     * 
     * @param deletCount #每次循环删除多少条数据
     * @param path #定期删除文件目录
     * @param days #删除多久之前的数据,单位天
     * @return
     */
    public boolean deleteFilesByOrd( final int deletCount, String path, final String days) {
        long t1 = System.currentTimeMillis();
        LocalSession s = this.getCurrentSession();
        if (s == null) {
            return false;
        }
        boolean needContinue = false;
        ChannelSftp channel = s.channel;
        final long currentTime = System.currentTimeMillis();
        try {
            channel.cd(path); // 打开存放目录
            final List<String> needDeletFileList = new ArrayList<String>(2048);
            ChannelSftp.LsEntrySelector selector = new ChannelSftp.LsEntrySelector() {
                long dayLong = Long.valueOf(days);
                int count = 0;

                public int select(ChannelSftp.LsEntry entry) {
                    if (count > deletCount) {
                        count = 0;
                        return BREAK;
                    }
                    if (".".equals(entry.getFilename()) || "..".equals(entry.getFilename())) {
                        return CONTINUE;
                    }
                   //得到文件的最近修改时间,单位是秒,可以参见entry.getAttrs().getMTimeString()得知
                    long fileDate = ((long) entry.getAttrs().getMTime()) * 1000L;
                    long exDays = (currentTime - fileDate) / (1000 * 60 * 60 * 24);

                    if (exDays >= dayLong) {
                        needDeletFileList.add(entry.getFilename());
                        count++;
                    }

                    return CONTINUE;
                }
            };
            channel.ls(path, selector);


            do {
                if (needDeletFileList.size() >= deletCount) {
                    needContinue = true;
                } else {
                    needContinue = false;
                }
                for (String filename:needDeletFileList
                     ) {
                    channel.rm(path+"/"+filename);
                }

                needDeletFileList.clear();
                if (needContinue) {
                    channel.ls(path, selector);
                }
            } while (needContinue);

            long t2 = System.currentTimeMillis();
            logger.info("删除文件花费时间:" + (t2 - t1) / (1000*60)+"min");
        } catch (Exception e) {
            logger.error("定时文件删除失败==={}", e);
            return false;
        }finally {
            close();
        }
        return true;
    }

  文件实例类也是ChannelSftp的内部类:

通过它可以得到文件名,文件属性,其他的属性相关信息在SftpATTRS类中。

 public class LsEntry implements Comparable{
    private  String filename;
    private  String longname;
    private  SftpATTRS attrs;
    LsEntry(String filename, String longname, SftpATTRS attrs){
      setFilename(filename);
      setLongname(longname);
      setAttrs(attrs);
    }
    public String getFilename(){return filename;};
    void setFilename(String filename){this.filename = filename;};
    public String getLongname(){return longname;};
    void setLongname(String longname){this.longname = longname;};
    public SftpATTRS getAttrs(){return attrs;};
    void setAttrs(SftpATTRS attrs) {this.attrs = attrs;};
    public String toString(){ return longname; }
    public int compareTo(Object o) throws ClassCastException{
      if(o instanceof LsEntry){
        return filename.compareTo(((LsEntry)o).getFilename());
      }
      throw new ClassCastException("a decendent of LsEntry must be given.");
    }
  }

 有不清楚的地方可以留言。

 

 

 

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

姑苏冷

您的打赏是对原创文章最大的鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值