前情提要
筆者所處環境為金融業,所在的科別屬寫週邊的應用系統,開發的每個系統多多少少都需要與別的系統串接,常見的有核心
、外匯
、通路科
等等。如[DotnetCore]Socket程式實作那篇所提,因為通常核心系統
環境比較特殊,相對應的串接方式,偏Socket或者交換檔案這類的傳統型居多。這篇主要以FTP
交換檔案為主介紹其作法,筆者會再寫兩篇相關的文章,請拭目以待。
內容
安裝FluentFTP套件
1
| dotnet add package FluentFTP
|
定義相關Model
FTP相關Config
筆者這邊把ftp相關的設定定義成一個ConfigModel
,以強型別的方式存取設定值
1 2 3 4 5 6 7 8 9 10
| public class FTPConfigBaseModel { public string Server { get; set; } public string Account { get; set; } public string Password { get; set; } public string RemotePath { get; set; } public string LocalPath { get; set; } }
|
FTP上傳統一結構
筆者這邊因為大部分上傳檔案,用意在於跟別的系統交換檔案,檔案內容由資料庫內容運算而得,筆者所在環境又屬於那種崇尚寫Store Procedure
,因此不要讓需求太發散,索性定一個統一的結構,是回傳ftp上傳使用的資料則統一使用該資料結構
1 2 3 4 5 6 7 8 9
| public class FileUploadResponseModel { public string FileName { get; set; } public string Encoding { get; set; } public string DataStr { get; set; } }
|
撰寫Service
宣告IFtpService
筆者這邊使用dotnet core
預設的Dependency Injection
,就注到底了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public interface IFtpService { void DownloadData(FTPConfigBaseModel downloadFtpInfo, List<string> fileList); void UploadData(FTPConfigBaseModel uploadFtpInfo, List<FileUploadResponseModel> fileUploadResponseModels); bool IsExist(FTPConfigBaseModel uploadFtpInfo, string filename); }
|
實作FTPService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
| public class FtpService : IFtpService { private readonly ILoggerManager _logger; private readonly IFilePathService _filePathService; private readonly IConfiguration _config;
public FtpService(IFilePathService filePathService , IConfiguration config, ILoggerManager logger) { _filePathService = filePathService; _config = config; _logger = logger; } public void DownloadData(FTPConfigBaseModel downloadFtpInfo, List<string> fileList) { var localDir = _filePathService.GetDirectoryPath(downloadFtpInfo.LocalPath); var remoteDir = downloadFtpInfo.RemotePath; FtpClient client = new FtpClient(downloadFtpInfo.Server); var password = Encoding.UTF8.GetString(Convert.FromBase64String(downloadFtpInfo.Password)); var securePwd = PasswordExtension.SecureStringToString( PasswordExtension.GetPasswordSecurity(password)); try { client.Credentials = new NetworkCredential(downloadFtpInfo.Account, securePwd); client.Connect(); foreach (var item in fileList) { var localPath = Path.Combine(localDir, item); var remotePath = Path.Combine(remoteDir, item); if (client.FileExists(remotePath)) { client.DownloadFile(localPath, remotePath); } } } catch (Exception exception) { throw exception; } finally { client.Disconnect(); } }
public void UploadData(FTPConfigBaseModel uploadFtpInfo, List<FileUploadResponseModel> fileUploadResponseModels) { var localDir = _filePathService.GetDirectoryPath(uploadFtpInfo.LocalPath); var remoteDir = uploadFtpInfo.RemotePath; var fileGroupList = fileUploadResponseModels.GroupBy(x => x.FileName).ToList(); var fileList = new List<string>(); foreach (var item in fileGroupList) { var fileContent = item.Select(x => x.DataStr) .Aggregate((x, y) => $"{x}\r\n{y}"); var encoding = item.FirstOrDefault()?.Encoding; File.WriteAllText(Path.Combine(localDir, item.Key) , fileContent, Encoding.GetEncoding(encoding)); fileList.Add(item.Key); } FtpClient client = new FtpClient(uploadFtpInfo.Server); var password = Encoding.UTF8.GetString(Convert.FromBase64String(uploadFtpInfo.Password)); var securePwd = PasswordExtension.SecureStringToString( PasswordExtension.GetPasswordSecurity(password)); try { client.Credentials = new NetworkCredential(uploadFtpInfo.Account, securePwd); client.Connect(); foreach (var item in fileList) { var localPath = Path.Combine(localDir, item); var remotePath = Path.Combine(remoteDir, item); client.UploadFile(localPath, remotePath, FtpRemoteExists.Overwrite); } } catch (Exception exception) { throw exception; } finally { client.Disconnect(); } }
public bool IsExist(FTPConfigBaseModel uploadFtpInfo, string filename) { FtpClient client = new FtpClient(uploadFtpInfo.Server); var password = Encoding.UTF8.GetString(Convert.FromBase64String(uploadFtpInfo.Password)); var securePwd = PasswordExtension.SecureStringToString( PasswordExtension.GetPasswordSecurity(password)); try { client.Credentials = new NetworkCredential(uploadFtpInfo.Account, securePwd); client.Connect(); var remotePath = Path.Combine(uploadFtpInfo.RemotePath, filename); return client.FileExists(remotePath); } catch (Exception exception) { throw exception; } finally { client.Disconnect(); } } }
|
其中IFilePathService
及PasswordExtension
,筆者會再寫一篇,說來話長,筆者這邊環境需要產出源掃報告才可允許上線,源掃軟體使用Fortify,只要跟System.IO.Path時需要再額外加入判斷才算安全,只要是密碼類型的需要透過SecureString
處理過才算安全,因此筆者這邊就索性抽出IFilePathService
及PasswordExtension
,大家都靠這幾個共用的Service做存取伺服器內部檔案及處理密碼字串。
Client端使用
Startup.cs中宣告
1
| services.AddTransient<IFtpService, FtpService>();
|
終端Service中引用
筆者這邊就不另外示範了,主要是將IFtpService
從建構值注入就可以使用了,再則可以透過IConfiguration
(若將ftp相關資訊設定於appsettings
中)的方式讀取出FTP相關設定,製作成FtpConfigBaseModel
的格式,傳進IFtpService
中即可呼叫下載、上傳與判斷是否存在。
結論
筆者之前工作都很少透過程式碼存取Ftp,既然這裡有存取需求,實作並記錄一下,成就感十足阿,FluentFTP
簡單易用,果然名字上有Fluent都特別好用。