持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情
在日常工作中,很多使用会要用到filezilla这个软件,进行文件操作。但是文件都是服务器上即传即用的,不太利于维护,所以接到了一个禅道任务,实现ftp连接,进行附件上传、下载、版本回溯功能。
技术选型
后端框架使用的是.net core 3.0,基于c#语言开发的一个框架。之前找了很多关于ftp的文档,比如FtpWebRequest,但是框架不支持(参考链接:learn.microsoft.com/zh-cn/dotne… 如图:
最终使用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);
}
}
}