基于java实现分布式定时同步FTP服务器文件的监听器

 代码以及注释在最下面

一、业务场景​

这是一个​​分布式文件监听与同步系统​​,核心功能为:

  1. ​实时监控FTP服务器​​上指定目录的文件变化(如气象观测设备生成的数据文件)。
  2. ​自动下载新增或更新的文件​​到本地存储,并记录元数据。
  3. 通过​​Kafka消息队列​​通知下游系统(如数据处理中心DPC)处理文件。
  4. 支持​​历史数据批量下载​​与异步处理,确保数据完整性。
  5. 适用于气象、物联网等领域,需实时采集多设备数据并触发后续分析的场景。

​二、核心流程与代码解析​

​1. 定时扫描FTP目录(FileListener.scanFtp)​
  • ​触发机制​​:通过@Scheduled(fixedDelay = 1000 * 30)每30秒执行一次。
  • ​设备遍历​​:遍历配置中的设备列表(ftpClientUtilProperties.getDeviceLogin()),每个设备对应不同的FTP连接参数(如雷达、激光雷达等)。
  • ​文件获取​​:
    • 调用ftpClientUtil.getRemoteDirFtpFiles()获取FTP目录下的文件列表。
    • ​重试机制​​:若首次获取失败,清理连接池(ftpClientUtil.clear())并重试最多5次,确保网络波动时的稳定性。
  • ​文件过滤​​:
    • ​正则匹配​​:通过ReUtil.isMatch()筛选符合命名规则的文件(如Z_URAD_CR_54399_20220501000000)。
    • ​时间范围​​:仅处理24小时内生成的文件(cTime - dataTime.getTime() < 24h),避免历史数据干扰。
​2. 文件下载与本地存储​
  • ​本地路径生成​​:getLocalDestPath()根据设备类型、站点号、日期等生成层级目录(如/data/54399/cloud_radar/2022/05/01),便于分类存储。
  • ​防重复下载​​:
    • 检查本地是否存在.check标记文件,若存在则跳过下载。
    • 下载完成后创建.check文件,作为下载完成的凭证。
  • ​异常处理​​:
    • 文件大小过小(<10B)时视为无效文件,等待下次同步。
    • 捕获异常并记录日志,避免单文件错误导致整体任务中断。
​3. 元数据处理与Kafka通知​
  • ​站点与设备映射​​:
    • 解析文件名获取站点号(如54399),通过syncService.getDeviceId()查询数据库或Redis缓存,转换为内部设备ID。
    • ​Redis缓存​​:使用KEY_LAST_TIME记录各站点的最新文件时间,确保按时间顺序处理数据。
  • ​Kafka消息发送​​:
    • 构建FileDto对象,包含设备标签、站点号、文件路径等元数据。
    • 发送至动态主题mco_filelog_{device},不同设备数据隔离,便于下游按需消费。
    • 示例消息内容:{"deviceTag":"cloud_radar", "stationNum":"54399", "absPath":"/data/.../Z_URAD_CR_54399_20220501000000"}
​4. 历史数据批量处理(SyncService.history)​
  • ​异步触发​​:通过@Async注解实现异步执行,避免阻塞实时任务。
  • ​ZIP包处理​​:
    • 下载指定日期的ZIP压缩包(如20220501.zip)到临时目录。
    • 解压后筛选有效文件,移动至正式存储路径,并发送Kafka消息。
  • ​清理机制​​:删除临时解压文件和空目录,释放存储空间。
​5. FTP连接管理(FTPClientUtil接口)​
  • ​连接池管理​​:通过ProducerFactory模式管理FTP客户端,支持多设备并行连接。
  • ​核心操作​​:
    • 文件下载(get())、上传(put())、移动(move())、删除(deleteFile())。
    • 目录遍历(getRemoteDirFtpFiles())和路径检查(isRemotePathExist())。
​6. Kafka集成(KafkaTemplate)​
  • ​消息发送​​:封装Spring Kafka的KafkaTemplate,支持同步/异步发送、事务管理。
  • ​事务支持​​:通过executeInTransaction()确保消息发送与文件下载的原子性。
  • ​动态主题​​:根据设备类型动态生成主题名,实现数据路由。

