使用ftpclient进行文件操作

300 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情

在日常工作中,很多使用会要用到filezilla这个软件,进行文件操作。但是文件都是服务器上即传即用的,不太利于维护,所以接到了一个禅道任务,实现ftp连接,进行附件上传、下载、版本回溯功能。

技术选型

后端框架使用的是.net core 3.0,基于c#语言开发的一个框架。之前找了很多关于ftp的文档,比如FtpWebRequest,但是框架不支持(参考链接:learn.microsoft.com/zh-cn/dotne… 如图:

image.png

最终使用ftpclient类库实现ftp服务器文件传输功能

需要准备的一些东西

  • ReportVersionInfo表:做备份记录,便于追踪和回溯
  • FluentFTP:ftp操作工具包
  • CSRedisClient:redis的一些操作,比如加锁、设置过期时间、解锁等等

连接ftp服务器

动态获取ftp参数

一般进行ftp操作,会经常进行ftp连接,可以新建一个FtpConfig.json文件,在service层中动态获取

<summary>
///  ftp服务ip
/// </summary>
private readonly string strServer = AppCoreConfigHelper.GetSectionConfig("FtpConfig", "ip");


/// <summary>
/// ftp服务port
/// </summary>

private readonly string port = AppCoreConfigHelper.GetSectionConfig("FtpConfig", "port");


/// <summary>
/// ftp服务--用户
/// </summary>
private readonly string strUser = AppCoreConfigHelper.GetSectionConfig("FtpConfig", "userName");


/// <summary>
/// ftp服务--密码
/// </summary>
private readonly string strPassword = AppCoreConfigHelper.GetSectionConfig("FtpConfig", "password");

连接ftp

// 连接FTP
FtpClient ftp = new FtpClient();
ftp.Host = strServer;
ftp.Port = Convert.ToInt32(port);
ftp.Credentials = new NetworkCredential(strUser, strPassword);
ftp.Connect();

ftp实现文件上传操作

主要思路:

  • 判断ftp是否连接,无连接则连接
  • ftp前一版本备份
  • 备份数据存储到数据库(reportversioninfo表)中
  • ftp文件版本覆盖
  • 关闭ftp连接
