前言
最近接到个简单的需求:实时监控文件夹,把新增的文件传到对应的ftp,要求监控的文件夹可实时配置,使用jline提供人机交互接口进行文件的重传.略一思考,WatchService可以实现实时监控需求.
WatchService是jdk1.7提供的类,基于操作系统的文件系统监控器,实现对文件新增,修改,删除的监控功能,详细的说明可以自行百度.
思路
1.系统启动,加载配置文件,为每个配置项提供一个监控器,当有文件新增,上传到对应的ftp
2.系统启动,监控配置文件,当配置文件内容发生变化,为每个新增的配置项提供监控器
3.WatchService监控会阻塞线程,使用线程池管理线程
4.由于文件较大,需要考虑文件还在传输程序就进行上传和上传是否成功的问题
代码实现
贴出关键的方法代码,仅供参考
线程配置类
@Configuration public class PoolConfig { @Bean public Executor pool() { ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor(); threadPool.setCorePoolSize(6); //核心线程数 threadPool.setMaxPoolSize(100);//最大线程数 threadPool.setKeepAliveSeconds(3000);//线程最大空闲时间 threadPool.setQueueCapacity(100);//队列 threadPool.setThreadNamePrefix("file-upload-");//线程名称前缀 //拒绝策略 threadPool.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); //初始化线程池,不然报错 threadPool.initialize(); return threadPool; } }
监控文件内容变化
Path path = Paths.get(configFilePath);//configFilePath是文件夹 WatchService watchService = FileSystems.getDefault().newWatchService(); //监听文件夹路径下的文件内容发生改变 path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY); while (true) { WatchKey wk = watchService.take();//阻塞 for (WatchEvent<?> event : wk.pollEvents()) { Path changed = (Path) event.context(); Path absolute = path.resolve(changed); //发生内容变化的文件 File configFile = absolute.toFile(); if (configFileName.equals(configFile.getName())) { long lastModified = configFile.lastModified(); // 利用文件时间戳,防止触发两次 if (lastModified != configFileModifiedTime && configFile.length() > 0) { log.info("文件{}发生内容变化", configFile); configFileModifiedTime = lastModified; addDirWatch(configFile); } } } // 必须重置WatchKey,否则无法监控下次变化 if (!wk.reset()) { log.error("重置WatchKey异常"); } }
记录配置文件的上一次修改的时间与当前修改时间对比,防止触发2次.
监控文件新增
WatchService watchService = FileSystems.getDefault().newWatchService(); //监听新增 Paths.get(dirPath).register(watchService, StandardWatchEventKinds.ENTRY_CREATE); while (true) { //没有文件增加时,阻塞在这里 WatchKey key = watchService.take(); for (WatchEvent<?> event : key.pollEvents()) { File file = new File(dirPath, event.context().toString()); log.info("增加文件是" + file.getAbsolutePath()); if (file.getName().endsWith(".ZIP") && !uploadService.getAlreadyUploadFiles().contains(file.getName())) { //等待文件传输完成 FileUtil.waitForFileTransfer(file.getAbsolutePath(),100L); uploadService.uploadAndWriter(removePath, file, "上传"); } } if (!key.reset()) { break; } }
每次上传成功需要记录,防止重传
等待文件传输完成
public static void waitForFileTransfer(String filePath,long waitTime) { try { File file = new File(filePath); long len1, len2; len2 = file.length(); do { len1 = len2; Thread.sleep(waitTime); file = new File(filePath); len2 = file.length(); } while (len1 < len2); } catch (Exception e) { log.error("文件传输失败", e); } }
检查是否上传成功
上传前记录文件大小==上传后获取ftp文件大小,上传成功
public static booleangetFtpFileSize(String host, int port,String username, String password, String remotePath, String fileName,long size) { FTPClient ftp = new FTPClient(); long fileSize = 0L; boolean flag = false; try { //登录 if (!login(ftp, host, port, username, password)) { log.error("登陆ftp失败"); } try { //进入目录 ftp.changeWorkingDirectory(remotePath); } catch (Exception e) { log.info("进入远程目录{}失败,case:[{}]", remotePath, e); } //遍历 for (FTPFile file : ftp.listFiles()) { if (file.getName().equals(fileName)){ log.info("成功找到文件{}", fileName); if(size == file.getSize()){ flag = true } break; } } } catch (Exception e) { log.error("操作ftp发生异常{}", e); } finally { try { //退出ftp ftp.logout(); //关闭FTP if (ftp.isConnected()) { ftp.disconnect(); } } catch (IOException e) { log.error("关闭ftp发生异常[{}]", e); } } return flag; }