​三、关键参数与配置​

  1. ​FTP设备配置(FTPClientUtilProperties.LoginInfo)​​:

    • device:设备类型标识(如微波辐射计、云雷达)。
    • homePath:FTP服务器上的监控目录。
    • regexFilter:文件名正则表达式(如.*\.DAT),用于过滤目标文件。
  2. ​本地存储路径​​:

    • Const.PATH_FILE:基础存储目录(如/data)。
    • 层级结构:城市ID/站点号/设备类型/年/月/日,便于按时空维度管理。
  3. ​Redis键设计​​:

    • KEY_LAST_TIME:记录各站点的最新文件时间戳,格式为last_time_{站号}_{设备类型}
    • DEVICE_FIRST_DEVICEID_PIDCODE_KEY:缓存站点与设备ID的映射关系,减少数据库查询。
  4. ​Kafka配置​​:

    • 主题命名规则:mco_filelog_{device},按设备类型隔离消息。
    • 序列化方式:FileDto对象转为JSON字符串发送。

​四、异常处理与优化​

  1. ​连接故障​​:FTP连接失败时清理连接池并重试,避免僵尸连接。
  2. ​数据一致性​​:
    • 通过.check文件标记下载完成,防止重复处理。
    • Redis记录最新时间戳,确保按时间顺序处理文件。
  3. ​性能优化​​:
    • 并行流处理(parallelStream())加速文件过滤与下载。
    • 异步历史数据处理(@Async)与实时任务解耦。

​五、技术栈与架构​

  • ​核心框架​​:Spring Boot(定时任务、依赖注入)、Spring Kafka。
  • ​存储组件​​:FTP服务器(原始数据)、本地文件系统(持久化)、Redis(元数据缓存)。
  • ​消息中间件​​:Kafka实现解耦与流量削峰。
  • ​监控与容错​​:重试机制、连接池管理、事务支持。