/// <summary>
/// ftp附件上传
/// </summary>
/// <param name="file">文件</param>
/// <param name="serverPath">上传到指定的ftp文件夹路径</param>
/// <param name="reportVersionInfo">当前用户信息</param>
public IEPResult FTPUpload(IFormFile file, string serverPath, ReportVersionInfoDTO reportVersionInfo)
{
    using (var unitWork = IOCManager.BuildUnitOfWork())
    {
        try
        {
            // 连接FTP
            FtpClient ftp = new FtpClient()
            // 检查ftp是否连接
            if (!ftp.IsConnected)
            {
                ftp.Host = strServer;
                ftp.Port = Convert.ToInt32(port);
                ftp.Credentials = new NetworkCredential(strUser, strPassword);
                ftp.Connect();
            
            // 存在该文件,才会备份
            // 一般第一次上传才不会进来
            if (ftp.FileExists($"{serverPath}/{file.FileName}"))
            {
                // 添加数据库备份记录
                var record = AutoMapperHelper.Map<ReportVersionInfo>(reportVersionInfo);
                unitWork.Add(record)
                // 不存在就创建文件夹
                if (!ftp.DirectoryExists(reportVersionInfo.Path))
                {
                    ftp.CreateDirectory(reportVersionInfo.Path);
                }
                // 创建ftp备份 在此之前,要判断文件是否备份
                ftp.Rename($"{serverPath}/{file.FileName}", reportVersionInfo.Path + "/" + file.FileName);
            
            //if (!File.Exists(sourcePath))
            //{
            //    return;
            //}
            //var fileInfo = new FileInfo(sourcePath)
            var fileStream = file.OpenReadStream();
            // 文件流附件上传
            var ftpStatus = ftp.UploadStream(fileStream, $"{serverPath}/{file.FileName}", createRemoteDir: true);
            // 文件路径上传
            // ftp.UploadFile(sourcePath, $"{serverPath}/{fileInfo.Name}", createRemoteDir: true)
            // 关闭ftp连接
            if (ftp.IsConnected)
            {
                // 关闭
                ftp.Disconnect();
                ftp.Dispose();
            
            if (ftpStatus.Equals("Failed"))
            {
                unitWork.Rollback();
                return Error("上传失败");
            
            return Success("上传成功");
        }
        catch (Exception ex)
        {
            throw ex;
            unitWork.Rollback();
            return Error(ex.Message);
        }
    }
}

ftp文件下载

下载流程为:

  • 连接ftp
  • redis锁定,设置过期时间为1小时
  • ftp下载文件
  • 上传到附件服务器中
  • 返回下载链接
  • 关闭ftp,redis解锁
/// <summary>
/// FTP下载文件
/// </summary>
/// <param name="serverPath">服务器路径,例子:"/serverPath/"</param>
/// <param name="localpath">本地保存路径,已废</param>
/// <param name="filename">文件名称</param>
/// <param name="businessId">businessId</param>
/// <param name="authInfo">token</param>
/// <returns>下载地址</returns>
public IEPResult FtpDownloadFile(string serverPath, string localpath, string filename, string businessId,AuthInfoVO authInfo)
{
    var redis = AppCoreConfigHelper.GetRedisConfig().Connections.First();
    var password = AppCoreConfigHelper.GetRedisConfig().Password;
    CSRedisClient redisClient = new CSRedis.CSRedisClient(redis+ ",defaultDatabase=1,password="+ password);
    // +"192.168.1.218,defaultDatabase=1,password=
    var lockKey = businessId;
    var releaseLockScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";//释放锁的redis脚本
    var id = Guid.NewGuid().ToString("N")
    var success = redisClient.Set(lockKey, id, TimeSpan.FromHours(1), exists: RedisExistence.Nx)
    if (!success)
    {
        return Error("锁表失败,请1小时后重试!");
    
    // 设置过期时间
    // redisClient.Expire(lockKey, TimeSpan.FromHours(1))
    try
    {
        // 开启ftp连接
        FtpClient ftp = new FtpClient();
        ftp.Host = strServer;
        ftp.Port = Convert.ToInt32(port);
        ftp.Credentials = new NetworkCredential(strUser, strPassword);
        ftp.Connect()
        // 拼地址
        var currentDir = System.IO.Directory.GetCurrentDirectory();
        var fullPath = System.IO.Path.Combine(currentDir, "tempfiles\\");
        if (!Directory.Exists(fullPath))
        {
            Directory.CreateDirectory(fullPath);
        
       // var result = ftp.DownloadFile(fullPath + filename, serverPath + "/" + filename)
        string downloadUrl = string.Empty
        //using (FileStream fs = File.OpenRead(fullPath + filename))
        //{
        //    int length = (int)fs.Length
        //    byte[] data = new byte[length];
        //    fs.Position = 0;
        //    fs.Read(data, 0, length);
        //    MemoryStream ms = new MemoryStream(data)
        //    errorUrl = uploadFileService.UploadFileDownloadUrl(authInfo, "ftp", "ftp", filename, ms, "ftp", businessId).Message;
        //
        //// 读取文件 返回stream流
       // using (MemoryStream ms = new MemoryStream()
        // ftp读取,返回stream
        using (Stream streamFile = ftp.OpenRead(serverPath +"/"+ filename))
        {
            // stream转换为memorystream
            byte[] buffer = new byte[streamFile.Length];
            streamFile.Read(buffer, 0, buffer.Length);
            MemoryStream ms = new MemoryStream(buffer);
            downloadUrl = uploadFileService.UploadFileDownloadUrl(authInfo, "ftp", "ftp", filename, ms, "ftp", businessId).Message;
        }
        // 关闭ftp连接
        if (ftp.IsConnected)
        {
            // 关闭
            ftp.Disconnect();
            ftp.Dispose();
        }

        // 生成一个链接返回可下载
        return Success(downloadUrl);
        //return Success(result);
    }
    catch (Exception ex)
    {
        return Error(ex.Message);
    }
    finally
    {
        // 解锁
        redisClient.Eval(releaseLockScript, lockKey, id);
        redisClient.Del(lockKey);
        redisClient.Dispose();
    }
}

ftp文件撤回操作

撤回流程

  • 判断ftp服务是否存在该备份文件 否--当前无该备份,IsDetele--1
  • 取出备份文件
  • 删除ftp备份文件
  • 备份当前文件,并创建数备份记录
  • 替换当前文件
/// <summary>
/// 撤回文件操作
/// </summary>
/// <param name="id">撤回id</param>
/// <param name="serverPath">当前存储目录</param>
/// <param name="authInfo">用户信息</param>
/// <returns></returns>
public IEPResult ReCallUpload(string id, string serverPath, AuthInfoVO authInfo) {
    using(var unitWork = IOCManager.BuildUnitOfWork()) {
        try {
            // 连接ftp
            FtpClient ftp = new FtpClient();
            ftp.Host = strServer;
            ftp.Port = Convert.ToInt32(port);
            ftp.Credentials = new NetworkCredential(strUser, strPassword);
            ftp.Connect();

            // 取出备份记录
            var currentRecord = this.reportVersionInfoRepository.FindBy(id);

            // 判断ftp是否存在备份文件
            if (!ftp.DirectoryExists(currentRecord.Path) || !ftp.FileExists(currentRecord.Path + "/" + currentRecord.FileName)) {
                // 不存在该备份文件--->删除记录
                currentRecord.IsDetele = 1;
                unitWork.Change(currentRecord);
                return Error("服务器无该版本文件备份");
            }


            currentRecord.IsReCall = 1;
            currentRecord.ReCallDate = DateTime.Now;
            currentRecord.UpdateDate = DateTime.Now;
            currentRecord.UpdateAccount = authInfo.Account;
            currentRecord.UpdateCnName = authInfo.CnName;

            unitWork.Change(currentRecord);

            // 取出备份文件
            var streamFile = ftp.OpenRead(currentRecord.Path + "/" + currentRecord.FileName);

            // 备份当前文件记录
            var reportVersionInfo = new ReportVersionInfo();
            reportVersionInfo.Id = SnowflakeHelper.GenerateId().ToString();
            reportVersionInfo.BusinessId = currentRecord.BusinessId;
            reportVersionInfo.FileName = currentRecord.FileName;
            reportVersionInfo.Version = DateTime.Now.ToFileTimeUtc().ToString();
            reportVersionInfo.CreatedDate = DateTime.Now;
            reportVersionInfo.Path = "/备份资料" + "/" + reportVersionInfo.Version + "/" + serverPath;
            reportVersionInfo.IsReCall = 0;
            reportVersionInfo.CreatedAccount = authInfo.Account;
            reportVersionInfo.CreatedCnName = authInfo.CnName;
            reportVersionInfo.IsDetele = 0;


            unitWork.Add(reportVersionInfo);


            #
            region ftp备份当前文件
            var curStreamFile = ftp.OpenRead($ "{serverPath}/{currentRecord.FileName}");
            // 不存在就创建文件夹
            if (!ftp.DirectoryExists(reportVersionInfo.Path)) {
                ftp.CreateDirectory(reportVersionInfo.Path);
            }
            // 当前文件创建ftp备份
            ftp.Rename($ "{serverPath}/{currentRecord.FileName}", reportVersionInfo.Path + "/" + currentRecord.FileName);#
            endregion


            // 覆盖源文件
            var ftpStatus = ftp.UploadStream(streamFile, $ "{serverPath}/{currentRecord.FileName}", createRemoteDir: true);


            if (ftpStatus.Equals("Failed")) {
                unitWork.Rollback();
                return Error("撤回失败");
            }
            // stream 转为 formfile
            //IFormFile file=new FormFile(streamFile, 0, streamFile.Length,
            //Path.GetFileNameWithoutExtension(currentRecord.FileName),
            //Path.GetFileName(currentRecord.FileName));


            //this.FTPUpload(file, serverPath, reportVersionInfo);


            unitWork.Commit();


            // 删除ftp备份文件,不能删除文件夹(可能存在其他文件)
            ftp.DeleteFile(currentRecord.Path + "/" + currentRecord.FileName);


            // 关闭ftp连接
            if (ftp.IsConnected) {
                // 关闭
                ftp.Disconnect();
                ftp.Dispose();
            }


            return Success("撤回成功");
        } catch (Exception ex) {
            throw ex;
            unitWork.Rollback();
            return Error(ex.Message);
        }
    }
}