白码一号的博客园
最近由于项目安全需要,将之前的ftp上传文件的方式,改用ftps
因为不太了解这个东西便开始了踩坑之旅
首先,最近在ubuntu 上搭建了这个服务
流程可以参考这些博客(部署网上的资源很多)
https://www.jianshu.com/p/f666278dc3b7
https://www.jianshu.com/p/413ac3ab26a3
https://blog.csdn.net/hdxyzlh_0225/article/details/50923185
ftps呢是在ftp的服务的前提上加上了 证书,保证在传输的过程中数据不是明文,防止被抓包泄露敏感信息
部署的流程也在是一个完整的ftp服务上再去加证书
然后我这边把我服务器上的配置拉一下给大家参考一下
我这边配置的 ssl 隐式连接
# Example config file /etc/vsftpd.conf # # The default compiled in settings are fairly paranoid. This sample file # loosens things up a bit, to make the ftp daemon more usable. # Please see vsftpd.conf.5 for all compiled in defaults. # # READ THIS: This example file is NOT an exhaustive list of vsftpd options. # Please read the vsftpd.conf.5 manual page to get a full idea of vsftpd's # capabilities. # # # Run standalone? vsftpd can run either from an inetd or as a standalone # daemon started from an initscript. listen=NO # # This directive enables listening on IPv6 sockets. By default, listening # on the IPv6 "any" address (::) will accept connections from both IPv6 # and IPv4 clients. It is not necessary to listen on *both* IPv4 and IPv6 # sockets. If you want that (perhaps because you want to listen on specific # addresses) then you must run two copies of vsftpd with two configuration # files. listen_ipv6=YES # # Allow anonymous FTP? (Disabled by default). anonymous_enable=NO # # Uncomment this to allow local users to log in. local_enable=YES # # Uncomment this to enable any form of FTP write command. write_enable=YES # # Default umask for local users is 077. You may wish to change this to 022, # if your users expect that (022 is used by most other ftpd's) #local_umask=022 # # Uncomment this to allow the anonymous FTP user to upload files. This only # has an effect if the above global write enable is activated. Also, you will # obviously need to create a directory writable by the FTP user. #anon_upload_enable=YES # # Uncomment this if you want the anonymous FTP user to be able to create # new directories. #anon_mkdir_write_enable=YES # # Activate directory messages - messages given to remote users when they # go into a certain directory. dirmessage_enable=YES # # If enabled, vsftpd will display directory listings with the time # in your local time zone. The default is to display GMT. The # times returned by the MDTM FTP command are also affected by this # option. use_localtime=YES # # Activate logging of uploads/downloads. xferlog_enable=YES # # Make sure PORT transfer connections originate from port 20 (ftp-data). connect_from_port_20=YES # # If you want, you can arrange for uploaded anonymous files to be owned by # a different user. Note! Using "root" for uploaded files is not # recommended! #chown_uploads=YES #chown_username=whoever # # You may override where the log file goes if you like. The default is shown # below. #xferlog_file=/var/log/vsftpd.log # # If you want, you can have your log file in standard ftpd xferlog format. # Note that the default log file location is /var/log/xferlog in this case. #xferlog_std_format=YES # # You may change the default value for timing out an idle session. #idle_session_timeout=600 # # You may change the default value for timing out a data connection. #data_connection_timeout=120 # # It is recommended that you define on your system a unique user which the # ftp server can use as a totally isolated and unprivileged user. #nopriv_user=ftpsecure # # Enable this and the server will recognise asynchronous ABOR requests. Not # recommended for security (the code is non-trivial). Not enabling it, # however, may confuse older FTP clients. #async_abor_enable=YES # # By default the server will pretend to allow ASCII mode but in fact ignore # the request. Turn on the below options to have the server actually do ASCII # mangling on files when in ASCII mode. # Beware that on some FTP servers, ASCII support allows a denial of service # attack (DoS) via the command "SIZE /big/file" in ASCII mode. vsftpd # predicted this attack and has always been safe, reporting the size of the # raw file. # ASCII mangling is a horrible feature of the protocol. #ascii_upload_enable=YES #ascii_download_enable=YES # # You may fully customise the login banner string: #ftpd_banner=Welcome to blah FTP service. # # You may specify a file of disallowed anonymous e-mail addresses. Apparently # useful for combatting certain DoS attacks. #deny_email_enable=YES # (default follows) #banned_email_file=/etc/vsftpd.banned_emails # # You may restrict local users to their home directories. See the FAQ for # the possible risks in this before using chroot_local_user or # chroot_list_enable below. #chroot_local_user=YES # # You may specify an explicit list of local users to chroot() to their home # directory. If chroot_local_user is YES, then this list becomes a list of # users to NOT chroot(). # (Warning! chroot'ing can be very dangerous. If using chroot, make sure that # the user does not have write access to the top level directory within the # chroot) #chroot_local_user=YES #chroot_list_enable=YES # (default follows) #chroot_list_file=/etc/vsftpd.chroot_list # # You may activate the "-R" option to the builtin ls. This is disabled by # default to avoid remote users being able to cause excessive I/O on large # sites. However, some broken FTP clients such as "ncftp" and "mirror" assume # the presence of the "-R" option, so there is a strong case for enabling it. #ls_recurse_enable=YES # # Customization # # Some of vsftpd's settings don't fit the filesystem layout by # default. # # This option should be the name of a directory which is empty. Also, the # directory should not be writable by the ftp user. This directory is used # as a secure chroot() jail at times vsftpd does not require filesystem # access. secure_chroot_dir=/var/run/vsftpd/empty # # This string is the name of the PAM service vsftpd will use. pam_service_name=vsftpd # # This option specifies the location of the RSA certificate to use for SSL # encrypted connections. #rsa_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem #rsa_private_key_file=/etc/ssl/private/ssl-cert-snakeoil.key ssl_enable=YES ssl_tlsv1=YES ssl_sslv2=NO ssl_sslv3=NO allow_anon_ssl=NO force_local_data_ssl=YES force_local_logins_ssl=YES listen_port=10012 rsa_cert_file=/etc/ssl/private/vsftpd.pem rsa_private_key_file=/etc/ssl/private/vsftpd.pem implicit_ssl=YES connect_from_port_20=NO debug_ssl=YES local_root=/home/uftp/testFiles # # Uncomment this to indicate that vsftpd use a utf8 filesystem. #utf8_filesystem=YES
这个是FileZilla 连接工具的设置()
配置完后,工具能正常连接上,现在开始上代码 FluentFTP用的版本是 FluentFTP, Version=37.0.2.0 最新的版本代码有变化
// See https://aka.ms/new-console-template for more information using FluentFTP; using System.Net; using System.Net.Security; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using System.Text; Console.WriteLine("Hello, World!"); string path = @"C:\Users\36493\Desktop\Test\UploadFileFtps\UploadFileFtps\2022-04-29182023.xml"; //创建目录 UploadToFtpFileHelper.CreateFileContent("2021/05/06/test"); //下载文件 //UploadToFtpFileHelper.DownloadFile(@"C:\Users\36493\Desktop\test002\111.txt", @"111.txt"); //上传文件 UploadToFtpFileHelper.UploadFile(path, @"2021/05/06/test/2022-04-29182023.xml"); public static class UploadToFtpFileHelper { //普通的ftp private static string ftp_postUrl = "ftp://xx.xxx.x.xxx/"; private static string ftp_user = "xxxxx"; private static string ftp_pwd = "xxxxx"; //使用ssl通讯加密的方式连接ftp private static string ftps_postUrl = "xx.xxx.x.xxx"; private static string ftps_user = "xxx"; private static string ftps_pwd = "xxxx"; private static int ftps_port = xx; #region Ftps private static FtpClient ftpClient = null; private static async Task<FtpClient> ConnectionFtp() { // 创建 FTP client FtpClient client = new FtpClient(ftps_postUrl); // 如果您不指定登录凭证,我们将使用"anonymous"用户帐户 client.Credentials = new NetworkCredential(ftps_user, ftps_pwd); client.Port = ftps_port; //选择证书连接的方式 TLS client.SslProtocols = System.Security.Authentication.SslProtocols.Tls | System.Security.Authentication.SslProtocols.Ssl2 | System.Security.Authentication.SslProtocols.Ssl3; client.Encoding = System.Text.Encoding.UTF8; client.EncryptionMode = FtpEncryptionMode.Explicit;//ssl链接错误 client.ValidateCertificate += new FtpSslValidation(OnValidateCertificate);//证书验证 void OnValidateCertificate(FtpClient control, FtpSslValidationEventArgs e) { //在这里添加逻辑以测试证书是否有效 e.Accept = true; } var connect = client.AutoConnect();//根据验证过程,远程证书无效 if (!client.IsConnected) { Console.WriteLine("连接建立失败"); throw new Exception("连接建立失败"); } return client; } /// <summary> /// 从ftp下载文件(ssl) /// </summary> /// <param name="localPath">要下载到本地哪儿</param> /// <param name="ftpFilePath">ftp的文件路径</param> public static async Task<FtpStatus> DownloadFile(string localPath, string ftpFilePath) { ftpClient = ftpClient == null ? await ConnectionFtp() : ftpClient; FtpStatus status = ftpClient.DownloadFile(localPath, ftpFilePath); //ftpClient.Disconnect(); return status; } /// <summary> /// 上传文件到ftp(ssl) /// </summary> /// <param name="localPath">本地的文件路径,完整的</param> /// <param name="ftpFilePath">要上传到的地址</param> /// <returns></returns> public static async Task<FtpStatus> UploadFile(string localPath, string ftpFilePath) { ftpClient = ftpClient == null ? await ConnectionFtp() : ftpClient; FtpStatus status = ftpClient.UploadFile(localPath, ftpFilePath); //ftpClient.Disconnect(); return status; } /// <summary> /// 创建文件目录 /// </summary> public static async Task<bool> CreateFileContent(string createPath) { ftpClient = ftpClient == null ? await ConnectionFtp() : ftpClient; bool result = ftpClient.CreateDirectory(createPath); //ftpClient.Disconnect(); return result; } public static void Mains() { // 创建 FTP client FtpClient client = new FtpClient("10.168.1.128"); // 如果您不指定登录凭证,我们将使用"anonymous"用户帐户 client.Credentials = new NetworkCredential("tgy", "test123456"); client.Port = 10012; //选择证书连接的方式 TLS client.SslProtocols = System.Security.Authentication.SslProtocols.Tls | System.Security.Authentication.SslProtocols.Ssl2 | System.Security.Authentication.SslProtocols.Ssl3; client.Encoding = System.Text.Encoding.UTF8; client.EncryptionMode = FtpEncryptionMode.Explicit;//ssl链接错误 client.ValidateCertificate += new FtpSslValidation(OnValidateCertificate);//证书验证 void OnValidateCertificate(FtpClient control, FtpSslValidationEventArgs e) { //在这里添加逻辑以测试证书是否有效 e.Accept = true; } //开始连接Server try { var connect = client.AutoConnect();//根据验证过程,远程证书无效 if (!client.IsConnected) { Console.WriteLine("连接建立失败"); } foreach (FtpListItem item in client.GetListing("testFiles")) { //如果是 file if (item.Type == FtpFileSystemObjectType.File) { // get the file size long size = client.GetFileSize(item.FullName); } // 获取文件或文件夹的修改日期/时间 DateTime time = client.GetModifiedTime(item.FullName); // 计算服务器端文件的哈希值(默认算法) //FtpHash hash = client.GetHash(item.FullName); } if (client.DirectoryExists("test")) { } var gg = client.DownloadFile(@"C:\Users\36493\Desktop\test002\111.txt", @"111.txt"); var oo = client.UploadFile(@"C:\Users\36493\Desktop\test002\yyds.txt", "yyds.txt"); } catch (Exception ex) { throw; } } #endregion #region Ftp /// <summary> /// 上传项目指定的文件到ftp服务器 /// </summary> /// <param name="localfilePath">服务文件路径</param> /// <param name="fileName">上传文件路径及名称(包含后缀)</param> /// <exception cref="Exception"></exception> public static async void UploadToFtp(string localfilePath, string urlfileName)//, UploadType Type { if (!File.Exists(localfilePath)) { throw new FileNotFoundException("未获取到本地服务器文件!"); } if (!CheckDirectoryExist(urlfileName)) { FtpCheckExis(urlfileName); } await UploadDetails_Ftp(localfilePath, urlfileName); } /// <summary> /// 把一个ftp文件从指定路径复制到 另一个路径下 /// </summary> /// <param name="oldfilePath">旧的ftp的url</param> /// <param name="newfilePath">新的ftp的url</param> /// <returns></returns> public static async Task<string> CopyFtpToFtp(string oldfilePath, string newfilePath) { return ""; } private static async Task UploadDetails_Ftp(string localfilePath, string urlfileName) { FileInfo fileInfo = new FileInfo(localfilePath); var reqFTP = (FtpWebRequest)FtpWebRequest.Create(ftp_postUrl + urlfileName); reqFTP.EnableSsl = true; reqFTP.Credentials = new NetworkCredential(ftp_user, ftp_pwd); reqFTP.KeepAlive = false;//默认位true,链接不会被关闭,再一个指令之后被执行 reqFTP.Method = WebRequestMethods.Ftp.UploadFile; //指定执行什么命令 reqFTP.UseBinary = true;//指定数据传输类型 reqFTP.ContentLength = fileInfo.Length;//上传文件通知服务器,文件的大小 int buffLength = 2048;//缓冲大小设置位2kb byte[] buff = new byte[buffLength]; int contentlen; FileStream fs = fileInfo.OpenRead(); try { Stream stream = await reqFTP.GetRequestStreamAsync();//把上传的文件写入流 contentlen = fs.Read(buff, 0, buff.Length);//每次读取文件的流的2kb while (contentlen != 0)//流内容没有结束 { stream.Write(buff, 0, contentlen); contentlen = fs.Read(buff, 0, buff.Length); } stream.Close(); fs.Close(); } catch (Exception ex) { } } /// <summary> /// 判断ftp服务器上是否存在该目录 /// </summary> /// <param name="ftpPath">ftp路径目录</param> /// <param name="dirName">目录上的文件夹名称</param> /// <returns></returns> private static bool CheckDirectoryExist(string dirName) { bool flag = true; try { var reqFTP = (FtpWebRequest)FtpWebRequest.Create(ftp_postUrl + dirName); reqFTP.Credentials = new NetworkCredential(ftp_user, ftp_pwd); reqFTP.Method = WebRequestMethods.Ftp.ListDirectory; FtpWebResponse response = (FtpWebResponse)reqFTP.GetResponse(); response.Close(); } catch (Exception ex) { flag = false; } return flag; } /// <summary> /// 创建文件夹 /// </summary> /// <param name="ftpPath">ftp路径</param> /// <param name="dirName">创建文件夹名称</param> private static async Task MakerDir(string dirName) { FtpWebRequest reqFtp; try { reqFtp = (FtpWebRequest)FtpWebRequest.Create((ftp_postUrl + dirName).Trim()); reqFtp.Method = WebRequestMethods.Ftp.MakeDirectory; reqFtp.UseBinary = true; reqFtp.Credentials = new NetworkCredential(ftp_user, ftp_pwd); FtpWebResponse resp = (FtpWebResponse)reqFtp.GetResponse(); Stream ftpStream = resp.GetResponseStream(); ftpStream.Close(); resp.Close(); } catch (Exception ex) { } } /// <summary> /// 判断文件的目录是否存在 /// </summary> public static void FtpCheckExis(string urlFilepath) { string fullDir = urlFilepath.Substring(0, urlFilepath.LastIndexOf("/")); string[] dirs = fullDir.Split('/'); string curDir = "/"; for (int i = 0; i < dirs.Length; i++) { string dir = dirs[i]; if (dir != null && dir.Length > 0) { try { curDir += dir + "/"; FtpMakerDir(curDir); } catch (Exception) { } } } } /// <summary> /// 新建文件目录 /// </summary> /// <param name="file"></param> /// <returns></returns> public static bool FtpMakerDir(string file) { FtpWebRequest req = (FtpWebRequest)WebRequest.Create(ftp_postUrl + file); req.Credentials = new NetworkCredential(ftp_user, ftp_pwd); req.Method = WebRequestMethods.Ftp.MakeDirectory; try { FtpWebResponse request = (FtpWebResponse)req.GetResponse(); request.Close(); } catch (Exception) { req.Abort(); return false; } req.Abort(); return true; } #endregion }
好,到这里踩坑完毕
经过项目复盘优化
性能更高、更快速的ftps的连接方式如下
public async Task<FtpClient> Connection(FtpConfigure ftpConfigure) { // 创建 FTP client Temporaryclient = new FtpClient(ftpConfigure.ftps_Url); // 如果您不指定登录凭证,我们将使用"anonymous"用户帐户 Temporaryclient.Credentials = new NetworkCredential(ftpConfigure.ftps_user, ftpConfigure.ftps_pwd); Temporaryclient.Port = ftpConfigure.ftps_port; //选择证书连接的方式 TLS Temporaryclient.SslProtocols = System.Security.Authentication.SslProtocols.Tls | System.Security.Authentication.SslProtocols.Tls11 | System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls13; Temporaryclient.Encoding = System.Text.Encoding.UTF8; Temporaryclient.EncryptionMode = FtpEncryptionMode.Implicit;//ssl链接错误 Temporaryclient.DataConnectionType = FtpDataConnectionType.PASV; Temporaryclient.ValidateCertificate += new FtpSslValidation(OnValidateCertificate);//证书验证 void OnValidateCertificate(FtpClient control, FtpSslValidationEventArgs e) { //在这里添加逻辑以测试证书是否有效 e.Accept = true; } //var connect = Temporaryclient.AutoConnect();//根据验证过程,远程证书无效 Temporaryclient.Connect(); if (!Temporaryclient.IsConnected) { Console.WriteLine("上级ftps连接建立失败"); _logger.LogError("上级ftps连接建立失败"); } return Temporaryclient; }
如果遇到并发传输记得枷锁哦
public async Task<FtpStatus> DownloadFile(string localPath, string ftpFilePath, SystemType type) { FtpStatus status = FtpStatus.Success; try { lock (_locker) { SelectConnection(type); if (!Publicclient.IsConnected) { _logger.LogError("ftp连接失败"); } status = Publicclient.DownloadFile(localPath, ftpFilePath); } //ftpClient.Disconnect(); } catch (Exception ex) { _logger.LogError("请求失败,原因:"+ex); } return status; }
上面服务连接较慢 继续更新继续优化
using FluentFTP; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Service.IntelligentRemotePatrol.Application.Contracts.Common; using Service.IntelligentRemotePatrol.ConfigOptions; using Service.IntelligentRemotePatrol.Enums; using Service.IntelligentRemotePatrol.Helper; using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Security.Authentication; using System.Text; using System.Threading.Tasks; using Volo.Abp; using Volo.Abp.Modularity; namespace Service.IntelligentRemotePatrol.Application.Common { public class UploadToFtpService : IUpLoadFtpFileService { private object _lockerArest = new object(); private object _locker = new object(); private object _locker_that = new object(); private readonly ILogger<UploadToFtpService> _logger; public UploadToFtpService(ServiceConfigurationContext context, ILogger<UploadToFtpService> logger) { _logger = logger; #region ftps var _appConfig = context.Services.BuildServiceProvider().GetService<IOptions<AppConfig>>()?.Value ?? new AppConfig(); List<FtpConfigure> ftpConfigureList = new List<FtpConfigure>(); FtpConfigure SuperiorFtpConfigure1 = new FtpConfigure { ftps_port = string.IsNullOrEmpty(_appConfig.FTPSuperiorSettings.Port) ? 0 : Convert.ToInt32(_appConfig.FTPSuperiorSettings.Port), ftps_pwd = _appConfig.FTPSuperiorSettings.Pwd, ftps_user = _appConfig.FTPSuperiorSettings.Account, ftps_Url = _appConfig.FTPSuperiorSettings.Ip, Type = SystemType.Superior, }; FtpConfigure AreaFtpConfigure1 = new FtpConfigure { ftps_port = string.IsNullOrEmpty(_appConfig.FTPAreaSettings.Port) ? 0 : Convert.ToInt32(_appConfig.FTPAreaSettings.Port), ftps_pwd = _appConfig.FTPAreaSettings.Pwd, ftps_user = _appConfig.FTPAreaSettings.Account, ftps_Url = _appConfig.FTPAreaSettings.Ip, Type = SystemType.Area, }; FtpConfigure EdgeFtpConfigure1 = new FtpConfigure { ftps_port = string.IsNullOrEmpty(_appConfig.FTPEdgeSettings.Port) ? 0 : Convert.ToInt32(_appConfig.FTPEdgeSettings.Port), ftps_pwd = _appConfig.FTPEdgeSettings.Pwd, ftps_user = _appConfig.FTPEdgeSettings.Account, ftps_Url = _appConfig.FTPEdgeSettings.Ip, Type = SystemType.Edge, }; if (!string.IsNullOrEmpty(SuperiorFtpConfigure1.ftps_Url)) { ftpConfigureList.Add(SuperiorFtpConfigure1); } if (!string.IsNullOrEmpty(AreaFtpConfigure1.ftps_Url)) { ftpConfigureList.Add(AreaFtpConfigure1); } if (!string.IsNullOrEmpty(EdgeFtpConfigure1.ftps_Url)) { ftpConfigureList.Add(EdgeFtpConfigure1); } Task.Run(() => ConnectionFtp(ftpConfigureList)).Wait(); //ftps #endregion } /// <summary> /// 临时 /// </summary> private FtpClient Temporaryclient = null; /// <summary> /// 上级的ftps /// </summary> private FtpClient Superiorclient = null; /// <summary> /// 区域的ftps /// </summary> private FtpClient Areaclient = null; /// <summary> /// 边缘的ftps /// </summary> private FtpClient Edgeclient = null; /// <summary> /// 公共的 /// </summary> private FtpClient Publicclient = null; public async void ConnectionFtp(List<FtpConfigure> ftpConfigures) { foreach (var item in ftpConfigures) { try { switch (item.Type) { case SystemType.Superior: Superiorclient = await Connection(item); break; case SystemType.Area: Areaclient = await Connection(item); break; case SystemType.Edge: Edgeclient = await Connection(item); break; default: break; } } catch (Exception ex) { _logger.LogError(ex, "ftps连接建立异常。"); } } } public async Task<FtpClient> Connection(FtpConfigure ftpConfigure) { // 创建 FTP client Temporaryclient = new FtpClient(ftpConfigure.ftps_Url); // 如果您不指定登录凭证,我们将使用"anonymous"用户帐户 Temporaryclient.Credentials = new NetworkCredential(ftpConfigure.ftps_user, ftpConfigure.ftps_pwd); Temporaryclient.Port = ftpConfigure.ftps_port; //选择证书连接的方式 TLS //Temporaryclient.SslProtocols = System.Security.Authentication.SslProtocols.Tls | System.Security.Authentication.SslProtocols.Tls11 | System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls13; Temporaryclient.Encoding = System.Text.Encoding.UTF8; Temporaryclient.ValidateCertificate += Temporaryclient_ValidateCertificate; Temporaryclient.Config.EncryptionMode = FtpEncryptionMode.Implicit; Temporaryclient.Config.DataConnectionType = FtpDataConnectionType.PASV; Temporaryclient.Config.SocketKeepAlive = true; Temporaryclient.Config.SslProtocols = System.Security.Authentication.SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Tls13; //var connect = Temporaryclient.AutoConnect();//根据验证过程,远程证书无效 Temporaryclient.Connect(); if (!Temporaryclient.IsConnected) { Console.WriteLine("上级ftps连接建立失败"); _logger.LogError("上级ftps连接建立失败"); } return Temporaryclient; } private void Temporaryclient_ValidateCertificate(FluentFTP.Client.BaseClient.BaseFtpClient control, FtpSslValidationEventArgs e) { e.Accept = true; } /// <summary> /// 从ftp下载文件(ssl) /// </summary> /// <param name="localPath">要下载到本地哪儿</param> /// <param name="ftpFilePath">ftp的文件路径</param> /// <param name="type">//1上级 2区域 3边缘</param> public async Task<FtpStatus> DownloadFile(string localPath, string ftpFilePath, SystemType type) { FtpStatus status = FtpStatus.Success; try { lock (_locker) { SelectConnection(type); if (!Publicclient.IsConnected) { _logger.LogError("ftp连接失败"); } status = Publicclient.DownloadFile(localPath, ftpFilePath); } //ftpClient.Disconnect(); } catch (Exception ex) { _logger.LogError("请求失败,原因:" + ex); } return status; } /// <summary> /// 从ftp下载文件(ssl) 下载目录下的所有文件 /// </summary> /// <param name="localPath">要下载到本地哪儿</param> /// <param name="ftpFilePath">ftp的文件路径</param> /// <param name="type">//1上级 2区域 3边缘</param> public async Task<MemoryStream> Download(string ftpFilePath, SystemType type, MemoryStream stream) { MemoryStream memoryStream = new MemoryStream(); try { lock (_locker) { SelectConnection(type); //var exist = Publicclient.FileExists(ftpFilePath); //_logger.LogInformation($"ft当前路径:{ftpFilePath} 文件是否存在:{exist}"); //var size = Publicclient.GetFileSize(ftpFilePath); //_logger.LogInformation($"ft当前路径:{ftpFilePath} 文件大小:{size}"); Publicclient.DownloadStream(memoryStream, ftpFilePath); //ftpClient.Disconnect(); } } catch (Exception ex) { _logger.LogError($"ftp下载图失败,当前路径:{ftpFilePath}原因:" + ex.Message); } return memoryStream; } public async Task<FtpListItem[]> GetListing(string url, SystemType type) { SelectConnection(type); return Publicclient.GetListing(url); } /// <summary> /// 上传Http文件到ftp(ssl) 先下载http的文件地址的图片再上传到指定ftp路径 /// </summary> /// <param name="localPath"></param> /// <param name="ftpFilePath"></param> /// <param name="type">//1上级 2区域 3边缘</param> /// <returns></returns> public async Task<FtpStatus> ConnectionFtpHttp(List<string> fileurl, string ftpFilePath, SystemType type) { SelectConnection(type); FtpStatus status = FtpStatus.Success; try { if (ftpFilePath.Substring(ftpFilePath.Length - 1, 1) == "/") { CheckThePathExists(ftpFilePath); } else { if (ftpFilePath.Contains("/")) { var path = ftpFilePath.Substring(0, ftpFilePath.LastIndexOf("/") + 1); CheckThePathExists(path); } } foreach (var item in fileurl) { //string fileName = string.Empty; //if (item.Contains("/")) //{ // fileName = item.Split("/")[item.Split("/").Length - 1]; //} _logger.LogInformation("下载地图文件的url为:" + item); status = Publicclient.UploadBytes(RestClientHelper.DownloadData(item), ftpFilePath); } //ftpClient.Disconnect(); } catch (Exception ex) { throw new UserFriendlyException(ex.Message); } return status; } /// <summary> /// 上传Http文件到ftp(ssl) 先下载http的文件地址的图片再上传到指定ftp路径 /// </summary> /// <param name="fileurl">文件地址集合</param> /// <param name="fileNames">文件名称集合,与文件地址一对一,如果不传,则自动默认取文件路径上的文件名称</param> /// <param name="ftpFilePath">要存储的文件路径</param> /// <param name="type">//1上级 2区域 3边缘</param> /// <returns></returns> public async Task<FtpStatus> ConnectionFtpHttpList(List<string> fileurl, string ftpFilePath, SystemType type, List<string> fileNames) { SelectConnection(type); FtpStatus status = FtpStatus.Success; try { if (ftpFilePath.Substring(ftpFilePath.Length - 1, 1) == "/") { CheckThePathExists(ftpFilePath); } else { if (ftpFilePath.Contains("/")) { var path = ftpFilePath.Substring(0, ftpFilePath.LastIndexOf("/") + 1); CheckThePathExists(path); } } string fileName = string.Empty; for (int i = 0; i < fileurl.Count; i++) { if (fileNames != null && fileNames.Count > 0 && fileNames.Count > i && !string.IsNullOrWhiteSpace(fileNames[i])) { fileName = fileNames[i]; } else { if (fileurl[i].Contains("/")) { fileName = fileurl[i].Split("/")[fileurl[i].Split("/").Length - 1]; } else { fileName = DateTime.Now.ToString("yyyyMMddHHmmssfff") + fileurl[i].Split(".")[fileurl[i].Split(".").Length - 1]; } } status = Publicclient.UploadBytes(RestClientHelper.DownloadData(fileurl[i]), ftpFilePath + fileName); } //ftpClient.Disconnect(); } catch (Exception ex) { throw new UserFriendlyException(ex.Message); } return status; } /// <summary> /// 上传本地文件到ftp服务器 /// </summary> /// <param name="filesDic">文件名为key,全路径为value</param> /// <param name="ftpFilePath"></param> /// <param name="type"></param> /// <returns></returns> public async Task<FtpStatus> UploadLocalFiles(Dictionary<string, string> filesDic, string ftpFilePath, SystemType type) { SelectConnection(type); FtpStatus status = FtpStatus.Success; try { if (ftpFilePath.Substring(ftpFilePath.Length - 1, 1) == "/") { CheckThePathExists(ftpFilePath); } else { if (ftpFilePath.Contains("/")) { var path = ftpFilePath.Substring(0, ftpFilePath.LastIndexOf("/") + 1); CheckThePathExists(path); } } foreach (var item in filesDic) { status = Publicclient.UploadFile(item.Value, ftpFilePath + item.Key);//目前就一个文件,status就这样了先 } } catch (Exception ex) { throw; } return status; } /// <summary> /// 上传Btye[]文件到ftp(ssl) /// </summary> /// <param name="fileurl">图片数组</param> /// <param name="ftpFilePath">要保存的路径</param> /// <param name="fileName">文件名称</param> /// <param name="type">//1上级 2区域 3边缘</param> /// <returns></returns> public async Task<FtpStatus> ConnectionFtpByByte(byte[] fileurl, string ftpFilePath, string fileName, SystemType type) { FtpStatus status = FtpStatus.Success; try { lock (_locker_that) { SelectConnection(type); if (ftpFilePath.Substring(ftpFilePath.Length - 1, 1) == "/") { CheckThePathExists(ftpFilePath); } else { if (ftpFilePath.Contains("/")) { var path = ftpFilePath.Substring(0, ftpFilePath.LastIndexOf("/") + 1); CheckThePathExists(path); } } status = Publicclient.UploadBytes(fileurl, ftpFilePath + fileName); } } catch (Exception ex) { _logger.LogError(ex, "图片上次至FTP服务器异常。"); //throw new UserFriendlyException(ex.Message); status = FtpStatus.Failed; } return status; } /// <summary> /// 上传Btye[]文件到ftp(ssl) /// </summary> /// <param name="fileurl">图片数组</param> /// <param name="ftpFilePath">要保存的路径</param> /// <param name="fileName">文件名称</param> /// <param name="type">//1上级 2区域 3边缘</param> /// <returns></returns> public async Task<FtpStatus> ConnectionFtpByByte(Stream fileStream, string ftpFilePath, string fileName, SystemType type) { FtpStatus status = FtpStatus.Success; try { lock (_locker_that) { SelectConnection(type); if (ftpFilePath.Substring(ftpFilePath.Length - 1, 1) == "/") { CheckThePathExists(ftpFilePath); } else { if (ftpFilePath.Contains("/")) { var path = ftpFilePath.Substring(0, ftpFilePath.LastIndexOf("/") + 1); CheckThePathExists(path); } } status = Publicclient.UploadStream(fileStream, ftpFilePath + fileName); } } catch (Exception ex) { _logger.LogError(ex, "图片上次至FTP服务器异常。"); //throw new UserFriendlyException(ex.Message); status = FtpStatus.Failed; } return status; } /// <summary> /// 上传文件到ftp(ssl) /// </summary> /// <param name="localPath">本地的文件路径,完整的</param> /// <param name="ftpFilePath">要上传到的地址</param> /// <param name="type">//1上级 2区域 3边缘</param> /// <returns></returns> public async Task<FtpStatus> UploadFileFtps(string localPath, string ftpFilePath, SystemType type) { FtpStatus status = FtpStatus.Success; try { lock (_lockerArest) { SelectConnection(type); string pathStr = ftpFilePath; if (ftpFilePath.Contains("/")) { var path = ftpFilePath.Substring(0, ftpFilePath.LastIndexOf("/") + 1); CheckThePathExists(path); pathStr = ftpFilePath.Substring(ftpFilePath.LastIndexOf("/") + 1); } status = Publicclient.UploadFile(localPath, ftpFilePath);//ftpFilePath //status = Publicclient.UploadFile(@"C:\Users\36493\Desktop\测试文件\2022-10-27180030189.xml", pathStr); //ftpClient.Disconnect(); } } catch (Exception ex) { throw new UserFriendlyException(ex.Message); } return status; } /// 2022年10/27 号出现了 文件能上传但是无文件大小的问题 持续到28号上午 问题还没找到 突然又正常了 ……………………………………………………心情复杂 /// <summary> /// 上传文件到ftp(ssl) /// </summary> /// <param name="localPath">本地的文件路径,完整的</param> /// <param name="ftpFilePath">要上传到的地址</param> /// <param name="type">//1上级 2区域 3边缘</param> /// 用流的形式 /// <returns></returns> public async Task<FtpStatus> UploadFileFtps_bug(string localPath, string ftpFilePath, SystemType type) { string name = string.Empty; if (!localPath.Contains('/')) { name = localPath.Split("\\")[(localPath.Split("\\").Length - 1)]; } else { name = localPath; } string path = string.Empty; SelectConnection(type); FtpStatus status = FtpStatus.Success; try { string pathStr = ftpFilePath; if (ftpFilePath.Contains("/")) { path = ftpFilePath.Substring(0, ftpFilePath.LastIndexOf("/") + 1); CheckThePathExists(path); pathStr = ftpFilePath.Substring(ftpFilePath.LastIndexOf("/") + 1); } // 打开文件 FileStream fileStream = new FileStream(localPath, FileMode.Open, FileAccess.Read, FileShare.Read); // 读取文件的 byte[] // 把 byte[] 转换成 Stream MemoryStream stream = new MemoryStream(); //byte[] bytes = new byte[fileStream.Length]; //fileStream.Read(bytes, 0, bytes.Length); await fileStream.CopyToAsync(stream); //fileStream.Close(); try { await ConnectionFtpByByte(stream, path, name, SystemType.Area); //await ConnectionFtpByByte(stream.ToArray(), path, name, SystemType.Area); //await ConnectionFtpByByte(stream.ToArray(), "/", name, SystemType.Area); } catch (Exception ex) { _logger.LogError(ex, "区域收到边缘上报图片,存储到ftp报错。"); } //status = Publicclient.UploadFile(localPath, ftpFilePath); //status = Publicclient.UploadFile(localPath, pathStr); //ftpClient.Disconnect(); } catch (Exception ex) { throw new UserFriendlyException(ex.Message); } return status; } /// <summary> /// 修改文件名 /// </summary> /// <param name="path"></param> /// <param name="name"></param> /// <param name="type"></param> /// <returns></returns> /// <exception cref="UserFriendlyException"></exception> public async Task<FtpStatus> Rename(string path, string name, SystemType type) { SelectConnection(type); FtpStatus status = FtpStatus.Success; try { if (!Publicclient.DirectoryExists(path)) { return FtpStatus.Success; } Publicclient.Rename(path, name); //ftpClient.Disconnect(); } catch (Exception ex) { throw new UserFriendlyException(ex.Message); } return status; } private void SelectConnection(SystemType type) { try { switch (type) { case SystemType.Superior: Publicclient = Superiorclient; break; case SystemType.Area: Publicclient = Areaclient; break; case SystemType.Edge: Publicclient = Edgeclient; break; default: break; } Publicclient.ValidateCertificate += Publicclient_ValidateCertificate; } catch (Exception ex) { _logger.LogError(ex, "验证ftps的证书异常:" + ex); } } /// <summary> /// //证书验证 /// </summary> /// <param name="control"></param> /// <param name="e"></param> private void Publicclient_ValidateCertificate(FluentFTP.Client.BaseClient.BaseFtpClient control, FtpSslValidationEventArgs e) { e.Accept = true; } /// <summary> /// 判断路径是否存在 不在就创建 /// </summary> /// <param name="ftpFilePath"></param> private void CheckThePathExists(string ftpFilePath) { try { Publicclient.ValidateCertificate += Publicclient_ValidateCertificate1; if (!Publicclient.DirectoryExists(ftpFilePath)) { var state = Publicclient.CreateDirectory(ftpFilePath); } } catch (Exception ex) { throw new UserFriendlyException(ex.Message); } } private void Publicclient_ValidateCertificate1(FluentFTP.Client.BaseClient.BaseFtpClient control, FtpSslValidationEventArgs e) { //在这里添加逻辑以测试证书是否有效 e.Accept = true; } } }
再次补充
public async Task<FtpClient> Connection(FtpConfigure ftpConfigure) { try { var ftpconfig = new FtpConfig(); ftpconfig.ConnectTimeout = 60 * 1000; ftpconfig.ReadTimeout = 60 * 1000; ftpconfig.DataConnectionConnectTimeout = 60 * 1000; ftpconfig.DataConnectionReadTimeout = 60 * 1000; // 创建 FTP client var Temporaryclient = new FtpClient(ftpConfigure.ftps_Url, ftpConfigure.ftps_user, ftpConfigure.ftps_pwd, ftpConfigure.ftps_port, ftpconfig, _logger); // 如果您不指定登录凭证,我们将使用"anonymous"用户帐户 Temporaryclient.Encoding = System.Text.Encoding.UTF8; Temporaryclient.ValidateCertificate += Temporaryclient_ValidateCertificate; Temporaryclient.Config.ValidateAnyCertificate = true; Temporaryclient.Config.EncryptionMode = FtpEncryptionMode.Implicit; Temporaryclient.Config.DataConnectionType = FtpDataConnectionType.PASV; Temporaryclient.Config.SocketKeepAlive = true; Temporaryclient.Config.SslProtocols = System.Security.Authentication.SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Tls13; //Temporaryclient.Config.SslProtocols = SslProtocols.Tls13; //var connect = Temporaryclient.AutoConnect();//根据验证过程,远程证书无效 Temporaryclient.Connect(); if (!Temporaryclient.IsConnected) { Console.WriteLine("连接建立失败"); _logger.LogError("连接建立失败:" + ftpConfigure.ToJson()); } _logger.LogInformation($"ftps连接成功IP:{ftpConfigure.ftps_Url}"); return Temporaryclient; } catch (Exception ex) { _logger.LogError(ex, "连接建立失败" + ftpConfigure.ToJson()); } return Temporaryclient; } private void Temporaryclient_ValidateCertificate(FluentFTP.Client.BaseClient.BaseFtpClient control, FtpSslValidationEventArgs e) { e.Accept = true; }
补充注入到服务中: context.Services.AddFtpClient(context);
当然首先要写一个服务扩展
/// <summary> /// Ftp服务注入 /// </summary> public static class FtpClientModuleprivate { /// <summary> /// 此处的服务为链接Ftp /// </summary> /// <param name="services"></param> public static void AddFtpClient(this IServiceCollection services, ServiceConfigurationContext context) { //先注入服务,不用具体管实现,全部写成一个服务去处理 context.Services.AddSingleton<IUpLoadFtpFileService, UploadToFtpService>(); } }