​六、典型应用场景​

  1. ​气象数据采集​​:实时监控雷达、辐射计等设备生成的数据文件,触发风场反演、降水预测等分析。
  2. ​工业物联网​​:采集传感器数据文件,实时推送至数据分析平台。
  3. ​日志聚合​​:集中采集分布式系统的日志文件,供ELK分析。

    /**
     * 定时同步文件目录,已经下载的文件存入Redis,4天之后过期
     */
    @Scheduled(fixedDelay = 1000*30)
    public void scanFtp()  {

        //查询每种设备
        for (FTPClientUtilProperties.LoginInfo loginInfo : ftpClientUtilProperties.getDeviceLogin()) {
            String device = loginInfo.getDevice();
            String homePath = loginInfo.getHomePath();
            if(StrUtil.isBlank(homePath)){
                homePath = "";
            }
            try {

                int retryTimes = 5;

                //获取根目录下的文件
                List<FTPFile> ftpFiles = null;
                try {
                    ftpFiles = ftpClientUtil.getRemoteDirFtpFiles(device, homePath);
                }catch (Exception e){
                    log.error( device+":获取文件失败,清理连接池并重试!",e);
                    ftpClientUtil.clear(device);
                    ftpFiles = ftpClientUtil.getRemoteDirFtpFiles(device, homePath);
                }

                while (ftpFiles.size() == 0 && retryTimes > 0){

                    logger.info("{}获取文件数量为0,开始清理连接池并重试,剩余重试次数:{}。",device,retryTimes);
                    ftpClientUtil.clear(device);
                    ftpFiles = ftpClientUtil.getRemoteDirFtpFiles(device, homePath);
                    retryTimes--;
                }

                Set<String> rawNames = ftpFiles.stream().map(FTPFile::getName).filter(fileName-> ReUtil.isMatch(loginInfo.getRegexFilter(),fileName.toUpperCase())).collect(Collectors.toSet());

                logger.info("{}总计{}个文件,匹配{}个文件。",device,ftpFiles.size(),rawNames.size());
  //过滤一天内的文件
                Set<String> names = Collections.synchronizedSet(new HashSet<>());
                long cTime = System.currentTimeMillis();
                int lastMil = 1 * 24 * 60 * 60 * 1000;
                rawNames.parallelStream().forEach(e->{
                    Date dataTime = getDataTime(e,device);
                    if(dataTime == null){
                        return;
                    }
                    if(cTime-dataTime.getTime()< lastMil){
                        names.add(e);
                    }
                });
                logger.info("{},一天内共{}个文件。",device,names.size());

                //下载不同的文件
                String finalHomePath = homePath;
                names.parallelStream().forEach(fileName->{
                    try {
                        String localDestPath = getLocalDestPath(device, fileName);

                        File checkDestFile = new File(localDestPath+File.separator+fileName+".check");
                        //判断文件是否在本地目录
                        if(FileUtil.exist(checkDestFile)){
                            log.debug("文件已存在本地目录:{}",fileName);
                            return;
                        }

                        //构建文件信息对象,用于向dpc发送
                        String stationNum = getStationNum(fileName);
                        Integer deviceId = syncService.getDeviceId(device, stationNum);
                        if(deviceId == null){
                            log.debug("设备ID不存在,不下载此文件:{}",fileName);
                            return;
                        }

                        //从ftp服务器下载文件,下载后的文件名与ftp服务器上的文件名一致
                        ftpClientUtil.get(device,localDestPath, finalHomePath +fileName);

                        File destFile = new File(localDestPath+File.separator+fileName);
                        //文件太小就不同步,等待下一次同步
                        if(destFile.length() < 10){
                            return;
                        }
                        //在本地生成一个检查文件是否存在的文件
                        checkDestFile.createNewFile();

                        //记录最后到达时间,last_time_{站号}_{设备类型:例如 Const.TAG_WBFS}   示例:last_time_54399_wbfs
                        Date dataTime = getDataTime(fileName, device);

                        //判断是否是衢州站,衢州站的云雷达,激光雷达,微波辐射计,是世界时要向后加8小时
                        dataTime = McoUtil.getFileDataTime(stationNum,dataTime);

                        ArrayList<String> stringStationList = McoUtil.getHzStationList();
                        //杭州地区改为世界时需要添加8小时 云雷达与微波辐射计
                        if(stringStationList.contains(stationNum) && (Const.TAG_CLOUDRADA.equals(device) || Const.TAG_WBFS.equals(device))){
                            dataTime = DateUtil.offsetHour(dataTime,8) ;
                        }

                        //获取记录的最新时间,避免读取历史数据导致时间错乱
                        String lastTimeKey = Const.KEY_LAST_TIME + stationNum.toUpperCase() + "_" + device;
                        Object lastTimeStr = RedisUtil.get(lastTimeKey);
                        if(lastTimeStr != null){
                            DateTime lastTime = DateUtil.parseDateTime(lastTimeStr.toString());
                            if(lastTime.getTime()<dataTime.getTime()){
                                RedisUtil.set(lastTimeKey,DateUtil.formatDateTime(dataTime));
                            }
                        }else{
                            RedisUtil.set(lastTimeKey,DateUtil.formatDateTime(dataTime));
                        }


                        FileDto fileDto = new FileDto();
                        fileDto.setDeviceTag(device);
                        fileDto.setDeviceId(deviceId);
                        fileDto.setCityId(Const.CITY_ID);
                        fileDto.setStationNum(stationNum.toUpperCase());
                        fileDto.setAbsPath(localDestPath+"/"+fileName);

                        kafkaTemplate.send("mco_filelog_"+device, JSONUtil.toJsonStr(fileDto));

                        log.info("文件下载成功:{},文件大小:{}",fileName, FileUtil.readableFileSize(destFile));
                    } catch (Exception exception) {
                        log.error("文件异常"+fileName,exception);
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

    public static String getLocalDestPath(String device, String fileName) throws Exception{
        String[] nameSplits = fileName.split("_");
        String stationNum = nameSplits[3];
        String year = nameSplits[4].substring(0, 4);
        String month = nameSplits[4].substring(4, 6);
        String day = nameSplits[4].substring(6, 8);
        return Const.PATH_FILE+"/"+Const.CITY_ID+ "/"+stationNum+ "/"+device+"/"+year+"/"+month+"/"+day;
    }

    public static String getStationNum(String fileName){
        String[] nameSplits = fileName.split("_");
       return nameSplits[3];
    }

    /**
     * 可获取文件名中包含yyyyMMddHHmmss格式的时间
     * 仅能获取20世纪的时间
     * @param fileName 文件名
     * @return java.util.Date
     *
     **/
    public static Date getDataTime(String fileName,String device) {
        List<String> allGroup0 = ReUtil.findAllGroup0("(20\\d\\d\\d{0,10})", fileName);
        DateTime parse;
        if(allGroup0.size() == 0){
            String[] nameArr = fileName.split("_");
            parse = DateUtil.parse(nameArr[4]);
        }else{
            parse = DateUtil.parse(allGroup0.get(0));
        }
        if(Const.TAG_WPR_RADAR.equals(device) || Const.TAG_CDWL.equals(device) ){
            parse = DateUtil.offsetHour(parse,8);
        }
        return parse;
    }

}
package com.smart.service;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.ZipUtil;
import cn.hutool.json.JSONUtil;
import com.smart.common.Const;
import com.smart.common.dto.FileDto;
import com.smart.ftp.FTPClientUtil;
import com.smart.ftp.FTPClientUtilProperties;
import com.smart.listener.FileListener;
import com.smart.model.Device;
import com.smart.redis.RedisUtil;
import org.apache.commons.net.ftp.FTPFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.zip.ZipFile;

/**
 * 文件同步类
 * 
*/
@Service
public class SyncService {
	private final static Logger logger = LoggerFactory.getLogger(SyncService.class);
	private final static String DEVICE_FIRST_DEVICEID_PIDCODE_KEY = "device_first_deviceId_pidCode_key_";

	@Autowired
	private KafkaTemplate kafkaTemplate;

	@Autowired
	FTPClientUtilProperties ftpClientUtilProperties;

	@Autowired
	private FTPClientUtil ftpClientUtil;

	/**
	 * 异步下载历史数据文件,并触发解析
	 * 
	 */
	@Async
    public synchronized void history(List<Date> dates){
		for (Date date : dates) {
			try {

				//服务端和本地文件的日期路径和文件前缀
				String datePath = DateUtil.format(date, "yyyy/MM/dd/");
				String filePrefix = DateUtil.format(date, "yyyyMMdd");

				//遍历每一个设备
				for (FTPClientUtilProperties.LoginInfo loginInfo : ftpClientUtilProperties.getDeviceLogin()) {
					String homePath = loginInfo.getHomePath();
					String device = loginInfo.getDevice();

					String remotePath = homePath + "/"+ datePath + filePrefix + ".zip";
					String zipDestPath = Const.PATH_TEMP_FILE+ "/"+device+ "/"+datePath+ filePrefix + ".zip";

					try {

						//获取压缩包
						ftpClientUtil.get(device,zipDestPath, remotePath);

						//解压并删除压缩包
						File zipFile = new File(zipDestPath);
						ZipUtil.unzip(zipFile);
						FileUtil.del(zipFile);

						//列出解压后的所有文件
						List<File> dataFiles = FileUtil.loopFiles(zipFile.getParentFile());

						//过滤所需的文件
						Set<File> filterFiles = dataFiles.stream().filter(file-> ReUtil.isMatch(loginInfo.getRegexFilter(),file.getName())).collect(Collectors.toSet());

						//每一个文件都要先分拣,再发送kafka通知dpc处理
						for (File dataFile : filterFiles) {

							//分拣的目标地址
							String localDestPath = FileListener.getLocalDestPath(device, dataFile.getName());
							File destPath = new File(localDestPath);
							FileUtil.move(dataFile, destPath,true);

							String fileName = destPath.getName();
							//构建文件信息对象,用于向dpc发送
							String stationNum = FileListener.getStationNum(fileName);
							Integer deviceId = getDeviceId(device, stationNum);
							FileDto fileDto = new FileDto();
							fileDto.setDeviceTag(device);
							fileDto.setDeviceId(deviceId);
							fileDto.setCityId(Const.CITY_ID);
							fileDto.setStationNum(stationNum);
							fileDto.setAbsPath(destPath.getAbsolutePath());

							kafkaTemplate.send("mco_filelog_"+device, JSONUtil.toJsonStr(fileDto));
						}

						//执行完毕之后删除解压后的文件,此时有用的文件已经挪走了
						FileUtil.del(zipFile.getParentFile());

						//记录当前日期已经下载过了
						RedisUtil.set(Const.KEY_HISTORY_DATE+date,true);

					} catch (Exception e) {
						e.printStackTrace();
					}
				}

			}catch (Exception e) {
				e.printStackTrace();
			}

		}

    }


	/**
	 * 获取具体设备号
	 * @param pidCode
	 * @param stationNum
	 * @return
	 */
	public Integer getDeviceId(String pidCode,String stationNum){
		Object deviceId = RedisUtil.get(DEVICE_FIRST_DEVICEID_PIDCODE_KEY + pidCode + stationNum);

		if (null != deviceId) {
			return Integer.parseInt(deviceId.toString());
		} else {
			Device res = new Device().findFirst("SELECT d.id FROM device d INNER JOIN device_category c ON c.id = d.dcId INNER JOIN device_category cc ON cc.id = c.pid "
					+ "INNER JOIN station s ON s.id = d.stationId WHERE s.num = ? AND cc.`code` = ?", stationNum, pidCode);
			if (res == null) {
				return null;
			}
			RedisUtil.set(DEVICE_FIRST_DEVICEID_PIDCODE_KEY + pidCode + stationNum, res.getId());
			return res.getId();
		}
	}


	public Integer test(String device,String homePath) {
		try {
			logger.info("{}:{}",device,homePath);
			List<FTPFile> ftpFiles = ftpClientUtil.getRemoteDirFtpFiles(device, homePath);
			if(ftpFiles.size() == 0){
				ftpClientUtil.clear(device);
			}
			return ftpFiles.size();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return 0;
	}
}
package com.smart.ftp;

import org.apache.commons.net.ftp.FTPFile;

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;

/**
 *
 */
public interface FTPClientUtil {
    /**
     * 上传文件到ftp服务器
     *
     * @param localInputStream 本地输入流,即需要传输到服务器的数据
     * @param remotePathUri    ftp服务器目录
     * @param remoteFilename   上传后,在ftp服务器上的文件名
     * @param suffix           文件上传时添加的文件后缀,上传完成时,重名为name; suffix 为null 时,则不加后缀
     * @throws Exception 连接不到ftp服务器,登录失败,文件上传失败等异常
     */
    void put(
            String device,
            InputStream localInputStream,
             String remotePathUri,
             String remoteFilename,
             String suffix) throws Exception;

    /**
     * 上传文件到ftp服务器 重载put()函数,将InputStream参数变为File
     *
     * @param localFile      本地文件,File类型参数
     * @param remoteDir      ftp服务器目录
     * @param remoteFilename 上传后,在ftp服务器上的文件名
     * @param suffix         文件上传时添加的文件后缀,上传完成时,重名为name; suffix 为null 时,则不加后缀
     * @throws Exception 连接不到ftp服务器,登录失败,文件上传失败等异常
     */
    void put(
            String device,
            File localFile,
             String remoteDir,
             String remoteFilename,
             String suffix) throws Exception;



    /**
     * 上传文件到ftp服务器 重载put()函数,将InputStream参数变为 本地文件路径
     *
     * @param localFileAbsolutePathUri 本地文件路径
     * @param remoteDir                ftp	服务器目录
     * @param remoteFilename           上传后,在ftp服务器上的文件名
     * @param suffix                   文件上传时添加的文件后缀,上传完成时,重名为name; suffix 为null 时,则不加后缀
     * @throws Exception 连接不到ftp服务器,登录失败,文件上传失败等异常
     */
    void put(
            String device,
            String localFileAbsolutePathUri,
             String remoteDir,
             String remoteFilename,
             String suffix) throws Exception;

    /**
     * 从ftp服务器下载文件,并写入到本地outputStream中
     *
     * @param localOutputStream 本地保存数据的输出流, 即下载的数据将写入该输出流
     * @param remoteDir         ftp 服务器目录
     * @param remoteFilename    要下载的文件名
     * @throws Exception ftp连接,登录失败,传输失败等异常。
     */
    void get(String device,OutputStream localOutputStream, String remoteDir, String remoteFilename) throws Exception;

    /**
     * 从ftp服务器下载文件
     *
     * @param localFile      本地文件, File类型参数
     * @param remoteDir      ftp 服务器目录
     * @param remoteFilename 要下载的文件名
     * @throws Exception
     */
    void get(String device,File localFile, String remoteDir, String remoteFilename) throws Exception;

    /**
     * 从ftp服务器下载文件, 下载后的文件名与ftp服务器上的文件名一致
     *
     * @param localFileDir   本地路径
     * @param remoteDir      ftp 服务器目录
     * @param remoteFilename 要下载的文件名
     * @throws Exception
     */
    void get(String device,String localFileDir, String remoteDir, String remoteFilename) throws Exception;

    /**
     * 从ftp服务器下载文件,下载后的文件名与ftp服务器上的文件名一致
     *
     * @param localDir          本地路径
     * @param remoteFilePathUri ftp服务器上文件的完整路径名(包括文件名)
     * @throws Exception
     */
    void get(String device,String localDir, String remoteFilePathUri) throws Exception;

    /**
     * ftp 服务器文件move
     *
     * @param remoteSrcDir   FTP远程源目录
     * @param remoteDestDir  FTP远程目的目录
     * @param remoteFilename FTP远程文件名
     * @throws Exception 连接不到ftp服务器,登录失败,文件上传失败等异常
     */
    void move(String device,String remoteSrcDir, String remoteDestDir, String remoteFilename) throws Exception;

    /**
     * ftp 服务器文件copy
     *
     * @param remoteSrcDir      FTP远程源目录
     * @param remoteDestDir     FTP远程目的目录
     * @param remoteSrcFilename FTP远程文件名
     * @param suffix            后缀
     * @param remoteNewFilename 重命名后名字(为空则不重命名)
     * @throws Exception 连接不到ftp服务器,登录失败,文件上传失败等异常
     */
    void copy(String device,
              String remoteSrcDir,
              String remoteDestDir,
              String remoteSrcFilename,
              String suffix,
              String remoteNewFilename) throws Exception;

    /**
     * 刪除ftp服務器上的文件
     *
     * @param remoteFileAbsolutePathUri 文件在ftp服務器上的全路徑(包括文件名)
     * @return true 成功刪除, false 刪除失敗。
     * @throws Exception
     */
    boolean deleteFile(String device,String remoteFileAbsolutePathUri) throws Exception;

    /**
     * 刪除ftp服務器上的目录(必须确保删除执行该方法时,需删除的目录下不能存在文件否则删除失败)
     *
     * @param remoteDir 文件夹路径
     * @return true 成功刪除, false 刪除失敗。
     * @throws Exception
     */
    boolean removeDirectory(String device,String remoteDir) throws Exception;

    /**
     * 获取ftp服务目录下文件或目录名,
     *
     * @param remoteDir ftp服务器路径
     * @return 注意返回的路径是全路径名
     * @throws Exception
     */
    List<String> getRemoteDirFilenames(String device,String remoteDir) throws Exception;

    /**
     * 获取ftp服务器目录下的文件或目录
     *
     * @param remoteDir ftp服务器路径
     * @return 返回 FTPFile对象, FTPFile对象可能是文件或目录,
     * 通过FTPFile.isFile或FTPFile.isDirectory判断,获取单独的文件通过FTPFile.getName()函数获取
     * @throws Exception
     * @Description: 获取ftp服务器目录下的文件或目录
     */
    List<FTPFile> getRemoteDirFtpFiles(String device,String remoteDir) throws Exception;

    /**
     * 获取远程ftp目录中的所有的文件
     *
     * @param remoteDir 用户访问ftp路径
     * @param recursive 是否扫描子文件夹
     * @return 文件列表
     * @throws Exception
     */
    List<RemoteFileInfo> getRemoteFileByDir(String device,String remoteDir, boolean recursive) throws Exception;

    /**
     * 获取远程ftp目录中的所有的文件
     *
     * @param remoteDir 用户访问ftp路径
     * @return 文件列表
     * @throws Exception
     */
    List<RemoteFileInfo> getRemoteFileByDir(String device,String remoteDir) throws Exception;

    /**
     * 判断远程ftp目录或文件是否存在
     * @param remotePath
     * @return
     */
    boolean isRemotePathExist(String device,String remotePath) throws Exception;

    void clear(String device);
}

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值