Ftp基礎(chǔ)(五):.NetCore中使用Ftp的建議(FluentFTP)
??上一篇說道C#使用FluentFTP來簡(jiǎn)單的連接使用Ftp,本篇是個(gè)人在.NetCore中使用Ftp的建議(可能有點(diǎn)啰嗦):
??1、為Ftp的配置創(chuàng)建基類
??在開發(fā)過程中,我們?nèi)绻褂肍tp,往往需要這幾個(gè)信息:??
Host:Ftp地址
Port:端口號(hào)
User:用戶名
Password:密碼
WorkingDirectory:工作目錄,如果你希望整個(gè)Ftp只給一個(gè)系統(tǒng)使用,可以不用,直接使用根目錄
Mode:模式:主動(dòng)模式、被動(dòng)模式
?除了上述幾個(gè)信息,我們?cè)陂_發(fā)過程中可能還會(huì)有其它配置,比如我們的業(yè)務(wù)配置,也有可能是Ftp的配置,但是這些配置都和具體場(chǎng)景有關(guān)。
??而這些信息,可能來自我們的配置文件,也可能來自數(shù)據(jù)庫(kù)配置,還可能來自我們UI的輸入,所以,我們應(yīng)該給Ftp的配置一個(gè)基類(也可以是抽象類),比如:
public class FtpOptions
{
/// <summary>
/// Ftp地址,默認(rèn)localhost
/// </summary>
public string Host { get; set; } = "localhost";
/// <summary>
/// Ftp端口號(hào),默認(rèn)21
/// </summary>
public int Port { get; set; } = 21;
/// <summary>
/// User
/// </summary>
public string User { get; set; }
/// <summary>
/// Password
/// </summary>
public string Password { get; set; }
/// <summary>
/// 根目錄
/// </summary>
public string WorkingDirectory { get; set; }
/// <summary>
/// 是否主動(dòng)模式
/// </summary>
public bool Active { get; set; }
}
??這樣,當(dāng)我們有一些特定需求的時(shí)候,可以繼承這個(gè)基類,比如我們下載的時(shí)候,可能需要限速,那么我們可以創(chuàng)建這樣的配置類:
public class DownloadFtpOptions : FtpOptions
{
/// <summary>
/// 下載限速(字節(jié))
/// </summary>
public long Bytes { get; set; }
}
??再比如,當(dāng)我們使用Ftp來作為兩個(gè)系統(tǒng)之間的信息傳遞的媒介時(shí),比如??
A系統(tǒng)上傳文件到Ftp指定的目錄,那么需要的配置是:Ftp基本配置、上傳目錄
B系統(tǒng)定時(shí)掃描指定的目錄來下載文件,那么需要的配置是:Ftp基本配置、掃描目錄、掃描時(shí)間間隔
??那么我們可以分別為A\B系統(tǒng)創(chuàng)建這么兩個(gè)配置類:??
public class UploadFtpOptions : FtpOptions
{
/// <summary>
/// 上傳文件的目錄
/// </summary>
public string Path { get; set; }
}
public class ScanFtpOptions : FtpOptions
{
/// <summary>
/// 掃描時(shí)間間隔
/// </summary>
public int Interval { get; set; }
/// <summary>
/// 掃描文件的目錄
/// </summary>
public string Path { get; set; }
}
??2、通過一個(gè)方法來獲取FtpClient或者AsyncFtpClient
??有了配置,接下來我們需要使用,利用FluentFTP來操作Ftp的話,我們需要?jiǎng)?chuàng)建FtpClient或者AsyncFtpClient,這個(gè)時(shí)候,我們應(yīng)該通過一個(gè)通用的方法來創(chuàng)建,而這個(gè)方法,可以是基類的一個(gè)公共方法,也可以是基類的一個(gè)拓展方法:
??使用基類的公共方法,可以使用virtual修飾,后續(xù)子類有需要,可以自定義重寫
public class FtpOptions
{
//省略屬性...
/// <summary>
/// 創(chuàng)建ftp連接
/// </summary>
/// <returns></returns>
public virtual FtpClient GetFtpClient()
{
var ftpClient = new FtpClient(Host);
if (Port > 0)
{
ftpClient.Port = Port;
}
if (!string.IsNullOrEmpty(User) && !string.IsNullOrEmpty(Password))
{
ftpClient.Credentials = new NetworkCredential(User, Password);
}
ftpClient.Encoding = Encoding.UTF8;
ftpClient.Config.DataConnectionType = Active ? FtpDataConnectionType.AutoActive : FtpDataConnectionType.AutoPassive;
ftpClient.Connect();
ftpClient.CreateDirectory(WorkingDirectory);
ftpClient.SetWorkingDirectory(WorkingDirectory);
return ftpClient;
}
/// <summary>
/// 創(chuàng)建ftp連接(異步)
/// </summary>
/// <returns></returns>
public virtual async Task<AsyncFtpClient> GetAsyncFtpClient()
{
var ftpClient = new AsyncFtpClient(Host);
if (Port > 0)
{
ftpClient.Port = Port;
}
if (!string.IsNullOrEmpty(User) && !string.IsNullOrEmpty(Password))
{
ftpClient.Credentials = new NetworkCredential(User, Password);
}
ftpClient.Encoding = Encoding.UTF8;
ftpClient.Config.DataConnectionType = Active ? FtpDataConnectionType.AutoActive : FtpDataConnectionType.AutoPassive;
await ftpClient.Connect();
await ftpClient.CreateDirectory(WorkingDirectory);
await ftpClient.SetWorkingDirectory(WorkingDirectory);
return ftpClient;
}
}
??使用基類的拓展方法:
public static class FtpOptionsExtensions
{
/// <summary>
/// 創(chuàng)建ftp連接
/// </summary>
/// <param name="klarfDiscoveryOptions"></param>
/// <returns></returns>
public static FtpClient GetFtpClient(this FtpOptions ftpOptions)
{
var ftpClient = new FtpClient(ftpOptions.Host);
if (ftpOptions.Port > 0)
{
ftpClient.Port = ftpOptions.Port;
}
if (!string.IsNullOrEmpty(ftpOptions.User) && !string.IsNullOrEmpty(ftpOptions.Password))
{
ftpClient.Credentials = new NetworkCredential(ftpOptions.User, ftpOptions.Password);
}
ftpClient.Encoding = Encoding.UTF8;
ftpClient.Config.DataConnectionType = ftpOptions.Active ? FtpDataConnectionType.AutoActive : FtpDataConnectionType.AutoPassive;
ftpClient.Connect();
ftpClient.CreateDirectory(ftpOptions.WorkingDirectory);
ftpClient.SetWorkingDirectory(ftpOptions.WorkingDirectory);
return ftpClient;
}
/// <summary>
/// 創(chuàng)建ftp連接(異步)
/// </summary>
/// <param name="klarfDiscoveryOptions"></param>
/// <returns></returns>
public static async Task<AsyncFtpClient> GetAsyncFtpClient(this FtpOptions ftpOptions)
{
var ftpClient = new AsyncFtpClient(ftpOptions.Host);
if (ftpOptions.Port > 0)
{
ftpClient.Port = ftpOptions.Port;
}
if (!string.IsNullOrEmpty(ftpOptions.User) && !string.IsNullOrEmpty(ftpOptions.Password))
{
ftpClient.Credentials = new NetworkCredential(ftpOptions.User, ftpOptions.Password);
}
ftpClient.Encoding = Encoding.UTF8;
ftpClient.Config.DataConnectionType = ftpOptions.Active ? FtpDataConnectionType.AutoActive : FtpDataConnectionType.AutoPassive;
await ftpClient.Connect();
await ftpClient.CreateDirectory(ftpOptions.WorkingDirectory);
await ftpClient.SetWorkingDirectory(ftpOptions.WorkingDirectory);
return ftpClient;
}
}
??兩種方式各有各的優(yōu)劣,具體開發(fā)再選擇吧。
??3、面向接口來使用Ftp或者FluentFTP
??在開發(fā)過程中,應(yīng)該有些同學(xué)第一反應(yīng)就是要把FtpClient封裝成類似HttpClient的方式去使用吧,先封裝個(gè)工廠,然后使用它來創(chuàng)建FtpClient,其實(shí)個(gè)人不推薦這種做法,因?yàn)樗@么做屬于對(duì)Ftp形成了強(qiáng)依賴。既然使用了.NetCore,那么我們很多時(shí)候應(yīng)該面向接口開發(fā),特別是針對(duì)Ftp這類第三方庫(kù)時(shí),我們應(yīng)該盡可能對(duì)它形成弱依賴,而不能說不使用它,我們系統(tǒng)就玩不轉(zhuǎn)了吧。
??其實(shí),這就需要我們能對(duì)自己系統(tǒng)的業(yè)務(wù)進(jìn)行區(qū)分,比如,當(dāng)我們只是想存儲(chǔ)文件時(shí),我們可以創(chuàng)建這么一個(gè)接口:??
public interface IFileStorage
{
/// <summary>
/// 文件是否存在
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
Task<bool> FileExistsAsync(string fileName);
/// <summary>
/// 刪除文件
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
Task DeleteFileAsync(string fileName);
/// <summary>
/// 上傳文件
/// </summary>
/// <param name="fileName"></param>
/// <param name="stream"></param>
/// <returns></returns>
Task UploadAsync(string fileName, Stream stream);
/// <summary>
/// 下載文件
/// </summary>
/// <param name="fileName"></param>
/// <param name="stream"></param>
/// <returns></returns>
Task DownloadAsync(string fileName, Stream stream);
}
??我們Ftp的操作只需要實(shí)現(xiàn)這個(gè)接口然后把接口注入就可以了,這樣我們哪天不想使用Ftp存儲(chǔ)文件,只需要使用其它的工具實(shí)現(xiàn)這個(gè)就可以了。
??注:你可能回想為什么沒有目錄的操作?這個(gè)是因?yàn)槟夸洸痪邆溥@個(gè)業(yè)務(wù)的共性,存的是文件,不是目錄,而且哪天如果要緩存MongoGridFS、某些對(duì)象存儲(chǔ)方案時(shí),可能根本就沒有目錄的概念,當(dāng)然我們也可以在這個(gè)接口上加上目錄的操作,如果不支持目錄操作,我們只需要空實(shí)現(xiàn)就好了
??再比如,如果我們需要掃描Ftp的某個(gè)目錄,我們可以創(chuàng)建這么一個(gè)接口:??
public interface IScanSource
{
/// <summary>
/// 返回指定目錄下的文件及目錄
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
Task<ScanItem[]> List(string path);
/// <summary>
/// 下載文件
/// </summary>
/// <param name="fileName"></param>
/// <param name="stream"></param>
/// <returns></returns>
Task DownloadAsync(string fileName, Stream stream);
}
public class ScanItem
{
/// <summary>
/// 文件或者名稱
/// </summary>
public string Name { get; set; }
/// <summary>
/// 是否是目錄
/// </summary>
public bool IsDirectory { get; set; }
}
??通用,我們只需要使用Ftp來實(shí)現(xiàn) 這個(gè)接口就可以了,后續(xù)如果我們掃描的是本地的目錄、Cifs/Smb等其它的方案,我們只需要對(duì)應(yīng)的實(shí)現(xiàn)就可以了
??
??總結(jié)
??Ftp很早就出現(xiàn)了,優(yōu)劣我們暫且不評(píng)論,以前系統(tǒng)用的很多,但是現(xiàn)在不一樣了,特別是互聯(lián)網(wǎng)項(xiàng)目,幾乎都不用的,而取代它的方案,現(xiàn)在還是有很多可以選擇,比如用的多的還有Cifs/Smb,所以我們?cè)陂_發(fā)時(shí),盡量不要對(duì)這種工具形成強(qiáng)依賴,如果確實(shí)避不開,我們應(yīng)該要考慮把它所有操作自己做個(gè)封裝,封裝在一個(gè)類中,后續(xù)如果要替代,我們也可以很方便平穩(wěn)的過渡。

浙公網(wǎng)安備 33010602011771號(hào)